Django QuerySet .order_by() method - django

I have this code down here and from my understanding order_by over writes the default behavior of the ordering option in the model’s Meta. see the documentation here https://docs.djangoproject.com/en/4.1/ref/models/querysets/#order-by
my question is what criteria does the order_by() in this case use to order the QuerySet if there are no fields provided? Does it make a difference having it there?
order.discounts.filter(voucher_id=OuterRef("pk"))
.order_by()
.values("voucher_id")
.annotate(Sum("amount"))
.values("amount__sum")
)

Calling order_by() with no parameters removes all ordering from the queryset and results are returned in an unspecified order by the database
Docs
If you don’t want any ordering to be applied to a query, not even the default ordering, call order_by() with no parameters
If a query doesn’t have an ordering specified, results are returned from the database in an unspecified order. A particular ordering is guaranteed only when ordering by a set of fields that uniquely identify each object in the results. For example, if a name field isn’t unique, ordering by it won’t guarantee objects with the same name always appear in the same order.

Related

Why do Get and Filter give different results? (Django)

I am developing an app where students can evaluate their teachers. I have several models, but the important ones for this question are these:
class Professor(models.Model):
name = models.CharField(max_length=50,null=True)
categories = models.ManyToManyField(Category, related_name='professors')
def __str__(self):
return self.name
class Student(models.Model):
name = models.CharField(max_length=50,null=True)
professors = models.ManyToManyField(Professor, related_name='students',through='Studentprofesor' )
def __str__(self):
return self.name
class Studentprofesor(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
professor = models.ForeignKey(Professor, on_delete=models.CASCADE)
tested = models.BooleanField(default=False)
As far as I knew the main difference between get and filter was that I couldn't use get when there were several objects with the features I was looking for. But apart from that, they worked in a similar way. get for a single object, filter for several objects.
However, in this case I get different results when I run get and filter.
if I use get:
Student.objects.get(name="Mike").professors.all()
I obtain:
<QuerySet [<Professor: Tom>, <Professor: Jenn>]>
But if I use filter:
Student.objects.filter(name="Mike").professors.all()
I obtain:
AttributeError: 'QuerySet' object has no attribute 'professors'
it's as if filter is not able to follow the manytomany relationship between the objects.
Why is this happening?
There are huge differences between .get(..) and .filter(..). In short: .get(..) obtains a single model instance that satisfies the given conditions whereas .filter(..) filters the queryset and produces a queryset that conceptually contains model instances (!) that satisfy the given conditions.
Django's .get(..)
.get means that you aim to retrieve exactly one instance. So that means that if you write:
Model.objects.get(..)
the result is a Model instance (given there is such instance). As a result we can thus obtain attributes (like .professors, etc.) from that single entity. We have guarantees if the call succeeds that: (1) there are no multiple objects for which the filter criteria hold; and (2) there is at least one element for which the filter criteria hold. So the output is always a model instance, not a None or a QuerySet.
The .get(..) function is evaluated eagerly: we immediately perform a query to the database. In case the database returns no entries, or two or more, the Model.DoesNotExist and MultipleObjectsReturned exceptions are respectively risen.
Note: since .get(..) acts eagerly, adding filters, etc. at the right of .get(..) has no use (well unless you define a filter function on that instance, but that is not a good idea either). You can however use functions like .values(), values_list, prefetch_related, etc. on the left side to change the type of output (and prefetch certain parts). For example:
Student.objects.values().get(name='Mike')
will result in a dictionary containing the values of that instance.
Django's .filter(..)
Filter on the other hand filters a queryset. That means that it is possible that after we filter, the queryset no longer contains any instances (if the filter is too restrictive) or two ore more (if the filter is too weak to pin to a single entry).
Django does not evaluate such .filter(..) eagerly. This means that by default Django will not make a query to the database to retrieve entries. Only if you call for example len(..) over the resulting queryset, or you iterate over it, Django will first perform a database query and then handle the corresponding result.
Since the result of a .filter(..) is another QuerySet, we can chain operations together. For example, we can call an extra .filter(..) or .exclude(..), values_list(..) or any other function supported by the QuerySet.
Since the result is not a model instance, we can not call the attributes of a model instance. What should be the outcome of a Student.objects.filter(..).name in case no student matches the criteria? Or what if there are multiple Students that match the given constraint?
We can however obtain the list of Professors that teach one or more Students with the name 'Mike' with:
# professors with a student called Mike
Professors.objects.filter(students__name="Mike")
A filter will never raise an Model.DoesNotExist or a MultipleObjectsReturned exception, since it is perfectly allowed to work with empty QuerySets, or QuerySets with multiple items.
Bacause filter() returns queryset (multiple students). But professors is attribute of single student instance. You can use first() with filter() to get single object:
Student.objects.filter(name="Mike").first().professors.all()

Get a queryset's current order_by ordering

I'd like to find adjacent items in a queryset given a primary key, as asked here. However I want this to work for any given queryset, that i haven't set the ordering for myself. How can I find the current ordering of a queryset for __gt and __lt filtering?
For example:
queryset = MyModel.objects.all().order_by('some_field')
next_obj = get_next(queryset, after_pk, 10)
...
def get_next(queryset, pk, n):
#ordering is unknown here
obj = queryset.get(pk=pk)
field = queryset.get_order_by_field_name() #???
return queryset.filter(**{field+'__gt': getattr(obj, field)})[:n]
The model may define a default ordering and I can check if the queryset is ordered, but I don't think either are helpful in this case.
You can retrieve the list of columns from order_by clause by accessing queryset.query.order_by property.
From django docs:
The query attribute is an opaque object. It represents the internals of the query construction and is not part of the public API. However, it is safe (and fully supported) to pickle and unpickle the attribute’s contents as described here.

Ordered list in Django Model

I have a Django project where I want a Model to contain an ordered list of objects of another Model. If I've understood correctly, lists are usually done with foreign keys pointing from the contained objects to the containing object. But if I do it this way, will I be sure to maintain the order of the objects (the order in which I add them in the admin panel)? And what determines their order internally?
Also, if it is so that the elements with foreign keys are unordered, how would I go about making an ordered list in a Django Model?
You can specify in the Meta options on the model what field(s) to order by.
See https://docs.djangoproject.com/en/dev/ref/models/options/#django.db.models.Options.ordering
If you need to order by different fields in different queries, you can override the order_by on individual querysets.
See https://docs.djangoproject.com/en/dev/ref/models/querysets/#order-by
If you want to order them based on a timestamp (for instance when they were added to your database) then you would specify this in the Meta options.
If you want, you can add a field that stores automatically the datetime when the object is created, with the auto_now_add attribute, like this example:
creation_date = models.DateTimeField('Date', auto_now_add=True)
Then, you can order in the DB by this field, with Meta attribute:
class Meta:
ordering = ('creation_date',)

How can I remove Model Meta ordering in Django or get the original queryset from the built in Manager?

I have a model that uses the Meta ordering property to make sure that all views that use it display things in the same order.
However, I have one method that needs to use the distinct() method - this does not work after ordering is applied. Is there a built in way to retrieve the original queryset before ordering is applied, or do I need to write a custom Manager class to allow me to do this.
Call order_by() before distinct() with no parameters.
From: https://docs.djangoproject.com/en/1.5/ref/models/querysets/#order-by
If you don’t want any ordering to be applied to a query, not even the default ordering, call order_by() with no parameters.
Just doing a quick test reveals that if you do Foomodel.objects.order_by().distinct('bar') it correctly removes all ORDER BY clauses, even ones defined in Foomodel's Meta.
from copy import deepcopy
class ExtendedManager(models.Manager):
def __init__(self):
super(ExtendedManager, self).init()
self.model = deepcopy(self.model)
if self.model._meta.ordering:
del self.model._meta.ordering
class MyModel(models.Model):
...
unordered_objects = ExtendedManager()
And normally you should be able to do MyModel.unordered_objects.all(), MyModel.unordered_objects.filter(...).distinct(), etc... And for the ordered querysets, nothing changes !

Can Tastypie handle arbitrary arrays of objects?

I'm working on a project which requires REST API. I have tried Piston but it doesn't suit my requirement as it currently allows only 1 handler per model.
Tastypie seems to be a better alternative. However, I'm stuck with the following problem. My article class is displayed according to a complex rule such as ranking and date created. To enhance server performance, I created a dummy table which records the order of all the articles so that upon user requests, the complex ordering process will not be executed. Instead, the server checks for the orders of each article from the dummy table.
With Tastypie, a query set is required. However, because I want to use the orders recorded in the dummy table, I have to use a more complex code snippet to retrieve the data.
Is there any possibility that I can return an array of article objects and Tastypie can transform them into a proper JSON format.
What you need is extending the queryset in Meta. Assuming your articles table should be ordered by some additional data your queryset would be defined for example like that:
Meta:
queryset = Article.objects.extra(select={
'ordering': 'SELECT foo FROM bar'
},).order_by('ordering')
You have to define the additional fields in your resources:
ordering = field.IntegerField(attribute="ordering", default=0, readonly=True)
The additional field should now be returned with all other fields retrieved from your queryset. Note that if you define the fields attribute in your meta you also have to add the new field there.