Ordering by number of one-to-many entries - django

I have models like this:
class Forum(models.Model):
title = models.CharField(max_length=100)
class Entry(models.Model):
entry = models.TextField()
good = models.BooleanField()
f = models.ForeignKey(Forum)
I want to list all Forum objects ordered by [the number of Entry objects in that forum satisfying good == True]. How can I do this with Django model methods?

Try this
from django.db.models import Sum
result = Forum.objects.filter(entry__good=True).annotate(Sum('entry')).order_by('-entry__sum')
-entry__sum - it's order from big to small count of good entry, if need order from small to big, then order_by('entry__sum')

Related

Django ManyToManyField delete across models

I've the below inefficient 'destroy' method for deleting Ratings that are held in Stimulus which itself is held within Experiment (I have simplified my models, for reasons of clarity).
Could you advise on a more efficient way of achieving this?
class Rating(models.Model):
rater = TextField(null=True)
rating = FloatField(null=True)
created = models.DateTimeField(null=True)
class Stimulus(TimeStampedModel):
genes = TextField()
weights = ListField()
ratings = ManyToManyField(Rating, null=True)
evaluation = FloatField(null=True)
complete = BooleanField(default=False)
Class Experiment(models.Model):
all_individuals = ManyToManyField(Stimulus, null=True)
def destroy(self):
all_ratings = Rating.objects.all()
for ind in self.all_individuals.all():
ratings = ind.ratings.all()
for rating in ratings:
if rating in all_ratings:
Rating.objects.filter(id = rating.id).delete()
Background: I am using Django to run an experiment (Experiment) which shows Users many Stimuli (Stimulus). Each Stimulus gets rated many times. Thus, I need to save multiple ratings per stimulus (and multiple stimuli per experiment).
Some simple improvements
Remove the if rating in all_ratings, every rating will be in the list of all ratings
Do the delete on the database side
ind.ratings.all().delete()
Use prefetch_related to get the foreign key objects
self.all_individuals.prefetch_related('ratings'):
Combined would be:
def destroy(self):
for ind in self.all_individuals.prefetch_related('ratings'):
ratings = ind.ratings.all().delete()
I think that in this case using ManyToManyField isn't the best choice.
You'll have less problems using common ForeignKey's changing a little the structure of this models.
Eg.
class Rating(models.Model):
rater = TextField(null=True)
rating = FloatField(null=True)
created = models.DateTimeField(null=True)
stimulus = models.ForeignKey('Stimulus', related_name='ratings')
class Stimulus(TimeStampedModel):
genes = TextField()
weights = ListField()
#ratings = ManyToManyField(Rating, null=True)
evaluation = FloatField(null=True)
complete = BooleanField(default=False)
experiment = models.ForeignKey('Experiment', related_name='stimulus')
class Experiment(models.Model):
#all_individuals = ManyToManyField(Stimulus, null=True)
name = models.CharField(max_length=100)
This is a more clear structure and when you delete Experiment by, experiment_instance.delete() a delete cascade will delete all other related models.
Hope it helps.

Displaying a sorted list with ManyToMany relation

I have a certain number ob Subjects and Users. An user can vote for one or more subjects. I'd like to make a view displaying the top 10 Subjects sorted by count of votes in descanding order.
In fact, I found a working way, but I'm asking myself if there is more suitable way to do that.
I started with models:
class Subject(models.Model):
text = models.TextField()
class User(models.Model):
name = models.CharField(max_length=64)
subject = models.ManyToManyField(Subject)
now I can type:
Subject.user_set.all()
and I will get those users who has voted for the subject.
Now I would like to make a list view, where top 10 subjects would be displayed sorted by the number of votes.
So I added a class method to the Subject class:
class Subject(models.Model):
text = models.TextField()
#staticmethod
def by_votes():
myList = list(Subject.objects.all())
return sorted(myList, key=lambda s: s.user_set.count(), reverse=True)[:10]
and defined that class based view:
class SubjectListView(ListView):
model = Subject
template_name = "subject_list.html"
context_object_name = "subject_list"
queryset = Subject.by_votes()
Which actually works well.
But I saw in the docs Manager and QuerySet classes, unfortunately I did not quite understand how to use them (defining custom ones) to get what I'm looking for (without dealing with raw SQL queries).
I'm little bit afraid, because I used a list I could run into memory problems having large number of subjects.
What would you say, would it be more suitable to use custom Manager oder QuerySet for that, wouldn't?
If yes, how to do it?
Any other alternative ideas?
Thanks!
Peter
No need for custom managers or raw SQL here, just aggregation.
from django.db.models import Count
Subject.objects.annotate(user_count=Count('user').order_by('-user_count')[:10]
I'm fairly new to this but I solved a similar problem with this:
Subject.objects.all().annotate(count=Count('user')).order_by('-count')[:10]
Aggregation Doc

Django: Filter in multiple models linked via ForeignKey?

I'd like to create a filter-sort mixin for following values and models:
class Course(models.Model):
title = models.CharField(max_length=70)
description = models.TextField()
max_students = models.IntegerField()
min_students = models.IntegerField()
is_live = models.BooleanField(default=False)
is_deleted = models.BooleanField(default=False)
teacher = models.ForeignKey(User)
class Session(models.Model):
course = models.ForeignKey(Course)
title = models.CharField(max_length=50)
description = models.TextField(max_length=1000, default='')
date_from = models.DateField()
date_to = models.DateField()
time_from = models.TimeField()
time_to = models.TimeField()
class CourseSignup(models.Model):
course = models.ForeignKey(Course)
student = models.ForeignKey(User)
enrollment_date = models.DateTimeField(auto_now=True)
class TeacherRating(models.Model):
course = models.ForeignKey(Course)
teacher = models.ForeignKey(User)
rated_by = models.ForeignKey(User)
rating = models.IntegerField(default=0)
comment = models.CharField(max_length=300, default='')
A Course could be 'Discrete mathematics 1'
Session are individual classes related to a Course (e.g. 1. Introduction, 2. Chapter I, 3 Final Exam etc.) combined with a date/time
CourseSignup is the "enrollment" of a student
TeacherRating keeps track of a student's rating for a teacher (after course completion)
I'd like to implement following functions
Sort (asc, desc) by Date (earliest Session.date_from), Course.Name
Filter by: Date (earliest Session.date_from and last Session.date_to), Average TeacherRating (e.g. minimum value = 3), CourseSignups (e.g. minimum 5 users signed up)
(these options are passed via a GET parameters, e.g. sort=date_ascending&f_min_date=10.10.12&...)
How would you create a function for that?
I've tried using
denormalization (just added a field to Course for the required filter/sort criterias and updated it whenever changes happened), but I'm not very satisfied with it (e.g. needs lots of update after each TeacherRating).
ForeignKey Queries (Course.objects.filter(session__date_from=xxx)), but I might run into performance issues later on..
Thanks for any tipp!
In addition to using the Q object for advanced AND/OR queries, get familiar with reverse lookups.
When Django creates reverse lookups for foreign key relationships. In your case you can get all Sessions belonging to a Course, one of two ways, each of which can be filtered.
c = Course.objects.get(id=1)
sessions = Session.objects.filter(course__id=c.id) # First way, forward lookup.
sessions = c.session_set.all() # Second way using the reverse lookup session_set added to Course object.
You'll also want to familiarize with annotate() and aggregate(), these allow you you to calculate fields and order/filter on the results. For example, Count, Sum, Avg, Min, Max, etc.
courses_with_at_least_five_students = Course.objects.annotate(
num_students=Count('coursesignup_set__all')
).order_by(
'-num_students'
).filter(
num_students__gte=5
)
course_earliest_session_within_last_240_days_with_avg_teacher_rating_below_4 = Course.objects.annotate(
min_session_date_from = Min('session_set__all')
).annotate(
avg_teacher_rating = Avg('teacherrating_set__all')
).order_by(
'min_session_date_from',
'-avg_teacher_rating'
).filter(
min_session_date_from__gte=datetime.now() - datetime.timedelta(days=240)
avg_teacher_rating__lte=4
)
The Q is used to allow you to make logical AND and logical OR in the queries.
I recommend you take a look at complex lookups: https://docs.djangoproject.com/en/1.5/topics/db/queries/#complex-lookups-with-q-objects
The following query might not work in your case (what does the teacher model look like?), but I hope it serves as an indication of how to use the complex lookup.
from django.db.models import Q
Course.objects.filter(Q(session__date__range=(start,end)) &
Q(teacher__rating__gt=3))
Unless absolutely necessary I'd indeed steer away from denormalization.
Your sort question wasn't entirely clear to me. Would you like to display Courses, filtered by date_from, and sort it by Date, Name?

Selecting distinct nested relation in Django

To describe the system quickly, I have a list of Orders. Each Order can have 1 to n Items associated with it. Each Item has a list of ItemSizes. Given the following models, which have been abbreviated in terms of fields for this question, my goal is to get a distinct list of ItemSize objects for a given Order object.
class ItemSize(models.Model):
name = models.CharField(max_length=10, choices=SIZE_CHOICES)
class Item(models.Model):
name = models.CharField(max_length=100)
sizes = models.ManyToManyField(ItemSize)
class OrderItem(models.Model):
order = models.ForeignKey(Order)
item = models.ForeignKey(Item)
class Order(models.Model):
some_field = models.CharField(max_length=100, unique=True)
So... if I have:
o = Order.objects.get(id=1)
#how do I use the ORM to do this complex query?
#i need o.orderitem_set.items.sizes (pseudo-code)
In your current set up, the answer by #radious is correct. However, OrderItems really shouldn't exist. Orders should have a direct M2M relationship with Items. An intermediary table will be created much like OrderItems to achieve the relationship, but with an M2M you get much simpler and more logical relations
class Order(models.Model):
some_field = models.CharField(max_length=100, unique=True)
items = models.ManyToManyField(Items, related_name='orders')
You can then do: Order.items.all() and Item.orders.all(). The query you need for this issue would be simplified to:
ItemSize.objects.filter(item__orders=some_order)
If you need additional data on the Order-Item relationship, you can keep OrderItem, but use it as a through table like:
class Order(models.Model):
some_field = models.CharField(max_length=100, unique=True)
items = models.ManyToManyField(Items, related_name='orders', through=OrderItem)
And you still get your simpler relationships.
ItemSize.objects.filter(items__orderitems__order=some_order)
Assuming you have reverse keys like:
ItemSize.items - reverse fk for all items with such size
Item.orderitems - reverse for all orderitems connected to item
Item.orders - you can guess ;)
(AFAIR that names would be choose by default, but I'm not sure, you have to test it)
More informations about reverse key queries are available in documentation.

Sorting Related objects in Django

I have 2 models Category and Item. An Item has a reference to a Category.
class Category(models.Model):
name = models.CharField(max_length=32)
class Item(model.Models):
name = models.CharField(max_length=32)
category = models.ForeignKey(Category)
sequence = models.IntegerField()
The sequence field is supposed to capture the sequence of the Item within a category.
My question is:
What Meta Options do i need to set on category and/or item such that when i do:
category.item_set.all()
that I get the Items sorted by their sequence number.
PS: I am now aware of a meta option called ordering_with_respect_to .. but it is still unclear how it works, and also i have legacy data in the sequence columns. I am open to data migration, if the right approach requires that.
What you're looking for is:
class Item(model.Models):
name = models.CharField(max_length=32)
category = models.ForeignKey(Category)
sequence = models.IntegerField()
class Meta:
ordering = ['sequence',]
That will ensure that Items are always ordered by sequence.
category.item_set.all().order_by('sequence')
Kinda late, and the previous answers don't solve my specific question, but they led me to an answer, so I'm gonna throw this in:
I need to sort my prefetch_related objects specifically for only one view, so changing the default ordering is no good (maybe a model_manager would do it, idk). But I found this in the docs.
I have the following models:
class Event(models.Model):
foo = models.CharField(max_length=256)
....
class Session(models.Model):
bar = models.CharField(max_length=256)
event = models.ForeignKey(Event)
start = models.DateTimeField()
....
class Meta:
ordering = ['start']
Now in a particular view, I want to see all the Events, but I want their Sessions in reverse order, i.e., ordering = ['-start']
So I'm doing this in the view's get_queryset():
from django.db.models import Prefetch
session_sort = Session.objects.all().order_by('-start')
prefetch = Prefetch('sessions', queryset=session_sort)
events = Event.objects.all().prefetch_related(prefetch)
Hope it helps somebody!
*BTW, this is just a simplified version, there are a bunch of filters and other prefetch_related parameters in my actual use case.