I'm not sure how to ask this question but I'll give it a shot.
I am writing a Todo application and want to display each todo under its respective category in the template. For example
Display each category
{% for category in categories %}
<h2>{{ category.name }}</h2>
Now show each todo that falls under the above category
{% for todo in todos %}
<p>{{ todo.description }}</p>
{% endfor %}
{% endfor %}
How do I create a queryset that will give my this type of structure? Or is there are different way to achieve this?
If something is unclear or require more info let me know and I'll add it to the post
Any help is appreciated, thank you.
Models
class Category(models.Model):
name = models.CharField(max_length=20)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.name
class Todo(models.Model):
# Priority choices
PRIORITY_CHOICES = (
("bg-danger", "High"),
("bg-info", "Normal"),
("bg-warning", "Low"),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
description = models.CharField(max_length=255)
priority = models.CharField(max_length=200, choices=PRIORITY_CHOICES, null=True)
completed = models.BooleanField(default=False)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
category = models.ManyToManyField(Category)
def __str__(self):
return self.description
View
def todo_listview(request):
template_name = "todos/listview.html"
context = {
"todos": get_users_todos(user=request.user),
"categories": Category.objects.all(),
}
return render(request, template_name, context)
You can prefetch the user's Todos, with:
from django.db.models import Prefetch
def todo_listview(request):
template_name = 'todos/listview.html'
context = {
'categories': Category.objects.prefetch_related(
Prefetch('todo_set', queryset=get_users_todos(user=request.user), to_attr='user_todos')
)
}
return render(request, template_name, context)
and then render this with:
Display each category
{% for category in categories %}
<h2>{{ category.name }}</h2>
{% for todo in category.user_todos %}
<p>{{ todo.description }}</p>
{% endfor %}
{% endfor %}
Since there is a many-to-many field between Category and Todo, it is possible that the same Todo will be printed multiple times: once per category.
Note: The documentation advises to
use the AUTH_USER_MODEL setting [Django-doc] over
get_user_model() [Django-doc].
This is safer, since if the authentication app is not yet loaded, the settings
can still specify the name of the model. Therefore it is better to write:
from django.conf import settings
class Todo(models.Model):
# …
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
Related
This is my 2nd week learning Django. I'm trying to get comfortable with Django Template Language. I'm trying to make an Inventory app with 4 models. The views for them are class-based.
The templates for Ingredient and Menu work as expected. However, I'm struggling with trying to loop through values from the Purchase model which has a foreign key field 'menu_item'. The template is not showing anything from the for loop. I've referred numerous articles here to find most of them use function-based views. I've tried using {% for purchase in purchase_set %}, {% for purchase in purchase_set.all %}. I know the object to iterate over is a query-set. I cannot figure out what to do?
MODELS.PY
from django.db import models
# Create your models here.
class Ingredient(models.Model):
Pounds = 'lbs'
Ounces = 'oz'
Grams = 'gms'
Eggs = 'eggs'
Piece = 'piece'
Litre = 'litre'
unit_choices = [(Pounds, 'lbs'),
(Ounces, 'ounces'),
(Grams, 'grams'),
(Eggs, 'eggs'),
(Piece, 'piece'),
(Litre, 'litre')]
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50)
unit_price = models.FloatField(default=0.0)
quantity = models.FloatField(default=0.0)
unit = models.CharField(max_length=10, choices=unit_choices)
class Meta:
ordering = ['id']
def __str__(self):
return self.name
class MenuItem(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=50)
price = models.FloatField(default=0.0)
class Meta:
ordering = ['id']
def __str__(self):
return self.title
class RecipeRequirement(models.Model):
menu_item = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
quantity = models.FloatField(default=0.0)
def __str__(self):
return self.menu_item.title
class Purchase(models.Model):
menu_item = models.ForeignKey(MenuItem, on_delete=models.CASCADE)
timestamp = models.DateTimeField()
id = models.AutoField(primary_key=True)
class Meta:
ordering = ['id']
def __str__(self):
return self.menu_item.title
VIEWS.PY:
from django.shortcuts import render
from .models import Ingredient, MenuItem, RecipeRequirement, Purchase
from django.views.generic import ListView
def home(request):
return render(request, 'inventory/home.html')
class IngredientView(ListView):
model = Ingredient
template_name = 'inventory/ingredients.html'
class PurchaseView(ListView):
model = Purchase
template_name = 'inventory/purchases.html'
class MenuView(ListView):
model = MenuItem
template_name = 'inventory/menu.html'
PURCHASES.HTML
<h3>This is purchases page.</h3>
{% block content %}
<table>
{% for purchase in purchase_set.all %}
<tr>
<td>{{ purchase.title }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
The title field you are trying to show is in MenuItem model that is used as foreignkey in Purchase model with menu_item field. ListView class returns to context yours purchases in object_list key. So in Your purchases.html template:
<h3>This is purchases page.</h3>
{% block content %}
<table>
{% for purchase in object_list %}
<tr>
<td>{{ purchase.menu_item.title }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
PS. This `purchase_set' is referring to reverse relationship. So if you would want to show all purchases of MenuItem you would do:
{% for item in menuitems %}
<h1>{{ item }}</h1>
{% for purchase in item.purchase_set.all %}
<p>{{ purchase.timestamp }}</p>
{% endfor %}
{% endfor %}
I'm a newbie to Django, trying to build site to allow people to register for football matches.
At the moment, a user can register multiple times for the same match, which is obviously not ideal! Is there a way that I can identify if the currently logged in user has already registered, and then replace the register button with a message telling them that they have already registered? I guess I need some kind of Boolean value in my EventDetail view, and then I can make a conditional statement in the template, but I'm unsure as to how to implement this. I hope this question is clear, it's my very first post!
views.py:
class EventDetail(View):
def get(self, request, id, *args, **kwargs):
event = get_object_or_404(Event, pk=id)
registrations = Registration.objects.filter(event=event)
total_participants = 0
for person in registrations:
total_participants += 1
if person.guest:
total_participants += 1
remaining_spaces = event.max_participants - total_participants
template = "event_detail.html"
context = {
"event": event,
"total_participants": total_participants,
"registrations": registrations,
"remaining_spaces": remaining_spaces,
}
return render(request, template, context)
template
{% extends "base.html" %}
{% block content %}
<p>{{ event.title }}</p>
<p>{{ total_participants }} / {{ event.max_participants }} ({{ remaining_spaces }} spot{{ remaining_spaces|pluralize }}
remaining!)</p>
{% if total_participants < event.max_participants %}
Register
{% else %}
<p>This event has filled up.</p>
{% endif %}
<h2>Current Participants</h2>
<ul>
{% for person in registrations %}
<li>
{{ person.name }}
{% if person.guest %}
+1
{% endif %}
</li>
{% endfor %}
</ul>
{% endblock %}
models.py
from django.db import models
from django.contrib.auth.models import User
class Event(models.Model):
title = models.CharField(max_length=100)
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name="event_posts")
event_date_and_time = models.DateTimeField()
venue = models.CharField(max_length=100)
max_participants = models.IntegerField()
extra_info = models.TextField(blank=True)
updated_on = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['event_date_and_time']
def __str__(self):
return self.title
class Registration(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="event_registration")
name = models.ForeignKey(User, on_delete=models.CASCADE)
ball = models.BooleanField(default=False)
bibs = models.BooleanField(default=False)
guest = models.BooleanField(default=False)
def __str__(self):
return str(self.name)
I'm trying to have a page where I loop over all my categories and all the products in each category.
Something like this:
Category 1:
product1
product2
product3
Category 2:
product1
product2
product3
Category 3:
product1
product2
product3
views.py
def model_categories_view(request):
context = {
"categories": ProductCategory.objects.all(),
"models": ProductModel.objects.filter(???),
}
return render(request=request, template_name='main/categories.html', context=context)
categories.html
{% for category in categories %}
<h1>{{category.title}}</h1>
{% for model in models %}
{{model.title}}
{% endfor %}
{% endfor %}
models.py
class ProductModel(models.Model):
title = models.CharField(max_length=80)
category = models.ManyToManyField(ProductCategory)
model_slug = AutoSlugField(null=True, default=None,
unique=True, populate_from='title')
class Meta:
verbose_name_plural = "Models"
def __str__(self):
return self.title
class ProductCategory(models.Model):
title = models.CharField(max_length=50)
category_slug = AutoSlugField(null=True, default=None,
unique=True, populate_from='title')
class Meta:
verbose_name_plural = "Product Categories"
def __str__(self):
return self.title
How can I list all the models for the current category being iterated?
You can obtain the queryset of related products with .productmodel_set. You thus can thus write the template as:
{% for category in categories %}
<h1>{{ category.title }}</h1>
{% for product in category.productmodel_set.all %}
{{product.title}}
{% endfor %}
{% endfor %}
To make sure that this is done efficiently, you can make use of .prefetch_related(..) [Django-doc] to efficiently fetch all the related Products for the given ProductCategorys:
def model_categories_view(request):
context = {
"categories": ProductCategory.objects.prefetch_related('productmodel')
}
return render(
request=request,
template_name='main/categories.html',
context=context
)
Note: normally models have no Model suffix, so you better rename ProductModel to Product.
Trying to get the children of a one to many relationship using the related_name property.
What I've tried so far doesn't work:
models.py
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
slug = models.SlugField(max_length=30, unique=True)
class Product(models.Model):
name = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=30, unique=True)
category = models.ForeignKey(
Category,
related_name='products',
on_delete=models.PROTECT,
default=1
)
views.py
from django.shortcuts import render, get_object_or_404
from Shop.models import Category
def product_list(request, slug):
category = get_object_or_404(Category, slug=slug)
products = category.products.all()
context = {
'customCSS': '/static/css/product_list.min.css',
'title': category.name,
}
return render(request, template, context)
product_list.html
{% block content %}
<ul>
{% for product in products %}
<li>{{ product.name }}</li>
{% endfor %}
</ul>
{% endblock %}
You are not passing products in context. Pass them to template like this.
products = category.products.all()
context = {
'customCSS': '/static/css/product_list.min.css',
'title': category.name,
'products': products,
}
The context you pass to render does not include products, so naturally it can't render products. Include it and the code should work:
context = {
'customCSS': '/static/css/product_list.min.css',
'title': category.name,
'products': products
}
this is my models.py
class Category(models.Model):
name = models.CharField(max_length=50)
ordering_num = models.IntegerField(default=0)
class Meta:
ordering = ['ordering_num']
def __str__(self):
return self.name
class SubCategory(models.Model):
category = models.ForeignKey('Category', on_delete=models.CASCADE)
name = models.CharField(max_length=50)
ordering_num = models.IntegerField(default=0)
class Meta:
ordering = ['ordering_num']
def __str__(self):
return self.name
class ProductBasicModels(models.Model):
whose = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
category = models.ForeignKey(SubCategory, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
standard = models.CharField(max_length=50)
maker = models.CharField(max_length=50, blank=True)
outbox = models.CharField(max_length=50, blank=True)
extra = models.CharField(max_length=100, blank=True)
orderto = models.ForeignKey(OrderCompany, null=True, blank=True, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
views.py
# login_required
def product_list(request):
categories = Category.objects.all()
context = {'categories': categories}
return render(request, 'medicalapp_1/products_h.html', context)
products_h.html
(simple structure...)
...
{% for category in categories %}
{{ category.name }}
{% for sub_category in category.subcategory_set.all %}
{{ sub_category.name }}
{% for list in sub_category.productbasicmodels_set.all %}
{% if list.whose.id is request.user.id %}
{{ list.name }}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
....
according to my code.. all of category and subcategory and products being displayed. But I want to make them display only products models has data.
like this..
category1
subcategory1
product1
product2
product3
category4
subcategory1
product4
product5
subcategory3
product6
(category2,3) and (subcategory2 of category4) are not displayed because they don't have product..
How can I make filter in the view to work like it?
Make use of regroup tag. No need to fetch all categories in the product_list() method. Instead fetch all products (as the name intents)
- with prefetch_related to its foreign key(s), to optimise the query. Then regroup the queryset. That's all!
def product_list(request):
products = ProductBasicModels.objects.all().prefetch_related('category','category__category')
context = {'products': products}
return render(request, 'medicalapp_1/products_h.html', context)
Then, in the template,
{% regroup products by category.category as cats %}
{% for cat in cats %}
<br>{{cat.grouper}}
{% regroup cat.list by category as subcats %}
{% for subcat in subcats %}
<br>{{subcat.grouper}}
{% for product in subcat.list %}
<br>{{product}}
{% endfor %}
{% endfor %}
{% endfor %}
PS :Two separate (& similar) models for category & sub-category is redundant and you shouldn't be using it. You may just use a single model with ForeignKey to 'self'