I have a Ingredient model and a Shopping model for a shopping list and I would like to group ingredients by their category in the shopping list view. Is there a database method to achieve this or will running the for loop I have in my Views.py the easiest?
The below image is exactly what I would like to achieve in my shopping list view:
html:
{% for key, value_list in shoppingList.items %}
<div class="fw-bold">{{key}}</div>
{% for value in value_list %}
<div>
<li>{{value}}</li>
</div>
{% endfor %}
{% endfor %}
models.py:
class Ingredient(models.Model):
name = models.CharField(max_length=220)
category = models.CharField(max_length=50, default="uncategorised")
class Shopping(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=220, blank=True, null=True) # users can create their own items not just those in the Ingredient model
category = models.CharField(max_length=80, default="uncategorised")
views.py:
def shopping_list_view(request):
queryset = Shopping.objects.filter(user=request.user)
shoppingList = {}
for item in queryset:
ingredientItem = item.name # e.g Fish
ingredientCategory = item.category # e.g. Meat & Seafood
if ingredientCategory in shoppingList:
shoppingLists[ingredientCategory].append(ingredientItem)
else:
shoppingLists[ingredientCategory] = []
shoppingLists[ingredientCategory].append(ingredientItem)
# print(shoppingLists)
# will return something like > {'Meat & Seafood': ['bacon', 'fish'], 'Baking & Spices': ['all purpose flour', 'parsley'], 'pantry': ['pasta']}
return render(request, "shopping/shoppinglist.html", {"shoppingList": shoppingList}
)
In case you use PostgreSQL, you can use ArrayAgg in annotate with values:
from django.contrib.postgres.aggregates import ArrayAgg
queryset = Shopping.objects.filter(
user=request.user
).values("ingredients__category").annotate(
ingredient_list=ArrayAgg('ingredients__name')
).all()
Then you'll get the list with desired structure and wouldn't need to perform any calculations at Python's side.
Related
I will pin some screenshots of my template and admin panel
I have history of orders in admin panel but when im trying to show title and img of order product in user profile in my template that`s not working and i got queryset
Im sorry for russian words in my site, i can rescreen my screenshots if you need that
models.py
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT, related_name='orders', verbose_name='Заказы',
default=1)
username = models.CharField(max_length=50, verbose_name='Имя пользователя')
email = models.EmailField()
vk_or_telegram = models.CharField(max_length=255, verbose_name='Ссылка для связи', default='vk.com')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
paid = models.BooleanField(default=False, verbose_name='Оплачено')
class Meta:
ordering = ['-created',]
verbose_name = 'Заказ'
verbose_name_plural = 'Заказы'
def __str__(self):
return 'Заказ {}'.format(self.id)
def get_cost(self):
return sum(item.get_cost() for item in self.items.all())
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='order', on_delete=models.CASCADE)
product = models.ForeignKey(Posts, related_name='order_items', on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return '{}'.format(self.id)
def get_cost(self):
return self.price
views.py
#login_required
def profile(request):
user_orders = Order.objects.filter(user=request.user)
data = {
'user_orders': user_orders,
}
return render(request, 'store/main_pages/profile.html', data)
order history template:
{% for item in user_orders %}
{{ item }}
{{ item.order.all }}
{% endfor %}
Profile template
admin order panel
Create a model for storing the orders. This model should have fields for storing information about the order, such as the total cost, the date the order was placed, and the status of the order.
Create a view that will display the order history for a user. This view should retrieve all of the orders for the logged-in user from the database and pass them to a template.
Create a template to display the order history. This template should loop through the list of orders passed to it by the view and display the relevant information for each order.
Add a URL pattern to your Django project's urls.py file that maps to the view that displays the order history.
Add a link to the order history page in your application's navigation menu or elsewhere on the site.
In user_orders = Order.objects.filter(user=request.user)
you have all of the user's order histories.
when sending these data to the front, don't use {{ item.order.all }}
each of your items is an order.
I found solution, OrderItem has product, which is a ForeignKey for my product with title, content and img
Views.py changed for:
#login_required
def profile(request):
user_orders = Order.objects.filter(user=request.user)
user_order_item = OrderItem.objects.all()
data = {
'user_orders': user_orders,
'user_order_item': user_order_item,
}
return render(request, 'store/main_pages/profile.html', data)
Template:
{% if user_orders %}
{% for item in user_order_item %}
<p>{{ item.product.title }}</p>
<p><img src="{{ item.product.photo.url }}" alt=""></p>
% endfor %}
{% endif %}
I'm practicing my python and django by making a recipe site.
I can add ingredients (class RecipeIngredient) per recipe using inlines, searching for ingredients i already have in my database (class Ingredient) (with nutrional facts etc). This results in one big list of ingredients per recipe.
To order them I added a step and a stepnum attribute. I return a list ordered by the stepnum, but how can I div them apart? For instance. If you make a cake you would have ingredients for the cake, and for the icing. all the ingredients for the icing would get a step value of "icing", and a stepnum of 0. all the ingredients for the cake would get a step value of "cake" and a stepnum of 1.
I would like all the ingredients with stepnum 1 to appear under one that has the value associated with the items that have a stepnum value of 1, in this case "cake".
How?
I have this code on my recipedetail.html:
<div><h1> INGREDIENTS: </h1></div>
<div>
{% for ingredients in recipe.get_text %}
<ul>
{% for item in ingredients %}
<li><em>
{{item.1|safe}} {% if item.2 is not None %} {{item.2|safe}} {% endif %} {{ item.0|safe}}
</em></li>
{% endfor %}
</ul>
{% endfor %}
</div>
and the function get_text:
def get_text(self):
a = []
ingredients = []
for ingredient in self.RecipeIngredients.all():
ingredients.append((ingredient.ingredientname, ingredient.amount, ingredient.unit, ingredient.step, ingredient.stepnum))
ingredients.sort(key=lambda tup: tup[4])
a.append(ingredients)
return a
and my model:
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe, related_name='RecipeIngredients', on_delete=models.CASCADE)
ingredientname = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
amount = models.IntegerField(blank=True, null=True)
unit = models.ForeignKey(Unit, on_delete=models.CASCADE, blank=True, null=True)
step = models.CharField(max_length=200, blank=True)
stepnum = models.IntegerField(default="0")
def __str__(self):
return str(self.ingredientname)
You could do this in your model by adding:
class Meta:
ordering = ['step', 'stepnum']
Or in views.py using group by.
Try to get the heavy lifting done before you get to the template.
I'm building an ecommerce app and I'd like to make a section that shows some featured items by category. I'm using three sliders that display those items; Each slider is a featured category and each item in the slider is a featured item.
The problem is that I can't figure out how to assign the item to the proper slider. For example: I want to assign a JeansJacket to "Clothes and accesories" and display it. I tried this:
{% for cat in categories %}
<h1>{{ cat.cat_name }}</h1>
<!--(carousel code in between)-->
<div class="carousel-inner" role="listbox">
{% for item in featured_items %}
{% if item.Categoría in cat.cat_name %}
{{ item }}
{% endif %}
This is a simplified version of what I have, without the rest of the content. I just can't figure out how to iterate though the featured items and display them in the corresponding category.
Edit: This is in models.py:
class Categorías(models.Model):
cat_name = models.CharField(max_length=30)
Destacado = models.BooleanField()
class Meta:
ordering = ('cat_name',)
verbose_name = 'Categoría'
verbose_name_plural = 'Categorías'
def __str__(self):
return self.cat_name
class publicaciones(models.Model):
Título = models.CharField(max_length=30)
Descripción = models.TextField(max_length=200)
Precio = models.FloatField()
Fotos = models.ImageField()
Categoría = models.ForeignKey(Categorías, on_delete=models.CASCADE)
Promocionado = models.BooleanField()
class Meta:
verbose_name = 'Publicación'
verbose_name_plural = 'Publicaciones'
def __str__(self):
return self.Título
You can use prefetch_related and Prefetch with a custom query to get all related articles for each category.
categories = Categorías.objects.prefetch_related(Prefetch(
'publicaciones_set',
queryset=publicaciones.objects.filter(Promocionado=True),
to_attr='featured_items'
))
Now you can loop over each category and then loop over this prefetch. You don't need to create a separate featured_items queryset
for category in categories:
for featured_item in category.featured_items:
...
You can use apply this pattern to your template
I want to be able to produce a dropdown menu in my template with a unique list of subjects.
Subjects are populated inside of admin rather than hard coding them in SUBJECT_CHOICES.
A course can have many subjects or only 1 subject. For example:
Course Title = Django
Subject = Technology
Course Title = Python
Subject = Technology
Course Title = Accounting
Subject = Business
Course Title = E-commerce
Subject(s) = Technology, Business
CourseListView corresponds to the course_list.html template.
models.py:
class Subject(models.Model):
SUBJECT_CHOICES = ()
name = models.CharField(max_length=20,choices=SUBJECT_CHOICES, unique=True)
def __str__(self):
return self.name
class Course(models.Model):
SKILL_LEVEL_CHOICES = (
('Beginner', 'Beginner'),
('Intermediate', 'Intermediate'),
('Advanced', 'Advanced'),
)
slug = models.SlugField()
title = models.CharField(max_length=120)
description = models.TextField()
allowed_memberships = models.ManyToManyField(Membership)
created_at = models.DateTimeField(auto_now_add=True)
subjects = models.ManyToManyField(Subject)
skill_level = models.CharField(max_length=20,choices=SKILL_LEVEL_CHOICES, null=True)
visited_times = models.IntegerField(default=0)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('courses:detail', kwargs={'slug': self.slug})
#property
def lessons(self):
return self.lesson_set.all().order_by('position')
views.py:
class CourseListView(ListView):
model = Course
def get_queryset(self):
qs = super().get_queryset()
title_contains_query = self.request.GET.get('title_contains')
view_count_min = self.request.GET.get('view_count_min')
view_count_max = self.request.GET.get('view_count_max')
date_min = self.request.GET.get('date_min')
date_max = self.request.GET.get('date_max')
skill_level_query = self.request.GET.get('skill_level')
if title_contains_query:
qs = qs.filter(title__icontains=title_contains_query)
if view_count_min:
qs = qs.filter(visited_times__gte=view_count_min)
if view_count_max:
qs = qs.filter(visited_times__lte=view_count_max)
if date_min:
qs = qs.filter(created_at__gte=date_min)
if date_max:
qs = qs.filter(created_at__lte=date_max)
if skill_level_query:
qs = qs.filter(skill_level=skill_level_query)
return qs
Desired Output:
I tried writing a for loop in my template that does return the subjects successfully but they are not unique or showing only once.
{% for item in object_list %}
<h2>{{ item.subjects.all}}</h2>
<ul>
{% for sub in item.subjects.all %}
<li>{{ sub.name }}</li>
{% endfor %}
</ul>
{% endfor %}
Result:
<QuerySet [<Subject: Business>]>
Business
<QuerySet [<Subject: Technology>]>
Technology
<QuerySet [<Subject: Technology>]>
Technology
<QuerySet [<Subject: Business>]>
Business
I would prefer to do it with a for loop that produces unique results, but maybe it can be done with django-select2 or use a form with model select or multiple model select? Can someone provide some code for either the loop or one of these methods?
I would appreciate any help with this.
I see two solutions here:
The first one if to simply get all the entered values in the Subject model but you couldn't use all the filters you are using in your view, only the one about the title.
To do so, just use:
title_contains_query = self.request.GET.get('title_contains')
if title_contains_query:
subjects = [title for title in Subject.objects.filter(title__icontains=title_contains_query)]
The other option is to use the distinct() method on your QuerySet which filters and remove the duplicate entries in it. Use it like this: qs = qs.distinct()
Hope it helps!
My models:
class customer(models.Model):
cstid = models.AutoField(primary_key=True, unique=True)
insurance_number = models.CharField(max_length=100, blank=True, null=True)
name = models.CharField(max_length=35)
ageyrs = models.IntegerField(blank=True)
class Admission(models.Model):
id = models.AutoField(primary_key=True, unique=True)
clinic = models.ForeignKey(Clinic, on_delete=models.CASCADE)
customer = models.ForeignKey(customer, on_delete=models.CASCADE)
diagnosis = models.CharField(max_length=2000, default='', blank=True)
date_admission = models.DateTimeField(default=timezone.now)
ward = models.ForeignKey(Ward, on_delete=models.CASCADE)
bed = models.ForeignKey(Bed, on_delete=models.CASCADE)
discharged = models.BooleanField(default=False)
ip_number = models.IntegerField(blank=True)
ip_prefix = models.CharField(max_length=20, default='', blank=True)
My objective: Set a variable to a query filter, adding a property, 'is_admitted' to the queryset, so that I can pass this query set to the template and use the property while displaying data.
Code:
def is_admitted(cust):
admission = Admission.objects.filter(customer=cust, discharged=False)
admission_results = len(admission)
if admission_results > 0:
return True
return False
my_q = or_q_if_truthfull(
cstid=HospitalID,
name__lower__contains=name.lower() if name else None,
ageyrs=ageyrs if ageyrs.isdigit() else None,
agemnths=agemnths if agemnths.isdigit() else None,
mobile__contains=mobile if mobile else None,
alternate__contains=alternate if alternate else None,
email__lower__contains=email.lower() if email else None,
address__lower__contains=address.lower() if address else None,
city__lower__contains=city.lower() if city else None
)
ORSearchResult = customer.objects.filter(my_q, linkedclinic=clinicobj)
cust_set = []
cust_admission_status = []
for cust in ORSearchResult:
cust_set.append(cust)
cust_admission_status.append(is_admitted(cust))
print(f"Customer name: {cust.name} Admission status: {is_admitted(cust)}")
cust_templ_set = zip(cust_set, cust_admission_status)
And in template, I will do:
{% for cust, status in cust_templ_set %}
{{ cust.name }} {{ status }}
{% endfor %}
I want to understand how I can convert my above code by generating an aggregate over the queryset, so that I can use a property of the query, and change the template code to the following, and avoid the loop in the view, and the zip. So that the template code becomes:
{% for cust in customers %}
{{ cust.name }} {{ cust.is_admitted }}
{% endfor %}
I am not sure whether I am making complete sense, and can explain further.
I'm not sure I understood you right, perhaps you might want this:
cust = customer.objects.filter(my_q, linkedclinic=clinicobj)
is_admitted_sub_q = Admission.objects.filter(customer=OuterRef('pk'), discharged=False)
cust_templ_set = cust.annotate(is_admitted=Exists(is_admitted_sub_q), )
this will return a list of customers with additional field is_admitted which will be True if there exists at least one linked (to this customer) record in Admission.
OuterRef, Exists
One option could be to use conditional-expressions together with annotate(). It could look like this:
from django.db import models
qs = Customer.objects.filter(...) # your filter conditions go here
# now add a field to the resulting queryset
qs = qs.annotate(
active_admissions=models.Count(
models.Case(
models.When(admission__discharged=False, then=1),
output_field=models.IntegerField())))
Now each object in the queryset will have an additional attribute called active_admissions which will contain the number of active admissions.
This could be used in the template like this:
{% for cust in qs %}
{{ cust.name }} {{ cust.active_admissions }}
{% endfor %}
Maybe you need to modify the subquery to fit your specific needs. Does that help?