Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Annotations are ignored in Django/Tastypie

In my Tastypie resource, I'm annotating my queryset, and yet I don't see that annotation flow through to the JSON Tastypie generates and passes back. The code is straightforward:

class CompetitionResource(ModelResource):
    total_tickets = fields.IntegerField(readonly=True)

    class Meta:
        queryset = Competition.objects.all().annotate(total_tickets=Count('ticket__ticketownership__user__id', distinct=True))

That Count I'm generating and annotating there in my queryset simply does not show up in the final JSON. The final JSON has a total_users field (because I declared one in my ModelResource), but it's null. Am I missing anything obvious to make sure annotations like this get passed through? If not, what would be a way to solve this?

One way to do it is to create an attribute in my Model and then tie the total_users field in my ModelResource to that attribute. But that would presumably result in a Count query for each individual Competition I pull from the database, and that is not good. I want to do it in one annotation-type query.

like image 765
Mario Avatar asked Sep 17 '12 13:09

Mario


1 Answers

Ok, I got it. You can simply use the custom dehydrate_[field name] methods that you can add to a ModelResource. For each ModelResource field, Tastypie checks if you specified a dehydrate_[field name] method, and if you did, then it calls that method when it processes an object into a bundle (which then gets put out as JSON or XML or whatever). This dehydrate_[field name] method gets the bundle that Tastypie has created up until that point, for that particular object. The good thing is that this bundle has the original object in it, under bundle.obj. And that object will still have the original annotation you provided in get_object_list (as shown in the answer above). So you can use the following code.

class CompetitionResource(ModelResource):
    total_tickets = fields.IntegerField(readonly=True)

    class Meta:
        queryset = Competition.objects.all()

    def get_object_list(self, request):
        return super(CompetitionResource, self).get_object_list(request).annotate(total_tickets=Count('ticket__ticketownership__user__id', distinct=True))

    def dehydrate_total_tickets(self, bundle):
        return bundle.obj.total_tickets

Whatever you return from the custom dehydrate_[field name] method will be properly stored as that field's final value in the bundle for that object, and then properly processed into output.

like image 162
Mario Avatar answered Sep 22 '22 01:09

Mario