Django Query using .order_by() and .latest() - python

I have a model:
class MyModel(models.Model):
creation_date = models.DateTimeField(auto_now_add = True, editable=False)
class Meta:
get_latest_by = 'creation_date'
I had a query in my view that did the following:
instances = MyModel.objects.all().order_by('creation_date')
And then later I wanted instances.latest(), but it would not give me the correct instance, in fact it gave me the first instance. Only when I set order_by to -creation_date or actually removed the order_by from the query did .latest() give me the correct instance. This also happens when I test this manually using python manage.py shell instead of in the view.
So what I've done now is in the Model's Meta I've listed order_by = ['creation_date'] and not used that in the query, and that works.
I would have expected .latest() to always return the most recent instance based on a (date)(time) field. Could anyone tell me whether it's correct that .latest() behaves strangely when you use order_by in the query?

I would have expected .latest() to always return the most recent instance based on a (date)(time) field.
The documentation says that
If your model's Meta specifies get_latest_by, you can leave off the field_name argument to latest(). Django will use the field specified in get_latest_by by default.
All this means is that when you fire MyModel.objects.latest() you will get the latest instance based on the date/time field. And when I tested your code using sample data, it indeed did.
And then later I wanted instances.latest(), but it would not give me the correct instance, in fact it gave me the first instance.
You have misunderstood the way latest() works. When called on MyModel.objects it returns
the latest instance in the table. When called on a queryset, latest will return the first object in the queryset. Your queryset consisted of all instances of MyModel ordered by creation_date in ascending order. It is only natural then that latest on this queryset should return the first row of the queryset. This incidentally happens to be the oldest row in the table.
One way to get a better understanding is to view the query fired for latest.
Case 1:
from django.db import connection
MyModel.objects.latest()
print connection.queries[-1]['sql']
This prints:
SELECT "app_mymodel"."id", "app_mymodel"."creation_date" FROM
"app_mymodel" ORDER BY "app_mymodel"."creation_date" DESC LIMIT 1
Note the ordering by creation_date DESC and the LIMIT clause. The former is thanks to get_latest_by whereas the latter is the contribution of latest.
Now, case 2:
MyModel.objects.order_by('creation_date').latest()
print connection.queries[-1]['sql']
prints
SELECT "app_mymodel"."id", "app_mymodel"."creation_date" FROM
"app_mymodel" ORDER BY "app_mymodel"."creation_date" ASC LIMIT 1
Note that the ordering has changed to creation_date ASC. This is the result of the explicit order_by. The LIMIT is tacked on er, later courtesy latest.
Let us also see Case 3: where you explicitly specify the field_name for objects.latest().
MyModel.objects.latest('id')
print connection.queries[-1]['sql']
shows
SELECT "app_mymodel"."id", "app_mymodel"."creation_date" FROM "app_mymodel"
ORDER BY "app_mymodel"."id" DESC LIMIT 1

I guess this is a known bug in Django that was fixed after 1.3 was released.

This worked for me
latestsetuplist = SetupTemplate.objects.order_by('-creationTime')[:10][::1]

if we have the value of id or date
post_id = BlogPost.objects.get(id=id)
try:
previous_post = BlogPost.objects.all().order_by('id')[post_id.id-2:post_id.id-1]
except:
previous_post = None
try:
next_post = BlogPost.objects.all().order_by('id')[post_id.id:post_id.id+1]
except:
next_post = None
it worked for me, even if an id is missing it picks next or previous value to that

Related

How to clear ordering when using .first() in django

I have a query in database as follows. Whenever I try to do a .first() on the query, The equivalent query that gets run in the database is as follows
SELECT * FROM “user" WHERE UPPER("user"."email"::text) = UPPER(%s) **ORDER BY** "registration_user"."id" ASC LIMIT 1
I want to get rid of the order by clause as it interferes with indexes being applied correctly in the db and is also a costly operation. How can I refactor the code below?
users = User.objects.filter(email__iexact=email)
users.query.**clear_ordering**(True)
if users.count() > 0 :
return users.first()
If no ordering is specified, that would mean that two calls with .first() can return a different element, and non-determinism often results in a lot of problems.
The ORDER BY pk is added by the .first() [Django-doc] call, so it is not part of your query at all. If the queryset has no ordering, then .first() will add an ordering by pk (primary key), as is described in the documentation:
first()
Returns the first object matched by the queryset, or None if there
is no matching object. If the QuerySet has no ordering defined, then
the queryset is automatically ordered by the primary key. This can
affect aggregation results as described in Interaction with default
ordering or order_by().
If you really do not want an ordering, you can subscript the queryset:
users = User.objects.filter(email__iexact=email)
if users.exists():
return users[0]
But that does not look like a very good idea. If no order is specified, then the database can return any record that matches the filtering condition, and it can thus return a different record each query.

display rawqueryset value in a model using django #property field

I am trying to insert a #property field into a django model.
However this field gets its value from a RawQuerySet like so;
class Medication_List(models.Model):
id = models.AutoField(primary_key=True)
medication_name = models.CharField(max_length=250, blank=True, editable=True)
...
def stock_level(self):
# "Returns the totalstock remaining"
current_stock = Medications_Bridge.objects.raw("SELECT T3.total_quantity_sold AS total_quantity_sold FROM (SELECT * FROM ...
return({'current_stock' : current_stock})
stock = property(stock_level)
def __str__(self):
return "%s %s %s" % (self.medication_name, self.supplier,self.stock)
This works fine with no errors, but django is not showing value for the queryset in the str function, rather it just shows the whole query just as it was written above. i.e. "SELECT T3.total_quantity....."
I want to know if it is even possible to use #property field to show a value from another unrelated database table.
I have seen some examples on StackOverflow where calculated values are shown using values from the same model with help of self.field_name but i cant find any example where the values for the calculations are derived from another model or from a raw SQL query.
Kindly help. Any hint will be very much appreciated. Thanks
Okay so upon further reading, i just found from this post
Django RawQuerySet not working as expected
that QuerySets in django are not evaluated until necessary, that is, it does not hit the database until necessary.
Upon following the links to the QuerySet API i was able to find a way to do exactly what i wanted.
As is mostly always the case, you find the answer soon after you blurt out the question. LOL

How to create the related field?

I have a Model you see bellow:
class PriceModel(models.Model):
name = models.CharField(max_length=12)
the PriceModel's id is increased from 1. I want to add other field, which is related to the id. Such as I want it to #PM1, #PM2... means the #PM + id.
How to create the related field?
I want to add other field, which is related to the id. Such as I want it to #PM1, #PM2... means the #PM + id.
First of all, it is not guaranteed that for all database systems, the id is always incremented with one. For example if the database works with transactions, or other mechanisms, it is possible that a transaction is rolled back, and hence the corresponding id is never taken.
If you however want some "field" that always depends on the value of a field (here id), then I think a #property is probably a better idea:
class PriceModel(models.Model):
name = models.CharField(max_length=12)
#property
def other_field(self):
return '#PM{}'.format(self.id)
So now if we have some PriceModel, then some_pricemodel.other_field will return a '#PM123' given (id is 123). The nice thing is that if the id is changed, then the property will change as well, since it is each time calculated based on the fields.
A potential problem is that we can not make queries with this property, since the database does not know such property exists. We can however define an annotation:
from django.db.models import Value
from django.db.models.functions import Concat
PriceModel.objects.annotate(
other_field=Concat(Value('#PM'), 'id')
).filter(
other_field__icontains='pm1'
)
I think you can in your model serializer add a special field, rather than add a field in the Model.
class PriceModelSerializer(ModelSerializer):
....
show_id = serializers.SerializerMethodField(read_only=True)
class Meta:
model = PriceModel
def get_show_id(self):
return '#PM' + self.id

Prefetch object not working with order_by queryset

Using Django 11 with PostgreSQL db. I have the models as shown below. I'm trying to prefetch a related queryset, using the Prefetch object and prefetch_related without assigning it to an attribute.
class Person(Model):
name = Charfield()
#property
def latest_photo(self):
return self.photos.order_by('created_at')[-1]
class Photo(Model):
person = ForeignKey(Person, related_name='photos')
created_at = models.DateTimeField(auto_now_add=True)
first_person = Person.objects.prefetch_related(Prefetch('photos', queryset=Photo.objects.order_by('created_at'))).first()
first_person.photos.order_by('created_at') # still hits the database
first_person.latest_photo # still hits the database
In the ideal case, calling person.latest_photo will not hit the database again. This will allow me to use that property safely in a list display.
However, as noted in the comments in the code, the prefetched queryset is not being used when I try to get the latest photo. Why is that?
Note: I've tried using the to_attr argument of Prefetch and that seems to work, however, it's not ideal since it means I would have to edit latest_photo to try to use the prefetched attribute.
The problem is with slicing, it creates a different query.
You can work around it like this:
...
#property
def latest_photo(self):
first_use_the_prefetch = list(self.photos.order_by('created_at'))
then_slice = first_use_the_prefetch[-1]
return then_slice
And in case you want to try, it is not possible to use slicing inside the Prefetch(query=...no slicing here...) (there is a wontfix feature request for this somewhere in Django tracker).

Count rows of a subquery in Django 1.11

I have a couple models
class Order(models.Model):
user = models.ForeignKey(User)
class Lot(models.Model):
order = models.ForeignKey(Order)
buyer = models.ForeignKey(User)
What I'm trying to do is to annotate Lot objects with a number of buys made by a given user to the same seller. (it's not a mistake, Order.user is really a seller). Like “you’ve bought 4 items from this user recently”.
The closest I get was
recent_sold_lots = Lot.objects.filter(
order__user_id=OuterRef('order__user_id'),
status=Lot.STATUS_SOLD,
buyer_id=self.user_id,
date_sold__gte=now() - timedelta(hours=24),
)
qs = Lot.objects.filter(
status=Lot.STATUS_READY,
date_ready__lte=now() - timedelta(seconds=self.lag)
).annotate(same_user_recent_buys=Count(Subquery(recent_sold_lots.values('id'))))
But it fails when recent_sold_lots count is more than one: more than one row returned by a subquery used as an expression.
.annotate(same_user_recent_buys=Subquery(recent_sold_lots.aggregate(Count('id'))) doesn't seem to work also: This queryset contains a reference to an outer query and may only be used in a subquery.
.annotate(same_user_recent_buys=Subquery(recent_sold_lots.annotate(c=Count('id')).values('c')) is giving me Expression contains mixed types. You must set output_field.. If I add output_field=models.IntegerField() to the subquery call, it throws more than one row returned by a subquery used as an expression.
I'm stuck with this one. I feel I'm close to the solution, but what am I missing here?
The models you defined in the question do not correctly reflect the query you are making. In any case i'll use the model as a reference to my query.
from django.db.models import Count
user_id = 123 # my user id and also the buyer
buyer = User.objects.get(pk=user_id)
Lot.objects.filter(buyer=buyer).values('order__user').annotate(unique_seller_order_count=Count('id'))
What the query does is:
Filters the lot objects to the ones you have bought
Groups the Returned lots into the user who created the order
Annotates/Counts the responses for each group

Categories