21

I have a scenario where I want to annotate a queryset with externally prepared data in a dict. I want to do something like the following:

value_dict = {"model1": 123.4, "model2": 567.8}
qs = ModelClass.objects.annotate(
    value=Value(value_dict.get(F('model__code'), 0))
)

The results currently show all as 0 as the F() doesn't seem to be the best way to look up the dict seeing as it doesn't return a string and it is resolved further down the track.

Your help and suggestions would be much appreciated

I'm currently on Python 3.6 and Django 1.11

2 Answers 2

39

The closest I've come so far is to loop through the dict and union a whole bunch of filtered query sets based on the dict keys. Like so:

value_dict = {"model1": 123.4, "model2": 567.8}
array_of_qs = []
for k, v in value_dict.items():
    array_of_qs.append(
        ModelClass.objects.filter(model__code=k).annotate(value=Value(v))
    )
qs = array_of_qs[0].union(*array_of_qs[1:])

The qs will then have the result that I want, which is the values of the dict based on the keys of the dict annotated to each instance.

The beauty of this is that there is only one call to the db which is when I decide to use qs, other than that this whole process doesn't touch the DB from what I understand.

Not sure there is a better way yet but will wait to see if there are other responses before accepting this one.

NEW IMPROVED WORKING METHOD BELOW

Unfortunately the above doesn't work with values_list or values as of this current version 1.11 of Django due to this issue: https://code.djangoproject.com/ticket/28900

I devised a cleaner way below:

value_dict = {"model1": 123.4, "model2": 567.8}
whens = [
    When(model__code=k, then=v) for k, v in value_dict.items()
]
qs = ModelClass.objects.all().annotate(
    value=Case(
        *whens,
        default=0,
        output_field=DecimalField()
    )
)

Hope it helps someone

Sign up to request clarification or add additional context in comments.

4 Comments

whens is a list and you cannot do **whens because it is not a dictionary but a list.
You are correct @user4426017 that it is a list, however I am not expanding it with **whens, I am expanding it with *whens which is what is intended
Building on what @Omar did, if your dictionary contains variables as key-values, use then=Value(v). Worked for me.
Nice workaround solution! But is it good enough to scale?
1

Not being able to do that has annoyed me for years.

Based on the accepted answer, I've designed a generic QuerySet :

def add_from_dict_value(self, reference_dict, fieldname_used_as_key, annotation_name_to_generate, output_field=CharField(), default=None)->:
    """
    enables annotations with values coming from a dictionary

    Use:
        def add_highlight(self)->models.QuerySet:
            return self.add_from_dict_value(MY_CONVERSION_DICTIONARY, 'rating', 'highlight')
    """ 

    whens = [
        When(**{fieldname_used_as_key:k, 'then':Value(v)}) for k, v in reference_dict.items()
    ]
    return self.annotate(**{
        annotation_name_to_generate : Case(
            *whens,
            default=default,
            output_field=output_field
        )}
    )

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.