Skip to content

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:

animals/queries.py
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:

Python
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:

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's age_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.