Django - count number of inline elements in class based view - python

I have 2 models: Product and ProductComponent. How can I count the number of components in each product so when I want to loop products in the template it will give me the title and the number of components per product?
class ProductComponent(models.Model):
name = models.CharField(max_length=255)
related_product = models.ForeignKey(Product, on_delete=models.CASCADE')
#property
def count_components(self):
return self.component_name.count()
class Product(models.Model):
title = models.CharField(max_length=400)
views.py
class TestView(ListView):
template_name = "test.html"
model = Product
def get_context_data(self, **kwargs):
context = super(TestView, self).get_context_data(**kwargs)
context['product_list'] = Product.objects.filter(?)
return context
I managed to do something like this in admin but only in the ProductComponent admin page but not in the Product admin page.
admin.py
class TestViewAdmin(ImportExportModelAdmin):
resource_class = TestViewAdminResource
list_display = ('get_component_count',)
def get_component_count(self, obj):
return obj.related_product.related_product.count()
What I want to do is to create a loop of products with a number of components that are related to this product.
{% for product in product_list %}
{{product.title}} - {{product.count_components}}
{% endfor %}
I'd appreciate any advice on how to achieve something like that.

you can use the reverse lookup with using related_name on your ForeignKey or if not specified appending '_set' to your related model name:
{{ product.productcomponent_set.count }}
I recommend using related_name parameter on your ForeignKeys though as it makes cleaner and more descriptive code when in some more complicated relationships:
related_product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='components')
then you access it like:
{{ product.components.count }}
More info about backward relationships in Django Docs

Related

How do I filter my model within itself twice in Django?

I'm looking for some help with filtering my model twice in Django.
This is my current model:
class Medarbejder(models.Model):
id = models.AutoField(primary_key=True)
slug = models.SlugField(max_length=200)
ma = models.IntegerField(help_text="Indtast medarbejderens MA-nummer. (F.eks 123456)")
fornavn = models.CharField(max_length=30, help_text="Indtast medarbejderens fornavn.")
efternavn = models.CharField(max_length=30, help_text="Indtast medarbejderens efternavn.")
holdnavn = models.CharField(max_length=200, null=False, help_text="Indtast medarbejderens hold.")
delingnavn = models.ForeignKey('Deling', on_delete=models.CASCADE, null=True)
fagnavn = models.ForeignKey('Fag', on_delete=models.CASCADE, null=True)
The model is a model for employees (medarbejder). Now I wish to filter the teamname (holdnavn) with distinct, which I have accomplished. The next step is to then filter all the departments (delingnavn) within each teamname (holdnavn). So when I click on one teamname such as "GSU19", then I wish to get a list of all the departments within that teamname only.
I can't seem to wrap my head around how to do this? I am able to do it with slug and with pk, but both teamname and department are not a slug or a pk, so I'm abit lost to how to get the value in the url and filter again.
This is currently how the URL looks after I click on a specific teamname:
http://127.0.0.1:8000/hold/%3CQuerySet%20%5B%7B'delingnavn_id':%202%7D,%20%7B'delingnavn_id':%204%7D,%20%7B'delingnavn_id':%205%7D,%20%7B'delingnavn_id':%203%7D,%20%7B'delingnavn_id':%206%7D,%20%7B'delingnavn_id':%204%7D,%20%7B'delingnavn_id':%202%7D,%20%7B'delingnavn_id':%204%7D,%20%7B'delingnavn_id':%205%7D,%20%7B'delingnavn_id':%205%7D,%20%7B'delingnavn_id':%206%7D,%20%7B'delingnavn_id':%206%7D,%20%7B'delingnavn_id':%202%7D,%20%7B'delingnavn_id':%203%7D,%20%7B'delingnavn_id':%202%7D,%20%7B'delingnavn_id':%203%7D,%20%7B'delingnavn_id':%203%7D%5D%3E/
I'm getting all the department id's in the url..which is not what I want, I want them to be looped out in my template. Below is my current ListView:
class HoldDetailView(ListView):
model = Medarbejder
template_name = 'evalsys/medarbejder/list.html'
def get_context_data(self, **kwargs):
context = super(HoldDetailView, self).get_context_data(**kwargs)
context['medarbejder_hold'] = Medarbejder.objects.filter().values('holdnavn').distinct().order_by('holdnavn')
context['medarbejder_deling'] = Medarbejder.objects.filter().values('delingnavn_id').distinct()
return context
def get_object(self, holdnavn=None):
if holdnavn:
medarbejder_deling = Medarbejder.objects.filter().values('delingnavn_id').distinct()
else:
medarbejder_deling = self.medarbejder_deling
return render(self, 'evalsys/medarbejder/list.html',
{ 'medarbejder_deling': medarbejder_deling})
Please ask any questions and I'll supply more code, I think my approach is incorrect, but I can't seem to figure out how to go about it.
For clarification:
Delingnavn = DepartmentName and is a foreign key to Departments.
Holdnavn = TeamName
Fornavn = FirstName
Efternavn = LastName
FagNavn = SubjectName
I wish to get all the teamNames, click on one, and see all the departments within that team.
Update
In template:
{% for h in medarbejder_hold %}
<li>
{{ h.holdnavn }}
</li>
{% endfor %}
And I've done it like this because usually I use slug or pk...
URLs:
path('', views.HoldDetailView.as_view(), name='home'),
path('hold/<str:holdnavn>/', views.HoldDetailView.as_view(), name='get_object'),
This code is all over the place, unfortunately. get_object isn't ever called in a ListView, which is just as well; you can't render inside that method, it's for returning an object. And as I said, the URL itself is nonsense, you can't expect to just dump a queryset into a URL.
But it's not clear why you think you need to do either of these. If you want to filter by a specific holdnavn, then just pass that value into the URL and filter appropriately. (Note, I've changed the way you get the list of holdavn to make things a bit simpler.)
{% for holdnavn in medarbejder_hold %}
<li>
{{ holdnavn }}
</li>
{% endfor %}
class HoldListView(ListView):
model = Medarbejder
template_name = 'evalsys/medarbejder/list.html'
context_object_name = 'medarbejder_deling'
def get_context_data(self, **kwargs):
context = super(HoldDetailView, self).get_context_data(**kwargs)
context['medarbejder_hold'] = Medarbejder.objects.filter().values_list('holdnavn').distinct().order_by('holdnavn')
return context
def get_queryset(self):
qs = super().get_queryset()
if 'holdavn' in self.kwargs:
qs = qs.filter(holdavn=self.kwargs['holdavn'])
return qs

Displaying the total price of the Items of my Offer in a template

I've got two Models: Offer and Items. My Offer always has an variable amount of Items, which have a price. Now I'd like to display the total sum of the price of the items for each Offer.
How can I do that?
These are my Models:
class Offer(models.Model):
name = models.CharField(default="", verbose_name='Offername', blank=False, max_length=255)
date = models.DateTimeField(default=django_now, verbose_name='Date')
number = models.TextField(default="", verbose_name='Offernumber', blank=True)
class Item(models.Model):
number = models.CharField('Nr.', blank=False, default="", max_length=10)
description = models.TextField(default="", blank=False, verbose_name='Beschreibung')
costs = models.DecimalField('Price', blank=False, default=0, decimal_places=2, max_digits=10)
offer = models.ForeignKey('Offer', null=True, verbose_name="Angebot", related_name="items", on_delete=models.CASCADE)
What I'm trying to do is showing the total sum of all ITEMS that belong to one OFFER. So lets say I've got 2 Offers, each has a DetailView and inside this DetailView I display the Items for every offer. Offer 1 has 3 Items and Offer 2 has 9 Items. Now I want to display for Offer 1 the sum of the 3 items and for Offer 2 the total sum of his 9 items and so on.
Edit: My view
class OfferDetailView(DetailView):
model = Offer
template_name = 'offers/offer_detail.html'
context_object_name = 'offer'
EDIT
You will have to override the DetailView behaviour for this:
class OfferDetailView(DetailView):
model = Offer
template_name = 'offers/offer_detail.html'
context_object_name = 'offer'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
offer = self.object
total_item_cost = sum([i.cost for i in offer.item])
context['total_item_cost'] = total_item_cost
return context
and in the template : {{ total_item_cost }}
(see this part of the doc)
Initial answer
I think the simplest way is to use django annotation to perform the sum at database level. Also see this for your case.
Something like this might work (not tested):
from django.db.models import Sum
offers = Offer.objects.all().annotate(total_costs=Sum("item__costs"))
and in template you can now use:
{% for offer in offers %}
{{ offer.total_costs }}
{% endfor %}
I suppose you can do something like this should work in your case.
from django.db.models import Sum
def get_total_item_offer(self):
total_items = Offer.objects.all()
return self.get_total_item.total_items.all().aggregate(sum=Sum('total')['sum']
Django annotation will work. In your offer model add
def get_total_price(self):
return self.objects.annotate(total_price_sum=Sum("item__costs"))
Each offer model now has a field called total_price_sum which contains the total price of each model associated with it

For the Django template {%url%} tag, how do I find all URL arguments from generic view contexts, not just the last?

In Django, I am trying to make an app with a tree hierarchy of models i.e Books containing Chapters containing Sections etc. I want my URLs to mirror the structure, like books/3/chapters/5/sections.
I want to use the generic class-based views. In my HTML templates, I will use the {% url %} command to specify link targets.
For URLs deep in the structure, I need to provide {% url %} with all the keys that appear in the URL. For the above example: {% url 'pattern-name' 3 5 %}
However, the generic class-based views only provide (if that) the primary key of the object they are concerned with in the template context (i.e. in the above: 5).
How do I retrieve the foreign keys of parent objects (i.e. in the above: 3)?
I have the following:
My Models in
models.py:
class Book(models.Model):
name = models.CharField("Book Name", max_length = 80)
class Chapter(models.Model):
name = models.CharField("Book Name", max_length = 80)
book = models.ForeignKey('Book', on_delete = models.CASCADE)
My URL patterns in urls.py:
urlpatterns = [
path('books/',
views.BookListView.as_view(), name='book_list'),
path('books/<int:book>/',
views.BookDetailView.as_view(), name='book_detail'),
path('books/<int:book>/chapters/',
views.ChapterListView.as_view(), name='chapter_list'),
path('books/<int:book>/chapters/<int:chapter>/',
views.ChapterDetailView.as_view(), name='chapter_detail'),
path('books/<int:book>/chapters/create/',
views.ChapterCreateView.as_view(), name='chapter_create'),
My Views in views.py:
class BookListView(generic.ListView):
model = 'Book'
class BookDetailView(generic.DetailView):
model = 'Book'
pk_url_kwarg = 'book'
class ChapterListView(generic.ListView):
model = 'Chapter'
def get_queryset(self):
return Chapter.objects.filter(book = self.kwargs['book'])
class ChapterDetailView(generic.DetailView):
model = 'Chapter'
pk_url_kwarg = 'chapter'
class ChapterCreateView(generic.CreateView):
model = 'Chapter'
fields = ['name', 'book']
def get_initial(self):
initial = super().get_initial().copy()
initial['book'] = self.kwargs.['book']
return initial;
In my HTML-Template used for the list of chapters, I want to have a link to create a new chapter. Currently, that template looks as follows:
Template chapter_list.html:
<ul>
{% for chapter in chapter_list %}
<li>{{ chapter.name }}</li>
{% endfor %}
</ul>
Add new chapter
Obviously, I don't want to add all new chapters to the book with ID 42, rather I would like to use the book ID from the chapter list, where I am at. For example, for an URL
.../books/17/chapters/create/
I would like to have the link point to:
Add new chapter
The only way I have found of doing this dynamically, is to add extra context to the chapter list view:
Updated views.py:
...
class ChapterListView(generic.ListView):
model = 'Chapter'
def get_queryset(self):
return Chapter.objects.filter(book = self.kwargs['book'])
# Additional Context
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['my_current_book_id'] = self.kwargs['book']
return context
...
And then have the template say:
Updated chapter_list.html:
<ul>
{% for chapter in chapter_list %}
<li>{{ chapter.name }}</li>
{% endfor %}
</ul>
Add new chapter
Here is my question:
Is there no easier (i.e more Django-ish built-in) way to do this?
It seems to me, that this must be a very commonplace task, and that with the beautiful and fine-grained design of the generic view classes and URL (reverse-)lookups, I must be missing something.
I would consider defining get_absolute_url for the book_detail and chapter_detail, and extra methods for other URLs like chapter_create:
class Book(models.Model):
name = models.CharField("Book Name", max_length = 80)
def get_absolute_url(self):
return reverse('book_detail', args=[self.pk])
def get_create_chapter_url(self):
return reverse('chapter_create', args=[self.pk])
class Chapter(models.Model):
name = models.CharField("Book Name", max_length = 80)
book = models.ForeignKey('Book', on_delete = models.CASCADE)
def get_absolute_url(self):
return reverse('chapter_detail', args=[self.book_pk, self.pk])
Then in the template, you can use {{ chapter.get_absolute_url }}, {{ book.get_absolute_url }}, {{ chapter.book.get_create_chapter_url }} and so on, and you don't need to worry about what parameters are in the URLs.
In the chapter detail view, you can use the foreign key to access methods on the Book model:
{{ chapter.book.get_absolute_url }}
In the chapter list view, you need to add the book to the context (in case there are no chapters in the book):
from django.shortcuts import get_object_or_404
class ChapterListView(generic.ListView):
model = 'Chapter'
def get_queryset(self):
return Chapter.objects.filter(book = self.kwargs['book'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['book'] = get_object_or_404(pk=self.kwargs['book'])
return context
Then you can access the {{ book }} in the context.
Or you can use a DetailView for the chapter list view, using the Book model:
class ChapterListView(generic.DetailView):
model = 'Book'
pk_url_kwarg = 'book'
Then book is already in the template context, and you can loop through the related chapters with:
{% for chapter in book.chapter_set.all %}
{{ chapter }}
{% endfor %}
As an aside, it might be simpler to leave out the book field in the create view. Then set it in the form_valid method before saving the form.
class ChapterCreateView(generic.CreateView):
model = 'Chapter'
fields = ['name']
def form_valid(self, form):
book = get_object_or_404(Book, pk=self.kwargs['book'])
form.instance.book = book
return super(ChapterCreateView, self).form_valid(form)
You could have a view with the Book as model and:
{% for chapter in book.chapter_set.all %}

How to edit forms to show fields from second model? And How iterate through manytomany field?

I've created shop. I have two models:
class Product(models.Model):
name = models.CharField(verbose_name="name", max_length=40)
cost = models.FloatField(verbose_name="price")
def __unicode__(self):
return self.name
class Shop(models.Model):
product = models.ManyToManyField(Product)
name = models.CharField(verbose_name="Nazwa", max_length=40)
budget = models.FloatField(verbose_name="kwota")
def __unicode__(self):
return self.name
I created forms.py file:
class ShopForm(forms.ModelForm):
product = forms.ModelMultipleChoiceField(queryset = Product.objects.all(), widget=forms.CheckboxSelectMultiple(),required=True)
name = forms.CharField(max_length=15, label='Name')
budget = forms.FloatField()
class Meta:
model = Shop
fields = ('product','name', 'budget')
Now I have something like that:
What must I change to add cost of product next to this product? I have no idea but I think it is so simple...
And second question:
When I want show details of Shop I don't know how to iterate through products.
For example when i want show name of this Shop I add
{{ shop.name }}
when i do
{{ shop.product }}
I get an error
StopIteration at /shop/1/
No exception message supplied
I know it is problem because it is ManyToManyField but how create solution?
for the first question, u may try to create your form without django forms with standard html form elements, probably there is a solution for your case but I dunno how to handle with django forms.
for second question,
u should use this:
{% for p in shop.product.all %}
{{p.name}}
{% endfor %}

Django template sort by 'unrelated' model's field

I'm trying to sort related items in a template by a field in a model three ForeignKey relationships away. I'm assembling the data for the template in the view as proposed in another StackOverflow answer:
Sorting related items in a Django template
As far as I can tell, I copied the code from this as-is except for I had to change variable names. It doesn't throw an error, it just displays no list items in the HTML unordered list.
# checkout.html
{% for item in cart_items %}
<tr>
<td class="left">
{{ item.name }}
<ul>
{% for part in part_list %}
<li>{{ part.name }}
{% endfor %}
</ul></td>
</tr>
{% endfor %}
And the view...
# views.py
def checkout(request):
cart_items = get_cart_items(request)
itemCollection = []
for item in cart_items:
item.part_list = item.product.buildpart.all().order_by('product__family__type')
itemCollection.append(item)
return render(request, 'checkout.html', locals())
And the get_cart_items function:
# cart.py
def get_cart_items(request):
""" return all items from the current user's cart """
return CartItem.objects.filter(cart_id=get_cart_id(request))
As I said, the template and view are pretty much copies of the solution presented in the aforementioned StackOverflow article. One thing I thought was curious was that itemCollection[] from the view is never referenced in the template.
I believe the order_by clause ('product__family__type') is right only because it doesn't generate an error. But in case that is the problem or a part of it here is the chain of models I am attempting to navigate in that order_by clause:
We start from the shopping cart model (CartItem):
class Item(models.Model):
cart_id = models.CharField(max_length=50)
quantity = models.IntegerField(default=1)
product = models.ForeignKey(PartModel, unique=False)
class Meta:
abstract = True
class CartItem(Item):
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['date_added']
verbose_name = "Cart Item"
Through the 'product' field we get to the model holding our inventory and its self-referential BuildPart ManyToMany model:
class PartModel(models.Model):
family = models.ForeignKey(PartFamily)
name = models.CharField("Model Name", max_length=50, unique=True)
buildpart = models.ManyToManyField('self', through='BuildPart',
symmetrical=False, related_name='+')
class Build(models.Model):
build = models.ForeignKey(PartModel, related_name='+')
part = models.ForeignKey(PartModel, related_name='+')
quantity = models.PositiveSmallIntegerField(default=1)
class Meta:
abstract = True
unique_together = ('build', 'part')
def __unicode__(self):
return self.build.name + ' with ' + str(self.quantity) + ' * ' + \
self.part.family.make.name + ' ' + self.part.name
class BuildPart(Build):
pass
class Meta:
verbose_name = "Build Part"
From there we follow the 'family' field to the PartFamily model:
class PartFamily(models.Model):
make = models.ForeignKey(PartMake)
type = models.ForeignKey(PartType)
name = models.CharField("Family Name", max_length=30,
unique=True)
slug = models.SlugField(unique=True)
And lastly, we get to the model with the 'order' field, the one we wish to sort the related items by, PartType:
class PartType(models.Model):
name = models.CharField("Part Type", max_length=30, unique=True)
slug = models.SlugField(unique=True)
order = models.PositiveSmallIntegerField()
description = models.TextField(blank=True, null=True)
To recap, how do I get the shopping cart products' related items, and sort them by the 'order' field in the PartType model?
You have two errors, both in the template.
Firstly, you've put your items with the sorted relationship in a list called itemCollection, but in the template you're iterating over cart_item instead. This is a very good example of why you should be explicit about what variables you pass to the template, rather than relying on locals().
Secondly, you then iterate over part_list without defining it. You mean item.part_list.

Categories

Resources