I am trying to access dict values in my django template. The dict keys are objects. Here is a snippet of my codebase:
models.py
class ProductCategory(models.Mode):
name = models.CharField(max_length=64, blank=False, null=False, unique=True)
slug = models.SlugField(blank=False, null=False, unique=True)
class Product(models.Model):
category = models.ForeignKey(ProductCategory, on_delete = models.CASCADE)
# other fields
pass
def __str__(self):
return self.title
views.py
def index(request):
products = Product.objects.all()
categorized_products = {}
for p in products:
prod_category = p.category
temp = categorized_products.get(prod_category,[])
temp.append(p)
categorized_products[prod_category] = temp
context = {'products_dict': categorized_products, 'categories': categorized_products.keys() }
return render(request,'product/index.html', context=context)
mytemplate.html (relevant snippet)
<div class="tab-content">
{% for category in categories %}
{% if not forloop.counter0 %}
<div class="tab-pane fade in active" id="{{ category.slug }}">
{% else %}
<div class="tab-pane fade in" id="{{ category.slug }}">
{% endif %}
<div >
<h4>{{ category.description }}</h4>
<div class="container-fluid">
<div class="row">
<div class="col-md-3" style="border:1px solid red;">
{% for product in products_dict.category %}
{{ product }}
{% endfor %}
When I step through the code with a debugger, I can see that the variable products_dict is a dict and correctly populated by the view. However, when I run the code, the for loop code is not executed.
Similarly, when I run the same code I have in views.py in the shell, the dict is correctly populated, so I know that there is data in the database and that the retrieved data is being correctly passed to the django template.
So why am I unable to access the dict value in the template and why is the product not displaying in my template?
The simple answer is that Django templates don't do that. Dot reference in template variables does dictionary lookup (among other things) but not based on the value of another variable name:
Note that “bar” in a template expression like {{ foo.bar }} will be interpreted as a literal string and not using the value of the variable “bar”, if one exists in the template context.
https://docs.djangoproject.com/en/3.0/ref/templates/language/#variables
Generally I would solve this by arranging for something iterable to exist on the category values directly, rather than trying to use the category object as a dict key. That could be a reverse FK manager:
{% for product in category.products_set.all %}
Or something I set on the in-memory category objects in the view, if I need to transform or filter the products per-category:
categories = ProductCategory.objects.all()
for category in categories:
category.products = [transform_product_somehow(product) for product in category.products_set.all()]
(Optionally with use of fetch_related to prefetch the category products rather than doing an additional query per category.)
You can use a custom filter for that. In custom_tags.py:
from django import template
register = template.Library()
#register.filter
def get_obj_field(obj, key):
return obj[key]
Then load the tags in django:
{% load custom_tags %}
And use it like this:
{% for product in products_dict|get_obj_field:category %}
{{ product }}
{% endfor %}
Related
I am trying to implement Listing Filter from django filters. First "type" is the attribute that I want my filter to be based inside models.py of my app.
class detaileditems(models.Model):
title = models.CharField(max_length= 255)
type = models.CharField(max_length= 45, null=True)
pubdate = models.DateTimeField()
body = models.TextField()
image = models.ImageField(upload_to= 'images/')
I have created a separate filters.py inside my application where I have called the filters.
import django_filters
from .models import detaileditems
class ListingFilters(django_filters.FilterSet):
class Meta:
model = detaileditems
fields = {'type': ['exact']}
Next here is my function inside views.py file-
from .models import detaileditems
from .filters import ListingFilters
def alldetailed2(request):
items = detaileditems.objects
listing_filter = ListingFilters(request.GET, queryset=items)
context = {
'listing_filter' : listing_filter,
'items': items,
}
return render(request, 'detailed2/detailed2.html',context)
Lastly in my html file "detailed2.html" which is inside the application template folder of "detailed2".
<div class = "col-lg-6 col-md-8 mx-auto">
<form method = "get">
{{ listing_filter.form }}
<button class="btn btn-sm btn-danger" type="submit">Search</button>
</form>
</div>
<div class = "container">
<div class = "row row-cols-1 row-cols-sm2 row-cols-md-3 g-3">
{% for listing in listing_filter.qs %}
<div class = "col">
{% include "detailed2/detailed2.html" %}
</div>
{% endfor %}
</div>
</div>
I am getting a maximum recursion depth error.
And here is my folder structure for better understanding.
There is an earlier post on maximum recursion depth:
earlier post
Sometimes a solution can be found by rethinking the algorithm so it uses iteration over for example a list instead of recursion. I guess you need to convince yourself you are not making an error and then file an issue with Django.
As a quick fix you can try to increase the recursion depth. See post I linked to.
I do see a self reference in the html:
'''
{% include "detailed2/detailed2.html" %}
'''
could that be the root cause of your trouble?
i guess you missed form action. When there is a keyword entered then form submission should call a same function as you're using for listing all the articles.
<form method = "post" action="{% url 'alldetailed2' %}">
<input type='text' name='articale_name' />
<button class="btn btn-sm btn-danger" type="submit">Search</button>
</form>
in View function:
article_name = request.POST.get("articale_name")
if article_name:
"filter model with article_name"
else:
list out all the Articles same as earlier
In OP's views.py, ensure it has .all() at the end when OP is getting the items
def alldetailed2(request):
items = detaileditems.objects.all()
listing_filter = ListingFilters(request.GET, queryset=items)
context = {
'listing_filter' : listing_filter,
'items': items,
}
return render(request, 'detailed2/detailed2.html',context)
Then one can render in one's template like this
{% for detailed_item in listing_filter.qs %}
<h2>{{ detailed_item.title }}</h2>
<p>{{ detailed_item.body }}</p>
{% endfor %}
instead of the
{% include "detailed2/detailed2.html" %}
Keep in mind as well that there are guidelines in the way one names classes, functions, ....
Django models should use the CapWords convention. Also, it's considered and anti-pattern to use model classes with a plural name. So, DetailedItem instead of detaileditems.
The functions names should be lowercase, with words separated by underscores, like all_detailed_2 instead of alldetailed2.
I have an object that contains a Many-to-Many field. I'm trying to iterate this field in Django template, but apparently I can't. Let me show you the code first.
models.py:
class Book(models.Model):
title = models.CharField(max_length = 100, blank=True)
category = models.ManyToManyField(Category)
def __str__(self):
return self.title
views.py:
def book_list(request):
books = Book.objects.all().order_by('-pk')
context = {
'books' : books,
}
return render(request, 'contents/book_list.html', context)
Template file:
{% for b in books %}
<div>
{{b.title}}
{% for cat in b.category %}
{{cat}}
{% endfor %}
</div>
{% endfor %}
Now I get 'ManyRelatedManager' object is not iterable error. How do I iterate the field and show all the category in each object?
It's because if you call b.category it returns only the relation object. To get its values (category objects) you have to add .all. Like this:
{% for b in books %}
<div>
{{ b.title }}
{% for cat in b.category.all %}
{{cat}}
{% endfor %}
</div>
{% endfor %}
By the way, I've also changed c.title to b.title, because I assume you want this book title, not something from global.
I'm trying to show a model's ForeignKey value in a template, all other fields are showing fine but I couldn't make it work. Here is my code:
models.py:
class BodyPart(models.Model):
body_part = models.CharField(max_length=20, unique = True)
class Exercise(models.Model):
body_part = models.ForeignKey(BodyPart, on_delete=models.CASCADE, default = "bodypart", related_name="bodypart")
views.py:
exercises = Exercise.objects.filter(category=exercisedetailcategory).values()
context = {
"exercises" : exercises,
}
return render(request,"exercises-categories.html",context)
template:
{% for exercise in exercises %}
<span class="post-meta-category">{{exercise.body_part}}</span>
<div class="post-item-description">
{{exercise.title}}
<p>{{exercise.content}}</p>
{% endfor %}
This is one of the many reasons why you should not use .values(). If you pass Exercise models, you can fetch the related object into memory. You can make use of .select_related(..) to optimize the query:
exercises = Exercise.objects.filter(
category=exercisedetailcategory
).select_related('body_part')
context = {
'exercises' : exercises,
}
return render(request, 'exercises-categories.html', context)
Then in the template, we can render this with:
{% for exercise in exercises %}
<span class="post-meta-category">{{ exercise.body_part.body_part }}</span>
<div class="post-item-description">
{{ exercise.title }}
<p>{{ exercise.content }}</p>
{% endfor %}
You can furthermore implement a __str__ method for BodyPart:
class BodyPart(models.Model):
body_part = models.CharField(max_length=20, unique=True)
def __str__(self):
return self.body_part
and then render this with:
{% for exercise in exercises %}
<span class="post-meta-category">{{ exercise.body_part }}</span>
<div class="post-item-description">
{{ exercise.title }}
<p>{{ exercise.content }}</p>
{% endfor %}
in your exercise model, ignore the default part.(its possible to show any message that tell users "no body_part" such as
{% if not exercise.body_part %} -> No thing to show)
and make sure, you have a value in your exercise.body_part Which it means you have to have an object in your BodyPart model in relation to the current object of this model.
also it should be {{ exercise.body_part.body_part }}
the second one is to extract the value of related BodyPart objects value
So I'm doing my first project using django showing examples of REST API methods like POST, GET, PUT, and DELETE through a made up database of first name, last name, and e-mail. I've been successful with POST and GET but now I am having trouble with PUT.
So I have three functions. First, a simple def function that shows all inputs of information so far. Second a class based function that lists the information in a specific order. And third, another class based function that shows the detail of that specific information. All functions work but now when I am linking the two html files together I am getting a error. I've tried a number of different ids when wanting to link to that specific information but they are just not working.
But here is my models.py:
class Information(models.Model):
"""Placeholder code that the viewer will be seeing."""
info_id = models.AutoField(primary_key=True,unique=True)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
e_mail = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
"""Return information."""
return f"Info ID: {self.info_id}, First Name: {self.first_name}, Last Name: {self.last_name}, E-mail: {self.e_mail}"
Here is my forms.py:
class NameForm(forms.ModelForm):
class Meta:
model = Information
fields = ['info_id', 'first_name', 'last_name', 'e_mail']
labels = {'first_name': 'First Name:', 'last_name': 'Last Name:', 'e_mail': 'E-mail:'}
widgets = {'first_name': '',
'last_name': '',
'e_mail': '',
}
Here is my urls.py:
# Page(s) that shows the viewer an example of PUT.
path('PUT/', InfoDataListView.as_view(), name='put_data'),
path('PUT/<int:pk>/', InfoDataDetailView.as_view(), name='put_detail'),
Here is my views.py:
def put(request):
put = Information.objects.all()
context = {'put': put}
return render(request, 'just_projects/put.html', context)
class InfoDataListView(ListView):
model = Information
template_name = 'just_projects/put.html'
context_object_name = 'put'
ordering = ['-date_added']
class InfoDataDetailView(DetailView):
model = Information
Here are my two htmls: put.html and information_detail.html
{% extends "just_projects/base.html" %}
{% block content %}
<p><h3>Information:</h3></p>
<ul>
{% for pt in put %}
<div class="card mb-3">
<h4 class="card-header">
{{ pt.date_added|date:'M d, Y H:i' }}
<small>Edit info.</small>
</h4>
<div class="card-body">
{{ pt }}
</div>
</div>
{% empty %}
<li>There is no information yet.</li>
{% endfor %}
</ul>
<hr />
{% endblock content %}
{% extends "just_projects/base.html" %}
{% block content %}
<p><h3>Information:</h3></p>
<ul>
<div class="card mb-3">
<h4 class="card-header">
{{ object.date_added|date:'M d, Y H:i' }}
</h4>
<div class="card-body">
{{ object }}
</div>
</div>
</ul>
<hr />
<p>
</p>
{% endblock content %}
So the error message is a reversematch because they can't find the correct id right? The specific exception error and value is:
Exception Type: NoReverseMatch
Exception Value:
Reverse for 'put_detail' with arguments '('',)' not found. 1 pattern(s) tried: ['PUT/(?P[0-9]+)/$']
EDIT: Thank you Daniel Roseman!!!
You are using the wrong variable name within your url tag. Your object is called pd, so that is where the ID needs to come from.
<a href="{% url 'just_projects:put_detail' pd.id %}">
(Unrelated, but your clean method is pointless; it is within the Meta class so it will never actually be called, but also it does nothing useful. You should delete it.)
Edit looking at your models, your primary key is called info_id for some reason. So either do pd.info_id, or just pd.pk.
Although I would question why you need to change the name of the primary key in the first place.
I am trying to display data from different models in my template and I have no idea why it doesn't work properly.
Models.py:
class Company(models.Model):
name = models.CharField(max_length=100)
class Location(models.Model):
company = models.ForeignKey('Company',
related_name='locations')
Views.py:
def sth(request):
loc_list = Location.objects.all()
return render(request, 'blabla/index.html', {'loc_list': loc_list})
Template:
{% for loc in loc_list %}
{% for entry in loc.company_set.all %}
{{entry.name}}
{% endfor %}
{% endfor %}
Why the name of the company is not showing?
You foreign key relationship is the wrong way around... the way you have currently defined them, a Location can have only one Company associated with it, but in your template you are trying to fetch a list of Companys.
For your template logic to work, your models would have to be defined as follows:
class Company(models.Model):
name = models.CharField(max_length=100)
location = models.ForeignKey('Location')
class Location(models.Model):
# You probably need some other fields in here.
Then this would work:
{% for loc in loc_list %}
{% for entry in loc.company_set.all %}
{{entry.name}}
{% endfor %}
{% endfor %}
Only use "_set" when accessing backwards relationships. There is no
loc.company_set.all
because each location only has one company (that is what a ForeignKey is. You can access the location's company by doing loc.company). If you want location to have multiple companies, either see solarissmoke's answer (where you put the ForeignKey attribute on Company and not on Location), or use a ManyToManyField relationship (this will allow companies to have multiple locations and each location to have multiple companies).
Then you can access all the companies of a given location by doing this in the template:
{% for loc in loc_list %}
{% for entry in loc.company.all %}
{{entry.name}}
{% endfor %}
{% endfor %}
You can also access all the locations for a given company by using the related_name (company.locations).
Short answer:
{% for loc in loc_list %}
{{loc.company.name}}
{% endfor %}