Django and aggregating grand-child records - django

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 %}

Related

Django: display many to many relationship through a model in template

I have this ManyToMany relationship through an intermediary model:
class Group(models.Model):
members = models.ManyToManyField(Student, through='NamedMembershipClub')
class Membership(models.Model):
year = models.IntegerField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
I'm trying to display members with the year they joined in my template.
I read on this post that I should use members_set.all
{% for member in object.members_set.all %}
<p>{{member.user.first_name}} {{member.year}}</p>
{% endfor %}
But it doesn't produce any output, the loop is just not entered because the set is empty.
I also tried :
{% for member in object.members.all %}
<p>{{member.first_name}}</p>
{% endfor %}
The view:
class DetailGroupView(TemplateView):
template_name = 'group/detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
self.object = Group.object.get(slug=kwargs['slug'])
context['object'] = self.object
return context
Which gives some satisfaction because I can display the user but not the associated data in the Membership model.
Is there any way to get the set directly in the template ? Thanks!
I'm running Django 3.0.

Django: how to show related model fields in template

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

how to make specific model field dynamic from another model

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 %}

Django select_related FK model

I'm trying to list all the objects that are in Movie model that are Genre ForeignKey related say comedy genre list all Movie objects related to comedy
# models.py
class Genre(models.Model):
name = models.CharField(max_length=80, unique=True)
slug = models.SlugField(unique=True)
def __unicode__(self):
return self.name
def get_absolute_url(self):
return reverse("genres:list", kwargs={"slug": self.slug})
class Movie(models.Model):
title = models.CharField(max_length=80)
genre = models.ForeignKey(Genre)
# views.py
def genre_list(request, slug):
instance = get_object_or_404(Genre, slug=slug)
movies = Movie.objects.select_related('genre').all()
context = {
"objects_list": movies,
}
return render(request, 'genres/genres_list.html', context)
template view genres/genres_list.html
{% for obj in objects_list %}
<div class="box">
<b>{{ obj.title }}</b>
</div>
{% endfor %}
nothing turns up what am I doing wrong?
Your variable name in the context, object_list, doesn't match the variable name in the template, objects_list.
context = {
"object_list": shows,
}
{% for obj in objects_list %}
You need to use the same variable name in both places. I would recommend object_list because it is more common in Django, however something descriptive like movies would be even better.
Finally, if you want to select all the movies in a particular genre, then you should use filter.
instance = get_object_or_404(Genre, slug=slug)
movies = Movie.objects.filter(genre=instance)
You are currently using select_related, which does something different.

What is the best way to display data in a template in Django?

Given the below model and view, for any given club I am trying to display that club's available courts ("court") and available times ("avail_time") in the template. I am having trouble doing this.
Model:
from django.db import models
class Club(models.Model):
establishment = models.CharField(max_length=200)
address = models.CharField(max_length=200)
def __unicode__(self):
return self.establishment
class Available(models.Model):
club = models.ForeignKey(Club)
court = models.CharField(max_length=200)
avail_time = models.DateTimeField('available time')
def __unicode__(self):
return self.court
class Taken(models.Model):
club = models.ForeignKey(Club)
court = models.ForeignKey(Available)
taken_time = models.DateTimeField('taken time')
userid = models.EmailField(max_length = 75)
View:
def avail_times(request, club_id):
p = get_object_or_404(Club,pk=club_id)
return render_to_response('reserve/templates/avail_times.html', {'club':p})
Template:
{% for court in club.court_set.all %}
{{court.court }}
{% endfor %}
Well, you don't seem to have a Court model, so I'm not sure why you're trying to call court_set.all. You could use club.available_set.all to show the list of Available instances for that club, which might be what you mean.
First step would be to set up your models appropriately. Clubs have Courts and Courts have Available Times. Clubs don't have Available Times, which is how you have it set up now. Further, "taken" is a status of an available time; it shouldn't be a model itself. Try something like:
class Club(models.Model):
establishment = models.CharField(max_length=200)
address = models.CharField(max_length=200)
def __unicode__(self):
return self.establishment
class Court(models.Model):
club = models.ForeignKey(Club, related_name='courts')
name = models.CharField(max_length=200)
class CourtTime(models.Model):
AVAILABLE = 0
TAKEN = 1
STATUS_CHOICES = (
(AVAILABLE, 'Available'),
(TAKEN, 'Taken'),
)
court = models.ForeignKey(Club, related_name='times')
time = models.DateTimeField('available time')
status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, default=STATUS_CHOICES[AVAILABLE])
def __unicode__(self):
return self.court
Then, I would suggest a custom manager on CourtTime to return available/taken querysets:
class CourtTimeQuerySet(models.query.QuerySet):
def available(self):
return self.filter(status=CourtTime.STATUS_CHOICES[CourtTime.AVAILABLE])
def taken(self):
return self.filter(status=CourtTime.STATUS_CHOICES[CourtTime.TAKEN])
class CourtTimeManager(models.Manager):
use_for_related_fields = True
def get_query_set(self, *args, **kwargs):
return CourtTimeQuerySet(self.model)
def available(self, *args, **kwargs):
return self.get_query_set().available(*args, **kwargs)
def taken(self, *args, **kwargs):
return self.get_query_set().taken(*args, **kwargs)
Then, add it to your model:
class CourtTime(models.Model):
...
objects = CourtTimeManager()
With all that in place, you can just do the following in your template:
{% for court in club.courts.all %}
<h2>{{ court.name }}</h2>
<ul>
{% for time in court.times.available %}
<li>{{ time|date:"m/d/Y g:i A" }}</li>
{% endfor %}
</ul>
{% endfor %}