Listing Animals
Our list page still renders a heading. It needs to show the animals visitors should see: every animal that isn't adopted yet, newest arrivals first.
Queries in animals/queries.py
The list page needs that query, and the site's JSON API will need the same ordering. We'll write each query once, as a function in a new module, animals/queries.py, and call it wherever it's needed:
from .models import Animal
def all_animals():
return Animal.objects.order_by("-intake_date", "name", "id")
def listed_animals():
return all_animals().exclude(status=Animal.Status.ADOPTED)
all_animals() holds the ordering: the newest arrivals first, then by name, with the id last so that no two rows tie. listed_animals() starts from it and leaves out the adopted animals.
Each function returns a QuerySet, which doesn't run until something uses its rows. A caller can still add a filter or change the ordering, and the result is one SQL query.
The View
Replace the animal_list view in animals/views.py:
from django.shortcuts import render
from . import queries
def animal_list(request):
context = {"animals": queries.listed_animals()}
return render(request, "animals/animal_list.html", context)
The context's animals is the QuerySet from listed_animals(), and its query runs when the template loops over it.
The Template
Replace everything in animals/templates/animals/animal_list.html:
{% extends "base.html" %}
{% block content %}
<ul>
{% for animal in animals %}
<li>
{% if animal.photo %}
<img src="{{ animal.photo.url }}" alt="">
{% endif %}
<h2>{{ animal.name }}</h2>
<p>{{ animal.species }}, {{ animal.age_label }}, {{ animal.status }}</p>
</li>
{% empty %}
<li>No animals are looking for a home right now.</li>
{% endfor %}
</ul>
{% endblock %}
- Variables:
{{ animal.name }}writes a value from the context. A dot looks up an attribute, so{{ animal.photo.url }}is the URL of the animal's photo. - Methods:
{{ animal.age_label }}calls the model'sage_label()method and writes what it returns. A template calls a method without parentheses. - Tags:
{% for %}repeats its contents for each animal, and writes what follows{% empty %}when there are none.{% if %}writes the<img>only when the animal has a photo.
Open http://127.0.0.1:8000/. Each animal that isn't adopted has an entry with its photo, its name, and a line such as rabbit, Bunny, available.
Why can't a template pass arguments to a method?
Django's template language calls a method only with no arguments. Anything that needs arguments, or any other logic, goes in Python, in the view or on the model, so the template only shows values.