Davtho1983
Davtho1983

Reputation: 3954

Sort by field value in Django 2.0

This question is like django sorting but I can't make any of the solutions work.

I have the following model:

from django.db import models
from django.contrib.auth.models import User

class Product(models.Model):

    title = models.CharField(max_length=200)
    pub_date = models.DateTimeField()
    body = models.TextField()
    url = models.TextField()
    image = models.ImageField(upload_to='images/')
    icon = models.ImageField(upload_to='images/')
    votes_total = models.IntegerField(default=1)
    hunter = models.ForeignKey(User, on_delete=models.CASCADE)

    def summary(self):
        return self.body[:50]

    def pub_date_pretty(self):
        return self.pub_date.strftime('%b %e %Y')

    def __str__(self):
        return self.title

I would like to sort by votes_total so that when I loop through the database results are ordered by votes_total.

{% extends 'base.html' %}

{% block content %}

{% for product in products.all %}
<div class="row pt-3">
  <div class="col-2" onclick="window.location='{% url 'detail' product.id %}';" style="cursor:pointer">
    <img src="{{ product.icon.url }}" class="img-fluid" alt="">
  </div>
  <div class="col-6" onclick="window.location='{% url 'detail' product.id %}';" style="cursor:pointer">
    <h1>{{ product.title }}</h1>
    <p>{{ product.summary }}</p>
  </div>
  <div class="col-4" onclick="window.location='{% url 'detail' product.id %}';" style="cursor:pointer">
    <a href="javascript:{document.getElementById('upvote{{ product.id }}').submit()}"><button class="btn btn-primary btn-lg btn-block"><span class="oi oi-caret-top"></span> Upvote {{ product.votes_total }}</button></a>
  </div>
</div>

<form id="upvote{{ product.id }}" method="POST" action="{% url 'upvote' product.id %}">
  {% csrf_token %}
  <input type="hidden">
</form>
{% endfor %}

{% endblock %}

How do I sort my results?

Upvotes: 3

Views: 6691

Answers (2)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476624

There are basically two ways you can do this:

  1. define the ordering in a view; or
  2. define a default ordering (an ordering that can be overridden with another ordering) in the model.

The last option is typically used if the model has a sensical (inherent) ordering (for example it is very logical to order a queryset in a certain way, and only occasionally in another way).

Sorting in a view

We can sort a queryset, by using the .order_by(…) [Django-doc] function of a queryset. The function takes an arbitrary number of parameters: strings that specify the column names. In case a column name is prefixed with a minus (-) it means we sort in descending order.

For example we can define a view:

def some_view(request):
    my_products = Product.objects.all().order_by('-votes_total')
    return render(request, 'my_template.html', {'my_products': my_products})

then we can use this 'my_products' variable, and iterate over it:

{% extends 'base.html' %}

{% block content %}

{% for product in my_products %}
<div class="row pt-3">
  <div class="col-2" onclick="window.location='{% url 'detail' product.id %}';" style="cursor:pointer">
    <img src="{{ product.icon.url }}" class="img-fluid" alt="">
  </div>
  <div class="col-6" onclick="window.location='{% url 'detail' product.id %}';" style="cursor:pointer">
    <h1>{{ product.title }}</h1>
    <p>{{ product.summary }}</p>
  </div>
  <div class="col-4" onclick="window.location='{% url 'detail' product.id %}';" style="cursor:pointer">
    <a href="javascript:{document.getElementById('upvote{{ product.id }}').submit()}"><button class="btn btn-primary btn-lg btn-block"><span class="oi oi-caret-top"></span> Upvote {{ product.votes_total }}</button></a>
  </div>
</div>

<form id="upvote{{ product.id }}" method="POST" action="{% url 'upvote' product.id %}">
  {% csrf_token %}
  <input type="hidden">
</form>
{% endfor %}

{% endblock %}

Define an ordering on a model

In case a certain ordering is quite logical from a model perspective, we can also encapsulate the ordering in the model itself. We specify this wth the ordering attribute of the Meta class of the attribute. In case we want for example to order Product.objects.all() by default by the number of votes in descending order, we can write:

class Product(models.Model):

    title = models.CharField(max_length=200)
    pub_date = models.DateTimeField()
    body = models.TextField()
    url = models.TextField()
    image = models.ImageField(upload_to='images/')
    icon = models.ImageField(upload_to='images/')
    votes_total = models.IntegerField(default=1)
    hunter = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        ordering = ['-votes_total']

So now we can still access Product.objects.all in the template, and the items will be sorted automatically.

Note however that we can define only one such ordering per model, and thus making a certain decision, can not be specified based on a particular view. In case you want to "override" the ordering, you need to user order_by on the queryset.

Upvotes: 7

SHIVAM JINDAL
SHIVAM JINDAL

Reputation: 2974

You should sort the queryset in your view and pass it to the template. For sorting it in ascending order

return render(request, template_name,{
    'products': Product.objects.all().order_by('votes_total')
})

For sorting in descending order:

return render(request, template_name,{
    'products': Product.objects.all().order_by('-votes_total')
})

You should use products (not products.all) in your template.

Upvotes: 3

Related Questions