Django query from multiple table base on FK - django

hi i am new in django coding
i have 3 tables(models) in my different Django app , i try to make a simple report in htmpl page , so need to retrive specific data for each item base on foreign key.
by below code i can accress machine stored table in Html page by
making a loop , but i want to fetch data from other table and filter them ,
{% for m in machines %}
<h2>{{m.title}}</h2>
<h3>{{}}</h3>.
//i** need to access the ordertitle field in tb_order base on machine id. ??**
<p>{{ }}</p>.
// **access ordertitle in tb_order base on status field ??**
{%end for %}`
here is a view.py
def allmachinesLOGO(request):
machines=Machine.objects.all()
context ={'machines':machines}
return render(request,'pline/machineslogos.html',context)
Models :
class tb_order(models.Model):
ordertitle= models.CharField(max_length=200)
customer=models.ForeignKey(Customer, null=True, on_delete=models.SET_NULL)
mould=models.ForeignKey(Mould, null=True, on_delete=models.SET_NULL, blank=True)
status=models.CharField(choices=status_choices, default="0", max_length=20)
accept=models.BooleanField
machine=models.ForeignKey(Machine,null=True, on_delete=models.SET_NULL, blank=True)
id =models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, unique=True)`
class Machine(models.Model):
title = models.CharField(max_length=200)
machine_model = models.CharField(max_length=200, null=True, blank=True)
description = models.CharField(max_length=600, null=True, blank=True)
featured_image=models.ImageField(default="defaultmachine.webp", blank=True, null=True)
id=models.UUIDField(default=uuid.uuid4, unique=True,primary_key=True,editable=False)`
advice a solution
access ordertitle in tb_order base on status field ??
// i need to access the ordertitle field in tb_order base on machine id.

By using the primary model, you can get the data associated with it from the secondary model. To do this, a special property (object) with the name secondary model_set is created in the primary model by default. In your case, for example: tb_order_set(lower case). Using related_name for the ForeignKey (in which case it will be whatever you write it without the _set prefix).
In the template, the outer loop loops through the rows of the model: Machine. The internal one, through tb_order_set.all, accesses the rows of the tb_order model. Although in my opinion it is better to do all calculations and comparisons in views, and in templates only display information.
Template file:
{% for m in machines %}
<p>{{ m.tb_order_set.all }}</p>
{% for r in m.tb_order_set.all %}
<p>{{ 'ordertitle' }} : {{ r.ordertitle }} {{ 'status' }} : {{ r.status }}</p>
{% endfor %}
{% endfor %}
To show by specific status:
{% for m in machines %}
<p>{{ m.tb_order_set.all }}</p>
{% for r in m.tb_order_set.all %}
{% if r.status == '2' %}
<p>{{ 'ordertitle' }} : {{ r.ordertitle }} {{ 'status' }} : {{ r.status }}</p>
{% endif %}
{% endfor %}
{% endfor %}

Related

Django - usage of prefetch_related

no matter how many tutorials/documentation i read i still don't quite understand how exactly i'm supposed to be using prefetch_related.
My models.py:
class ProfileComment(models.Model):
author = models.ForeignKey('Profile', on_delete=models.CASCADE, null=True)
date_posted = models.DateTimeField(default=timezone.now, editable=False)
body = models.CharField(max_length=180, blank=True)
...
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
comments = models.ManyToManyField(ProfileComment, related_name='comments', blank=True)
avatar = models.FileField(upload_to=avatar_folder, default='user-avatar/default.png')
...
My views.py:
profile = Profile.objects.prefetch_related('comments').get(user=request.user)
And in template:
{% for comment in profile.comments.all %}
<div>
<p>Author: {{ comment.author.user }}</p><img src="{{ comment.author.avatar.url }}">
<p>Message: {{ comment.body }}</p>
<p>Date posted: {{ comment.date_posted }}</p>
</div>
{% endfor %}
However, no matter what i put in prefetch_related, amount of queries just go up by like 5 for every record
.prefetch_related(…) [Django-doc] should be used to fetch related objects for a queryset in bulk. Since you write:
….get(user=request.user)
however it will not make any difference, since you only retrieve a single object, and it will thus prefetch the comments in an extra query.
What you can minimize however is the number of extra queries that originate from comment.author.user. This will make two extra queries per comment, since ForeignKeys are loaded lazily.
Your view can thus look like:
profile = Profile.objects.get(user=request.user)
comments = profile.comments.select_related('author', 'author__user')
where you pass both profile and comments to the template, and then retrieve it with:
{% for comment in comments %}
<div>
<p>Author: {{ comment.author.user }}</p><img src="{{ comment.author.avatar.url }}">
<p>Message: {{ comment.body }}</p>
<p>Date posted: {{ comment.date_posted }}</p>
</div>
{% endfor %}
here we thus read the comments from a queryset where we fetch the profile and the user of that profile in the same query. As a result we will make two queries: one for the Profile, and one for all the Comments including the .author and .author.user.

Django problem with foreginKey in templates

I am started learning Django and I have question. I have code like this:
from django.db import models
from django.utils import timezone
class Worker(models.Model):
firstname = models.CharField(max_length = 32, blank=False, null=False)
lastname = models.CharField(max_length = 32, blank=False, null=False)
class Computer(models.Model):
TYPE = {
(0, 'PC'),
(1, 'Laptop'),
}
workerId = models.ForeignKey(Worker, on_delete=models.SET_NULL, blank=True, null=True)
prod = models.CharField(max_length = 32, blank=False, null=False)
type = models.PositiveSmallIntegerField(default=0, choices=TYPE)
What I should do in views and templates if I want to do list with workers and theirs computers? One computer can be for one user or for no one but one worker can have a few computers.
My template for this look like:
{% block site %}
{% for work in worker %}
<p>{{ work.id }} {{ work.fullname }}
{{ work.lastname }}</p><br>
{% if comp.workerId == work.id%}
{{ comp }}
{% else %}
<p>Empty</p>
{% endif %}
{% endfor %}
{% for c in comp %}
<p>{{ c }}</p>
{% endfor %}
{% endblock %}
But I always have "Empty". Thanks for help
You can try like this with for-empty in template:
{% for work in worker %}
<p>{{ work.id }} {{ work.fullname }}
{{ work.lastname }}</p><br>
{% for comp in work.computer_set.all %}
{{ comp }}
{% empty %}
<p>Empty</p>
{% endfor %}
{% endfor %}
I've actually explained this before in 2 questions, here and here.
Basically, a ForiegnKey object is a key because it's unique for the class owning it, This means your computer has only one worker, BE SURE TO CALL IT WORKER, NOT WORKERID as this is OOP and when you access mycomputer.worker, you have a Worker object which has .id and .pk so it makes no sense to call it workerId.
Each worker now has a .computer_qs which you can change like this
workerId = models.ForeignKey(Worker,
on_delete=models.SET_NULL,
blank=True, null=True, related_name="computers")
don't forget to call it worker. now it's .computers instead of .computer_qs.
In your templates iterate workerObj.computers.all to access single computer objects and have fun with it!

Django templatetags seem to make the prefetch_related useless

When I add a filter based on a template tag ("provinceonly") to my Django template, debug-toolbar indicates that the database is hit 115 time instead of 5 previously.
Is it a well-known issue ? I cant' find a way to prefetch correctly the required information to reduce the number of hits. Is there something I can do about it ?
Template:
{% for country in allcountries %}
{% for province in country.fkcountrysecondaries.all|provinceonly %}
{{ province.basicname }}
{% for populatedplace in province.fkprovince.fkprovincesecondaries.all %}
{{ populatedplace.basicname }}
{% endfor %}
{% endfor %}
{% endfor %}
Template tag:
from django import template
register = template.Library()
#register.filter
def provinceonly(thelist):
return thelist.filter(type="province")
View:
def indexconsistencycheck(request):
allcountries = NaturalEarthCountry.objects.all().prefetch_related('fkcountrysecondaries__fkprovince__fkprovincesecondaries').select_related('fkcountrymain')
Model:
class NaturalEarthAll(models.Model):
fkcountry = models.OneToOneField(NaturalEarthCountry, blank=True, null=True, on_delete=models.SET_NULL, related_name="fkcountrymain")
fkprovince = models.OneToOneField(NaturalEarthProvince, blank=True, null=True, on_delete=models.SET_NULL, related_name="fkprovincemain")
fktouristicarea = models.ForeignKey(TouristicArea, blank=True, null=True, related_name='relatednatmerged', on_delete=models.SET_NULL)
fkcountrysecondary = models.ForeignKey(NaturalEarthCountry, blank=True, null=True, on_delete=models.SET_NULL, related_name="fkcountrysecondaries")
fkprovincesecondary = models.ForeignKey(NaturalEarthProvince, blank=True, null=True, on_delete=models.SET_NULL, related_name="fkprovincesecondaries")
As explained by Sachin, the templatetag generates a new queryset which loses prefetched data. I therefore had to perform the main queryset directly on the model I needed to filter (provinces) and not on the previous one (countries), and then regrouped the results by country.
Thanks a lot
view:
allprovinces_list = NaturalEarthMerged.objects.filter(type="province").order_by('fkcountrysecondary__name').prefetch_related('fkprovince__fkprovincesecondaries', 'fkcountrysecondary__fkcountrymain').select_related('fkcountrysecondary')
template:
{% regroup allprovinces by fkcountrysecondary.name as countrylist %}
{% for listofprovinces in countrylist %}
{{ listofprovinces.grouper }}
{% for province in listofprovinces.list %}
{{ province.basicname }}
{% for populatedplace in province.fkprovince.fkprovincesecondaries.all %}
{{ populatedplace.basicname }}
{% endfor %}
{% endfor %}
{% endfor %}

Django remove duplicates in queryset and access their list of foreign key

Here is my model:
class Item(models.Model):
title = models.TextField()
class Storage(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
rating = models.IntegerField(blank=True, null=True)
review = models.CharField(max_length=1500, blank=True, null=True)
class SocialProfile(models.Model):
user = models.OneToOneField(User, unique=True)
follows = models.ManyToManyField('self', related_name='followed_by', symmetrical=False)
Say there are 4 users A,B,C,D. User A follows only B and C.
I'm trying to do a view that when log in as User A. Show a list of items that B and C has (duplicates show only one time). And under each item, show their summary and ratings.
I have something here: (user = User A here)
//get a list of users that User A follows
followings = user.socialprofile.follows.all()
//get B and C's saved item, eliminate the duplicates
context['following_saved_article'] = Item.objects.filter(storage__user__in=followings.values('user')).distinct()
//get a flat list of all users A follows
context['followings'] = list(followings.values_list('user', flat=True).order_by('user'))
Then in the template:
{% for item in following_saved_item %}
{{ item.title }}
{% for x in item.storage_set.all %}
{% if x.user_id in followings %}
{{ x.review }} {{ x.rating }}
{% endif %}
{% endfor %}
{% endfor %}
This seems too redundant, and I will have a pretty hard time if I want to sort reviews by user rating.
Is there any way to generate a list of distinct Item instance, fetch only the storage_set based on following, sort by rating or other field in storage, and pass that to view? Thanks
I think a custom Prefetch object will do the trick
followings = user.socialprofile.follows.all()
items = Item.objects.filter(
storage__user__in=followings.values('user')
).distinct().prefetch_related(
models.Prefetch('storage_set',
queryset=Storage.objects.filter(
user__in=followings.values('user')
),
to_attr='storage_from_followings'
)
)
#in the template
{% for item in items %}
{{ item.title }}
{% for x in item.storage_from_followings %}
{{ x.review }} {{ x.rating }}
{% endfor %}
{% endfor %}

Get aggregate in a template

I have this Django model:
class Location(models.Model):
name = models.CharField(primary_key=True, max_length=100)
customer = models.OneToOneField(Customer, default=None)
class Customer(models.Model):
name = models.CharField(primary_key=True, max_length=100)
class Order(models.Model):
amount = models.PositiveIntegerField(default=0)
customer = models.ForeignKey(Customer, default=0)
in my view I get them like this:
locations = models.Location.objects.all()
and the template lists them like this:
{% for location in locations %}
{{ location.customer.name }}
{% endfor %}
I would like to add a sum of all amount of all Orders connected that customer, something like:
{% for location in locations %}
{{ location.customer.name }} ordered {{ location.customer.orders.sum(amount) }} items
{% endfor %}
And according to this question, I should do that in the view, but how?
You should use .annotate (look in docs):
from django.db.models import Count
customers = models.Customer.objects.annotate(orders_count=Count('order'))
Then in templates you can use it like this:
{% for customer in customers %}
{{ customer.name }} ordered {{ customer.orders_count }} items
{% endfor %}
After some folling around I found this works:
locations = models.Location.objects.annotate(num_order=Count('customer__order'))
and then use this in the template:
{% for location in locations %}
{{ location.customer.name }} ordered {{ location.num_order }} items
{% endfor %}