0

So I am trying to add a temporary attribute to two separate QuerySets from the same model, but it seems that as soon as I try to merge them together, the new fields disappear.

Example:

class IndexView(View):
    def get(self, request):
        q1 = MyModel.objects.filter(points=0)
        q2 = MyModel.objects.filter(points=100)

        for q in q1:
            q.title = 'Loser'
            print(q.title)  # Prints 'Loser'

        for q in q2:
            q.title = 'Winner'
            print(q.title)  # Prints 'Winner'

        q = q1 | q2  # Merges the two QuerySets together

        for item in q:
            print(item.title)  # ERROR: "Title" is not a field anymore apparently...

        return render(request, 'index.html', {})

I have tried another approach that uses .chain() (mentioned in another thread), but that changes the type from QuerySet to List, which I do not want. I will post it below. Is there any way to keep the list as a QuerySet while achieving the same results? Please note that I want the title attribute to be temporary so I can separate items in the template. Also, the points filter is not the actual filter and is actually a lot more complicated, so template tags and model methods are not an option.

from itertools import chain
feed = sorted(
    chain(q1, q2),
    key=lambda instance: instance.created)

Django v1.9.6 Python v3.4.3

Hybrid
  • 6,741
  • 3
  • 25
  • 45
  • 3
    `q = q1 | q2` This creates a brand new queryset, that will select the union of what `q1` and `q2` would. When you then go through `q`, you trigger a new query and load objects from the database again. – spectras May 25 '16 at 20:57
  • Interesting - so is there an alternative approach that would avoid wiping the temporary attributes I defined? – Hybrid May 25 '16 at 20:58
  • 1
    Ideally, you would add those attributes as part of the query itself, using `annotate`, perhaps with a `Case` clause. You wouldn't even need the two querysets and the merge, just explain the database how to calculate the field by itself. – spectras May 25 '16 at 20:59
  • Or, since this is a really simple calculation, do it in a model method or even directly in the template: `{% if q.points == 0 %}loser{else %}winner{% endif %}`. Again, then you only need a single query. – Daniel Roseman May 25 '16 at 21:03
  • @spectras: Hmmm I'm not sure how to go about that. Again, this is sample code, my actual code uses `GenericRelations` to `GenericForeignKeys` in the filtering process, so it get's a bit too crazy. I'd like to hear more about annotations if that's a viable option. – Hybrid May 25 '16 at 21:04
  • @DanielRoseman: I would love to do that, but as I mentioned the actual code is a fair bit more complex, so using simple `if` conditions is not an option. – Hybrid May 25 '16 at 21:05
  • ̀annotate` logic probably won't be that helpful then, unless you're willing to create a stored procedure on the database side – spectras May 25 '16 at 21:07
  • Yeah I definitely don't want additions to the DB, especially since the attributes I am adding are relative to the logged in user. – Hybrid May 25 '16 at 21:10
  • Possible duplicate of [Beginner SQL IF Clause in Django](http://stackoverflow.com/questions/37141743/beginner-sql-if-clause-in-django) – e4c5 Nov 23 '16 at 12:00

1 Answers1

0

There's no way you can do this, because when you do:

q = q1 | q2

You were executing a new database query. There's no straightforward way for this, you could only query them all together then mark then respectively:

queryset = MyModel.objects.filter(Q(points=0) | Q(points=100))
for item in queryset:
    if q.points  == 0:
        q.title = 'Loser'
    elif q.points == 100:
        q.title = 'Winner'

Given that you might have complicated condition about points, you could also create a method for model MyModel then call that function to return a value similar to assign title to it.

Shang Wang
  • 24,909
  • 20
  • 73
  • 94
  • Can you also tell me why you don't want to convert them into lists? You've already looped through them so there's no real benefit to keep it as queryset, right? – Shang Wang May 25 '16 at 21:05
  • Moreso in case I need to use filters or templatetags in the future, which may need access to QuerySet methods. – Hybrid May 25 '16 at 21:07
  • Then do the filters in the views as much as you can instead of templatetags and convert them into list. I still believe that template could get the final data if you process everything in views.py method. – Shang Wang May 25 '16 at 21:09
  • @Hybrid> All QuerySet methods return new querysets (or plain objects). This means setting attributes on objects won't work. You're down to `annotate` with potential db-side function, or make it a [property](http://stackoverflow.com/questions/6193556/how-do-python-properties-work) – spectras May 25 '16 at 21:09
  • @ShangWang: That's what I guess will happen in the end, unfortunately it seems there's no way around that – Hybrid May 25 '16 at 21:11
  • @spectras: I appreciate the information, so I guess I have no choice but to keep it this way until there is the possibility of an alternative non-db-altering solution. – Hybrid May 25 '16 at 21:12