Django: Ordering by last item in reverse ForeignKey relation - python

I'm trying to implement a qs.sort_by operation, but I'm not sure how to go about it.
Given the following models
class Group(models.Model):
name = models.CharField(max_length=300)
( ... )
class Budget(models.Model):
( ... )
amount = models.DecimalField(
decimal_places=2,
max_digits=8,
)
group = models.ForeignKey(
Group,
related_name="budgets",
on_delete=models.CASCADE,
)
Where each Group has either 0 or > 5 budgets assigned
I am trying to sort a Group.objects.all() queryset by the amount of their last (as in, most recently assigned) Budget.
I know that if it was a OneToOne I could do something like the following:
Group.objects.all().order_by('budget__amount')
With ForeignKey relations I was hoping I could do something like
Group.objects.all().order_by('budgets__last__amount')
But alas, that is not a valid operation, but I am not sure on how to proceed otherwise.
Does anyone know on how to perform this sorting operation? (if it is indeed possible)

This should do it:
latest = Budget.objects.filter(group=OuterRef('pk')).order_by('-pk')
Group.objects.annotate(
latest_budget=Subquery(
latest.values('amount')[:1]
)
).order_by('latest_budget')

You can try use sorted with a custom key:
sorted(Group.objects.all(), key=lambda x: x.last__budget__amount)
Perhaps .last__budget__amount is not a valid method, but you get the idea.

Related

How to do an exclude django query on multiple foreign key

My model example:
class Thing(models.Model):
alpha = models.ForeignKey('auth.User', on_delete=models.CASCADE,
related_name='alpha_thing')
beta = models.ForeignKey('auth.User', on_delete=models.CASCADE,
related_name='beta_thing')
assigned_at = models.DateTimeField(
_('assigned at'),
null=True,
help_text=_('Assigned at this date'))
I wish to query all the users which don't have a Thing with an assigned_at date, ie they could have other Things, but that should have a date set.
I've tried:
return User.objects.exclude(
alpha_thing__assigned_at__isnull=True
).exclude(
beta_thing__assigned_at__isnull=True
).all()
but the result is empty (the thing table is empty, so i'm not sure if it has something to do with the join?).
What about this,
from django.db.models import Q
User.objects.filter(Q(alpha_thing__assigned_at__isnull=False) | Q(beta_thing__assigned_at__isnull=False)).distinct()
Screenshots
1. Auth model structure - User
2. Thing model
There is another way, since you want to filter user which "things" contains all an assigned_date.
You could:
User.objects.filter(
alpha_thign__assigned_at__isnull=False,
beta_thign__assigned_at__isnull=False,
)
Simple.
There are no need to Use Q objects here or | (or) operations.
What you want is not
alpha_thing__assigned_at__isnull=False OR
beta_thing__assigned_at__isnull=False
What you're looking for is
alpha_thing__assigned_at__isnull=False AND
beta_thing__assigned_at__isnull=False
For all users which don't have a Thing with an empty date try:
return User.objects.exclude(
alpha_thing__assigned_at=None
).exclude(
beta_thing__assigned_at=None
).all()
By the way, I got the same result whether I used .all() at the end or not, so:
return User.objects.exclude(
alpha_thing__assigned_at=None
).exclude(
beta_thing__assigned_at=None
)
returned the same result as the first example.
Have you tried something like this?
from django.db.models import Q
has_null_alpha = Q(alpha_thing__isnull=False, alpha_thing__assigned_at__isnull=True)
has_null_beta = Q(beta_thing__isnull=False, beta_thing__assigned_at__isnull=True)
User.objects.exclude(has_null_alpha | has_null_beta)
Reasoning
I think the reason you're seeing unexpected results may not have anything to do with the fact that there are multiple ForeignKey paths in the queryset. Your statement that "the thing table is empty" might be the key, and the reason users aren't showing up is because they have no alpha_thing or beta_thing relation.
NOTES:
The QuerySet User.objects.exclude(alpha_thing__assigned_at__isnull=True) produces a left outer join between the User table and the Thing table, which means that before doing any comparisons in the WHERE clause, you're getting NULL for assigned_at in any row where there is no Thing.
One really weird thing here is that a filter causes an INNER join, so that the statement User.objects.filter(alpha_thing__assigned_at__isnull=False) actually only yields the users who actually have alpha_thing related objects with a non-NULL value for assigned_at (leaving out those guys with no related alpha_thing).

Django filter by the number of rows matching a certain condition in a ManyToMany

I need to filter for objects where the number of elements in a ManyToMany relationship matches a condition. Here's some simplified models:
Place(models.Model):
name = models.CharField(max_length=100)
Person(models.Model):
type = models.CharField(max_length=1)
place = models.ManyToManyField(Place, related_name="people")
I tried to do this:
c = Count(Q(people__type='V'))
p = Places.objects.annotate(v_people=c)
But this just makes the .v_people attribute count the number of People.
Since python-2.0, you can use the filter=... parameter of the Count(..) function [Django-doc] for this:
Place.objects.annotate(
v_people=Count('people', filter=Q(people__type='V'))
)
So this will assign to v_people the number of people with type='V' for that specific Place object.
An alternative is to .filter(..) the relation first:
Place.objects.filter(
Q(people__type='V') | Q(people__isnull=True)
).annotate(
v_people=Count('people')
)
Here we thus filter the relation such that we allow people that either have type='V', or with no people at all (since it is possible that the Place has no people. We then count the related model.
This generates a query like:
SELECT `place`.*, COUNT(`person_place`.`person_id`) AS `v_people`
FROM `place`
LEFT OUTER JOIN `person_place` ON `place`.`id` = `person_place`.`place_id`
LEFT OUTER JOIN `person` ON `person_place`.`person_id` = `person`.`id`
WHERE `person`.`type` = V OR `person_place`.`person_id` IS NULL

Build a list of values from queryset list (pk__in) in special order

i have a model with ratings, the results of a filter query must be in a special order for chartit (comparing ratings for trainee's) but I can't find the right way to do it. (ok I'm new to Django and python ;)
class Bewertung(models.Model):
auffassung = models.PositiveSmallIntegerField()
interesse = models.PositiveSmallIntegerField()
arbeitsabw = models.PositiveSmallIntegerField()
aufmerksamkeit = models.PositiveSmallIntegerField()
arbeitsgenauigkeit = models.PositiveSmallIntegerField()
verhalten = models.PositiveSmallIntegerField()
ausb_sach = models.PositiveSmallIntegerField(null=True, blank=True)
ausb_fuehr = models.PositiveSmallIntegerField(null=True, blank=True)
the query:
qs = Bewertung.objects.filter(pk__in=pk_list)
I want to compare the integer values in a multi bar chart e.g.
auffassung_from_pk(1,2,3) interesse_from_pk(1,2,3) .. n
but every try ends in a list with a lot of unordered values
(Auffassung_from_pk(1), interesse_from_pk(1), Auffassung_from_pk(2) ..)
I can't find a way to solve it nice and efficient in an python way.
so I need a little help, can you help?
#Sachin Kukreja correct, separate it and order it. so every field (e.g. auffassung must be one list with every result from the queryset.)
if I have 3 resulting query sets (pk_list=(1,2,3)) I need something like ((1,2,1),(2,3,3)...) ((auffassung),(interesse))
#Rajez there are no multiple filters
i set all my approach's to zero to start new. I have only this (in the Django shell)
for q in qs:
print(q.auffassung)
print(q.interesse)
i am really struggling at this at the moment
try it:
import itertools
qs = Bewertung.objects.filter(pk__in=pk_list)
values = qs.values_list('auffassung', 'interesse')
result = list(itertools.chain(*values))

Iterating through an unlimited number of child objects in a Django queryset

I have a Django model that defines a list of categories. Each category can be a child of another category, and it is possible that depth of this list of categories could go on quite a bit.
models.py
class Category(models.Model):
code = UUIDField(unique=True, primary_key=True)
name = models.CharField(max_length=100, null=True, blank=True, unique=True)
parent_cat = models.ForeignKey('self', null=True, blank=True)
Consider for example, a Category for women's clothing. It may have something like the following:
Women's > Clothing > Dresses > Summer
That all works fine, but separately I want to build a function that can build a single list containing an extrapolated view of every category in the tree. To do that, I have written the following:
queryset = Category.objects.filter(parent_cat=kwargs['code'])
all_children = []
all_children.append(queryset.values())
for c in queryset:
children = Category.objects.filter(parent_cat=c.code)
if children:
all_children.append(children.values())
all_children.append(children)
I realise this code is probably quite sloppy, I'm still learning!
As you probable realise, this will only give me two levels of child objects.
My question is: What is the most efficient way that I can write this code so that it will find every possible category until it reaches the last object with no more children?
Thanks!!
you could try using recursion something along the lines of this:
Replace list with the apprpriate data type.
def parseQueryset(queryset)
for x in queryset:
if isinstancex(x, list):
parseQueryset(x)
else:
#do stuff
You are looking for https://en.wikipedia.org/wiki/Tree_traversal - and you can use django-mptt package: https://django-mptt.github.io/django-mptt/overview.html#what-is-modified-preorder-tree-traversal

Is it possible to sort using sorted() with attributes from two different models?

I'm trying to sort a list of posts where votes have priority over the date.
I have my own app called UserPost and I'm using the django-voting app to do votes.
class UserPost(models.Model):
user = models.ForeignKey(User)
datetime = models.DateTimeField(auto_now_add=True)
text = models.CharField(max_length=255, blank=True)
is_deleted = models.BooleanField(default=False)
vote = models.ForeignKey(Vote)
Right now, I'm sorting without votes taking precedence yet:
posts_list = sorted(posts_list, key=attrgetter('datetime'))
What's the best way to go about this?
Thanks!
Tuples are sorted lexicographically, therefore if you return a tuple for sorted's key= argument you can sort by votes then by dates:
posts_list = sorted(posts_list, key=lambda post: (Vote.objects.get_score(post)['score'], post.datetime))
Alternatively, you might want to also look at the ordering option in a django Model's Meta class or the order_by method on django Queryset. They will do the sorting on the database in one query, so can be much faster. Alternatively, you can try the posts_list.get_score_in_bulk() to reduce the number of queries to two (one for posts_list, and one for get_score_in_bulk), like so:
scores = Vote.objects.get_score_in_bulk(posts_list)
posts_list = sorted(posts_list, key=lambda post: (scores[post.id]['score'], post.datetime))
You can sort it like that:
def posts_sorting(post1, post2):
# put here the way you compare posts
pass # return 1 if post2 before post1, -1 otherwise, 0 if they are "equal"
posts_list = sorted(posts_list, cmp=posts_sorting)
where posts_sorting is the function that compares your posts.

Categories