Django sort queryset by related model field - mysql

I have the following models (abbreviated for clarity):
class Order(models.Model):
some fields
class OrderStatus(models.Model):
order = models.ForiegnKey(Order)
status = models.CharField(choices=['ORDERED', 'IN_TRANSIT', 'RECEIVED'])
time = models.DateTimeField()
I would like to sort all Orders that contain all three OrderStatuses by their order received time.
In other words, select the orders that have records of Ordered, In Transit, and Received like so:
Order.objects.filter(orderstatus__status=OrderStatus.ORDERED)
.filter(orderstatus__status=OrderStatus.IN_TRANSIT)
.filter(orderstatus__status=OrderStatus.RECEIVED)
... and then sort them by the time field of their related OrderStatus model for which status=OrderStatus.RECEIVED.
This is where I'm stuck. I have read the Django docs on the .extra() queryset modifier and direct SQL injection, but I'm still at a loss. Could I achieve it with an annotated field and Q objects or am I better going the .extra route?

Didn't you try to do like this?
Order.objects.filter(orderstatus__status=OrderStatus.ORDERED)
.filter(orderstatus__status=OrderStatus.IN_TRANSIT)
.filter(orderstatus__status=OrderStatus.RECEIVED)
.order_by('orderstatus__time')
On my models it worked as expected - order_by picked the last joined orderstatus just as you need. If you're not sure you can check the real query like this (in django shell):
from django.db import connection
# perform query
print(connection.queries)
Also it can be done like this:
OrderStatus.objects.filter(status=OrderStatus.RECEIVED)
.order_by('time').select_related('order')
.filter(order__orderstatus__status=OrderStatus.ORDERED)
.filter(order__orderstatus__status=OrderStatus.IN_TRANSIT)

Related

How do I form a Django query with an arbitrary number of OR clauses?

I'm using Django 2.0 with Python 3.7. I want to write a query that returns results if the fields contains at least one of the strings in an array, so I want to set up an OR query. I have tried this
class CoopManager(models.Manager):
...
# Meant to look up coops case-insensitively by part of a type
def contains_type(self, types_arr):
queryset = Coop.objects.all()
for type in types_arr:
queryset = queryset.filter(type__name__icontains=type)
print(queryset.query)
return queryset
However, this produces a query that ANDs the clauses together. How do I perform the above but join all the clauses with an OR instead of an AND?
I'm using MySql 5.7 but I'd like to know a db independent solution if one exists.
You can create a Q object that constructs this disjunction:
from django.db.models import Q
filter = Q(
*[('type__name__icontains', type) for type in types_arr],
_connector=Q.OR
)
queryset = Coop.objects.filter(filter)
Here filter is a Q object that is a disjunction of all the type__name__icontains=type filters.

Is there a specific ordering needed for classes in Peewee models?

I'm currently trying to create an ORM model in Peewee for an application. However, I seem to be running into an issue when querying a specific model. After some debugging, I found out that it is whatever below a specific model, it's failing.
I've moved around models (with the given ForeignKeys still being in check), and for some odd reason, it's only what is below a specific class (User).
def get_user(user_id):
user = User.select().where(User.id==user_id).get()
return user
class BaseModel(pw.Model):
"""A base model that will use our MySQL database"""
class Meta:
database = db
class User(BaseModel):
id = pw.AutoField()
steam_id = pw.CharField(max_length=40, unique=True)
name = pw.CharField(max_length=40)
admin = pw.BooleanField(default=False)
super_admin = pw.BooleanField()
#...
I expected to be able to query Season like every other model. However, this the peewee error I run into, when I try querying the User.id of 1 (i.e. User.select().where(User.id==1).get() or get_user(1)), I get an error returned with the value not even being inputted.
UserDoesNotExist: <Model: User> instance matching query does not exist:
SQL: SELECT `t1`.`id`, `t1`.`steam_id`, `t1`.`name`, `t1`.`admin`, `t1`.`super_admin` FROM `user` AS `t1` WHERE %s LIMIT %s OFFSET %s
Params: [False, 1, 0]
Does anyone have a clue as to why I'm getting this error?
Read the error message. It is telling you that the user with the given ID does not exist.
Peewee raises an exception if the call to .get() does not match any rows. If you want "get or None if not found" you can do a couple things. Wrap the call to .get() with a try / except, or use get_or_none().
http://docs.peewee-orm.com/en/latest/peewee/api.html#Model.get_or_none
Well I think I figured it out here. Instead of querying directly for the server ID, I just did a User.get(1) as that seems to do the trick. More reading shows there's a get by id as well.

Convert UUID to string in Django queryset

I have a model with a UUID as primary key.
class Books(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = .....
And I have a simple query:
results = Books.objects.all()
All is working fine in terms of saving and retrieving data, but part of the record editing process requires storing records in the session variables which means I get the 'UUID('…') is not JSON serializable' error.
It seems to me, that the simplest answer is to convert the UUID objects to strings immediately after making the initial query, thus preventing multiple changes elsewhere. Does that sound logical? If so, I assume I could do it with some sort of list comprehension. Could someone help with the syntax please? Or direct me on the approach if preferred!
Many thanks.
#you have to use
import json
results = Books.objects.all().values('id')
json_str = json.dumps(results)

what is the equivalent ORM query in Django for sql join

I have two django models and both have no relation to each other but have JID in common(I have not made it foreign key):
class result(models.Model):
rid = models.IntegerField(primary_key=True, db_column='RID')
jid = models.IntegerField(null=True, db_column='JID', blank=True)
test_case = models.CharField(max_length=135, blank=True)
class job(models.Model):
jid = models.IntegerField(primary_key = True, db_column='JID')
client_build = models.IntegerField(max_length=135,null=True, blank=True)
I want to achieve this sql query in ORM:
SELECT *
FROM result
JOIN job
ON job.JID = result.JID
Basically I want to join two tables and then perform a filter query on that table.
I am new to ORM and Django.
jobs = job.objects.filter(jid__in=result.objects.values('jid').distinct()
).select_related()
I don't know how to do that in Django ORM but here are my 2 cents:
any ORM makes 99% of your queries super easy to write (without any SQL). For the 1% left, you've got 2 options: understand the core of the ORM and add custom code OR simply write pure SQL. I'd suggest you to write the SQL query for it.
if both table result and job have a JID, why won't you make it a foreign key? I find that odd.
a class name starts with an uppercase, class *R*esult, class *J*ob.
You can represent a Foreign Key in Django models by modifying like this you result class:
class result(models.Model):
rid = models.IntegerField(primary_key=True, db_column='RID')
# jid = models.IntegerField(null=True, db_column='JID', blank=True)
job = models.ForeignKey(job, db_column='JID', blank=True, null=True, related_name="results")
test_case = models.CharField(max_length=135, blank=True)
(I've read somewhere you need to add both blank=True and null=True to make a foreign key optional in Django, you may try different options).
Now you can access the job of a result simply by writing:
myresult.job # assuming myresult is an instance of class result
With the parameter related_name="results", a new field will automatically be added to the class job by Django, so you will be able to write:
myjob.results
And obtain the results for the job myjob.
It does not mean it will necessarilly be fetched by Django ORM with a JOIN query (it will probably be another query instead), but the effect will be the same from your code's point of view (performance considerations aside).
You can find more information about models.ForeignKey in Django documentation.

Django queryset count with extra select

I have a model with PointField for location coordinates. I have a MySQL function that calculates the distance between two points called dist. I use extra() "select" to calculate distance for each returned object in the queryset. I also use extra() "where" to filter those objects that are within a specific range. Like this
query = queryset.extra(
select={
"distance":"dist(geomfromtext('%s'),geomfromtext('%s'))"%(loc1, loc2)
},
where=["1 having `distance` <= %s"%(km)]
) #simplified example
This works fine for getting and reading the results, except when I try counting the resultset I get the error that 'distance' is not a field. After exploring a bit further, it seems that count ignores the "select" from extra and just uses "where". While the full SQL query looks like this:
SELECT (dist(geomfromtext('POINT (-4.6858300000000003 36.5154300000000021)'),geomfromtext('POINT (-4.8858300000000003 36.5154300000000021)'))) AS `distance`, `testmodel`.`id`, `testmodel`.`name`, `testmodel`.`email`, (...) FROM `testmodel` WHERE 1 having `distance` <= 50.0
The count query is much shorter and doesn't have the dist selection part:
SELECT COUNT( `testmodel`.`id`) FROM `testmodel` WHERE 1 having `distance` <= 50.0
Logically, MySQL gives an error because "distance" is undefined. Is there a way to tell Django it has to include the extra select for the count?
Thanks for any ideas!
You could use a raw query if you are not plannig to use any other database system.
params = {'point1':wktpoint1, 'point2':wktpoint2}
query = """
SELECT
dist(%(point1)s, %(point2)s)
FROM
testmodel
;"""
query_set = self.raw(query, params)
Also, if you need more GIS support, you should evaluate PostgreSQL+PostGIS (If you don't like to reinvent the wheel, you should not make your own dist function)
Django offers GIS support through GeoDjango. There you got functions like distance. You should check support here
In order to use GeoDjango you need to add a field on yout model, to tell them to use the GeoManager, Then you can start doing geoqueries, and you should have no problems with count.
with mysql you cando something like this using geodjango
### models.py
from django.contrib.gis.db import models
class YourModel(models.Model):
your_geo_field=models.PolygonField()
#your_geo_field=models.PointField()
#your_geo_field=models.GeometryField()
objects = models.GeoManager()
### your code
from django.contrib.gis.geos import *
from django.contrib.gis.measure import D
a_geom=fromstr('POINT(-96.876369 29.905320)', srid=4326)
distance=5
YoourModel.objects.filter(your_geo_field__distance_lt=(a_geom, D(m=distance))).count()
you can see better examples here and the reference here