Search code examples
pythondjangojsonmany-to-manytastypie

Django-Tastypie: Omit one specific object to be serialized in ManyToMany


I have a simple Chat app. Here are my models.

class Thread(models.Model):
    participants = models.ManyToManyField(User, related_name= 'threads_partof')
    last_message_time = models.DateTimeField(null=True, blank=True)

class Message(models.Model):
    message = models.CharField(max_length=500)
    sender = models.ForeignKey(User)
    thread = models.ForeignKey(Thread, related_name = 'thread_to_message')
    datetime = models.DateTimeField(auto_now_add=True)

def update_last_message_datetime(sender, instance, created, **kwargs):
    '''
    Update Thread's last_message field when
    a new message is sent.
    '''
    if not created:
        return

    Thread.objects.filter(id=instance.thread.id).update(last_message_time=instance.datetime)
post_save.connect(update_last_message_datetime, sender=Message)

I use Django-Tastypie for my API. Here is an example of how my app currently works, if Steve wants to message Bill, a new "Message" object is created, where Steve is the sender. When a message object is created, it also creates a "Thread" object. In this thread, the participants are Steve & Bill.

I currently have a ThreadPreview resource.

class ThreadPreview(ModelResource):
    participants = fields.ManyToManyField(BasicFoodieActivityResource, 'participants', full=True)
    class Meta:
        queryset = Thread.objects.all()
        resource_name = 'thread-preview'
        fields = ['id', 'participants', 'last_message_time']
        authorization = Authorization()
        authentication = BasicAuthentication()
        serializer = Serializer(formats=['json'])
        include_resource_uri = False
        always_return_data = True

    def dehydrate(self, bundle):
        bundle.data['last_message'] = bundle.obj.thread_to_message.latest('datetime').message
        return bundle

    def get_object_list(self, request):
        return super(ThreadPreview, self).get_object_list(request).filter(participants = request.user).order_by('-last_message_time')

When I do a HTTP GET request on this resource, this is what I get back:

{
    "meta": {
        "limit": 20,
        "next": null,
        "offset": 0,
        "previous": null,
        "total_count": 1
    },
    "objects": [{
        "id": "12",
        "last_message": "Hey, Bill, you're stealing from us!",
        "last_message_time": "2013-11-07T19:27:52",
        "participants": [{
            "user": {
                "first_name": "Steve",
                "id": "28",
                "last_name": "Jobs",
                "username": "steve"
            },
            "userPic": "http://apple.com/stevejobs.jpg"
        }, {
            "user": {
                "first_name": "Bill",
                "id": "6",
                "last_name": "Gates",
                "username": "bill"
            },
            "userPic": "http://microsoft.com/billgates.jpg"
        }],
    }]
}

I would like to not include the user Steve when the data comes back since he is request.user. Anytime request.user accesses this data, I would like their user data to not be serialized since I already know who they are. For example, this is what I would like:

{
    "meta": {
        "limit": 20,
        "next": null,
        "offset": 0,
        "previous": null,
        "total_count": 1
    },
    "objects": [{
        "id": "12",
        "last_message": "Hey, Bill, you're stealing from us!",
        "last_message_time": "2013-11-07T19:27:52",
        "participants": [{
            "user": {
                "first_name": "Bill",
                "id": "6",
                "last_name": "Gates",
                "username": "bill"
            },
            "userPic": "http://microsoft.com/billgates.jpg"
        }],
    }]
}

How can I about doing this so that request.user's data is always omitted?


Solution

  • The case for this question (filtering related resources)

    You can use dehydrate_participants hook (see docs on dehydration) on ThreadPreview to filter participants:

    class ThreadPreview(ModelResource):
    
        ...
    
        def dehydrate_participants(self, bundle):
            if not hasattr(bundle.request, 'user'):
                return bundle.data['participants']
    
            new_participants = []
            for p in bundle.data['participants']:
                if p['user']['id'] != bundle.request.user.pk:
                    new_participants.append(p)
    
            return new_participants
    
        ...
    

    NB: I could be wrong about p['user']['id'] != bundle.request.user.pk. p['user']['id'] is possibly string already.

    Only when this resource is serialized (not when it is related)

    Resource has a hook method def get_object_list(self, request) which could be used to filter list based on request.user. Realisation is strateforward

    class BasicFoodieActivityResource(ModelResource):
    
        ...
    
        def get_object_list(self, request):
            qs = super(BasicFoodieActivityResource, self).get_object_list(request)
    
            if request and hasattr(request, 'user'):
                qs = qs.exclude(pk = request.user.pk)
    
            return qs
    
        ...