Django ForeignKey attribute is not showing up in HTML - python

I have some models and connected each other. And in my views I get the Chapter model ordered by date published, and then remove the ones with same Manga (foreign key associated with other model).
But apparently accept for the fields is not connected with anything, and I can't access the models fields. But also because I am working with a query set I can't sort by id or anything. Also, I have to access the Manga models fields associated with chapter. But it does not show up in the HTML. How can I do it?
My models:
class Manga(models.Model):
manga_name = models.CharField(max_length=255, default="null")
manga_image = models.ImageField(upload_to="thumbnail", default="thumbnail/noimage.png")
manga_views = models.IntegerField(default=0)
def __str__(self):
return f"{self.id}, {self.manga_name}"
class Fansub(models.Model):
fansub_name = models.CharField(max_length=255, default="null")
def __str__(self):
return f"{self.id}, {self.fansub_name}"
class Chapter(models.Model):
chapter_number = models.IntegerField(default=0)
chapter_url = models.URLField(default="www.example.com")
manga = models.ForeignKey(Manga, on_delete=models.CASCADE)
fansub = models.ForeignKey(Fansub, on_delete=models.CASCADE)
relase_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.chapter_number}, {self.manga}, {self.fansub}"
My view:
def Index(request):
manga = Manga.objects.all()
chapter = Chapter.objects.all().values()
fansub = Fansub.objects.all().values()
mostview = manga.order_by("-manga_views")[:5]
relasedateorder = chapter.order_by("relase_date")
context = {
"Manga": manga,
"Chapter": chapter,
"Fansub": fansub,
"Mostview": mostview,
"LastUpdated": relasedateorder,
}
template = loader.get_template("Index.html")
And finally the HTML:
{%for d in LastUpdated%}
<p>{{d.manga}}</p>
{%endfor%}

You can access the all the instances of Manga model associated with chapter instance by using _set as suffix in the following way:
{% for i in LastUpdated %}
<p>{{i.chapter_number}}</p>
<h2> manga model attributes below</h2>
{% for j in i.manga_set.all %}
{{j.manga_name}}
{{j.manga_views}}
{% endfor %}
{% endfor %}
Also you should use Chapter.objects.all().order_by("relase_date") this queryset as using .values() gives you a key-value pair (dict).

Related

Django templates: data filtering by foreign key

I'm new to Django and I have a problem I can't seem to solve. Long story short, I created a text based app that helps me create a meal plan and generates a shopping list. And I'm trying to recreated with django.
Here are my models:
class Recipe(models.Model):
name = models.CharField(max_length=40)
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.CharField(max_length=20)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def __str__(self):
return self.name
class IngredientSet(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.SET_NULL, null=True)
ingredient = models.ForeignKey(Ingredient, on_delete=models.DO_NOTHING)
quantity = models.FloatField()
def __str__(self):
return self.ingredient.name
Now, on my List Views I want to display the names of stored recipes as links. Each recipe link should call a Detail View which will display selected recipe's sets of ingredients. I can't figure out how to access those by their foreign key(which points to a recipe).
One can access reverse relations by using the model name in lowercase with _set appended. For Recipe and IngredientSet you can write recipe.ingredientset_set.all() to get all the instances of IngredientSet related to a recipe. One can customize this name by setting the related_name e.g.:
class IngredientSet(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.SET_NULL, null=True, related_name="ingredient_sets")
ingredient = models.ForeignKey(Ingredient, on_delete=models.DO_NOTHING)
quantity = models.FloatField()
def __str__(self):
return self.ingredient.name
Now you can write recipe.ingredient_sets.all().
To loop over this in the template one can simply write:
{% for ingredient_set in recipe.ingredient_sets.all %}
{{ ingredient_set.ingredient.name }}
{{ ingredient_set.quantity }}
{% endfor %}
Note: This will make a query to the database for each recipe to get it's ingredient set. You can use prefetch_related [Django
docs]
if you want to reduce the amount of queries made.
assuming that you want to display the names of stored recipes in a html template as links:
views.py
from .models import Recipe, Ingredient, IngredientSet
from django.shortcuts import render
def recipes(request):
recipes_for_template = Recipe.objects.all()
return render(request, 'main/recipes.html', {"recipes": recipes_for_template})
def ingredients(request, cod_recipe):
ingredients_sets = IngredientSet.objects.filter(recipe=cod_recipe)
ingredients_to_template = []
for ingredient_set in ingredients_sets:
ingredient_obj = Ingredient.objects.get(id=ingredient_set.id)
dict_of_ingredient = {
"name": ingredient_obj.name,
"quantity": ingredient_set.quantity
}
ingredients_to_template.append(dict_of_ingredient)
return render(request, 'main/ingredients.html', {"ingredients": ingredients_to_template})
where main is the name of the app
recipes.html
<table>
<thead>
<th>Recipe</th>
</thead>
<tbody>
{% for recipe in recipes %}
<tr>
<td>
{{recipe.id}}
</td>
</tr>
{% endfor %}
</tbody>
</table>
ingredients.html
{% for ingredient in ingredients %}
{{ingredient.name}}
{{ingredient.quantity}}
{% endfor %}
urls.py
from django.urls import path
from . import views
app_name = "main"
urlpatterns = [
path('recipe/', views.recipes, name='recipes'),
path('recipe/<int:cod_recipe>/', views.ingredients, name='ingredients'),
]
I think this is it, any doubts i will be glad to answer.

How to pass multiple values from models.py to HTML via views.py in Django

I have a following models.py for my Django blog, I made a following views.py to pass the value of the slug for my URL parameter.
However I am struggling to create a model in views to get other data(person & description) from Category class.
I have tried some patterns by myself but can not pass them to HTML. (always Error or not showing)
Can you please give me some idea of how to solve this.
models.py
class Category(models.Model):
person = models.CharField(max_length=20)
description = models.TextField()
slug = models.SlugField()
def __str__(self):
return self.person
views.py
def blog_category(request, category):
posts = Post.objects.filter(categories__slug__contains=category).order_by("-created_on").distinct()
context = {"category": category, "posts": posts}
return render(request, "blog_category.html", context)
HTML(Localhost:8000/slug)
{{ person }}
{{ description }}
this is full code of my models.py
class Category(models.Model):
person = models.CharField(max_length=20)
description = models.TextField()
slug = models.SlugField()
def __str__(self):
return self.person
class Recommender(models.Model):
recommender_name = models.CharField(max_length=20)
slug = models.SlugField()
def __str__(self):
return self.recommender_name
class Post(models.Model):
book_title = models.CharField(max_length=255)
author = models.CharField(max_length=255)
book_link = models.CharField(max_length=255)
recommenders = models.ForeignKey("Recommender", on_delete=models.CASCADE,)
source = models.TextField()
source_link = models.CharField(max_length=255)
created_on = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
categories = models.ManyToManyField("Category", related_name="posts")
slug = models.SlugField()
def __str__(self):
return self.book_title
posts = Post.objects.filter(categories__slug__contains=category).order_by("-created_on").distinct()
Is going to return a queryset. It can have more than one instance of the model class (since you are using filter). In your context you are sending this queryset as posts to your templates.
So in your HTML you can use something like this. You need to use a for loop since there can be more than one item in posts.
{% for post in posts %}
{% for category in post.categories.all %}
{{ category.person }}
{{ category.description }}
{% endfor %}
{% endfor %}
I would look at this example.
Namely, if you render the template like it is shown in the example, you should be able to do
{{ category.person }} {{ category.description }}

Displaying objects by categories in Django

I'm building an ecommerce app and I'd like to make a section that shows some featured items by category. I'm using three sliders that display those items; Each slider is a featured category and each item in the slider is a featured item.
The problem is that I can't figure out how to assign the item to the proper slider. For example: I want to assign a JeansJacket to "Clothes and accesories" and display it. I tried this:
{% for cat in categories %}
<h1>{{ cat.cat_name }}</h1>
<!--(carousel code in between)-->
<div class="carousel-inner" role="listbox">
{% for item in featured_items %}
{% if item.Categoría in cat.cat_name %}
{{ item }}
{% endif %}
This is a simplified version of what I have, without the rest of the content. I just can't figure out how to iterate though the featured items and display them in the corresponding category.
Edit: This is in models.py:
class Categorías(models.Model):
cat_name = models.CharField(max_length=30)
Destacado = models.BooleanField()
class Meta:
ordering = ('cat_name',)
verbose_name = 'Categoría'
verbose_name_plural = 'Categorías'
def __str__(self):
return self.cat_name
class publicaciones(models.Model):
Título = models.CharField(max_length=30)
Descripción = models.TextField(max_length=200)
Precio = models.FloatField()
Fotos = models.ImageField()
Categoría = models.ForeignKey(Categorías, on_delete=models.CASCADE)
Promocionado = models.BooleanField()
class Meta:
verbose_name = 'Publicación'
verbose_name_plural = 'Publicaciones'
def __str__(self):
return self.Título
You can use prefetch_related and Prefetch with a custom query to get all related articles for each category.
categories = Categorías.objects.prefetch_related(Prefetch(
'publicaciones_set',
queryset=publicaciones.objects.filter(Promocionado=True),
to_attr='featured_items'
))
Now you can loop over each category and then loop over this prefetch. You don't need to create a separate featured_items queryset
for category in categories:
for featured_item in category.featured_items:
...
You can use apply this pattern to your template

Django Show Unique List Of Subjects (Many To Many) In Dropdown Menu

I want to be able to produce a dropdown menu in my template with a unique list of subjects.
Subjects are populated inside of admin rather than hard coding them in SUBJECT_CHOICES.
A course can have many subjects or only 1 subject. For example:
Course Title = Django
Subject = Technology
Course Title = Python
Subject = Technology
Course Title = Accounting
Subject = Business
Course Title = E-commerce
Subject(s) = Technology, Business
CourseListView corresponds to the course_list.html template.
models.py:
class Subject(models.Model):
SUBJECT_CHOICES = ()
name = models.CharField(max_length=20,choices=SUBJECT_CHOICES, unique=True)
def __str__(self):
return self.name
class Course(models.Model):
​
SKILL_LEVEL_CHOICES = (
('Beginner', 'Beginner'),
('Intermediate', 'Intermediate'),
('Advanced', 'Advanced'),
)
​
slug = models.SlugField()
title = models.CharField(max_length=120)
description = models.TextField()
allowed_memberships = models.ManyToManyField(Membership)
created_at = models.DateTimeField(auto_now_add=True)
subjects = models.ManyToManyField(Subject)
skill_level = models.CharField(max_length=20,choices=SKILL_LEVEL_CHOICES, null=True)
visited_times = models.IntegerField(default=0)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('courses:detail', kwargs={'slug': self.slug})
#property
def lessons(self):
return self.lesson_set.all().order_by('position')
​
views.py:
class CourseListView(ListView):
model = Course
def get_queryset(self):
qs = super().get_queryset()
title_contains_query = self.request.GET.get('title_contains')
view_count_min = self.request.GET.get('view_count_min')
view_count_max = self.request.GET.get('view_count_max')
date_min = self.request.GET.get('date_min')
date_max = self.request.GET.get('date_max')
skill_level_query = self.request.GET.get('skill_level')
if title_contains_query:
qs = qs.filter(title__icontains=title_contains_query)
if view_count_min:
qs = qs.filter(visited_times__gte=view_count_min)
if view_count_max:
qs = qs.filter(visited_times__lte=view_count_max)
if date_min:
qs = qs.filter(created_at__gte=date_min)
if date_max:
qs = qs.filter(created_at__lte=date_max)
if skill_level_query:
qs = qs.filter(skill_level=skill_level_query)
return qs
Desired Output:
I tried writing a for loop in my template that does return the subjects successfully but they are not unique or showing only once.
{% for item in object_list %}
<h2>{{ item.subjects.all}}</h2>
<ul>
{% for sub in item.subjects.all %}
<li>{{ sub.name }}</li>
{% endfor %}
</ul>
{% endfor %}
​
Result:
​
<QuerySet [<Subject: Business>]>
Business
<QuerySet [<Subject: Technology>]>
Technology
<QuerySet [<Subject: Technology>]>
Technology
<QuerySet [<Subject: Business>]>
Business
I would prefer to do it with a for loop that produces unique results, but maybe it can be done with django-select2 or use a form with model select or multiple model select? Can someone provide some code for either the loop or one of these methods?
I would appreciate any help with this.
I see two solutions here:
The first one if to simply get all the entered values in the Subject model but you couldn't use all the filters you are using in your view, only the one about the title.
To do so, just use:
title_contains_query = self.request.GET.get('title_contains')
if title_contains_query:
subjects = [title for title in Subject.objects.filter(title__icontains=title_contains_query)]
The other option is to use the distinct() method on your QuerySet which filters and remove the duplicate entries in it. Use it like this: qs = qs.distinct()
Hope it helps!

For the Django template {%url%} tag, how do I find all URL arguments from generic view contexts, not just the last?

In Django, I am trying to make an app with a tree hierarchy of models i.e Books containing Chapters containing Sections etc. I want my URLs to mirror the structure, like books/3/chapters/5/sections.
I want to use the generic class-based views. In my HTML templates, I will use the {% url %} command to specify link targets.
For URLs deep in the structure, I need to provide {% url %} with all the keys that appear in the URL. For the above example: {% url 'pattern-name' 3 5 %}
However, the generic class-based views only provide (if that) the primary key of the object they are concerned with in the template context (i.e. in the above: 5).
How do I retrieve the foreign keys of parent objects (i.e. in the above: 3)?
I have the following:
My Models in
models.py:
class Book(models.Model):
name = models.CharField("Book Name", max_length = 80)
class Chapter(models.Model):
name = models.CharField("Book Name", max_length = 80)
book = models.ForeignKey('Book', on_delete = models.CASCADE)
My URL patterns in urls.py:
urlpatterns = [
path('books/',
views.BookListView.as_view(), name='book_list'),
path('books/<int:book>/',
views.BookDetailView.as_view(), name='book_detail'),
path('books/<int:book>/chapters/',
views.ChapterListView.as_view(), name='chapter_list'),
path('books/<int:book>/chapters/<int:chapter>/',
views.ChapterDetailView.as_view(), name='chapter_detail'),
path('books/<int:book>/chapters/create/',
views.ChapterCreateView.as_view(), name='chapter_create'),
My Views in views.py:
class BookListView(generic.ListView):
model = 'Book'
class BookDetailView(generic.DetailView):
model = 'Book'
pk_url_kwarg = 'book'
class ChapterListView(generic.ListView):
model = 'Chapter'
def get_queryset(self):
return Chapter.objects.filter(book = self.kwargs['book'])
class ChapterDetailView(generic.DetailView):
model = 'Chapter'
pk_url_kwarg = 'chapter'
class ChapterCreateView(generic.CreateView):
model = 'Chapter'
fields = ['name', 'book']
def get_initial(self):
initial = super().get_initial().copy()
initial['book'] = self.kwargs.['book']
return initial;
In my HTML-Template used for the list of chapters, I want to have a link to create a new chapter. Currently, that template looks as follows:
Template chapter_list.html:
<ul>
{% for chapter in chapter_list %}
<li>{{ chapter.name }}</li>
{% endfor %}
</ul>
Add new chapter
Obviously, I don't want to add all new chapters to the book with ID 42, rather I would like to use the book ID from the chapter list, where I am at. For example, for an URL
.../books/17/chapters/create/
I would like to have the link point to:
Add new chapter
The only way I have found of doing this dynamically, is to add extra context to the chapter list view:
Updated views.py:
...
class ChapterListView(generic.ListView):
model = 'Chapter'
def get_queryset(self):
return Chapter.objects.filter(book = self.kwargs['book'])
# Additional Context
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['my_current_book_id'] = self.kwargs['book']
return context
...
And then have the template say:
Updated chapter_list.html:
<ul>
{% for chapter in chapter_list %}
<li>{{ chapter.name }}</li>
{% endfor %}
</ul>
Add new chapter
Here is my question:
Is there no easier (i.e more Django-ish built-in) way to do this?
It seems to me, that this must be a very commonplace task, and that with the beautiful and fine-grained design of the generic view classes and URL (reverse-)lookups, I must be missing something.
I would consider defining get_absolute_url for the book_detail and chapter_detail, and extra methods for other URLs like chapter_create:
class Book(models.Model):
name = models.CharField("Book Name", max_length = 80)
def get_absolute_url(self):
return reverse('book_detail', args=[self.pk])
def get_create_chapter_url(self):
return reverse('chapter_create', args=[self.pk])
class Chapter(models.Model):
name = models.CharField("Book Name", max_length = 80)
book = models.ForeignKey('Book', on_delete = models.CASCADE)
def get_absolute_url(self):
return reverse('chapter_detail', args=[self.book_pk, self.pk])
Then in the template, you can use {{ chapter.get_absolute_url }}, {{ book.get_absolute_url }}, {{ chapter.book.get_create_chapter_url }} and so on, and you don't need to worry about what parameters are in the URLs.
In the chapter detail view, you can use the foreign key to access methods on the Book model:
{{ chapter.book.get_absolute_url }}
In the chapter list view, you need to add the book to the context (in case there are no chapters in the book):
from django.shortcuts import get_object_or_404
class ChapterListView(generic.ListView):
model = 'Chapter'
def get_queryset(self):
return Chapter.objects.filter(book = self.kwargs['book'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['book'] = get_object_or_404(pk=self.kwargs['book'])
return context
Then you can access the {{ book }} in the context.
Or you can use a DetailView for the chapter list view, using the Book model:
class ChapterListView(generic.DetailView):
model = 'Book'
pk_url_kwarg = 'book'
Then book is already in the template context, and you can loop through the related chapters with:
{% for chapter in book.chapter_set.all %}
{{ chapter }}
{% endfor %}
As an aside, it might be simpler to leave out the book field in the create view. Then set it in the form_valid method before saving the form.
class ChapterCreateView(generic.CreateView):
model = 'Chapter'
fields = ['name']
def form_valid(self, form):
book = get_object_or_404(Book, pk=self.kwargs['book'])
form.instance.book = book
return super(ChapterCreateView, self).form_valid(form)
You could have a view with the Book as model and:
{% for chapter in book.chapter_set.all %}

Categories

Resources