I'm new in Django 3.0 and I'm lost in this easy question, please help me.
I have 2 models:
class Product(models.Model):
fields...
class ProductType(models.Model):
product = models.ForeignKey(Product, related_name='product_types', on_delete=models.CASCADE)
color = models.Charfield(...)
In my template, I would like to show all the related product types and their fields to a specific product:
...
{% for product in products %}
{{ product.??? }}
{% endfor %}
Here is my view:
class ProductsView(ListView):
collection = None
model = Product
paginate_by = 6
template_name = 'shop/product/list.html'
context_object_name = 'products'
def get_queryset(self):
products = Product.objects.filter(available=True)
collection_name = self.kwargs['collection_name'] if 'collection_name' in self.kwargs else None
if collection_name:
collection = get_object_or_404(Collection, name=collection_name)
products = products.filter(collection=collection)
return products
def get_context_data(self):
context = super().get_context_data(**self.kwargs)
context['notification'] = Notification.objects.all()
if 'collection_name' in self.kwargs:
context['collection'] = get_object_or_404(Collection, name=self.kwargs['collection_name'])
context['collection_name'] = self.kwargs['collection_name']
context['collections'] = Collection.objects.all()
return context
Thank you
You access the related ProductTypes through a manager that has as name the value you specify as related_name=… [Django-doc], so in this case:
{% for product in products %}
{% for type in product.product_types.all %}
{{ type.color }}
{% endfor %}
{% endfor %}
To boost efficiency, you can fetch all related ProductTypes for the elements in the queryset with .prefetch_related(…) [Django-doc]:
class ProductsView(ListView):
# …
def get_queryset(self):
products = Product.objects.prefetch_related('product_types').filter(available=True)
collection_name = self.kwargs['collection_name'] if 'collection_name' in self.kwargs else None
if collection_name:
collection = get_object_or_404(Collection, name=collection_name)
products = products.filter(collection=collection)
return products
Related
I am new to Django but have been around RdB for a while. I am finally getting the hang of model-view-template. I am struggling a little on "aggregate" and "annotate" especially when my model has grand-child records and I want aggregate.
I use Django 3 on Python 3.
Here is my example setup, I need help straightening it out. I am all sorts of wrong.
There are stores, each that served many pizzas, each pizza has many toppings, each topping used a different qty of items. I want to know the total topping qty for each pizza made and for total topping qty for each store.
models.py
class Parlor(models.Model):
name = models.CharField(max_length=64)
class Pizza(models.Model):
name = models.CharField(max_length=64)
store = models.ForeignKey(Parlor, on_delete=models.CASCADE)
class Topping(models.Model):
name = models.CharField(max_length=64)
pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE)
qty = models.IntegerField(default=0)
views.py
class ParlorDetail(generic.DetailView):
model = Parlor
template_name = 'pizza/parlor_detail.html'
context_object_name = 'parlor'
def get_context_data(self, **kwargs):
context = super(ParlorDetail, self).get_context_data(**kwargs)
id = self.kwargs['pk']
topping_qty = Pizza.objects.filter(parlor=id).annotate(sum=Sum('qty')).aggregate(sum=Sum('qty'))
return context
class PizzaDetail(generic.DetailView):
model = Pizza
template_name = 'pizza/pizza_detail.html'
context_object_name = 'pizza'
def get_context_data(self, **kwargs):
context = super(PizzaDetail, self).get_context_data(**kwargs)
id = self.kwargs['pk']
topping_qty = Topping.objects.filter(pizza=id).annotate(sum=Sum('qty'))
return context
parlor_detail.html
{% extends "pizza/my_base.html" %}
{% block content %}
<div>Parlor: {{ name }}</div>
<div>Pizzas Made at Parlor</div>
{% for pizza in pizzas.all %}
<div>Pizza: {{ pizza.name }}</div>
<div>Toppings: {{ pizza.topping_qty.sum }}</div>
{% endfor %}
<div> Total Qty of Toppings For Parlor: pizzas.topping_qty </div>
{% end block content %}
I have two models Damaged and Product.In the Product model the quantity of the product depends upon the value of damaged_quantity which is stored in another table.For example if the damaged_quantity is in damaged table then the value of quantity in product should be quantity-damaged_quantity if the damaged.product_id == product.id .I tried like this but it is not working
models.py
class Damaged(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE)
damaged_quantity = models.IntegerField(default=0)
def __str__(self):
return self.product
class Product(models.Model):
name = models.CharField(max_length=250)
category = models.ForeignKey(Category,on_delete=models.CASCADE)
quantity = models.IntegerField()
views.py I access the queryset of product like this in my views
def list_products(request):
products = Product.objects.annotate(damaged_product_quantity=Sum('damagedproducts__damaged_quantity')).annotate(
real_quantity=ExpressionWrapper(F('quantity') - F('damaged_product_quantity'), output_field=IntegerField()))
list_product_template. Here while displaying the real_quantity if the damage.damaged_quanity and the product.quantity are euqal then it doesnot changes the value.Instead of becomming Zero it doesnot change the value.In other case it is working fine.
{% if not product.real_quantity %}
{{product.quantity}}
{% elif product.real_quantity == product.quantity %}
0
{% else %}
{{ product.real_quantity }}
{% endif %}
product_detail page
def product_detail(request, slug):
product = get_object_or_404(Product, slug=slug)
damaged = Damaged.objects.all()
return render(request, 'pos/product_detail.html', {'product': product,'damaged':damaged})
product_detail template.I tried like this to get the current quantity of products after adding damaged_products but it is nt working well.It is giving me both {% if %} and {% else %} part.How can i solve this?
product quantity:
{% for damage in damaged %}
{% if product.id == damage.product_id %}
{{product.quantity|subtract:damage.damaged_quantity}}
{% else %}
{{product.quantity}}
{% endif %}
{% endfor %}
I think you need to override the save method in Damaged model:
class Damaged(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE)
damaged_quantity = models.IntegerField(default=0)
def __str__(self):
return self.product
def save(self, *args, **kwargs):
super(Damaged, self).save(*args, **kwargs)
self.product.quantity = self.product.quantity - self.damaged_quantity
self.product.save()
But this solution might be inconsistent. For example, if you try to update the Damaged model, then value of the product will be updated again.
I would recommend using annotation to attach value with Product, so that you can query if needed. For example:
For this, lets add a related_name field in Damaged model:
class Damaged(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE, related_name='damagedproducts')
damaged_quantity = models.IntegerField(default=0)
Usage:
from django.db.models import Sum, F, IntegerField
products = Product.objects.annotate(damaged_product_quantity=Sum('damagedproducts__damaged_quantity')).annotate(real_quantity=ExpressionWrapper(F('quantity') - F('damaged_product_quantity'), output_field=IntegerField())
real_quantity_more_than_ten = products.filter(real_quantity__gt=10)
for p in real_quantity_more_than_ten:
print(p.real_quantity)
Update
from django.db.models import Sum
def product_detail(request, slug):
product = get_object_or_404(Product, slug=slug)
damaged = product.productdamaged.all()
if damaged.exists():
damage_amount = damaged.aggregate(d_amount = Sum('productdamaged__damaged_quantity')).get('d_amount', 0)
else:
damage_amount = 0
return render(request, 'pos/product_detail.html', {'product': product,'damage_amount':damage_amount})
# template
{% if damage_amount != 0 %}
{{product.quantity|subtract:damage_amount}}
{% else %}
{{product.quantity}}
{% endif %}
I've got two models, Question and Project. Project has a ManyToManyField referencing Question:
## models.py
class Question(models.Model):
category = models.CharField(
max_length=4000,
blank=True,
null=True
)
question = models.CharField(
max_length=4000,
blank=True,
null=True
)
class Project(models.Model):
name = models.CharField(
max_length=100,
blank=False,
)
questions = models.ManyToManyField(
Question,
blank=True,
)
From these I have a CreateView and a custom form assigning the CheckboxSelectMultiple widget to the ManyToManyField.
## views.py
class ProjectCreate(LoginRequiredMixin, CreateView):
model = Project
form_class = ProjectForm
## forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['name','questions',]
widgets = {'questions':forms.CheckboxSelectMultiple()}
So, what I want to do now is group the individual labels and checkboxes for the questions CheckboxSelectMultiple form field according to each question's category. Something along the lines of this (not sure exactly how it would work):
{% for field in form.questions %}
{% regroup field by <INSERT MYSTERY CODE> as question_list %}
{% for category, field in question_list %}
{{ category }}
{{ field }}
{% endfor%}
{% endfor %}
EDIT:
A more detailed example of one approach I've tried:
{% for field in form.questions %}
{% regroup field by field.category as question_list %}
{% for category, fields in question_list %}
{{ category }}
{% for f in fields%}
{{ f }}
{% endfor %}
{% endfor%}
{% endfor %}
The above results in TypeError: 'BoundWidget' object is not iterable
I have been trying to figure this out for the best part of a week. I was looking for a simple solution but no luck. The following seems to achieve what you want.
## forms.py
from itertools import groupby
from django import forms
from django.forms.models import ModelChoiceIterator, ModelMultipleChoiceField
from .models import Project, Question
class GroupedModelMultipleChoiceField(ModelMultipleChoiceField):
def __init__(self, group_by_field, group_label=None, *args, **kwargs):
"""
``group_by_field`` is the name of a field on the model
``group_label`` is a function to return a label for each choice group
"""
super(GroupedModelMultipleChoiceField, self).__init__(*args, **kwargs)
self.group_by_field = group_by_field
if group_label is None:
self.group_label = lambda group: group
else:
self.group_label = group_label
def _get_choices(self):
if hasattr(self, '_choices'):
return self._choices
return GroupedModelChoiceIterator(self)
choices = property(_get_choices, ModelMultipleChoiceField._set_choices)
class GroupedModelChoiceIterator(ModelChoiceIterator):
def __iter__(self):
"""Now yields grouped choices."""
if self.field.empty_label is not None:
yield ("", self.field.empty_label)
for group, choices in groupby(
self.queryset.all(),
lambda row: getattr(row, self.field.group_by_field)):
if group is None:
for ch in choices:
yield self.choice(ch)
else:
yield (
self.field.group_label(group),
[self.choice(ch) for ch in choices])
class GroupedCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
def optgroups(self, name, value, attrs=None):
"""
The group name is passed as an argument to the ``create_option`` method (below).
"""
groups = []
has_selected = False
for index, (option_value, option_label) in enumerate(self.choices):
if option_value is None:
option_value = ''
subgroup = []
if isinstance(option_label, (list, tuple)):
group_name = option_value
subindex = 0
choices = option_label
else:
group_name = None
subindex = None
choices = [(option_value, option_label)]
groups.append((group_name, subgroup, index))
for subvalue, sublabel in choices:
selected = (
str(subvalue) in value and
(not has_selected or self.allow_multiple_selected)
)
has_selected |= selected
subgroup.append(self.create_option(
name, subvalue, sublabel, selected, index,
subindex=subindex, attrs=attrs, group=group_name,
))
if subindex is not None:
subindex += 1
return groups
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None, group=None):
"""
Added a ``group`` argument which is included in the returned dictionary.
"""
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
return {
'name': name,
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
'wrap_label': True,
'group': group,
}
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['name', 'questions']
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['questions'] = GroupedModelMultipleChoiceField(
group_by_field='category',
queryset=Question.objects.all(),
widget=GroupedCheckboxSelectMultiple())
Then the following should work in your template:
{% regroup form.questions by data.group as question_list %}
{% for group in question_list %}
<h6>{{ group.grouper|default:"Other Questions" }}</h6>
<ul>
{% for choice in group.list %}
<li>{{ choice }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
Make sure you add a __str__() method to your Question class so that the question names appear correctly.
You need GroupedModelMultipleChoiceField and GroupedModelChoiceIterator to ensure your choices are properly grouped within the field instance. This means you are able to use {{ form.questions }} in the template to render a structured list.
However, if you want to use the regroup tag in a template, each choice needs to have the group name as an attribute. The GroupedCheckboxSelectMultiple widget provides this.
Credit to the following page for part of the solution:
https://mounirmesselmeni.github.io/2013/11/25/django-grouped-select-field-for-modelchoicefield-or-modelmultiplechoicefield/
I want to use prefetch_related with Django's DetailView.
Model:
class Customer(models.Model):
name = models.CharField(
verbose_name='customer name',
max_length=100
)
# Other fields
class Packet(models.Model):
customer = models.ForeignKey(
Customer
)
# Other fields
class Credit(models.Model) :
customer = models.ForeignKey(
Customer
)
# Other fields
View:
class CustomerDetailsView(LoginRequiredMixin, DetailView):
model = Customer
http_method_names = ['get']
template_name = 'detail_templates/customer_details.html'
Templates:
{% for p in object.packet_set %}
{{ do something }}
{% endif %}
{% for p in object.credit_set %}
{{ do something }}
{% endif %}
Tried:
class CustomerDetailsView(LoginRequiredMixin, DetailView):
model = Customer
http_method_names = ['get']
template_name = 'detail_templates/customer_details.html'
def get_queryset(self):
queryset = super(CustomerDetailsView, self).get_queryset()
pk = self.kwargs.get(self.pk_url_kwarg, None)
return queryset.filter(id=pk).prefetch_related('packet_set', 'credit_set')
debug_toolbar shows no improvement.
How do I prefetch_related packet and credit
There is no sense to use prefetch_related() in the DetailView. This view loads the single master object with get() while prefetch_related() is usable for loading related objects of multiple master objects.
Im using django-taggit to create a tagging system for a blog. How do you separate and filter objects so that only ones with selected tags are shown? Kind of like how on StackOverflow if you click on django
it will give you all the questions tagged django. I have tried the method described on this blog post, but I get an IndexError: tuple index out of range. This is the code I am using:
url(r'^tagged/(?P<tag>[\w-]+)/$', TagView.as_view(), name='tag_url'),
class TagView(ListView):
context_object_name = 'blog'
template_name = 'links/index.html'
def get_queryset(self):
return Blog.objects.filter(tags__name__in=[self.args[0]])
def get_context_data(self, **kwargs):
context = super(TagView, self).get_context_data(**kwargs)
context['requested_tag'] = self.args[0]
return context
<a href='{% url tag_url tag=tag %}'>{{ tag.name }}</a>
Am I missing something to get this method to work?
It seems like this is a pretty common programming necessity. Maybe you know a better method... Thanks for your ideas!
EDIT: TagView based on #catherine's suggestions:
class TagView(ListView):
model = Blog
context_object_name = 'blog_list'
template_name = 'tag-list.html'
def get_queryset(self):
queryset = super(TagView, self).get_queryset()
return queryset.filter(tags__name__in=self.kwargs['tags'])
class Blog(models.Model):
name = models.CharField(max_length=50)
date = models.DateTimeField()
slug = models.SlugField()
article = models.TextField()
tags = TaggableManager()
def __unicode__(self):
return self.name
tag-list.html:
{% block content %}
stuff
{% for blog in blog_list %}
{{ blog.article }}
{{ blog.name }}
{% endfor %}
{% endblock %}
The blog_list does not exist in the template, and no blog objects are available. Rather, only 'stuff' is rendered to the template. Any ideas are appreciated! Thanks!
class TagView(ListView):
model = Blog
......
def get_queryset(self):
# Fetch the queryset from the parent get_queryset
queryset = super(TagView, self).get_queryset()
return queryset.filter(tags__name__in=self.kwargs['tag'])
This answer is based on "EDIT: TagView based on #catherine's suggestions:".
You have a typo, in get_queryset method:
return queryset.filter(tags__name__in=self.kwargs['tags'])
you use tag and not tags thus it should be:
return queryset.filter(tags__name__in=[self.kwargs['tag']])