Django - reduce the number of queries in ORM - python

I used related_name in a Django template to look the foreign key record, and call the count method. Because I have so many "Main" record, the for loop inside the template will create too many queries to the database. If there an easy way for me to reduce the number of queries to the database? Please see below for my setup.
# models.py
class Main(models.Model):
name = models.CharField(_('Name'), max_length=255)
class Sub1(models.Model):
main = models.ForeignKey(Main, on_delete=models.CASCADE)
name = models.CharField(_('Name'), max_length=255)
class Sub2(models.Model):
main = models.ForeignKey(Main, on_delete=models.CASCADE)
name = models.CharField(_('Name'), max_length=255)
class Sub3(models.Model):
main = models.ForeignKey(Main, on_delete=models.CASCADE)
name = models.CharField(_('Name'), max_length=255)
# views.py
def get_main(request):
main_list = Main.objects.all()
...
# template
{% for main in main_list %}
{{main.sub1_set.count}}
{{main.sub2_set.count}}
{{main.sub3_set.count}}
{% endfor %}

You can use annotations to do this logic all in one query:
from django.db.models import Count
def get_main(request):
main_list = Main.objects.all().annotate(sub1_count=Count('sub1', distinct=True),
sub2_count=Count('sub2', distinct=True),
sub3_count=Count('sub3', distinct=True))
Then in the template:
{% for main in main_list %}
{{ main.sub1_count }}
{{ main.sub2_count }}
{{ main.sub3_count }}
{% endfor %}
(Edit: added distinct)

Related

Django html template getting data from nested dictionary

I'm having a problem with an html template not displaying model fields sent from a view in a context dictionary called content. This dictionary holds a nested dictionary like:
content = {'indredients': {recipe id1: QuerySet 1,
recipe id2: QuerySet 2, ... } }
In model.py:
class Ingredients(models.Model):
id = models.IntegerField(primary_key=True)
recipe = models.ForeignKey(Recipes, on_delete=models.CASCADE, related_name='ingredients')
ingredient = models.CharField(max_length=128)
class Meta:
managed = False
db_table = 'ingredients'
verbose_name_plural = 'Ingredients'
def __str__(self):
return f"{self.id} {self.recipe} - {self.ingredient}"
class Recipes(models.Model):
id = models.IntegerField(primary_key=True)
category = models.TextField(db_column='Category', null=False)
submitted_by = models.TextField(
db_column='Submitted_By', null=False)
origin = models.TextField(db_column='Origin', null=False)
title = models.TextField(db_column='Title', null=False)
directions = models.TextField(
db_column='Directions', null=False)
comments = models.TextField(db_column='Comments', null=False)
created = models.DateTimeField(null=False)
modified = models.DateTimeField(null=True)
def __str__(self):
return f"{self.id} - {self.title}"
class Meta:
managed = False
db_table = 'recipes'
verbose_name_plural = 'Recipes'
In views.py:
recipes = Recipes.objects.all().order_by(
"category", "title")
content['ingredients'] = {}
for recipe in recipes:
ingredients = Ingredients.objects.filter(
recipe=recipe.id).order_by("id")
content['ingredients'][recipe.id] = ingredients
content['recipes'] = recipes
return render(
request,
"myApp/recipes.html",
context=content)
In recipes.html:
{% for recipe in recipes %}
<div id="id-{{recipe.id}}" class="grid-item {{recipe.category}} {{recipe.submitted_by}}">
<div class="row">
<div class="col-12 col-md-3 ingredients">
{% for queryQbject in ingredients.recipe.id %}
{{ queryQbject.ingredient }}<br>
{% empty %}
<span>No ingredients provided</span>
{% endfor %}
</div>
</div>
{% endfor %}
I do get the correct data from the sqlite database and the Queryset is stored in the dictionary 'content' that is passed correctly into the html file. However, the html template doesn't display any of the data and only prints the 'No ingredients provided' {% empty %} case.
See debug info:
What do I need to do to fix this problem?
nigel239's answer got me thinking and researching some more. I found this post
https://fedingo.com/how-to-lookup-dictionary-value-with-key-in-django-template/
to write a custom filter to lookup a dictionary value with a key.
This is my custom_tags.py:
#register.filter
def get_item(dictionary, key):
try:
key = int(key)
value = dictionary.get(key)
except:
value = None
return value
and my updated recipes.html:
<div class="col-12 col-md-3 ingredients">
{% for queryset in ingredients|get_item:recipe.id %}
{{ queryset.ingredient }}<br>
{% empty %}
<span>No ingredients provided</span>
{% endfor %}
</div>
Now the code correctly pulls all the ingredients from the Django Queryset that was passed into the html template in a dictionary called 'ingredients' using the 'recipe.id' as keys.
You are trying to loop over the ID, which is an integer. Not an iterable.
Change
{% for queryQbject in ingredients.recipe.id %}
To
{% for queryQbject in ingredients.recipe %}

Adding Context to Class Based View Django Project

I have a problem with showing a queryset of a specific model in my Gym project. I have tried many different query's but none is working
the models:
class Workout(models.Model):
name = models.CharField(max_length = 30,blank=True, null=True)
class Exercise(models.Model):
workout = models.ForeignKey(Workout, on_delete=models.CASCADE, related_name='exercises',blank=True, null=True)
name = models.CharField(max_length = 30, blank=True, null=True)
class Breakdown(models.Model):
exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE, related_name='excercise',blank=True, null=True)
repetitions = models.IntegerField()
I am trying to showing the repetitions in the Breakdown model which has a ForeignKey relation with Exercise which has a ForeignKey relation with Workout
views.py
class home(ListView):
model = Workout
template_name = 'my_gym/home.html'
context_object_name = 'workouts'
class workout_details(ListView):
model = Exercise
template_name = 'my_gym/start_workout.html'
context_object_name = 'exercises'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['breakdown'] = Exercise.objects.filter(breakdown=self.breakdown)
return context
urls.py
urlpatterns = [
path('', home.as_view(), name='home'),
path('workout/<int:pk>/', workout_details.as_view(), name='workout'),
]
template:
{% for e in exercises %}
{{ e.name }}
{{ breakdown.repetitions }}
{% endfor %}
My question what is my mistake here that is either getting me an error or not showing the required data set. my objective is the choose from the home page a Workout from list and next page to be the list of exercises with the repitions related to it.
get_context_data() is a method calculated for view, not a loop to use inside a template or something similar. You have set nice relations, use them properly.
Firstfully, change related_name here to something that will not be confusing:
class Breakdown(models.Model):
exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE, related_name='breakdowns', blank=True, null=True)
With that done, delete whole get_context_data() and change template to:
{% for e in exercises %}
{{ e.name }}
{% for b in e.breakdowns.all %}
{{ b.repetitions }}
{% endfor %}
{% endfor %}

How to get a parent from a child FK?

models.py
class Product(models.Model)
name = models.CharField()
class User(models.Model)
name = models.CharField()
class List(models.Model)
user = models.ForeignKey(User)
class Item(models.Model)
name = models.CharField()
list = models.ForeignKey(List)
user = models.ForeignKey(User)
product = models.ForeignKey(Product)
views.py
class ListView(View)
def get(self, request, pk)
list = List.objects.get(id=pk)
return render(request, "list.html", {"list" : list})
list.html
{% for item in list.item_set.all %}
{{ item.name }}
{{ item.user.name }} - ??
{{ item.product.name }} - ??
{% endfor %}
How get user.name и product.name?
I tried it:
{% item.user_set.first.name %} - does not work
{% for user in item.user_set.all %}{{ user.name }}{% endfor %} - does not work
Model method:
def get_user(self):
self.user_ser.get() #does not work
How do I solve this problem?
Since an Item has a ForeignKey to a Product object, you can access the related object with item.product, so you can render this with:
{{ item.product.name }}
{{ item.user.name }}
If you use the standard User module, it is {{ item.user.username }}. It is possible that this is empty, for example if the name of the related user is the empty string ''.
In order to retrieve the elements more efficient however, it might be better to perform a .prefetch_related(..) [Django-doc] to retrieve these items in bulk:
class ListView(View)
def get(self, request, pk)
list = List.objects.prefetch_related('item_set', 'item_set__product', 'item_set__user').get(id=pk)
return render(request, "list.html", {'list' : list})
From user : {{user.items}} # to access to the items objects
From item: {{item.user.name}} # to access to the user name field
class Item(models.Model)
name = models.CharField()
list = models.ForeignKey(List)
user = models.ForeignKey(User, related_name='items')
product = models.ForeignKey(Product)
It works!
My error was that I looked at the field names in the database, not in the file models.py. They are different.
Thank you for your help!
All the answers above are correct!

Trouble getting related fields in ForeignKeys to show in Template

I'm working in Django 2.2 trying to build a view for a database that compiles everything for a specific company (locations of all of their stores and notes on the companies) into a single view. I've tried methods in several different answers, but still cannot seem to get data from related foreign keys to show in the template.
models.py
class Company(models.Model):
name = models.CharField(max_length=30, primary_key=True)
official_name = models.CharField(max_length=50)
corporate_address1 = models.CharField(max_length=50)
corporate_address2 = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.ForeignKey('Country', on_delete=models.CASCADE)
slug = models.SlugField(max_length=30, unique=True)
def __str__(self):
return self.name
class Stores(models.Model):
store_name = models.CharField(max_length=30, primary_key=True)
owner = models.ForeignKey('Company', on_delete=models.CASCADE)
store_address1 = models.CharField(max_length=50)
store_address2 = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.ForeignKey('Country', on_delete=models.CASCADE)
type = models.CharField(max_length=30,choices=store_types)
slug = models.SlugField(max_length=30, unique=True)
def get_absolute_url(self):
return reverse("store-detail", kwargs={"slug": self.slug})
class notes(models.Model):
title = models.CharField(max_length=120)
content = models.TextField()
posted = models.DateTimeField(db_index=True, auto_now_add=True)
category = models.ForeignKey('Company', on_delete=models.CASCADE)
active = models.BooleanField(default=True)
def get_absolute_url(self):
return reverse("article-detail", kwargs={"id": self.id})
class Country(models.Model):
country = models.CharField(max_length=30,choices=countries,primary_key=True)
class Meta:
ordering = ["-country"]
db_table = 'country'
def __str__(self):
return self.country
views.py
class CompanyOverView(LoginRequiredMixin, DetailView):
model = Company
template_name = "company-overview.html"
slug_url_kwarg = 'slug'
query_pk_and_slug = True
pk_url_kwarg = "company.name"
template
<div align="center">
<p>{{ object.name }}<br>({{ object.official_name }})</p>
<p>{{ object.corporate_address1 }}<br>{{ object.corporate_address2 }}<br>
{{ object.city }}<br>{{ object.state_province }}<br>
{{ object.country }}</p>
</div>
<p>List of Stores</p>
<p>
{% for instance in object_list %}
{{ instance.company.stores.store_name }}
{{ instance.company.stores.store_address1 }}
{{ instance.company.stores.store_address2 }}
{{ instance.company.stores.city }}
{{ instance.company.stores.state_province }}
{{ instance.company.stores.country }}
{% endfor %}
</p>
<p>Notes</p>
<p>
{% for instance in object_list %}
{{ instance.company.notes.title }}
{{ instance.company.notes.posted }}
{% endfor %}
</p>
With the above code, the only thing that appears when you enter in the company's name is everything at the top (e.g."object.name" appears on the page as "Acme Corporation"). Nothing in the for loop appears on the web page.
Looking at the documentation, object_list is the default name for context unless specified. I have tried different combinations such as "for store_name in company.store_set.all" and other combinations I found in other posts, but none have worked. I've combed the documentation for everything related to foreign keys, but can't find a solution that works.
Thanks in advance if you can help.
No. object_list is the default context name in a ListView. But you have a DetailView, and you already know what the default context name is for those because you're already using it: object. You just need to iterate over the reverse relation from there:
{% for store in object.stores_set.all %}
{{ store.store_name }}
{{ store.store_address1 }}
...
{% endfor %}

Django menu & childmenu from models

I'm trying to a menu system for my sport site project where the sports are grouped together. For example the main category would be "ballsports" and under that (the child menu) people would select football, baseball or whatever else. I've got that all setup and functioning but I can't workout how to call the child menus into the templates.
Models:
class Sport(models.Model):
name = models.CharField(max_length=100, db_index=True)
sport_slug = models.SlugField(max_length=100, db_index=True)
category = models.ForeignKey('Sport_Category', on_delete=models.CASCADE,)
class Sport_Category(models.Model):
name = models.CharField(max_length=100, db_index=True)
category_slug = models.SlugField(max_length=100, db_index=True)
Views:
class IndexView(generic.ListView):
template_name="sports/index.html"
context_object_name='all_sport_category'
def get_queryset(self):
return Sport_Category.objects.all()
def list_of_sports_in_category(self):
sport_cat = self.category.name
return sport_cat
class SportListView(generic.ListView):
template_name="sports/sport-home.html"
context_object_name='sport_list'
def get_queryset(self):
return Sport.objects.all()
Template:
{% for sport_category in all_sport_category %}
<li>{{ sport_category.name }} </li> *(Working)*
{% for sports in list_of_sports_in_category %}
hi
{% endfor %}
{% endfor %}
list_of_sports_in_category method seems to return the category name, rather than the list of sports. try replacing the second for-loop in your template with {% for sport in sport_list %}.

Categories

Resources