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 %}
Related
I am new to Django and as such I have a few gaps in my knowledge, particularly with regards to the get_context_data() in views. My aim is to try and make use of the DRY aspect of Django so that I have a list of categories on my webpage and each category reveals a list of posts that fall under it. To achieve this, I have a ListView and DetailView class (shown below). Note, the categories and posts are all objects of the same model.
My issues are the following:
I would like to display only some of the posts in my ListView not all of them. Is there a dynamic way to do this?
In my DetailView, I want to display a list of posts but from a single choice, for eg say choice1 is selected when creating a post, then only all posts with choice 1 selected will be displayed. I would like to do that dynamically, currently its hardcoded to only display choice1. I have tried to use the get_context_data() to do this but this only outputs a queryset in my template which leads me to problem 3
My HTML file currently displays all posts of choice1 as a QuerySet but not the actual content
Views.py
class PostListView(ListView):
model = Post
template_name = 'blog/post.html'
context_object_name = 'posts'
ordering = ['-date_posted']
class PostDetailView(DetailView):
model = Post
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data(*args, **kwargs)
list_of_relevant_articles = Post.objects.filter(choice = "choice1")
context.update({'list_of_relevant_articles': list_of_relevant_articles})
return context
Models.py
POST_CHOICES = (
('choice1','CHOICE1'),
('choice2', 'CHOICE2'),
('choice3','CHOICE3'),
('choice4','CHOICE4'),
)
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ImageField(blank=True, null=True)
choice = models.CharField(max_length=14, choices = POST_CHOICES, default ='choice1')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog-post', kwargs={'pk': self.pk})
Html file
{% block content %}
<!-- This for loop does not output anything but I believe i need one to iterate through
a queryset-->
{% for post in object_list %}
{{ list_of_relevant_articles }}
{% endfor %}
{% endblock content %}
Any help/advice will be greatly appreciated
1
you can use context['posts'] = context['posts'][:3] and a normal loop in template, but it is better to use pagination in general.
class PostListView(ListView):
model = Post
template_name = 'blog/post.html'
context_object_name = 'posts'
ordering = ['-date_posted']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts'] = context['posts'][:3]
return context
{% for post in posts %}
{{ post }} <br>
{% endfor %}
The other way is do not use get_context_data and inside the template use slicing like:
{% for post in posts|slice:":2" %}
2, 3
class PostDetailView(DetailView):
model = Post
template_name = 'blog/detail.html'
slug_url_kwarg = "choice"
slug_field = "choice"
context_object_name = 'posts'
def get_object(self, queryset=None):
return Post.objects.filter(choice=self.kwargs.get("choice"))
{% for post in posts %}
{{ post }} <br>
{% endfor %}
urls.py
urlpatterns = [
# ...
path('post/', views.PostListView.as_view(), name='list'),
path('post/<str:choice>/', views.PostDetailView.as_view(), name='detail'),
]
so in the browser: .../post/choice2/, .../post/choice3/, ...
I have one simple model called Product
class Product(models.Model):
title = models.CharField(max_length=150)
def __str__(self):
return self.title
And one other model called ExternalProduct
class ExternalProduct(models.Model):
title = models.CharField(max_length=100)
external_id = models.CharField(max_length=25)
internal_product = models.ForeignKey(
Product,
on_delete=models.CASCADE,
blank=True,
related_name='external_products',
)
price = models.IntegerField()
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, blank=True)
image_url = models.CharField(max_length=255)
product_url = models.CharField(max_length=255)
store = models.CharField(max_length=50)
active = models.BooleanField(default=True)
class Meta:
ordering = ['price']
def __str__(self):
return self.title
On the detailed view of the Product, I want to display the price of all ExternalProduct related to the Product. It works all fine with this view
# products/views.py
class ProductDetailView(DetailView):
model = Product
context_object_name = 'product'
template_name = 'products/product_detail.html'
and this template
# product_detail.html
{% extends '_base.html' %}
{% block title %}{{ product.title }}{% endblock title %}
{% block content %}
<div class="book-detail">
<h2>{{ product.get_active_external_products }}</h2>
</div>
<div>
<ul>
{% for ep in product.external_products.all %}
<li>{{ ep.price }}</li>
{% endfor %}
</ul>
</div>
{% endblock content %}
The problem is that I just want to display the price of ExternalProduct which has active=True
Been trying to solve it with a custom method in products views.py
class ProductDetailView(DetailView):
model = Product
context_object_name = 'product'
template_name = 'products/product_detail.html'
def get_active_external_products(self):
return self.external_products.objects.filter(active=True)
And a modified for loop inside product_detail.html
{% for ep in product.get_active_external_products %}
<li>{{ ep.price }}</li>
{% endfor %}
But unfortunately without any success. The site does not crash, just doesn't show anything other than the rest of the html file.
Any advice?
You can use prefetch_related with Prefetch to filter only active external products. For this you need to override get_queryset() method in your view:
from django.db.models import Prefetch
class ProductDetailView(DetailView):
model = Product
context_object_name = 'product'
template_name = 'products/product_detail.html'
def get_queryset(self):
return Product.objects.prefetch_related(Prefetch("external_products", queryset=ExternalProduct.objects.filter(active=True), to_attr="active_external_products"))
Note to_attr="active_external_products" part. It tells that active external products will be available as active_external_products attribute. So in template you can get them like this:
{% for ep in product.active_external_products %}
<li>{{ ep.price }}</li>
{% endfor %}
Or you can just override get_context_data() to insert external products to the context directly:
class ProductDetailView(DetailView):
model = Product
context_object_name = 'product'
template_name = 'products/product_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(****kwargs)
context["active_external_products"] = self.object.external_products.filter(active=True)
return context
In this case in template you can use active_external_products variable name directly:
{% for ep in active_external_products %}
<li>{{ ep.price }}</li>
{% endfor %}
I am working on MDN Django tutorial on LocalLibrary.
I am using get_absolute_url to render generic ListView and DetailView page.
App is rendering page with else statement i.e. There are no books in the library.
But when I add books in the library through admin portal in my models. Same pages are not rendering and reflecting below error
NoReverseMatch at /catalog/books/
Reverse for 'book-detail' not found. 'book-detail' is not a valid view function or pattern name.
Please find the models.py as follows
import uuid # Required for unique book instances
from django.db import models
# Create your models here.
# Used to generate URLs by reversing the URL patterns
from django.urls import reverse
class Genre(models.Model):
"""Model representing a book genre."""
name = models.CharField(
max_length=200, help_text='Enter a book genre (e.g. Science Fiction)')
def __str__(self):
"""String for representing the Model object."""
return self.name
class Language(models.Model):
name = models.CharField(max_length=200, help_text='Enter a book language(e.g. English)')
def __str__(self):
return self.name
class Book(models.Model):
"""Model representing a book (but not a specific copy of a book)."""
title = models.CharField(max_length=200)
# Foreign Key used because book can only have one author, but authors can have multiple books
# Author as a string rather than object because it hasn't been declared yet in the file
author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
summary = models.TextField(
max_length=1000, help_text='Enter a brief description of the book')
isbn = models.CharField('ISBN', max_length=13, unique=True,
help_text='13 Character ISBN number')
# ManyToManyField used because genre can contain many books. Books can cover many genres.
# Genre class has already been defined so we can specify the object above.
genre = models.ManyToManyField(
Genre, help_text='Select a genre for this book')
language = models.ManyToManyField(
Language, help_text='Select Language for this book')
def __str__(self):
"""String for representing the Model object."""
return self.title
def get_absolute_url(self):
"""Returns the url to access a detail record for this book."""
return reverse('book-detail', kwargs= {'pk': str(self.id)})
def display_genre(self):
"""Create a string for the Genre. This is required to display genre in Admin."""
return ', '.join(genre.name for genre in self.genre.all()[:3])
display_genre.short_description = 'Genre'
def get_language(self):
"""Create a string for the Genre. This is required to display genre in Admin."""
return ', '.join(language.name for language in self.language.all()[:3])
get_language.short_description = 'Language'
class BookInstance(models.Model):
"""Model representing a specific copy of a book (i.e. that can be borrowed from the library)."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4,
help_text='Unique ID for this particular book across whole library')
book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True)
imprint = models.IntegerField()
due_back = models.DateField(null=True, blank=True)
LOAN_STATUS = (
('m', 'Maintenance'),
('o', 'On loan'),
('a', 'Available'),
('r', 'Reserved'),
)
status = models.CharField(
max_length=1,
choices=LOAN_STATUS,
blank=True,
default='m',
help_text='Book availability',
)
class Meta:
ordering = ['due_back']
def __str__(self):
"""String for representing the Model object."""
return f'{self.id} ({self.book.title})'
class Author(models.Model):
"""Model representing an author."""
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
date_of_death = models.DateField('Died', null=True, blank=True)
class Meta:
ordering = ['last_name', 'first_name']
def get_absolute_url(self):
from django.urls import reverse
"""Returns the url to access a particular author instance."""
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
"""String for representing the Model object."""
return f'{self.last_name}, {self.first_name}'
Attached is the urls.py
from django.urls import path
from catalog import views
from catalog.views import BookListView, BookDetailView
app_name = 'catalog'
urlpatterns = [
path('', views.index, name='index'),
path('books/', views.BookListView.as_view(), name='books'),
path('book/<int:pk>', views.BookDetailView.as_view(), name='book-detail'),
path('authors/', views.AuthorListView.as_view(), name='authors'),
path('author/<int:pk>', views.AuthorDetailView.as_view(), name='author-detail'),
]
Views.py
from django.shortcuts import get_object_or_404
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView
from django.http import HttpResponse
from django.shortcuts import render
from catalog.models import *
def index(request):
num_books = Book.objects.all().count()
num_instances = BookInstance.objects.all().count()
num_genre = Genre.objects.all()
num_instances_available = BookInstance.objects.filter(status__exact='avail').count()
num_authors = Author.objects.count()
context = {
'num_books': num_books,
'num_instances': num_instances,
'num_instances_available': num_instances_available,
'num_authors': num_authors,
'num_genre': num_genre,
}
return render(request, 'catalog/index.html', context=context)
class BookListView(ListView):
model = Book
paginate_by = 2
class BookDetailView(DetailView):
model = Book
class AuthorListView(ListView):
model = Author
class AuthorDetailView(DetailView):
model = Author
Template of book_list.html
This is saved as per this method
catalog/templates/catalog/book_list.html
{% extends "catalog/base.html" %}
{% block title %}
<title>List of Books</title>
{% endblock %}
{% block content %}
<h1>Book List</h1>
{% if book_list %}
<ul>
{% for book in book_list %}
<li>
{{ book.title }}
({{ book.author }})
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no books in the library.</p>
{% endif %}
{% endblock %}
book_detail.html
This is saved as per this method
catalog/templates/catalog/book_detail.html
{% block content %}
<h1>Title: {{ book.title }}</h1>
<p><strong>Author:</strong> {{ book.author }}</p>
<p><strong>Summary:</strong> {{ book.summary }}</p>
<p><strong>ISBN:</strong> {{ book.isbn }}</p>
<p><strong>Language:</strong> {{ book.language }}</p>
<p><strong>Genre:</strong> {{ book.genre.all|join:", " }}</p>
<div style="margin-left:20px;margin-top:20px">
<h4>Copies</h4>
{% for copy in book.bookinstance_set.all %}
<hr>
<p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'd' %}text-danger{% else %}text-warning{% endif %}">{{ copy.get_status_display }}</p>
{% if copy.status != 'a' %}<p><strong>Due to be returned:</strong> {{copy.due_back}}</p>{% endif %}
<p><strong>Imprint:</strong> {{copy.imprint}}</p>
<p class="text-muted"><strong>Id:</strong> {{copy.id}}</p>
{% endfor %}
</div>
{% endblock %}```
I also find error on the same page for bootstrap link which is getting extended from base file and working fine with index.html
whereas on books.html page
It is showing this error
In template /Users/rishipalsingh/Projects/notes/mdndjango/mdn/catalog/templates/catalog/base.html, error at line 7
base.html
```
Thank you
You have namespaced your urls by writing:
app_name = 'catalog'
Hence reverse('book-detail', ...) will not work and you need to specify the namespace along with the url name, hence it should be:
reverse('catalog:book-detail', kwargs= {'pk': str(self.id)})
Similarly for the author:
reverse('catalog:author-detail', args=[str(self.id)])
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 %}.
I have been trying to do this nested loop for couple of hours, but so far none of my results worked. Here is what i have so far:
index.html
{% for category in categories %}
<div class="col-md-12 column category list-group">
<p><b>{{ category.name }}</b></p>
{% for subcategory in subcategories %}
<div class="list-group-item">
<h5 class="mb-1">{{ subcategory.name }}</h5>
<small>{{ subcategory.description }}</small>
<small>{{ subcategory.count_threads }}</small>
</div>
{% endfor %}
</div>
{% endfor %}
views.py
class IndexView(ListView):
template_name = 'myForum/index.html'
context_object_name = 'SubCategory_list'
queryset = SubCategory.objects.all()
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all()
context['subcategories'] = SubCategory.objects.all()
return context
models.py
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class SubCategory(models.Model):
name = models.CharField(max_length=255)
description = models.CharField(max_length=255)
category = models.ForeignKey('Category', default=0)
def __str__(self):
return self.name
Output
Output
My problem is that SubCategory - News, belongs to Infromation, category Off-Topic belongs to General. For some reason loop displays all of subcategories, and i can't figure out how to narrow it to current Category.
You can change the inner loop to:
{% for subcategory in category.subcategory_set.all %}
This will loop over the subcategories for the current category.
Since you are looping over categories in the outer loop, you can change the queryset in the list view to use the Category model. You can cut down the number of queries by prefetching the subcategories.
It also looks as if you can remove the get_context_data method, since the list view already makes the queryset available in the template context.
class IndexView(ListView):
template_name = 'myForum/index.html'
context_object_name = 'catrgories'
queryset = Category.objects.all().prefetch_related('subcategory_set')