Get related objects from a queryset, preserving the ordering - python

I have a model, which looks something like this:
class Agent(models.Model):
class Meta:
ordering = ['first_name', 'last_name', ]
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
... some other fields ...
class Point(models.Model):
agent = models.ForeignKey(Agent)
... some other fields ...
When I select some points I want them to be ordered by agent names, like this:
points = Point.objects.filter(...smth...).order_by(-agent)
So the resulting queryset is ordered by agent names, from Z to A
or
points = Point.objects.filter(...smth...).order_by(agent)
or even without ordering
The question is:
How to get a queryset or a list of unique agent objects from points queryset, preserving the ordering?
Preferrably, without any more order_by() statements, because the view may or may not be explicitly aware of ordering type
Something like set(points.values_list('agent', flat=True)), but with objects instead of pk-s?

You can use your own idea and nest it like so (where points is any QuerySet of Point):
Agent.objects.filter(id__in=points.values_list('agent', flat=True))
Django ORM will translate this into a single db query. This should have Agent's default order. In order to preserve the agent order in points, you could do:
[p.agent for p in points.distinct('agent')]
This workaround, obviously, is not a queryset and distinct with field arguments is only supported in Postgres.

Use Agent objects manager, and QuerySet.distinct to get unique agents:
Agent.objects.filter(point__condition=...).distinct().order_by('-pk')
NOTE: condition=... should be adjusted to point__condition=....

This should work.
Agent.objects.filter(id__in=Point.objects.values_list('agent_id', flat=True)).order_by('-first_name')
Update:
Point.objects.filter(agent_id__in=Agent.objects.values_list('id', flat=True)).distinct('agent').order_by('-agent__first_name')

Related

Filter Django queryset by related field (with code-value)

Simplifying my model a lot, I have the following:
class Player(models.Model):
name = models.CharField(max_length=50)
number = models.IntegerField()
class Statistic(models.Model):
'''
Known codes are:
- goals
- assists
- red_cards
'''
# Implicit ID
player = models.ForeignKey(
'Player', on_delete=models.CASCADE, related_name='statistics')
code = models.CharField(max_length=50)
value = models.CharField(max_length=50, null=True)
I'm using a code-value strategy to add different statistics in the future, without the need of adding new fields to the model.
Now, I want to filter the players based on some statistics, for example, players who scored between 10 and 15 goals.
I'm trying something like this:
.filter('statistics__code'='goals').filter('statistics__value__range'=[10,15])
but I'm getting duplicated players, I'm guessing because that value__range could refer to any Statistic.
How could I properly filter the queryset or avoid those duplicates?
And how could I filter by more than one statistic, for example, players who scored between 10 and 15 goals and have more than 5 assists?
By the way, note that the value field (in Statistic) is a string, and it will need to be treated as an integer in some scenarios (when using __range, for example).
You don't need to chain the filter. Use the filter() method only once with distinct() method.
.filter(statistics__code='goals', statistics__value__range=[10,15]).distinct()
NOTE: I can see few quotes around statistics__code and statistics__value__range, no need to put that.

Order by with specific rows first

I have a generic ListView in django 1.11 and I need to return the object ordered by alphabetical order, but changing the first 2 :
class LanguageListAPIView(generics.ListCreateAPIView):
queryset = Language.objects.all().order_by("name")
serializer_class = LanguageSerializer
with the following Language model :
class Language(models.Model):
name = models.CharField(max_length=50, unique=True)
And I'd like to return ENGLISH, FRENCH then every other languages in the database ordered by name.
Is there a way to achieve this with django ORM ?
Thank you,
Maybe you can use two querysets and combine them to obtain the result as:
q1 = Language.objects.filter(Q(name='ENGLISH'|name='FRENCH'))
and
q2 = Language.objects.filter(~Q(name='ENGLISH'|name='FRENCH')).order_by('name')
Then join the querysets as:
queryset = list(chain(q1, q2))
Import Q from django.db.models and chain from itertools
Since Django 1.8 you use Conditional Expressions:
from django.db.models import Case, When, Value, IntegerField
Language.objects.annotate(
order=Case(
When(name="ENGLISH", then=Value(1)),
When(name="FRENCH", then=Value(2)),
default=Value(3),
output_field=IntegerField(),
)
).order_by('order', 'name)
This will annotate a field called order, then sort the results first by the order field, then by the name field, where English/French will get a a lower order value, all following languages the same so that they are only sorted by name.

How to sort queryset by annotated attr from ManyToMany field

Simplest example:
class User(models.Model):
name = ...
class Group(models.Model):
members = models.ManyToManyField(User, through='GroupMembership')
class GroupMembership(models.Model):
user = ...
group = ...
I want to get list of Groups ordered by annotated field of members.
I'm using trigram search to filter and annotate User queryset.
To get annotated users I have something like that:
User.objects.annotate(...).annotate(similarity=...)
And now I'm trying to sort Groups queryset by Users' "similarity":
ann_users = User.objects.annotate(...).annotate(similarity=...)
qs = Group.objects.prefetch_related(Prefetch('members',
queryset=ann_users))
qs.annotate(similarity=Max('members__similarity')).order_by('similarity')
But it doesn't work, because prefetch_related does the ‘joining’ in Python; so I have the error:
"FieldError: Cannot resolve keyword 'members' into field."
I expect that you have a database function for similarity of names by trigram search and its Django binding or you create any:
from django.db.models import Max, Func, Value, Prefetch
class Similarity(Func):
function = 'SIMILARITY'
arity = 2
SEARCHED_NAME = 'searched_name'
ann_users = User.objects.annotate(similarity=Similarity('name', Value(SEARCHED_NAME)))
qs = Group.objects.prefetch_related(Prefetch('members', queryset=ann_users))
qs = qs.annotate(
similarity=Max(Similarity('members__name', Value(SEARCHED_NAME)))
).order_by('similarity')
The main query is compiled to
SELECT app_group.id, MAX(SIMILARITY(app_user.name, %s)) AS similarity
FROM app_group
LEFT OUTER JOIN app_groupmembership ON (app_group.id = app_groupmembership.group_id)
LEFT OUTER JOIN app_user ON (app_groupmembership.user_id = app_user.id)
GROUP BY app_group.id
ORDER BY similarity ASC;
-- params: ['searched_name']
It is not exactly what you want in the title, but the result is the same.
Notes: The efficiency how many times will be the SIMILARITY function evaluated depends on the database query optimizer. The query plan by EXPLAIN command will be an interesting your answer, if the original idea by raw query in some simplified case is better.

Filter on calculated representation of some fields

I have three numeric columns in my model that together create a string, which is presented to the user:
class Location(models.Model):
aisle = models.PositiveIntegerField()
rack = models.PositiveIntegerField()
plank = models.PositiveIntegerField()
def __unicode__(self):
return "{a:02}{r:02}{p:02}".format(a=self.aisle, r=self.rack, p=self.plank)
Now I want to filter on (part of) that string, so say I have three locations, 010101, 010102, 010201, and I filter on 0101, I want to select only the first two.
How would I do that, I looked into the Q objects and available database functions, but I don't find a solution.
After a lot of experimenting, I managed to do it using a Func:
class LocationLabel(Func):
function = 'CONCAT'
template = '%(function)s(RIGHT(CONCAT(\'00\',%(expressions)s),2))'
arg_joiner = '),2), RIGHT(CONCAT(\'00\','
models.Location.object.
annotate(locationlabel=
LocationLabel('aisle','rack','plank', output_field=CharField())
).
filter(locationlabel__icontains=query)
You cannot perform a filter on a property, it has to be on fields.
In this case i think this will do what you require because unicode is just a formatted form actual integer value in fields:
Location.objects.filter(aisle=1, rack=1)

Getting only the models related to a Django queryset

I don't have much experience with Django (I'm using 1.3) so I have the feeling on the back of my head that this is a dumb question... But anyway:
I have models like this:
class User(models.Model):
name = models.CharField()
class Product(models.Model):
name = models.CharField()
public = models.BooleanField()
class Order(models.Model):
user = models.ForeignKey(User)
product = models.ManyToManyField(Product, through='OrderProduct')
class OrderProduct(models.Model):
product = models.ForeignKey(Product)
order = models.ForeignKey(Order)
expiration = models.DateField()
And let's say I do some query like this
Product.objects.filter(order__status='completed', order__user____id=2)
So I'd get all the products that User2 bought (let's say it's just Product1). Cool. But now I want the expiration for that product, but if I call Product1.orderproduct_set.all() I'm gonna get every entry of OrderProduct with Product1, but I just want the one returned from my queryset.
I know I can just run a different query on OrderProducts, but that would be another hit on the database just to bring back data the query I ran before can already get. .query on it gives me:
SELECT "shop_product"."id", "shop_product"."name"
FROM "shop_product"
INNER JOIN "shop_orderproducts" ON ("shop_product"."id" = "shop_orderproducts"."product_id")
INNER JOIN "shop_order" ON ("shop_orderproducts"."order_id" = "shop_order"."id")
WHERE ("shop_order"."user_id" = 2 AND "shop_order"."status" = completed )
ORDER BY "shop_product"."ordering" ASC
If I could SELECT * instead of specific fields I'd have all the data that I need in one query. Is there anyway to build that query and get only the data related to it?
EDIT
I feel I need to clarify some points, I'm sorry I haven't been clearer:
I'm not querying against OrderProduct because some products are public and don't have to be bought but I still have to list them, and they'd not be returned by a query against OrderProduct
The result I'm expecting is a list of products, along with their Order data (in case they have it). In JSON, it'd look somewhat like this
[{id: 1, order: 1, expiration: 2013-03-03, public: false},
{id: 1, order: , expiration: , public: true
Thanks
I'm gonna get every entry of OrderProduct with Product1, but I just
want the one returned from my queryset.
You just want which "one"? Your query is filtering on the Product model, so all Users, Orders, and OrderProducts associated with each of the Products in the returned queryset will be accessible.
If you want one specific OrderProduct, then you should be filtering as op = OrderProduct.objects.filter(xxxxx) and then accessing the models up the chain like so:
op.product, op.order, etc.
I would have suggested the method prefetch_related, but this isn't available in Django 1.3.
Dan Hoerst is right about selecting from OrderProduct, but that still hits the database more than necessary. We can stop that by using the select_related method.
>>> from django.db import connection
>>> len(connection.queries)
0
>>> first_result = OrderProduct.objects.select_related("order__user", "product")
... .filter( order__status="completed",
... order__user__pk=2 )[0]
>>> len(connection.queries)
1
>>> name = first_result.order.user.name
>>> len(connection.queries)
1
>>> product_name = first_result.product.name
>>> len(connection.queries)
1

Categories

Resources