I am trying to create a specific complex query in Django that I am struggling with.
I am using pagination to display a list of cakes on the index page. I want to sort them by different attributes. Below is a screenshot of my index page.
Name (A-Z) & Name (Z-A) are implemented by simply ordering the Cake queryset by 'name' or '-name' which I am getting as POST from the form at the top of the page.
cakes_list = Cake.objects.all().order_by('name')
However I am struggling to order the queryset by minimum price.
Each cake is available in different dimensions which have different prices (dimensions and prices between cakes are different). These are stored in Dimension with a foreign key pointing to the Cake they belong to.
I want to find out the cheapest option for each cake and order my list of cakes that is used in the pagination based on that (both min price asc and desc).
I have also created a method from_price which returns the price of that cake. Which I use in my template to display each cake name together with the minimum price. But I cannot seem to be able to implement that into my sorting.
I appreciate help with how I can create a query or similar that allows me to sort all of my cakes based on the minimum price for each. I am just learning Django, so my current implementations might not be ideal.
vault/models.py:
class Cake(models.Model):
name = models.CharField(max_length=200)
def from_price(self):
temp = self.dimension_set.aggregate(Min('price')).get('price__min')
if not temp:
temp = 0
return temp
class Dimension(models.Model):
cake = models.ForeignKey(Cake, on_delete=models.CASCADE)
dimension = models.CharField(max_length=50)
price = models.DecimalField(max_digits=6, decimal_places=2)
vault/views.py
def index(request):
#from the form on the index page
order_by = request.POST.get('order_by')
if not order_by:
order_by = 'name'
cakes_list = Cake.objects.all().order_by(order_by)
paginator = Paginator(cakes_list, 5)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
return render(request, 'vault/index.html', {'page_obj': page_obj, 'order_by': order_by})
vault/templates/vault/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{# Form for cake sorting dropdown #}
<form action="{% url 'vault:index' %}" method="post">
{% csrf_token %}
<label for="order_by">Order by:</label>
<select name="order_by" id="order_by">
<option {% if order_by == "name" %} selected="selected" {% endif %} value="name">Name (A-Z)</option>
<option {% if order_by == "-name" %} selected="selected" {% endif %} value="-name">Name (Z-A)</option>
{% comment %}
New options for ordering by price
<option {% if order_by == "" %} selected="selected" {% endif %} value="name">Price from (Low to High)</option>
<option {% if order_by == "" %} selected="selected" {% endif %} value="-name">Price from (High to Low)</option>
{% endcomment %}
</select>
<input type="submit" value="Select">
</form>
{# Code for printing the list of cakes #}
{% if page_obj %}
<ul>
{% for cake in page_obj %}
<li>
<a href="{% url 'vault:detail' cake.id %}">
{{ cake.name }}
{% if cake.from_price %}
- from £{{ cake.from_price|floatformat:'2' }}
{% endif %}
</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>No cakes are available</p>
{% endif %}
{# Code for the pagination navigation elements #}
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
next
last »
{% endif %}
</span>
</div>
</body>
</html>
Try to use annotation before ordering like that:
cakes_list = Cake.objects.annotate(
max_price=Max('dimension__price'),
).order_by('max_price')
If you need a min price value on each Cake record then probably the easies way is to use subquery:
from django.db.models import OuterRef, Subquery
sub_q = Dimension.objects.filter(cake=OuterRef('id')).order_by('price')
qs = Cake.objects.annotate(min_price=Subquery(sub_q.values('prize')[:1]).order_by('min_price')
Related
I want to list all items in my template, but I want to list items under the same year. For example,
under the 2021 title, model objects for that year should be listed. Year titles should come dynamically. How can I do it?
views.py
def press_list(request):
press_contents = PressContent.objects.all().order_by('-date')
context = {
'press_contents': press_contents
}
return render(request, "press_list.html", context)
models.py
class PressContent(models.Model):
label = models.CharField(max_length=500)
url = models.URLField(max_length=500)
date = models.DateTimeField(blank=True, null=True)
press_list.html
{% for press in press_contents %}
<div class="card" style="width: 18rem; margin:15px">
<div class="card-header">
{{ press.date.year }}
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">{{ press.label }}</li>
# Other objects from this year should come here.
</ul>
</div>
{% endfor %}
To be clear:
2021
obj 1
obj 2
2020
obj 3
obj 4
...
...
You can work with the {% regroup … by … %} template tag [Django-doc]:
{% regroup press_contents by year as pressitems %}
{% for pressyear in pressitems %}
<div class="card" style="width: 18rem; margin:15px">
<div class="card-header">
{{ pressyear.grouper }}
</div>
<ul class="list-group list-group-flush">
{% for press in pressyear.list %}
<li class="list-group-item">{{ press.label }}</li>
# Other objects from this year should come here.
{% endfor %}
</ul>
</div>
{% endfor %}
If you can afford to convert a queryset into a list of objects, then you can use the built-in template filter regroup (I think, I've never used it).
Another approach would be to write a python generator function and pass it in the context, to be iterated over by the template. This avoids issues with resource consumption when the queryset comprises a large number of objects. Something like
def year_grouper():
qs = PressContent.objects.all().order_by('-date')
last_object_year = 1000000000
for obj in qs:
obj.year_changed = ( obj.date.year != last_object_year )
yield obj
last_object_year = obj.date.year
and
{% for obj in year_grouper %}
{% if obj.year_changed %}
... year header, etc.
{% endif %}
... display obj
{% endfor %}
I have a dropdown list that is populated with "price range" from the database.
I want to show the relevant "price" according to the "Price Range"
for example, in the database, I have this row
Price Range Price
"0-1500" 28
"1501-1750" 30
What I want when I select the range "1501-1750" the corresponding "price" value shown in an input field or paragraph tag.
Below are my Model.py, views.py, and home.html files
models.py
from django.db import models
class price_range(models.Model):
name = models.CharField(max_length=100, null=False)
price = models.IntegerField(null=False)
views.py
from django.shortcuts import render
from range.models import price_range
def showobj(request):
displayvalues = price_range.objects.all()
return render(request, 'range/home.html', {"range":displayvalues})
home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Range</title>
</head>
<body>
<center>
<select name="drop1">
<option selected disabled="true">Select the Price Range</option>
{% for results in range %}
<option> {{results.name}} </option>
{% endfor %}
</select>
</center>
</body>
</html>
This Question Helped me a lot in creating the logic for this problem.
[Django]How to return value to view after select item from dropdown list
views.py
from django.shortcuts import render
from range.models import price_range
def showobj(request):
selected_range = None
range_price = price_range.objects.all()
if request.method == "POST":
selected_range = request.POST.get("range")
range_price = range_price.filter(name=selected_range)
price = price_range.objects.order_by('name').values_list('name', flat=True)
context = {
'ranges': price,
'range_price': range_price,
'selected_range': selected_range,
}
return render(request, 'range/price_range.html', context)
price_range.html
<center>
<h1>Index</h1>
<form method="post">
{% csrf_token %}
{% if selected_range %}
{% for rest in range_price %}
<input name="price_value" value="{{ rest.price }}"></input>
{% endfor %}
{% endif %}
<select name="range">
<option selected disabled="true">Select the Price Range</option>
{% for range in ranges %}
<option value="{{ range }}" {% if selected_range == range %} selected="selected" {% endif %}>{{ range }}</option>
{% endfor %}
</select>
<input type="submit" value="Select">
</form>
</center>
I have a MenuSection model and a Product model. The Product model has MenuSection set as a ForeignKey. Everything works fine, except I am having difficulty figuring out how to query the Product model and listing out a list of objects unique to the ForeignKey, but print the ForeignKey value once at the top of the template.
Example of how I would like the Products printing to the template:
Salads (FK) <= Only printed once
Cobb (product.name)
Ceasar (product.name)
Pizza (FK)
Supreme (product.name)
Veggie (product.name)
...
Tag
#register.inclusion_tag('tags/_starters.html', takes_context=True)
def products(context):
product = Product.objects.all()
return {
'product': product,
'request': context['request'],
}
Tag Template
{% for p in product %}
<div class="menu-wrapper">
<div class="menu-description">
<h1>{{ p.section.name }}</h1> <======= This is the (FK) that I need to print once.
<div class="menu-list">
<h5>{{ p.name }}</h5>
<p class="price">
{% if p.price_b %}
{{ p.price_a }}/
{{ p.price_b }}
{% else %}
{{ p.price_a }}
{% endif %}
</p>
<span class="menu-dot-line"></span>
</div>
<p class="menu-ingredients">{{ p.description }}</p>
</div>
</div>
{% endfor %}
Model
#register_snippet
class Product(ClusterableModel):
section = models.ForeignKey(MenuSection, verbose_name='Menu Section')
name = models.CharField(max_length=255, verbose_name='Menu Item Name')
...
Instead of querying Product in your tag, return
return {
'menu_sections': MenuSection.objects.all()
}
Then, in your template,
{% for menu_section in menu_sections %}
{{ menu_section.name }}
{% for product in menu_section.product_set.all %}
{{ product.name }}
{% endfor %}
{% endfor %}
I've an object containing an attribute defined by a series of choices - here's it's class definition:
class StashURLJobRequest(models.Model):
STATUS_CHOICES = ((0,"Requested"),(1,"Done"),(2,"Failed"))
url = models.URLField()
created = models.DateTimeField(auto_now_add = True, auto_now=False)
processed = models.DateTimeField(auto_now_add = False, auto_now = True)
status = models.IntegerField(choices=(STATUS_CHOICES))
requestBy = models.CharField(max_length=32)
def __unicode__(self):
return smart_unicode(self.url + str(self.created))
def status_display(self):
status_dict = dict(self.STATUS_CHOICES)
return status_dict[self.status]
I get a list of these and pass through into a template, intending to render each one as an individual row, again, here's the template code:
{% for job_instance in jobs %}
<div class="width100pc">
<div class="width10pc"> <img src="{% static 'img/empty.png' %}" /> </div>
<div class="width80pc">
<div class="width70pc textleft sans textsmall "> {{ job_instance.url }} </div>
<div class="width15pc textcentre sans texttiny "> {{ job_instance.processed }} </div>
<div class="width5pc textcentre sans texttiny {% ifequal job_instance.status_display 'Done' %} status_green {% endifequal %}
{% ifequal job_instance.status_display 'Requested' %} status_amber {% endifequal %}
{% ifequal job_instance.status_display 'Failed' %} status_red {% endifequal %}"> {{ job_instance.status_display }} </div>
<div class="width10pc textcentre sans texttiny"> {{ job_instance.requestBy }} </div>
</div>
<div class="width10pc"> <img src="{% static 'img/empty.png' %}" /> </div>
</div>
{% endfor %}
In particular, note the section:
{% ifequal job_instance.status_display 'Done' %} status_green {% endifequal %}
{% ifequal job_instance.status_display 'Requested' %} status_amber {% endifequal %}
{% ifequal job_instance.status_display 'Failed' %} status_red {% endifequal %}
Each of {status_green, status_amber and status_red} refer to a different css class, and allows me to render the status in an appropriate colour.
This seems a little unwieldy, and I don't want to code the css-class into the model, or the view (preferring to leave format and display to the template/html definitions) - so the question is, is there a way to do this that's more "pythonic" - ideally, I'd like to use something like:
{{ class_dict = {'Done' : 'status_green', 'Requested' : 'status_amber', 'Failed' : 'status_red' } }}
< ... some code ... >
{{ class_dict[job_instance.status_display] }}
(dodgy curly-braces aside!)
Add a helper function to the object, so that you don't need any logic in the template?
Add to StashURLJobRequest
def status_display_colour(self):
return {
"Done":"status_green",
"Requested":"status_amber",
"Failed":"status_red"
}[ self.status_display() ]
And the template becomes <div class="width5pc textcentre sans texttiny {{ job_instance.status_display_colour }} ...
Another way would be class="... my-class-{{job_instance.status_display}}" with my-class-Done, my-class-Requested, my-class-Failed appropriately defined in your css. It's inferior because it depends implicitly on status_display remaining a single word with no spaces or other special characters.
Lots of documentation on this already, but haven't been able to get any of them to work for me. Like the title implies, trying to get a set of objects that use Django Voting to sort based on the number of their votes (high to low). Have tried this and a few others, but nothing fruitful.
Using Django Voting, here is URL Conf & HTML
#urls.py
url(r'^$', 'questions'),
url(r'^(?P<object_id>\d+)/(?P<direction>up|down|clear)/vote/$',
vote_on_object, dict(model = Question, template_object_name = 'question',
template_name = 'qanda/confirm_vote.html', post_vote_redirect = '/home/', allow_xmlhttprequest=True)),
Questions in the URLconf leads to the questions view which renders questions.html
#questions.html
{% load voting_tags %}
{% votes_by_user user on the_question as vote_dict %}
{% scores_for_objects the_question as score_dict %}
<div class="question">
{% if the_question %}
<ul>
{% for question in the_question %}
{% dict_entry_for_item question from vote_dict as vote %}
{% dict_entry_for_item question from score_dict as score %}
<div class="votearrowdiv">
<div class="upvotearrow"></div></a>
<form class="linkvote" id="linkup{{ question.id }}" action="/home/{{ question.id }}/{% if vote and vote.is_upvote %}clear{% else %}up{% endif %}/vote/" method="POST">
{% csrf_token %}
<input type="image" id="linkuparrow{{ question.id }}" src="{{ media_url }}img/aup{% if vote and vote.is_upvote %}mod{% else %}grey{% endif %}.png">
</form>
<div class="downvotearrow"></div></a>
<form class="linkvote" id="linkdown{{ question.id }}" action="/home/{{ question.id }}/{% if vote and vote.is_downvote %}clear{% else %}down{% endif %}/vote/" method="POST">
{% csrf_token %}
<input type="image" id="linkdownarrow{{ question.id }}" src="{{ media_url }}img/adown{% if vote and vote.is_downvote %}mod{% else %}grey{% endif %}.png">
</form>
</div>
<li>
<div class="votecounter"><div class="numbercount">
<span class="score" id="linkscore{{ question_id }}"
title="after {{ score.num_votes|default:0 }} vote{{ score.num_votes|default:0|pluralize }}">
{{ score.score|default:0 }}
</span>
</div></div>
{{ question.question_text }}
{% endfor %}
{% endif %}
Here's my current view:
#views.py
def questions(request, movie_id):
p = Movie.objects.get(pk=movie_id)
k = Question.objects.filter(movie=p).order_by('q_pub_date')
l = k.reverse()
return render_to_response('qanda/questions.html', {'movie':p, 'the_question':l}, context_instance = RequestContext(request))
I know I can't sort using "score" because it's not in the model. What do I need to change in my view to sort this correctly?
EDIT:
Robert, here's models.py. Tried your solution, and a number of variations, but I don't have a voting attribute to the model. Take a look:
#models.py
class Question(models.Model):
movie = models.ForeignKey(Movie, blank=True, null=True)
question_text = models.CharField(verbose_name = "Question", max_length = 250)
question_detail = models.CharField(verbose_name = "Details (Optional)", max_length = 5000, blank = True, null = True)
q_pub_date = models.DateTimeField(auto_now_add = True)
q_author = models.ForeignKey(User)
Any insight?
It'd be handy if you posted your model.py, but I'm going to make some guesses.
Firstly, this might help:
#views.py
def questions(request, movie_id):
p = Movie.objects.get(pk=movie_id)
k = Question.objects.filter(movie=p).order_by('-q_pub_date')
...
(don't need to use reverse, can just begin it with -)
I'm going to guess that your score could be sorted as follows:
k = Question.objects.filter(movie=p).order_by('movie__score', '-q_pub_date')
The __ (double underscore) will refer to an attribute of related model.
I've been known to live and die by this: https://docs.djangoproject.com/en/dev/topics/db/queries/#related-objects