Django Template - Nested Dictionary Iteration - python

My template receives from views.py following nested dictionary of shopping cart content.
{
'20':
{'userid': 1,
'product_id': 20,
'name': 'Venus de Milos',
'quantity': 1,
'price': '1500.00',
'image': '/media/static/photos/pngegg.png'},
'23':
{'userid': 1,
'product_id': 23,
'name': 'Bicycle',
'quantity': 1,
'price': '1000.00',
'image': '/media/static/photos/366f.png'}
}
I am having problem with iteration through it.
For example, when I am using following code,
{% for key, value in list %}
{{ key }} {{ value }}
{% endfor %}
instead of keys and values I receive just this:
2 0
2 3
My goal is to calculate grand total through multiplying quantity and price for each product and dding it all together with each product in cart.
May sombody give me a hand on this, or at least help to figure out how to iterate properly through nested dictionary?
i am using following lib for cart:
https://pypi.org/project/django-shopping-cart/
views.py:
#login_required(login_url="/users/login")
def cart_detail(request):
cart = Cart(request)
queryset = cart.cart
context = {"list": queryset }
return render(request, 'cart_detail.html', context)
SOLVED (kind of):
Following your advice, I've wrote calculation for "total" in views.py
BUT, since dictionary of product has 6 attributes, "total" is added 6 times in loop, for each product in cart.
For now I've just added division by 6, but obviously this is not rational solution
def cart_detail(request):
cart = Cart(request)
queryset = cart.cart
total_price=0
for key, value in queryset.items():
for key1, value1 in value.items():
total_price = total_price + (float(value['quantity']) * float(value['price']))
#Temporal decision
total_price = total_price / 6
context = {"list": queryset, "total_price": total_price }
return render(request, 'cart_detail.html', context)

you can try like this:
{% for key, value in list.items %} <-first loop
{{ key }}
{% for key1, value1 in value.items %} <-- second loop
{{ key1 }} - {{ value1 }}
{% endfor %}
{% endfor %}
{{ key }} will give you the key of outer dict, in your case 20 and 23
{{ key1 }} will give you the key of nested dict user_id, name,...
{{ value1 }} will give you the value of nested dict.
Hope it can help

I suggest you to do the calculations in views.py, save them into variables and then pass it to template.
Assuming that your
is saved in the variable cart_dict:
total_price=0
for product in cart_dict:
total_price = total_price + (float(product['quantity']) * float(product['price']))
context = {"cart_dict: cart_dict, "total_price": total_price }
return render(request, 'cart_detail.html', context)

Related

Filter False in a list of Booleans

I've five Product objects with 3 product's availability set to True and rest False, I'm trying to set Checkout Status Button to Out of Stock even if one product has availablity set to False.
Because cart view cannot use slug, {% if product.availability %} is pointless, and can't use 'for loop' or it would create multiple checkout buttons, what's the way to fix this?
Model
class Product(models.Model):
availablity = models.BooleanField()
View
def cart(request):
products = Product.objects.all()
Cart template
{% for product in products %}
<p>{product.name}</p>
<p>{product.price}</p>
{% endfor %}
<!--Checkout Status Button-->
{% if product.availability %}
Checkout
{% else %}
<p>Out of stock</p>
{% endif %}
You should do all calculation in view and send only single True/False to template.
For example
products = Product.objects.all()
available = all(x.availability for x in products)
context = {..., "available": available}
{% if available %}
Checkout
{% else %}
<p>Out of stock</p>
{% endif %}
def cart(request):
products = Product.objects.all()
availibility = True
for prod in products:
if prod.availability = False:
availability = False
break
context = { "products" :products, "availability" :availability }
in Html
if availability` True then show `Checkout` else `Out of Stock`
In your cart view, you could add a context dictionary to your render() call?
So basically :
Check products for the availability condition and set a variable available to True/False
change your render call to render(cart_template, {'products' : products, 'available' : available})
change your cart template to use {% if available %}

Filtering a reverse lookup in Django template

I have a model that looks like this
class Invoice(models.Model):
inv_number = models.CharField(max_length=10, primary_key=True)
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
inv_date = models.DateField()
class Txn(models.Model):
invoice = models.ForeignKey(Invoice, on_delete=models.PROTECT)
transaction_date = models.DateField()
reference = models.CharField(max_length=12)
amt = models.IntegerField()
I would like to display a report in my template that lists filtered invoices each with a sub-list of filtered transactions.
In my view I have done the following:
invoice_list = Invoice.objects.filter(customer=customer)
Which I pass into my template. In the template, I do something like the following:
{% for invoice in invoice_list %}
{{ invoice.inv_number }}, {{ invoice.customer}}, {{ invoice.inv_date }}
{% for txn in invoice.txn_set.all %}
{{ txn.transaction_date }}, {{ txn.reference }}, {{ txn.amt }}
{% endfor %}
{% endfor %}
This works great to show the entire transaction list per filtered invoice. The question is, how does one also filter the list of transactions per invoice in the template - what if I wanted just the transactions within a certain date range or that match a specific reference? Is there maybe a way to pass a filter to the txn_set queryset per invoice in the view before putting the main queryset in the context and without converting them to lists?
Thank you for any response!
Suggestion: collect the invoices and transactions in the view, not in the template.
With this view code you can reduce the amount of queries to 1, so it is a lot more optimal than your code (with queries the Txn table for each invoice):
# build basic query
qs = Txn.objects.select_related('invoice')\
.filter(invoice__customer=customer)\
.order_by('transaction_date')
# add filtering; here is an example, but it could be with date filter as well
reference = request.GET.get('reference', '')
if len(reference) > 0:
qs = qs.filter(reference__icontains=reference)
invoice_dict = {}
for txn in qs:
# add the Invoice if it does not exist yet
if txn.invoice_id not in invoice_dict:
invoice_dict[txn.invoice_id] = {
'invoice': txn.invoice,
'txn_list': [],
}
# add the Txn
invoice_dict[txn.invoice_id]['txn_list'].append(txn)
# sort by Invoice date
invoice_data_list = sorted(
invoice_dict.values(),
key=lambda x: x['invoice'].inv_date)
Then in your template:
{% for elem in invoice_data_list %}
{{ elem.invoice.inv_number }}
{{ elem.invoice.customer}}
{{ elem.invoice.inv_date }}
{% for txn in elem.txn_list %}
{{ txn.transaction_date }}
{{ txn.reference }}
{{ txn.amt }}
{% endfor %}
{% endfor %}

how to access items in a python dictionary by their name

I am creating a shopping cart with the code below
`
SESSION_COOKIE_AGE=300
SESSION_EXPIRE_AT_BROWSER_CLOSE =True
#require_POST
def cart_add(request, product_id):
product = get_object_or_404(Product, id=product_id)
quantity = request.POST.get('quantity')
product_id =str(product.id)
request.session[product_id] ={"product_name":str(product.title),"price":str(product.price),"quantity":quantity}
items =request.session.items()
print(items)
return render(request, 'cart/display_cart.html', {"products":request.session})`
Scenario one
when I pass items to my view like this=> return render(request, 'cart/display_cart.html', {"products":items}) and loop over it in the template using {% for item in products %}{{ item.product_name }} {% endfor %} nothing shows. but when I print it=> print(items) I get dict_items([('7', {'product_name': 'Tomatoe', 'price': '15.00', 'quantity': '3'})]) in my console. how can i print the items in my template
Scenario 2
when I pass request.session like this => return render(request, 'cart/display_cart.html', {"products":request.session}) and I do
{% for key, value in products.items %}
<li>{{value}}</li>
{% endfor %}
in my template I get
{'product_name': 'Tomatoe', 'price': '20.00', 'quantity': '1'}. how can I display the items in my template using product_name instead of key? Thank you
items is a nested dict
Use:
{% for k, item in products.items %}
{{ item.product_name }}
{% endfor %}

Django - Annotation dictionary does not display correctly in template

I got these models in my models.py
class Rating(models.Model):
stars = models.IntegerField()
user_rating = models.ForeignKey(UserProfile, related_name="rater", on_delete=models.CASCADE)
user_rated = models.ForeignKey(UserProfile, related_name="rated", on_delete=models.CASCADE)
ride_number = models.ForeignKey(Ride, related_name="rider", on_delete=models.CASCADE)
class UserProfile(models.Model):
user = models.ForeignKey(User)
And my views
def user_view(request):
avg = user_avg
total = user_count
context = {
'avg': avg,
'total': total
}
return render(request, 'rating/home.html', context)
def user_avg():
avg = (Rating.objects
.values('user_rated').annotate(Avg('stars'))
.distinct())
return avg
and lastly my template looks like this
{% for rating in avg %}
{{ avg }}
{% endfor %}
Right now it renders like this;
[{'user_rated': 1L, 'stars__avg': 3.2222}, {'user_rated': 2L, 'stars__avg': 3.625}] [{'user_rated': 1L, 'stars__avg': 3.2222}, {'user_rated': 2L, 'stars__avg': 3.625}]
I've tried changing the template to {{ avg.user_rated }} but then it renders me nothing, so I'm not sure what im doing wrong here. And how do I get the name of the users avg rating instead of user_rated
This line in your template:
{{ avg }}
should be changed to:
{{ rating.user_rated }}: {{ rating.stars__avg }}
full template:
{% for rating in avg %}
{{ rating.user_rated }}: {{ rating.stars__avg }}
{% endfor %}
Your 2 errors: 1 - loop var is rating - you should use it to access data in loop, 2 - to access data in dict you should use dot notation, check docs.
Docs: https://docs.djangoproject.com/en/2.0/topics/templates/#variables
Dictionary lookup, attribute lookup and list-index lookups are
implemented with a dot notation:
{{ my_dict.key }}
{{ my_object.attribute }}
{{ my_list.0 }}
To get user name in results of annotation you should add it to values in query:
# assuming that your UserProfile model have name field
avg = (Rating.objects.values(
'user_rated', 'user_rated__name').annotate(Avg('stars')).distinct())
But be careful with values and annotations - order is matter and resulting values is depend on values list:
Docs: https://docs.djangoproject.com/en/2.0/topics/db/aggregation/#order-of-annotate-and-values-clauses
As with the filter() clause, the order in which annotate() and
values() clauses are applied to a query is significant. If the
values() clause precedes the annotate(), the annotation will be
computed using the grouping described by the values() clause.
avg is an array of Rating objects, which is exactly what you're seeing in the render. Try {{ rating.user_rated }}.
In answer to your second question, you need to join the Rating table with the UserProfile and User tables, assuming that the User table contains the users' names. See this link for how to do that.

How could iterate over this specific data (dict inside a list which is the second value of a tuple inside another list) in a django template?

How could I take the below collection and iterate over it in a
django template ?
[('Foo Key', [{'lname': 'Bar', 'fname': 'Foo'}])]
Please note the above example is much smaller just to keep things simple.
If we need the fully expanded collection, e.g if size matters, I can update this post.
My closest successful attempt is noted below.
{% for key0, value0 in data %}
<tr>
<td> key0 {{ key0 }}:</td>
<td> value0 {{ value0 }} </td>
</tr>
<p> {% for value1 in value0 %}
<td> {{ value1 }}</td>
{% endfor %}
</p>
{% endfor %}
and this will leave me with the below output.
Foo Key {'lname': 'Bar', 'fname': 'Foo'}
I cannot seem to get 'Bar' or 'Foo' out of it.
Some other context
Here is the view where the data is produced
from django.shortcuts import render
from .models import Person, PersonMeta
# Create your views here.
def thanks(request):
return render(request, 'thanks.html')
from .forms import NameForm
def home(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
data = {}
# process the data in form.cleaned_data as required
currentUser = request.user
person = Person.objects.create(first_name=currentUser.first_name, last_name=currentUser.last_name,
nickname=form.cleaned_data['nickname'])
# getting front loaded personMeta
personDetails = PersonMeta.objects.filter(frontLoaded_first_name=currentUser.first_name,frontLoaded_last_name=currentUser.last_name).values()
# setting key
currUserKey = "{0} {1}".format(currentUser.first_name, currentUser.last_name)
# if person details is not false
# this if may not have to be here.
if (personDetails):
data[currUserKey] = personDetails
# redirect to a new URL:
return render(request, 'signUp.html', {'data': sorted(data.items())})
# if a GET (or any other method) we'll create a blank form
#
else:
form = NameForm()
return render(request, 'home.html', {'form': form})
You have a dict inside a list which is the second value of a tuple inside another list:
[('Foo Key', [{'lname': 'Bar', 'fname': 'Foo'}])]
So here we go:
{% for tuple in data %}
<p>Tuple key: {{ tuple.0 }}</p>
{% for key, value in tuple.1.0.items %}
<p>Dict key, value: {{ key }}: {{ value }}</p>
{% endfor %}
{% endfor %}

Categories