Django query fetch foreignkey association without N+1 queries in database - python-3.x

I have two models, Product and Price. I have used the ForeignKey association of the Django models to define the association between product and price. the scenario is, one product can have multiple prices according to size. On the home page, I have to fetch all the products with their prices and need to show their price(probably base price).
Following is the code that I tried.
class Product(BaseModel):
name = models.CharField(max_length=50)
category = models.ForeignKey(Category, null=True, on_delete=models.SET_NULL, help_text='Please add new category if not given.')
image = models.ImageField(upload_to='images/')
tag = models.ManyToManyField(Tag, help_text='You can add multiple tags')
slug = models.SlugField(unique=True, null=True)
time = models.TimeField(verbose_name='Time Required')
class Price(BaseModel):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
size = models.FloatField(validators=[MinValueValidator(0)])
amount = models.FloatField(validators=[MinValueValidator(0)])
Then in the view.py file
class ProductListView(ListView):
model = Product
context_object_name = 'products'
paginate_by = 32
def get_context_data(self,*args, **kwargs):
object = super(ProductListView, self).get_context_data(*args, **kwargs)
object['categories'] = Category.objects.order_by('name')
return object
def get_queryset(self):
return Product.objects.order_by('name')
In the template, I am able to get and loop through the categories and products but I am not able to access the related prices of each product.
If I tried something in the get_context_data, will it cause N+1 queries to fetch prices for every product?
In the template I tried to use something like {{ product.price_set }} but it returns order.Price.None.

use {{ product.price_set.all }}.
To avoid N+1 queries in your filter, use prefetch_related so it looks something like.
Product.objects.all().prefetch_related('price_set')
See prefetch_related in the Django documentation.
See also select related vs prefetch related

Related

Django ORM query with user defined fields

I'm trying to create an Django ORM query to replace a really messy raw SQL query i've written in the past but i'm not sure if Django ORM can let me do it. I have three tables:
contacts
custom_fields
custom_field_data
What I'm hoping to be able to do with the ORM is create an output as if i've queryied one single table like this:
Is such a thing possible with the ORM?
EDIT:
The models are:
class Contact(models.Model):
company = models.ForeignKey(Company, on_delete=models.PROTECT, null=True, blank=True)
class CustomField(models.Model):
name = models.CharField(max_length=100)
company = models.ForeignKey(Company, on_delete=models.PROTECT, null=False, blank=False)
class ContactCustomFieldValue(models.Model):
custom_field = models.ForeignKey(CustomField, on_delete=models.PROTECT, related_name='contact_values')
contact = models.ForeignKey(Contact, on_delete=models.PROTECT, related_name='custom_field_values', null=True)
value = models.TextField(null=True)
Solved this one in large part thanks to Ken from the Django forums.
The solution he provided looked like this:
subquery1 = Subquery(ContactCustomFieldValue.objects.filter(contact=OuterRef('id'), custom_field_id=1).values_list('value'))
subquery2 = ...
subquery3 = ...
subquery4 = ...
contact_list = Contact.objects.filter(...).annotate(field1=subquery1, ...)
I've built on it to fit my needs but as a starting point this was perfect

retrieve data from manytomany field in the sequence of it was saved django

I Have a Invoice system where employee or staff can create invoice and can add multiple product and quantity for the specific customer . as i am using mysql i cant take json data or an array data .so i was taking the quantity and price( after discount and other modificaion) as a string and then when showing or printing the invoice i used regex to find the quantity and price .i added product ids in a manytomay field from where i am getting the product name and selling price. while showing the data on printing page in when i use zip the products are showing as the id of the product so i want to retrive the data the way it is being saved . or could you tell me any way to do it more easier way?
Here is my models.py
class Invoice(models.Model):
customers = models.ForeignKey(Customer, on_delete=models.CASCADE)
products = models.ManyToManyField(Product)
total = models.FloatField()
date = models.DateField(auto_now_add=True)
amounts = models.CharField(max_length=500, default="00")
quantity = models.CharField(max_length=500, blank=True)
def save(self, *args, **kwargs):
if not Invoice.objects.count():
self.id = 20210001
else:
self.id = Invoice.objects.last().id + 1
super(Invoice, self).save(*args, **kwargs)
Here is my views.py of printing page function
def final_billing(request, id=None):
pk = id
obj = Invoice.objects.get(id=pk)
products = obj.products.all()
customer = Customer.objects.get(id=obj.customers.id)
amn = obj.amounts
qt = obj.quantity
list_of_comma = re.findall("[\d+.+\d]+", amn)
amnts = [float(n) for n in list_of_comma]
list_of_quantity = re.findall('[0-9]+', qt)
qty = [int(n) for n in list_of_quantity if n.isdigit()]
products = list(products)
both = zip(products,amnts,qty)
return render(request, 'finalbill.html',{'bills': obj, "due": customer, "both": both})
I want it to be retrieved the product objects in the sequence of it was saved
The query can only be sorted with a specific field, Django cannot guess otherwise, so in your case the best case is to sort your products by the date they were created, for example :
obj.products.all().order_by("created")
This suppose that you have a "created" field that is added each time a product is save in your database.
Another way of doing it is to specify the through option, from the documentation :
you can use the through option to specify the Django model that represents the intermediate table that you want to use.
The most common use for this option is when you want to associate extra data with a many-to-many relationship.
The through table contains an the primary key of the relation, you can use that to retrieve the sequence in which your objects were added.
for example :
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
Through Field

Merge two redundant models in Django

I'm working on an online shop at the moment. The shop is written in Django, and was programmed by another person, so I spent days trying to make sense of what he did. At the moment the shop sells only two articles, on two different pages (meaning they have to be bought separately), so the shop is heavily oriented towards this situation. The problem is, the shop's owner expressed his interest in selling everything in one page in the near future, and in adding more products. And that's where my problems start.
The model structure was something like this (extremely simplified):
class Category1(BaseModel):
name = models.CharField(max_length=32)
class Category2(BaseModel):
name = models.CharField(max_length=64)
class Price1(BaseModel):
category = models.ForeignKey(Category1, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Price2(BaseModel):
category = models.ForeignKey(Category2, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Order1(BaseModel):
[personal information fields]
class Order2(BaseModel):
[personal information fields]
class Article1(BaseModel):
price = models.ForeignKey(Price1, on_delete=models.CASCADE)
order = models.ForeignKey(Order1, on_delete=models.CASCADE, related_name='articles')
class Article2(BaseModel):
price = models.ForeignKey(Price2, on_delete=models.CASCADE)
order = models.ForeignKey(Order2, on_delete=models.CASCADE, related_name='articles')
There is much more than this, of course, but this should be enough to show the relationships between the models. The complete structure of course makes more sense than this one. BaseModel is a class that contains an ID, creation time and last edit.
I managed to put all the common elements into abstract classes BaseCategory, BasePrice, BaseOrder and BaseArticle, but this is not enough if I want to really expand the shop. Finishing this work is just a matter of time and patience, but how should I proceed once I'm in this situation?
class BaseCategory(BaseModel):
name = models.CharField(max_length=64)
class Meta:
abstract = True
class Category1(BaseCategory):
pass
class Category2(BaseCategory):
pass
class BasePrice(BaseModel):
price = models.DecimalField(max_digits=16, decimal_places=2)
currency = models.CharField(max_length=3)
class Meta:
abstract = True
class Price1(BasePrice):
category = models.ForeignKey(Category1, on_delete=models.CASCADE)
class Price2(BasePrice):
category = models.ForeignKey(Category2, on_delete=models.CASCADE)
class BaseOrder(BaseModel):
[personal information fields]
class Meta:
abstract = True
class Order1(BaseOrder):
pass
class Order2(BaseOrder):
pass
class BaseArticle(BaseModel):
class Meta:
abstract = True
class Article1(BaseArticle):
price = models.ForeignKey(Price1, on_delete=models.CASCADE)
order = models.ForeignKey(Order1, on_delete=models.CASCADE, related_name='articles')
class Article2(BaseArticle):
price = models.ForeignKey(Price2, on_delete=models.CASCADE)
order = models.ForeignKey(Order2, on_delete=models.CASCADE, related_name='articles')
I need to get rid of the specific classes completely, otherwise when I will add new articles, I will have to create new classes, and this is not a scalable solution.
My problems are the following:
How do I get rid of the empty specific classes like Price1 or Order1 without losing any information? I know I will have to get rid of the abstract variable, but I don't know what to do next.
How do I manage the foreign keys in the remaining classes? I'm experimenting a bit with GenericForeignKey at the moment, and this would probably let me move the declarations into the base classes, but I'm not sure if changing a definition will reset all fields.
Just to be clear, the shop is already up and running. We can't stop it, and we can't lose data. We sell services, so the customers have to be able to access their products even long after the purchase.
Thanks in advance for your interest and your time.
To keep this answer short we will only discuss one model here. In this instance Category. Firstly add a new model Category (keep your other models for now):
class Category(BaseModel):
name = models.CharField(max_length=64)
Next run makemigrations this would generate a migration file to make this new table in the database. After this you need to make a Data Migration [Django docs] to copy the data from the other two tables that you have.
To do this first run:
python manage.py makemigrations --empty yourappname
This will generate a migration file that does nothing for now. We will edit this migration file and add some code to copy the data from your other tables to this new table. In the end your migration file would look something like:
from django.db import migrations
def populate_category(apps, schema_editor):
Category1 = apps.get_model('yourappname', 'Category')
Category2 = apps.get_model('yourappname', 'Category')
Category = apps.get_model('yourappname', 'Category')
# add all fields except the pk in values(), i.e. values('field1', 'field2')
for category in Category1.objects.values('name'):
Category.objects.create(**category) # Add some field indicating this object is of Category1 if needed
for category in Category2.objects.values('name'):
Category.objects.create(**category) # Add some field indicating this object is of Category2 if needed
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(populate_category, reverse_code=migrations.RunPython.noop),
]
Now you can simply run python manage.py migrate and you would have a new table Category which has all the data from Category1 and Category2. (This might take some time if there are many rows). After this you can remove the models Category1 and Category2 and migrate again to remove those tables.
Note: Perform these operations carefully, and make sure you have got the data properly in the new table before deleting the old ones.
Refer the documentation linked above for more information on
migrations. (Test this on a local development server before doing it
on production to be safe)

Proper way to do a robust search in Django models via REST framework

I'm writing a web application (DRF + Vue.js) where frontend should have an ability to narrow down GET request results via different filters.
For example, I have a model like this:
class Human(models.Model):
first_name = models.CharField(_('first name'), max_length=50, null=True, blank=True)
last_name = models.CharField(_('last name'), max_length=50, null=True, blank=True)
birth_date = models.DateField(_('birth date'), blank=True, null=True)
city = models.ForeignKey('City', on_delete=models.SET_NULL, blank=True, null=True)
phone_number = models.ForeignKey('Contact' on_delete=models.SET_NULL, blank=True, null=True)
#property
def full_name(self):
# Last name + First name
return ' '.join(str(x) for x in (self.last_name, self.first_name) if x)
#property
def is_adult(self):
now = timezone.now()
if self.birth_date:
if now.year - self.birth_date.year - \
((now.month, now.day) < (self.birth_date.month, self.birth_date.day)) >= 18:
return True
return False
Now I have simple CRUD ViewSet where I can use a list of search_fields to search by all needed fields (in my case that's birth_date, city, phone_number and full_name/is_adult). But here next problems arise:
Using search_fields I can do a search only by all fields specified in the ViewSet's search_fields (frontend can't search only by city or other distinct fields if it wants to) - other way I'll need to create a separate ViewSet (and separate URL?) for every combination of fields to filter by. Terrific.
So it looks like the correct decision must be capable of filtering by several GET parameters at once. Ideally - with opportunity to choose exact/icontains/etc comparison method on each query.
That sounds like a work for django-filter but I'm not sure yet.
It's impossible to search/filter by full_name or is_adult because they are dynamic model properties, not usual fields.
So it looks like instead of using model properties I need to use separate QuerySets (Manager methods?) that will do the logic of fiddling with model fields and creating the filtered result.
But for now I didn't find a way to choose different QuerySets in a single ViewSet depending on GET parameters (or how to use search by these complex properties together with simple search from problem 1?).
And I have no understanding if it is possible to provide this kind of search by the same URL as a "simple" search - like site.com/api/people/?city=^New&full_name=John%20Doe (perfectly - with opportunity to document query parameters for OpenAPI schema / Swagger)
So maybe someone knows which is the most elegant way to provide a capability of such complex search with Django/DRF? In which direction should I look?
"full_name" alias needs to be part of your queryset.
You can achieve it by queryset annotation.
In your People view (api/people/ controller) you have to set your queryset to be:
from django.db.models.functions import Concat
from django.db.models import Value
queryset = Human.objects.annotate(fullname=Concat('first_name', Value(' '), 'last_name'))
Also, customize your "filter_backed" to act the way you need.
class PeopleFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
params_serializer = PeopleFilterSerializer(data=request.params)
params_serializer.is_valid(raise_exception=True)
valid_params = params_serializer.data
if full_name := valid_params.get("full_name")
queryset = queryset.filter(full_name__icountains=full_name)
# insert here all of other filters logic
return queryset
PeopleFilterSerializer is your custom params serializer,
You have to specify there all of your accepted params:
from rest_framework import serializers
class PeopleFilterSerializer(serializers.Serializer):
full_name = serializers.CharField() # set required=False if not required
city = serializer.CharField()

How can I join two child tables using Django Querset API?

I have a model with two child tables and one parent table. Here is the sample model classes.
# Stores list of unique category names
class Category(models.Model):
category = models.CharField(max_length=20, unique=True, validators=[MinLengthValidator(limit_value=5)])
name = models.CharField(max_length=20, validators=[MinLengthValidator(limit_value=8)])
# Parent class for the next two child classes
class DailyLog(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=100, validators=[MinLengthValidator(limit_value=8)])
code = models.CharField(max_length=4, validators=[MinLengthValidator(limit_value=3)])
suggested_values = models.CharField(max_length=100, null=True, blank=True)
# First child class defines display order for dailylog items
class DailyLogDisplayOrder(models.Model):
category_item = models.ForeignKey(DailyLog, on_delete=models.CASCADE)
display_order = models.PositiveIntegerField()
# Second Child class publishes dailylog on a daily bases
class DailyLogCheckList(models.Model):
daily_task = models.ForeignKey(DailyLog, on_delete=models.CASCADE)
publish_date = models.DateField(auto_now=True)
daily_task = DailyTaskCategoryManager() # Log manager to get records per category
How do I perform a cartesian product query? The last column comes from the first child table Dailylogdisplayorder. Here is the raw sql.
select daily_task_id, checklist.publish_date, disporder.display_order
from dailylogchecklist checklist, compliance_dailylogdisplayorder disporder
where checklist.daily_task_id = disporder.category_item_id and checklist.publish_date='2020-07-12'
I have tried using cursor.execute() method per Django documentation. However, I am not able to figure out how to return results in QuyerySet. And also looking for a better way to combine child columns using QuerySet. The return queryset is assigned to a formset.
class DailyTaskCategoryManager(models.Manager):
def with_displayorder(self, user):
from django.db import connection
dtrange = datetime.today().date()
with connection.cursor() as cursor:
cursor.execute("select daily_task_id, checklist.publish_date, disporder.display_order
from dailylogchecklist checklist, compliance_dailylogdisplayorder disporder
where checklist.daily_task_id = disporder.category_item_id and
checklist.publish_date=%s", [dtrange])
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], daily_task_id=row[1], publish_date=row[2])
p.display_order = row[3]
result_list.append(p)
return result_list
I already answered to a similar question, You can use prefetch_related() to get the related child table data. Check this answer
https://stackoverflow.com/a/71571509/9561654

Resources