Reputation: 68
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 %}
<a href="?page=1">« first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
</body>
</html>
Upvotes: 0
Views: 858
Reputation: 4432
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')
Upvotes: 1
Reputation: 5669
Try to use annotation before ordering like that:
cakes_list = Cake.objects.annotate(
max_price=Max('dimension__price'),
).order_by('max_price')
Upvotes: 1