querying foreignkey nested for loop django - python

I am trying to return a list of categories for a business, and for each category I would like to list all the items related to the category.
I was returning all of my items, not by category, but I have decided I want them sorted by category. This is what I have tried (among other attempts as well) I simply am having trouble getting the items into there categories. This is my latest attempt
In my models.py I have
Business(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
logo = models.CharField(max_length=300)
ItemCategory(models.Model):
name = models.CharField(max_length=50)
Item(models.Model):
name = models.CharField(max_length=100)
business = models.ForeignKey(Business)
category = models.ForeignKey(ItemCategory)
short_description = models.CharField(max_length=250)
in my views.py
def business_profile(request, business_id):
items = Item.objects.filter(business_id = business_id).select_related('itemcategory')
return render(request, 'business_profile.html', {"items": items})
in my template I am trying
{% for itemcategory in items.itemcategory_set.all %}
{{ itemcategory.name }}
{% for item in itemcategory %}
{{ item.name }} <br>
{{ item.short_description }}
{% endfor %}
{% endfor %}
From my research into other questions and reading the documents, i felt this would be right.. However from my results I can not get the correct output in my template.
Would this be the correct practice? Or should I try getting the categories first with
categories = ItemCategory.objects.filter(business = business_id)
and then possibly add a select_related('item') to process all the items for the category?
When I refer to data in the template but my model has 2 words (ItemCategory) - for instance when I move backward for a foreign key or use the model in select_related('') - would I use item_category or do you simply use itemcategory with no underscore?
UPDATE: I answered below in comment with explanation.

To list the items by category, I used #yusuf.oguntola's idea and initially got the business with .get(), then I created a queryset to get all the items. My function in views.py included
business = Business.objects.get(id = business_id)
items = business.item_set.all().order_by('category')
note: the business_id was passed in url pattern
However, the key change was in my template. I found this awesome built in functionality for templates in the django documents here
I implemented it in template like so...
{% regroup items by category as category_list %}
<ul>
{% for category in category_list %}
<li>{{ category.grouper }}
<ul>
{% for item in category.list %}
<li>{{ item.name }}<br> {{ item.short_description }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
One of the most important parts is you have to have order_by() and put the parameter of the field you are ordering by - in my case, category. If this is not included, it will simply put the items listed in chronological order and will repeat your field instance.

items = Item.objects.filter(business_id = business_id) is wrong.
You should rather say:
items = Item.objects.filter(business__id = business_id) #Notice the double underscore.
You may also rather get the business and simply say:
business.item_set.all() #This would retrieve all Item for the business.
select_related() expects a fieldname as a paramter. So use a field name that exists on the Item model. Like:
select_related('category')
Where's the meal? May be you can proceed from there though.

Related

Placing items below category in template

So I wrote a small shopping list application using Django. Users may enter their desired items which are stored in a database alongside a category they reside in to make the list presented to the user more clean and giving them (the users) a good overview of what they are going to buy.
The goal is to show the user a list which is categorized, something like this:
VEGETABLES
Paprika
Tomatoes
VARIOUS
Toothpaste
Toilet Paper
And so on. I have like five categories saved in my database and users may choose one corresponding category once they add an item to the list below which the item will be displayed in the list.
These are my database models:
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=20)
tag = models.CharField(max_length=2)
def __str__(self):
return self.name
class Item(models.Model):
text = models.CharField(max_length=40)
count = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
complete = models.BooleanField(default=False)
def __str__(self):
return self.text
this is my views.py
def items(request):
item_list = Item.objects.order_by('id')
categories = Category.objects.all()
form = ItemForm()
context = {'item_list' : item_list, 'form' : form, 'categories' : categories}
return render(request, 'buyit/index.html', context)
and this is my index.html (very basic, stripped off all the css and html stuff):
{% for category in categories %}
<h4>{{ category.name }}</h4>
{% for item in category.item_set.all() %}
{{ item.text }}
{% endfor %}
{% endfor %}
I got this functions from the Jinja2 template from a code snippet and it might be, that I didn't understand it correctly.
However, the debugger tells me:
TemplateSyntaxError at /
Could not parse the remainder: '()' from 'category.item_set.all()'
Any hints on what I am doing wrong?
Simply displaying the categories worked fine but after adding
{% for item in category.item_set.all() %}
{{ item.text }}
{% endfor %}
things crashed.
Any help is highly appreciated!
Thanks in advance!
You can't use () in django templates. Just remove them to call the function / method.
From https://docs.djangoproject.com/en/3.1/ref/templates/language/#variables
Behind the scenes
Technically, when the template system encounters a dot, it tries the following lookups, in this order:
Dictionary lookup
Attribute or method lookup
Numeric index lookup
If the resulting value is callable, it is called with no arguments. The result of the call becomes the template value.
try this:
views.py:
def items(request):
commoditys = {category: Item.objects.filter(category=category) for category in Category.objects.all()}
form = ItemForm()
context = {'commoditys' : commoditys, 'form' : form}
return render(request, 'buyit/index.html', context)
index.html:
{% for category, commoditys in commoditys.items %}
<h3>{{ category.name }}</h3>
{% for commodity in commoditys %}
{{ commodity.text }}
{% endfor %}
{% endfor %}
Change the Category variable to a dictionary, where the Key of each item is the Category object, and its corresponding value is the item Queryset it belongs to

Django display table in templates

I am using Django 2.0.1, and I have the following code:
Models.py:
class Category(models.Model):
category_name = models.CharField(max_length=50)
class CategoryItems(models.Model):
category_name = = models.ForeignKey(Categories, related_name='categoriesfk', on_delete=models.PROTECT)
item_name = models.CharField(max_length=50)
item_description = models.CharField(max_length=100)
Thereafter my views.py:
def data(request):
categories_query = Categories.objects.all()
category_items_query = CategoriesItems.objects.all()
return render_to_response("data.html",
{'categories_query': categories_query,'category_items_query': category_items_query}
In the template I'm trying to display all items for each category, for example, suppose there are 4 categorizes, e.g. Bicycle, then it display all items belonging to that category only. For example, as follows:
Category 1:
Category_Item 1,
Category_Item 2,
Category_Item 3,
and so on ...
Category 2:
Category_Item 1,
Category_Item 2,
Category_Item 3,
and so on ...
I have tried to write so many different for-loops, but they just display all items, I need it to show items only for that category, then for loop to next category and show items for that.
You don't need your category_items_query variable, just category_query:
{% for category in category_query %}
<b>{{ category.category_name }}</b><br />
{% for item in category.categoriesfk.all %}
{{ item.item_name }}<br />
{% endfor %}
{% endfor %}
Your related_name of categoriesfk is weird, it'd make more sense to be something like items.
What you need is two for loops with an if to check if the second loop should belong to the first.
Try this:
{% for category in categories_query %}
{% for category_item in category_items_query %}
{% if category_item.category_name == category %}
{# do something with category_item #}
{% endif %}
{% endfor %}
{% endfor %}
I believe it would be more clear if you named the ForeignKey in CategoryItems to just "category", instead of "category_name", since this field will have the category itself, not just it's name. Your "if" would then be more readable and make more sense:
{% if category_item.category == category %}
Hope it helps.
A few things,
Since your model name is already Category, your field names should be like name instead of category_name.
Model names must be singular, so it should beCategoryItem instead of CategoryItems
When you do a model_name.objects.all(), you do not get a query but a Queryset, make your variable names such that they describe what they do. Currently, categories_query is wrong. You could instead use category_qs.
Now, coming to your question, you require two for loops. One to loop through the categories and then one to loop through items in a particular category.
Something like,
for category in category_qs:
for item in category:
# Do something with item
You have the basic idea here, now you can convert it to real working code. Good luck!
Grouping data in template is not best idea as template should be logic free,
also good idea would be to use database capabilities and use select_related to query out related set
category_data = Category.objects.select_related('item').all()
afterwards you could do following in template
{% for category in category_data %}
{# print category #}
{% for item in category.items %}
{# print item #}
{% endfor %}
{% endfor %}
Django has a built-in regroup template tag for this functionality.
{% regroup category_items_query by category_name as categoryname_list %}
{% for category in categoryname_list %}
<strong>{{ category.grouper }}</strong><br>
{% for item in category.list %}
{{ item.item_name }}: {{ item.item_description }}<br>
{% endfor %}
{% endfor %}
You have to order your CategoryItems for this functionality to work.
category_items_query = CategoriesItems.objects.all().order_by('category_name')

Getting spesific data from three models in django

I am starting working with django and i want get specific data from three related models.
my models are
class Institution(models.Model):
name = models.CharField(max_length=100, unique=True)
...
class Certification(models.Model):
name = models.CharField(max_length=100)
...
class Course(models.Model):
name = models.CharField(max_length=100)
institution = models.ForeignKey(Institution)
certification = models.ForeignKey(Certification)
in my html page i want display the courses offered by a particular institution ordered by certification. like this
name of a particular institution I
certification 1
list courses that offer certification 1
certification 2
list courses that offer certification 2
...
my current template is
{{institution.name}}
{% for certification in preselected_certifications %}
<h1> {{ certification.name }} </h1>
<ul>
{% for course in courses %}
<li>{{ course.name }}</li>
{% endfor %}
</ul>
{% endfor %}
my view
def detail(request, slug):
context = RequestContext(request)
context_dict = {'slug_requested': slug}
try:
institution = Institution.objects.get(slug=slug)
courses = Course.objects.filter(etablissement=etablissement)
context_dict['courses'] = courses
context_dict['institution'] = institution
except Institution.DoesNotExist:
pass
return render_to_response('institutition/details.html', context_dict, context)
my question is how define "preselected_certifications" so it contains only certifications offered by all courses in the selected institution, but without repeating any certification
Question:
how define "preselected_certifications" so it contains only certifications offered by all courses in the selected institution, but without repeating any certification
I don't know exactly how you want to represent this information (meaning, which attributes you want available), but think you want something like the following:
def detail(request, slug):
...
institution = Institution.objects.get(slug=slug)
# `select_related` not strictly necessary
courses = institution.course_set.all().select_related('certification')
context_dict['courses'] = courses
context_dict['institution'] = institution
Now, each course object will have access to its certification relation and you could iterate through them to display the unique ones.
However, if you want to ensure from the outset these certifications are unique, one way to do that is with another query:
institution = Institution.objects.get(slug=slug)
courses = institution.course_set.select_related('certification')
certification_ids = courses.values_list('certification_id', flat=True).distinct()
preselect_certifications = Certification.objects.filter(id__in=certification_ids)
That last query will get you the unique certifications for a particular institution.
It seems like Certification should potentially have a relationship to Institution, though, no?
yeah thanks a lot, now i use in my template the following code
{% for certification in preselect_certifications %}
{{certification.name}}
{% for course in courses %}
<ul>
{% if certification = course.certification %}
<li>{{ course.name }}</li>
{% endif %}
</ul>
{% endfor %}
{% endfor %}
it works perfectly, but is there another way to avoid using
{% if certification = course.certification %}
?
thanks again

Getting limited random items from related object in templates?

I have an Author model with many books assigned to each one. In my Books model I have: author = models.ForeignKey(Author)
In my page, I am listing all the authors I have and under each author I want to display 5 random books.
Is this feasible through the templates only or I must do that in my view? I mean limiting the results and getting them randomly.
I'm currently only passing the authors to template, like this: a = Author.objects.all()
Please advise.
You have two options:
Do it in your view:
This query is "return 5 random books for author a"
random_books = Book.objects.filter(author=a).order_by('?')[:5]
Create a custom tag that takes an author and returns random 5 books:
Create a custom tag:
from myapp.models import Book
def randbooks(author):
return {'books': Book.objects.filter(author=author).order_by('?')[:5]}
register.inclusion_tag('book_listing.html')(randbooks)
Create a template to show the titles (book_listing.html):
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% endfor %}
</ul>
Use it in your template like this:
{% for author in a %}
{% randbooks author %}
{% endfor %}

Django and models with multiple foreign keys

I am new to Django and I've been impressed so far by its capabilities. I am playing with more complex models and I am have problem to use them properly. Using Django 1.3, I am trying to write a summary page which would present the three models below with the following structure. In other words, a list of trips with their destinations and activities.
Trip 1
Destination 1
Destination 2
Activity 1
Trip 2
Destination 1
Activity 2
Models
Trip <-> TripDestination <-> Destination (a trip can have multiple destinations)
Activity -> Trip, Activity -> Destination (an activity is defined for a trip at a specific location/destination)
class Destination(models.Model):
city_name=models.CharField()
class Trip(models.Model):
departing_on=models.DateField()
returning_on=models.DateField()
destinations=models.ManyToManyField(Destination)
class Activity(models.Model):
destination=models.ForeignKey(Destination, null=False)
trip=models.ForeignKey(Trip, null=False)
I am trying to write a view which would generate a page with the structure presented above. The main problem I am having right now is to display the activities for a specific trip and destination. As you can see in the code below, I am building a dictionary and I doubt it is the right thing to do. In addition, the view becomes
View
def list_trip(request, template_name = 'trip-list.html'):
trips = Trip.objects.all()
# Build a dictionary for activities -- Is this the right thing to do?
activities = Activity.objects.filter(trip__in=trips)
activities_by_trips = dict()
for activity in activities:
if activity.trip_id not in activities_by_trips:
activities_by_trips[activity.trip_id] = dict()
if activity.destination_id not in activities_by_trips[activity.trip_id]:
activities_by_trips[activity.trip_id][activity.destination_id] = []
activities_by_trips[activity.trip_id][activity.destination_id].append(activity)
return render_to_response(template_name, {
'page_title': 'List of trips',
'trips': trips,
'activities_by_trips': activities_by_trips,
})
Template
{% block content %}
{% for trip in trips %}
{{ trip.id }} - {{ trip.name }}
{% for destination in trip.destinations.all %}
{{ destination.city_name }}
** This is terrible code -- How to fix that **
{% for key, value in activities_by_trips|dict_lookup:trip.id %}
{% if value %}
{% for key_prime, value_prime in value|dict_lookup:destination.id %}
{{ value_prime.description }}
{% endfor %}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endblock %}
In brief, can someone please help me to get a summary of all the trips and activities? What's the best way to accomplish that? Is the model correct?
Thanks!
There is plenty of room for improvement. By using through on ManyToManyField you can explicitly define the join table, which we can conveniently consider as a single visit to a city during a particular trip. During that visit we had activities, so activity should have a foreignkey to a visit.
For each foreignkey in a table, Django will add API convenience manager for sets of objects on the opposite side of the relationship. Destination will have a visit_set, but so will Trip. Similarly, because of visit foreignkey in Activity each visit will have an activity_set.
First start with the models:
from django.db import models
# Create your models here.
class Destination(models.Model):
city_name=models.CharField(max_length=50)
class Trip(models.Model):
departing_on=models.DateField()
returning_on=models.DateField()
destinations=models.ManyToManyField(Destination, through='Visit')
class Visit(models.Model):
destination=models.ForeignKey(Destination)
trip=models.ForeignKey(Trip)
class Activity(models.Model):
name=models.CharField(max_length=50)
visit=models.ForeignKey(Visit)
Then lets change list_trip a bit, added print_trip for clarity of what is going on in template:
def list_trip(request, template_name = 'trip-list.html'):
return render_to_response(template_name, {
'page_title': 'List of trips',
'trips': Trip.objects.all(),
})
def print_trips():
for trip in Trip.objects.all():
for visit in trip.visit_set.select_related().all():
print trip.id, '-', visit.destination.city_name
for act in visit.activity_set.all():
print act.name
And finally the improved template:
{% block content %}
{% for trip in trips %}
{{ trip.id }} - {{ trip.name }}
{% for visit in trip.visit_set.select_related.all %}
{{ visit.destination.city_name }}
{% for act in visit.activity_set.all %}
{{ act.name }}
{% endfor %}
{% endfor %}
{% endfor %}
{% endblock %}
There is still some more room for improvement performance wise. Notice I used select_related. That will prefetch all destinations at the time visits are fetched, so that visit.destination.city_name will not incur another db call. However this doesn't work for reverse ManyToMany relationships (in our case all members of activity_set). Django 1.4 will come out with new method called prefetch_related which will resolve that as well.
In the mean time, read up on Efficient reverse lookups for an idea how to even further reduce the number of DB hits. In the comments few readily available solutions are mentioned as well.

Categories