In my model I have a ManyToManyField to select related products. I'm wondering what would be the best way to bring these into my view and render them in my template.
models.py
class Product(models.Model):
title = models.CharField(max_length=80)
category = models.ManyToManyField(ProductCategory)
featured_image = models.ImageField(upload_to=image_dir)
about_this_product = models.TextField()
standard_features = models.TextField(null=True)
featured = models.BooleanField(default=False)
related_models = models.ManyToManyField("self", blank=True, null=True)
model_slug = AutoSlugField(null=True, default=None,
unique=True, populate_from='title')
class Meta:
verbose_name_plural = "Products"
def __str__(self):
return self.title
views.py
def model_detail_view(request, category_slug, model_slug):
product_model = get_object_or_404(Product, model_slug=model_slug)
context = {
"title": "Products | %s" % product_model.title,
"product": product_model,
}
return render(request=request, template_name='main/product_model_detail.html', context=context)
You can use .prefetch_related(..) just like you do on any one-to-many relation in the view:
def my_view(request):
products = Product.objects.prefetch_related('related_models')
return render(request, 'some_template.html', {'products': products})
Then in the template, you can iterate over the .related_models collection:
{% for product in products %}
{{ product.title }}
related:
{% for rel in product.related_models.all %}
{{ rel.title }}
{% endfor %}
{% endfor %}
Related
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.
I have an Image model and a Category model. I want to display only images of the corresponding category in my category_detail view.
models.py
class Category(models.Model):
category_title = models.CharField(max_length=200)
category_image = models.ImageField(upload_to="category")
category_description = models.TextField()
slug = models.SlugField(max_length=200, unique=True, default=1)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.category_title
class Image(models.Model):
category = models.ForeignKey(Category, on_delete="CASCADE")
image = models.ImageField()
caption = models.CharField(max_length=250)
class Meta:
verbose_name_plural = "Images"
def __str__(self):
return str(self.image)
views.py
def category_detail_view(request, slug):
category = get_object_or_404(Category, slug=slug)
context = {
"gallery": Image.objects.filter(Category),
}
return render(request, 'main/category_detail.html', context)
category_detail.html
{% for image in gallery %}
<div class="col-md-4">
<a href="{{ image.url }}"> <img src="{{ image.url }}" class="img-responsive img-thumbnail" width="304" height="236"/>
</a>
</div>
{% endfor %}
You can filter these with:
def category_detail_view(request, slug):
category = get_object_or_404(Category, slug=slug)
context = {
'gallery': Image.objects.filter(category=category),
}
return render(request, 'main/category_detail.html', context)
or without fetching the category in a separate query:
You can filter these with:
def category_detail_view(request, slug):
context = {
'gallery': Image.objects.filter(category__slug=slug),
}
return render(request, 'main/category_detail.html', context)
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
}
I'm trying to use django formset for the first time in order to combine both forms on the same page.
My form is well displayed but I don't overvome to save data in my database. When I click on submit button, nothing happens.
This is my model.py file :
class Publication(models.Model):
title = models.CharField(max_length=512, verbose_name=_('title'), null=False)
category = models.ForeignKey(Category, verbose_name=_('category'), null=False)
creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('creation date'), null=False)
modification_date = models.DateTimeField(auto_now=True, verbose_name=_('modification date'), null=False)
class Meta:
verbose_name = _('publication')
verbose_name_plural = _('publication')
def __str__(self):
return f"{self.title}"
class Document(models.Model):
FORMAT_CHOICES = (
('pdf', 'pdf'),
('epub', 'epub'),
)
format = models.CharField(max_length=10, verbose_name=_('format'), choices=FORMAT_CHOICES, null=False)
title = models.CharField(max_length=512, verbose_name=_('title'), null=False)
publication = models.ForeignKey(Publication, verbose_name=_('publication'), null=False)
upload = models.FileField(upload_to='media/', default="")
creation_date = models.DateTimeField(auto_now_add=True, verbose_name=_('creation date'), null=False)
modification_date = models.DateTimeField(auto_now=True, verbose_name=_('modification date'), null=False)
class Meta:
verbose_name = _('document')
verbose_name_plural = _('document')
def __str__(self):
return f"{self.age_id} : {self.title}"
My form file is very simple too with defined Formset :
class PublicationForm(forms.ModelForm):
class Meta:
model = Publication
fields = ('title', 'category')
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
fields = ['publication', 'format', 'title', 'upload']
DocumentFormSet = inlineformset_factory(Publication, Document, form=DocumentForm, extra=1)
My view is a bit more complicated :
class PublicationCreateUpdateView(AgePermissionRequiredMixin, UpdateView):
""" Display a form to create or update a publication
Only for age admin.
**Context**
``subtitle``
Title of the page
**Template:**
:template:`app/category_form.html`
"""
model = Publication
form_class = PublicationForm
success_url = reverse_lazy('app:app-publication-list')
template_name = 'app/publication_form.html'
permission_required = 'publication.change_webapplication'
def get_object(self, queryset=None):
try:
return super(PublicationCreateUpdateView, self).get_object(queryset)
except AttributeError:
return None
def get_title(self):
if self.object:
return _('Edit publication: ') + str(self.object)
return _('Add new publication')
def get_context_data(self, **kwargs):
context = super(PublicationCreateUpdateView, self).get_context_data(**kwargs)
if self.request.POST :
context['documents'] = DocumentFormSet(self.request.POST)
else :
context['documents'] = DocumentFormSet()
context.update({
'subtitle': self.get_title(),
})
return context
def form_valid(self, form):
context=self.get_context_data()
documents = context['documents']
with transaction.atomic():
self.object = form.save()
if documents.is_valid():
documents.instance = self.object
documents.save()
return super(DocumentCreateUpdateView, self).form_valid(form)
And finally my template looks like this :
{% extends "publication/base_backend.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block main %}
<form method="post" novalidate>
{% csrf_token %}
{% crispy form %}
{{ documents.management_form }}
{{ documents.non_form_errors }}
{% crispy documents %}
<br>
<input type="submit" class="btn btn-default" value="{% trans 'Save' %}" />
{% trans 'Cancel' %}
</form>
{% endblock main %}
I don't understand where I could make a mistake, furthermore I'm pretty new with Django Class Based View.
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'