Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display name of a choice field in Django while using annotate

I am trying to get display names of choices when using annotate, but I haven't been able to figure out. I have the following query:

Survey.objects.values('what').annotate(count=Count('why')).order_by()

And the result is:

[{'count': 34, 'what': u'a'}, 
{'count': 39, 'what': u'c'}, 
{'count': 40, 'wat': u'p'}]

But I want something that displays the name of the choice field and not the key:

[{'count': 34, 'what': u'appreciative'}, 
{'count': 39, 'what': u'creative'}, 
{'count': 40, 'wat': u'promising'}]

I tried get_what_display (as mentioned on the docs and other stackoverflow answers on this topic), but django throws an error. i.e the following doesn't seem to work

Survey.objects.values('get_what_display').annotate(count=Count('why')).order_by()
like image 275
bachkoi32 Avatar asked Apr 02 '15 19:04

bachkoi32


2 Answers

To accomplish this without iterating over the queryset you can use a conditional expression to annotate with the display attribute. Annotated attributes are available to use in .values().

from django.db.models import Case, CharField, Value, When

choices = dict(Survey._meta.get_field('what')[0].flatchoices)
whens = [When(what=k, then=Value(v)) for k, v in choices.items()]
survey_counts = (
    Survey.objects
    .annotate(get_what_display=Case(*whens, output_field=CharField()))
    .values('get_what_display')
    .annotate(count=Count('why'))
    .order_by()
)
like image 75
bdoubleu Avatar answered Sep 19 '22 13:09

bdoubleu


Building upon @bdoubleu's answer, I wrote the following generic conditional expression:

# myapp/utils.py
from django.db.models import Case, CharField, Value, When

class WithChoices(Case):
    def __init__(self, model, field, condition=None, then=None, **lookups):
        choices = dict(model._meta.get_field(field).flatchoices)
        whens = [When(**{field: k, 'then': Value(v)}) for k, v in choices.items()]
        return super().__init__(*whens, output_field=CharField())

# example usage
from myapp.utils import WithChoices
from myapp.models import MyModel
MyModel.objects.annotate(what_with_choices=WithChoices(MyModel, 'what')).values('what_with_choices')

There's probably a cleaner way to build that doesn't require passing the model arg to WithChoices but, hey, this works.

like image 32
airstrike Avatar answered Sep 20 '22 13:09

airstrike