Is it possible to do arithmetic operation on OuterRef expression? - python

I'm building an Experience booking system in django and I've the following models.
class Experience(models.Model):
name = models.CharField(max_length=20)
capacity = models.IntegerField()
class Booking(models.Model):
experience = models.ForeignKey(Experience)
occupied = models.IntegerField()
Each experience has a limited number of capacity and when user perform booking, it will be added to the Booking table with occupied number. Now how will I find the experiences which are not occupied completely?
available_experiences = Experience.objects.all().exclude(id__in=Subquery(Booking.objects.filter(occupied__gt=OuterRef('capacity') - request_persons).values_list('experience', flat=True)))
Here, request_persons is the number of required vacancy in an experience. This is not working and showing an error like 'ResolvedOuterRef' object has no attribute 'relabeled_clone'. Is it possible to do arithmetic operation on OutRef() expression like F()?
Without adding request_persons, the above code works. Why it is not possible to add a value to the OutRef() expression?
NOTE: My actual code is much complex one and it will be really great to get an answer without modifying the entire structure of the above code.

By doing arithmetic operations in the query referenced by OuterRef() directly you can resolve this issue:
available_experiences = Experience.objects.annotate(
total=models.F('capacity') - request_persons
).exclude(
id__in=Subquery(Booking.objects.filter(
occupied__gt=OuterRef('total')
).values_list('experience', flat=True))
)
If you found another way without modifying your structure or using RawSQL() or .extra(), let us know!

This seems to be fixed in Django 2.0: https://github.com/django/django/pull/9722/files
The fix can be backported to 1.11.x in a similar fashion:
from django.db.models.expressions import ResolvedOuterRef
if not hasattr(ResolvedOuterRef, 'relabeled_clone'):
ResolvedOuterRef.relabeled_clone = lambda self, relabels: self

Related

django queryset filter with back reference

I'm a C++ developer and noob of python, just following django tutorials.
I want to know how to filter queryset by its back references' information.
Below is my models.
# models.py
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
Now, I want to get query set of Question which pub_date is past from now AND is referenced by any Choice. The second statement causes my problem.
Below are what I tried.
# First method
question_queryset = Question.objects.filter(pub_date__lte=timezone.now())
for q in question_queryset.iterator():
if Choice.objects.filter(question=q.pk).count() == 0:
print(q)
# It works. Only #Question which is not referenced
# by any #Choice is printed.
# But how can I exclude #q in #question_queryset?
# Second method
question_queryset = Question.objects.filter(pub_date__lte=timezone.now()
& Choice.objects.filter(question=pk).count()>0) # Not works.
# NameError: name 'pk' is not defined
# How can I use #pk as rvalue in #Question.objects.filter context?
Is it because I'm not familiar with Python syntax? Or is the approach itself to data wrong? Do you have any good ideas for solving my problem without changing the model?
=======================================
edit: I just found the way for the first method.
# First method
question_queryset = Question.objects.filter(pub_date__lte=timezone.now())
for q in question_queryset.iterator():
if Choice.objects.filter(question=q.pk).count() == 0:
question_queryset = question_queryset.exclude(pk=q.pk)
A new concern arises: if the number of #Question rows is n and #Choice's is m, above method takes O(n * m) times, right? Is there any way to increase performance? Could it be that my way of handling data is the problem? Or is the structure of the data a problem?
Here is the documentation on how to follow relationships backwards. The following query yields the same result:
queryset = (Question.objects
.filter(pub_date__lte=timezone.now())
.annotate(num_choices=Count('choice'))
.filter(num_choices__gt=0))
It is probably better to rely on the Django ORM than writing your own filter. I believe that in the best scenario the time complexity will be the same.
Related to the design, this kind of relationship will lead to duplicates in your database, different questions sometimes have the same answer. I would probably go with a many-to-many relationship instead.
Thats not how the querysets are supposed to work. Iterating the quueryset is iterating every data in the queryset that is returned by your database. You don't need to use iterate()
question_queryset = Question.objects.filter(pub_date=timezone.now())
for q in question_queryset:
if Choice.objects.filter(question=q.pk).count() == 0:
print(q)
I didn't test it. But this should work.

How can I override __str__ in models.py?

Apologies if this is a silly question, I am pretty new to python and django.
I am following along with a django tutorial, and we are creating a fake movie site that lists movies by genre, title, etc.
I am currently trying to override the __str__ function in the models.py file, so rather than displaying Genre (1), it displays the actual genre (ex. "Action").
Here's how my models.py looks currently:
from tkinter import CASCADE
from django.db import models
from django.utils import timezone
# Create your models here.
class Genre(models.Model):
name = models.CharField(max_length=255)
def __str__ (self):
return self.name
class Movie(models.Model):
title = models.CharField(max_length=255)
release_year = models.IntegerField()
number_in_stock = models.IntegerField()
daily_rate = models.FloatField()
genre = models.ForeignKey(Genre, on_delete=models.CASCADE)
date_created = models.DateTimeField(default=timezone.now)
However, vscode is underlining the
def __str__ (self):
When I hover over it, it tells me:
str does not return str pylint(E0307: invalid -str- returned
I tried looking at other solutions, but I could not find one that seemed to match my scenario, so I do apologize if this has been solved elsewhere, and I am too incompetent to understand the problem.
Thanks for your patience!
This question has a couple of good illustrations of why it's important to understand your code before you rely too heavily on IDEs and tools like pylint. I've two suggestions:
The pylint error is just a warning to indicate that your code might have a problem - it is not an absolute fact. The E0307 error says:
Used when a __str__ method returns something which is not a string.
It would be more accurate if it had said "when the pylint checker cannot be sure that you're returning a string". In this case, it's because it doesn't recognise that a Django CharField will in fact return a valid string instance. Your existing __str__ method would work perfectly fine if you ran the code - regardless of what pylint thinks.
You can work around this by forcing the return value to look like a string, e.g., with:
return str(self.name)
or
return f"{self.name}"
But it would be equally valid to disable the check for this line in pylint, with the understanding of why it reported the error. Just applying a fix found on Stack Overflow without understanding the issue is going to make it hard to debug your code in future.
There is a second, completely unrelated issue in your code which will cause you problems later, and that is this line:
from tkinter import CASCADE
I am pretty confident that this has been inserted by your IDE, without your explicitly adding it, because you tried to use CASCADE in your ForeignKey. This is the IDE trying to be helpful - unfortunately it has imported something completely useless, that can cause you problems when you try to deploy your code.
Both of these highlight an important principle: don't rely on IDEs or linters. They are not a substitute for understanding your own code.

Django and multiple joins

I'm trying to do a quite long join, but it seems like django can't handle the last layer. Am I getting it wrong or is there any way around this?
Models.py
class A(models.Model):
object_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=50)
class B(models.Model):
class_A_object = models.ForeignKey(A, on_delete=models.PROTECT)
value = models.CharField(max_length=50)
class C(models.Model):
class_B_object = models.ForeignKey(B, on_delete=models.PROTECT)
class D(models.Model):
value= models.IntegerField(primary_key=True)
class_C_object = models.ForeignKey(C, on_delete=models.PROTECT)
I'm then trying to select the value in class D when the related class A object name = ABC.
D.objects.filter(class_C_object__class_B_object__class_A_object__name='ABC')
This fails, to begin with pycharm refuses the autocomplete it and if I run it i get a name not defined error.
However, if i drop one layer it works.
D.objects.filter(class_C_object__class_B_object__value='ABC')
I haven't found any documentation mentioning a maximum number of joins, but it feels like there is a limitation here.
Does anyone know if that's the case, and if so, what the best way would be to work around this?
The database is external to me and can not be modified. The only working decent workaround i have at the moment is to use the cursor directly and write the sql by hand. However, this is not ideal for a lot of reasons.
Any help would be much appreciated.
Kind Regards,
Peter
I have solved this, maybe not the neatest solution, but by getting a queryset of A matching name ABC and then using that queryset for a __in filter on D get the result I want, and it's just doing one query.
It works, and is efficient, but the code is not very clean. If anyone has a suggestion of another way to do this i would be very happy to hear.
Thanks,
Peter

Django ORM Left Join With GROUP BY and SUM

I am using Django 1.8.
I have a User model and a UserAction model. A user has a type. UserAction has a time, which indicates how long the action took. They look like this:
class User(models.Model):
user_type = models.IntegerField()
class UserAction:
user = models.ForeignKey(User)
time = models.IntegerField()
Now what I want to do is get ALL users of a certain type along with the sum of the time of their actions.So it would look something like this:
{user:1,total_time=5000}, {user:2,total_time=230}, {user:3,total_time=0}
Given I have the required type in a var called type, what I am doing is:
UserAction.objects.filter(user__type=type)
.values('user').annotate(total_time=Sum(time))
This almost does what I need it to however it does not include users of the given type that don't have any UserAction associated with them, in that case I would want the total_time to just be 0. I've been doing some searching but am not quite sure how I would do this. I know how I would do it in raw SQL(just do a left join) but the Django ORM is still pretty new to me. Could anyone point me in the right direction here? Any advice would be appreciated, thanks much!
User.objects.filter(user_type=type).values('id').annotate(total_time=Sum(useraction__time))

Column comparison in Django queries

I have a following model:
class Car(models.Model):
make = models.CharField(max_length=40)
mileage_limit = models.IntegerField()
mileage = models.IntegerField()
I want to select all cars where mileage is less than mileage_limit, so in SQL it would be something like:
select * from car where mileage < mileage_limit;
Using Q object in Django, I know I can compare columns with any value/object, e.g. if I wanted to get cars that have mileage say less than 100,000 it would be something like:
cars = Car.objects.filter(Q(mileage__lt=100000))
Instead of a fixed value I would like to use the column name (in my case it is mileage_limit). So I would like to be able to do something like:
cars = Car.objects.filter(Q(mileage__lt=mileage_limit))
However this results in an error, since it is expecting a value/object, not a column name. Is there a way to compare two columns using Q object? I feel like it would be a very commonly used feature and there should be an easy way to do this, however couldn't find anything about it in the documentation.
Note: this is a simplified example, for which the use of Q object might seem to be unnecessary. However the real model has many more columns, and the real query is more complex, that's why I am using Q. Here in this question I just wanted to figure out specifically how to compare columns using Q.
EDIT
Apparently after release of Django 1.1 it would be possible to do the following:
cars = Car.objects.filter(mileage__lt=F('mileage_limit'))
Still not sure if F is supposed to work together with Q like this:
cars = Car.objects.filter(Q(mileage__lt=F('mileage_limit')))
You can't do this right now without custom SQL. The django devs are working on an F() function that would make it possible: #7210 - F() syntax, design feedback required.
Since I had to look this up based on the accepted answer, I wanted to quickly mention that the F() expression has indeed been released and is available for being used in queries.
This is what the Django documentation on F() says about it:
An F() object represents the value of a model field, transformed value of a model field, or annotated column. It makes it possible to refer to model field values and perform database operations using them without actually having to pull them out of the database into Python memory.
Instead, Django uses the F() object to generate an SQL expression that describes the required operation at the database level.
The reference for making queries using F() also gives useful examples.

Categories