Django queryset: order_by ForeignKey boolean attribute - python

I'm trying to order a queryset so that:
the first elements of the queryset are those which have a ForeignKey boolean attribute (first) set to True, and amongst them, they are ordered by creation date
the following elements are those having ForeignKey first attribute set to False, and again amongst them, they are ordered by creation date
Here is an example of the models:
class A(models.Model):
first = models.BooleanField(blank=False, default=False)
class B(models.Model):
a = models.ForeignKey(A)
created = models.DateTimeField(auto_now_add=True)
The queryset looks like:
queryset = B.objects.all().order_by("-a__first", "-created")
This snipper, however, is not doing the work.
A solution that I'm thinking of is to use two different databases call (one filtering for a__first=True and the other filtering for a__first=False), and then sum up the querysets results. But I would like to understand if there is a better and cleaner way of solving this problem.

Try the following conditional query, it might possibly work
from django.db.models import Case, When
B.objects.annotate(
first_true=Case(
When(a__first=True, then=('created')),
default=None
),
first_false=Case(
When(a__first=False, then=('created')),
default=None
)
).order_by(
'-first_true',
'-first_false',
)

Related

using F expression to access a field from one to many relationship not using the queryset resulted from prefetch_related method

hey guys let's say I have these models
class Object1:
.....
class Object2:
user = models.ForeignKey(User)
rel = models.ForeignKey(Object1, related_name = 'objects')
isCOmpleted = models.BooleanField()
and I wanted to perform this query:
Object1.objects.all().prefetch_related(Prefetch('objects', Object2.objects.filter(user = specific_user))).annotate(is_completed=F('objects__isCompleted'))
and the user is only related to one object from the Object2, but I got duplicate objects in my query, and I know the reason is that I have two objects from Object2 in my database for two different users, but the problem is that F expression didn't look in my prefetched query using prefetch_related method, I tried the exact same thing in the shell and it's giving me the results that I have expected but not in the view, so what's the problem here exactly any help would be really appreciated

Django queryset has no attribute when using select related

I'm trying to using select_related to queryset, and it returns queryset has no attribute when using select related. I made two models, and one model has foreignkey column, it is 1:1.
models
class User(models.Model):
name = Charfield()
class Item(models.Model):
user = models.ForegnKey(User, on_delete=models.CASCADE, related_name='user_item_set', null=True)
When I try this queryset, it says queryset does not have select related.
users = User.objects.get(id=pk).select_related('user_item_set')
Looks like you misunderstood the usage of select_related().
Returns a QuerySet that will “follow” foreign-key relationships, selecting additional related-object data when it executes its query.
It can be used on Item model (the model which defines the ForegnKey field) and not User model.
What you need to use is prefetch_related(). Something like this:
users = User.objects.get(id=pk).prefetch_related('item_set')

Fast filter on related fields in django

I have 2 models in my django project.
ModelA(models.Model):
id = models.AutoField(primary_key=True)
field1 = ...
~
fieldN = ...
ModelB(models.Model):
id = models.AutoField(primary_key=True)
a = models.ForeignKey(A, on_delete=models.CASCADE)
field1 = ...
~
fieldN = ...
Here I have one-to-mane relation A->B. Table A has around 30 different fields and 10.000+ rows and table B has around 15 and 10.000.000+ rows. I need to filter firstly by the several ModelA fields and then for each of the filtered ModelA row/object get related ModelB objects and filter them by several fields. After that I need to serialize them in JSON where all ModelB packed in one field as array.
Is it possible to perform this around the 1-3 second? If yes, what is the best approach?
I use PostgreSQL.
EDIT:
Now I am doing it like chain .filter() on simple ModelA fields and then iterate over resulted QuerySet and get set of ModelB for each ModelA instance,but i suspect, that the second part of this solution will slow down whole process, so I suppose there is a better way to do it.
It may be faster to do a query like this:
model_a_queryset = ModelA.objects.filter(field=whatever)
model_b_queryset = ModelB.objects.filter(a__in=model_a_queryset)
Because Django does lazy queryset evaulation, this will only result in one hit to the database.
As an aside, there is no need to define id = Autofield fields on your models. Django includes them by default.

Order a django queryset by ManyToManyField = Value

If have some models like:
class Tag(models.Model):
name = models.CharField()
class Thing(models.Model):
title = models.CharField()
tags = models.ManyToManyField(Tag)
I can do a filter:
Thing.objects.filter(tags__name='foo')
Thing.objects.filter(tags__name__in=['foo', 'bar'])
But is it possible to order a queryset on the tags value?
Thing.objects.order_by(tags__name='foo')
Thing.objects.order_by(tags__name__in=['foo','bar'])
What I would expect (or like) back in this example, would be ALL Thing models, but ordered where they have a Tag/Tags that I know. I don't want to filter them out, but bring them to the top.
I gather this is possible using the FIELD operator, but seemingly I can only make it work on columns in that models table, e.g. title, but not on linked tables.
Thanks!
EDIT: After having accepted the below solution, I realised a bug/limitation with it.
If a particular Thing has multiple Tags, then (due to the left join done behind the scenes in the SQL) it will produce one entry for that Thing, for each Tag that it has. With a True or False for each Tag that matches or not.
Adding .distinct() to the queryset helps only slightly, limiting to a max of 2 rows per Thing (i.e. one tagged=True, and one tagged=False).
I know what I need to do in the SQL, which is to MAX() the CASE(), and then GROUP BY Thing's primary key, which means I will get one row per Thing, and if there has been any tag matches, tagged will be True (and False otherwise).
I see the way that people typically achieve this kind of thing is to use .values() like this:
Thing.objects.values('pk').annotate(tagged=Max(Case(...)))
But the result is only pk and tagged, I need the whole Thing model as the result. So I've managed to achieve what I want, thusly:
from django.db.models import Case, When, Max, BooleanField
tags = ['music'] # for example
queryset = Thing.objects.all().annotate(tagged=Max(Case(
When(tags__name__in=tags, then=True),
default=False,
output_field=BooleanField()
)))
queryset.query.group_by = ['pk']
queryset.order_by('-tagged')
This seems to work, but the group by mechanism feels weird/hacky. Is it acceptable/reliable to group in this way?
Sorry for the epic updated :(
I'd try annotate the query with the conditional value that turns true when the tag is in the list you provide
from django.db.models import Case, When, IntegerField
Thing.objects.annotate(tag_is_known=Case(
When(tags__name__in=['foo', 'bar'], then=1),
default=0,
output_field=IntegerField()
))
Next we use that annotation we called tag_is_known to sort with order_by():
Thing.objects.annotate(tag_is_known=...).order_by('tag_is_known')
Boolean version
Thing.objects.annotate(tag_is_known=Case(
When(tags__name__in=['foo', 'bar'], then=True),
default=False,
output_field=BooleanField()
))

Django query annotation with boolean field

Let's say I have a Product model with products in a storefront, and a ProductImages table with images of the product, which can have zero or more images. Here's a simplified example:
class Product(models.Model):
product_name = models.CharField(max_length=255)
# ...
class ProductImage(models.Model):
product = models.ForeignKey(Product, related_name='images')
image_file = models.CharField(max_length=255)
# ...
When displaying search results for products, I want to prioritize products which have images associated with them. I can easily get the number of images:
from django.db.models import Count
Product.objects.annotate(image_count=Count('images'))
But that's not actually what I want. I'd like to annotate it with a boolean field, have_images, indicating whether the product has one or more images, so that I can sort by that:
Product.objects.annotate(have_images=(?????)).order_by('-have_images', 'product_name')
How can I do that? Thanks!
I eventually found a way to do this using django 1.8's new conditional expressions:
from django.db.models import Case, When, Value, IntegerField
q = (
Product.objects
.filter(...)
.annotate(image_count=Count('images'))
.annotate(
have_images=Case(
When(image_count__gt=0,
then=Value(1)),
default=Value(0),
output_field=IntegerField()))
.order_by('-have_images')
)
And that's how I finally found incentive to upgrade to 1.8 from 1.7.
As from Django 1.11 it is possible to use Exists. Example below comes from Exists documentation:
>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef('pk'),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
Use conditional expressions and cast outputfield to BooleanField
Product.objects.annotate(image_count=Count('images')).annotate(has_image=Case(When(image_count=0, then=Value(False)), default=Value(True), output_field=BooleanField())).order_by('-has_image')
Read the docs about extra
qs = Product.objects.extra(select={'has_images': 'CASE WHEN images IS NOT NULL THEN 1 ELSE 0 END' })
Tested it works
But order_by or where(filter) by this field doesn't for me (Django 1.8) 0o:
If you need to order the resulting queryset using some of the new
fields or tables you have included via extra() use the order_by
parameter to extra() and pass in a sequence of strings. These strings
should either be model fields (as in the normal order_by() method on
querysets), of the form table_name.column_name or an alias for a
column that you specified in the select parameter to extra().
qs = qs.extra(order_by = ['-has_images'])
qs = qs.extra(where = ['has_images=1'])
FieldError: Cannot resolve keyword 'has_images' into field.
I have found https://code.djangoproject.com/ticket/19434 still opened.
So if you have such troubles like me, you can use raw
If performance matters, my suggestion is to add the hasPictures boolean field (as editable=False)
Then keep right value through ProductImage model signals (or overwriting save and delete methods)
Advantages:
Index friendly.
Better performance. Avoid joins.
Database agnostic.
Coding it will raise your django skills to next level.
When you have to annotate existence with some filters, Sum annotation can be used. For example, following annotates if there are any GIFs in images:
Product.objects.filter(
).annotate(
animated_images=Sum(
Case(
When(images__image_file__endswith='gif', then=Value(1)),
default=Value(0),
output_field=IntegerField()
)
)
)
This will actually count them, but any pythonic if product.animated_images: will work same as it was boolean.

Categories