Django is code

Overview

Django uses text files and code to create a cluster of interconnected web-pages with relatively painless database use. It's as no-nonsense as a web backend can be. Sure, it uses the silly language Python, but that's fine for a server stack. The official "start making django" intro is rough. It's "make this, try it, now add this..." and scatters things around. It's fine, but I wanted to write something organized by topic.

Django has several systems, with different languages for each:

Altogether on the server-side: a page request goes to the URL files, finds the line, possibly extracts variables from url's, packages GET and POST data into a dictionary, and runs the Python function. Your Python probably plays with the database, and creates another dictionary of variables which it sends to its html template. The template runs the embedded code, using those variables, to get the final page for the client.

Files structure, standard commands

A basic django directory has 1 main project, looking like this (one command will create most of it):

projectA/  # overall directory
  manage.py  # contains standard django-making commands
  projectA/
    __init__.py  # the basic empty python package-marker
    settings.py
    urls.py  # the master urls file (inside points to sub-urls.py's)
    asgi.py
    wsgi.py
  App1Directory
  App2Directory
  ...

Each sub-part is called an App. They go in the top project directory. This isn't difficult to remember since the newApp command runs from manage.py (which was auto-created as in the picture). It creates the App directory right there. You'd need to work to put an App in the wrong place. An App's file structure looks like this:

catApp/  # a sub-project (App)
    __init__.py
    admin.py
    apps.py
    migrations/  # holds info for turning your models.py into the database
        __init__.py
    urls.py  # single lines translating url's into function calls and inputs
    models.py  # your database classes
    views.py  # your python code creating the web pages

All together, here are the command-line level helpful django commands:

#create new project directory:
django-admin startproject projectName

# create new django App (sub-directory):
python manage.py startapp catApp  # makes most of the dirs and files above

After making a new App, you'll need to go to the Project's settings.py, find: INSTALLED_APPS = [ and add 'catApp.apps.CatappConfig'. Yikes! But tracing it helps: catApp.apps is the file apps.py in our catApp directory. We can see that file above. Inside apps.py there's a class named CatappConfig. So it's all verifiable by us. Note the funny changes to your app name: the first letter is upper-cased, the rest lower-case, and 'Config' at the end.

The database API in the testing shell works the same as for real. To start a "database testing" python shell, which can also interact with your database), use:

# start a django python shell for testing the database commands:
python manage.py shell

Django can make a local web server, for testing:

# start test server:
 python manage.py runserver  # local server url is http://127.0.0.1:8000

Starting the test server "compiles" everything, with decent error messages. It nicely auto-recompiles when you change anything (it stays running on the terminal, listening for changes to your files).

Each time you change the classes making the database, you'll need to run 2 commands to update the real database:

# create database from models.py:
python manage.py makemigrations catApp
python manage.py migrate

NOTES:
For a new project, django-admin is installed in a subfolder of Python. You may need to search to find the path to it.
manage.py was created by the new project command. It seems generic, but has the specific project name in a few important places.

The database-defining classes

Django uses a backwards system for making the database. Write a class for each table and let Django create the database from them. The classes aren't complicated. They need to inherit from django's models class. Fields use its CharField, IntegerField ... classes. They go in the App's models.py:

from django.db import models  # the base class

class Breed(models.Model):
  name=models.CharField(max_length=24)  # standard fix-len character data
  rating=models.IntegerField()

You may notice something weird -- they aren't attributes -- they're statics. There's no __init__. The base model class takes care of that.

Foreign keys are handled in the usual OR/M way: give the type of the other class and let the system secretely handle the actual foriegn-key field. Here Cat has a foreign key into Breed:

class Cat(models.Model):
  name=models.CharField(max_length=30)
  breed=models.ForeignKey(Breed, on_delete=models.CASCADE)  # a basic foreign key

As usual, if you don't specify a primary key, the system makes one (named id). Of course you can designate one. For fun, we can also validate by passing a list of functions (they are only checked when the object is saved). Ex of primary keys and validators:

class Dog(models.Model):
  # set up a validator as a simple bool-returning function:
  def ssnInRange(ssn): return ssn>=000000000 and ssn<=999999999
  ssRangeValidator=[ssnInRange]  # validators go into a list
  
  socialSec=models.PositiveIntegerField(primary_key=True, validators=ssRangeValidator)

Django gives us a short cut for simple ManyToMany relations. Instead of creating the extra table, we can choose just one side and give it a ManyToMany field, aimed at the other table. Here we use that to for a Many-Many relating States and Birds:

class State(models.Model):
  name=models.CharField(max_length=20)
  birds=models.ManyToManyField(Bird, blank=True)

This is weirdly asymetrical, but does what we need: states can find their birds, birds can find their states, and either can delete (we'll see how later). blank=True isn't technically needed and mostly does nothing. Your table can be empty no matter what. But some high-level django parts, such as the free admin page, force states created through them to have at least one bird unless blank is true.

If you need your many-many arrows annotated -- maybe you need the frequency of birds in a state -- of course you can hand-make ManyMany's the old-fashioned way, nothing new required:

# an explicit many-to-many table:

class BirdStateHabitation(models.Model):
  state=ForeignKey(State, on_delete=models.CASCADE)
  bird=ForeignKey(Bird, on_delete=models.CASCADE)
  frequency=models.PositiveIntegerField()

The Python database API

A neat thing about the database API is we can play with it in the shell -- queries, as well as actually adding and removing items from the real DB.

Inserting an item involves creating a Python classes instance for it, then save(), which performs the actual DB insert (including any validation you added):

# add a new Breed to the DB:
% b1=Breed(name="Siameese", rating=6)
% b1.save()

Alternately, you can save an item directly into the database through objects (which we'll see later):

# adds directly to the database:
Breed.objects.create(name="Siameese", rating=6)

Given an item, we can do the usual operations. Using save() to update. We can also delete (the item is gone from the DB, but the class instance you have is unaffected):

# b1 is an existing Breed

if b1.name=="al": b1.name="albert"; b1.save()  # updates b1
if b1.id==b2.id: b1.delete()  # removes from database, b1 still exisits

We rarely use id since pk is an more useful alias (to either id, or your explicit primary key).

We can examine a table through dot-objects from the class. Some uses:

Breed.objects.count()  # size
B=Breed.obects.all()  # an iterable (of type QuerySet)
b2=Breed.objects.get(name="Siameese")  # 1 matching item, or error
B2=Breed.objects.filter(name="Alley")  # list of all alley cats (can be empty)
get returns only a single item, giving an error when it finds none, or more than one. Clearly it's fragile and only good for unique values you know exist. filter returns a, more useful, list. all() and filter() use the trick of returning an iterable instead of a list.

Creating an item with a foreign key generally takes the entire foreign object. A special syntax allows the actual key -- add _id at the end of the name. Ex. of both ways:

c1=Cat(name="Fred", breed=b1)  # supply a breed object
c2=Cat(name="Daisy", breed_id=2)  # supply an id of a breed, with _id suffix

Django uses the breed_id Python trick in several places, where the parameter name is merely a string describing your intent. For example, breed_name="Alley" is allowed. It checks that Cats have a breed, and that breed has a name. They do, so the command is OK, and it attempts to locate that breed.

Gazing through a foreign key is easy. You experience them as object references:

foreach c in Cat.objects.all()
  if c.breed.name=="Alley": ... # foreign keys are real links

Foreign keys create a free reverse look-up in the other table, using that table's name, plus _set. For example, if each Rabbit has a foreign key for their Hutch, then each hutch automatically gets a list of "its" rabbits in rabbit_set. Pretty slick. For our Cats example, say we want to list all Ally cats. Start with an Alley cat (an instance of Breed), and examine its cat_set:

# assume b2 is the breed alley cats:
b2.cat_set.all()  # list of Alley cats

The type of otherTable_set is QuerySet, an iterator amd the same type as objects. So cat_set can use all(), filter() and whatever else. b3.cat_set.filter(name="Max") will find all Persian(?) cats named Max, the same as Breed.objects.filter(name="Max") finda all Max's.

The special ManyToMany field works in an odd asymetrical way. The table with the ManyToMany uses it as a list. That's new. The other table uses it as a basic reverse-look-up (which was already a list). This makes a surprising amount of sense once you do it. For our birds-to-states relation it looks like this (recall that it happens to be on the State class):

# class State ... :
#   birds=models.ManyToManyField(Bird)
  
s1.birds.all()  # in a state, birds acts as a list of Birds
b1.state_set.all()  # in a bird we need the special reverse state_set

Apart from needing to remember if you're the "other" table, needing to add _set, it's fiendishly clever. Adding or removing can be done through the list-like commands add() and remove(). They work from ether side. A single use removes the entire two-way arrow:

# these both do the same thing: link s1 and b2:
s1.birds.add(b2)
b2.state_set.add(s1)

# these each remove the s1 to b2 link:
s1.birds.remove(b2)
b2.state_set.remove(s1)

You may have noticed how the reverse look-up trick keys off of the name of the other table. This usually works fine. Suppose Dogs and Cats each have a foreign key into PetOwners. PetOwners reverse look-ups would be Dog_set and Cat_set. No problem. The problem occurs when Dogs have 2 foreign keys into PetOwners, maybe Owner and Favorite Person. Over in PetOwner, those would both be named Dog_set. Oops. For cases like that, or whenever you feel like it, django allows you to specify the name of the reverse look-up:

class Dog(models.Model):
  owner=ForeignKey(Person, on_delete=models.CASCADE, related_name="myDogs")
  favorite=ForeignKey(Person, on_delete=models.CASCADE, related_name="dogFriends")
    ...

d1.owner.name  # no change, a person
d1.favorite.name # no change, a person
p1.myDogs.all()  # a lit of dogs I own
p1.dogFriends.all()  # another list of dogs, who like me

The parameter is related_name, and it goes with the foreign key. That might seem odd, but it's telling you the reverse look-up name for that particular foreign key. So that's the only place it could be.

more filters

exclude() works the opposite of filter, giving items that don't match your condition:

Cats.objects.exclude(breed=b1)  # Cats that are not breed#1

filter and exclude don't need to use only ==. There's a bonkers syntax for other ways of selecting. You add the operation, following a double-underscore, to the end of the name. You can even make chain them. Exs:

Cats.objects.filter(age=4)  # a basic one, for comparison

Cats.objects.filter(age__gte=5)  # age>=5
             ...   (breed__id=3)  # breed.id==3
             ...   (breed__avgLife__lt=12)  # breed.avgLife<12

Cats.objects.filter(name__startswith("Mr")
             ...   (name__contains(" the ")
            .exclude(breed__name__startswith="American")

As you can see, compares use old fashioned gte (greater than or equal), lte, lt, and gt (there's no != since exclude does that). Attributes of other tables can be used; that's what breed__id and breed__name__startswith are doing. It can use just whatever field names, since these aren't really parameters -- just more python "paramter named used as input string" trickery.

The url listings

Each App has a urls.py file which lists every url it handles. At top level, the Project has another urls.py as the main entry. It may list some actual url's, but mainly points to the files for each App, using an include(). This points to just catApp:

#  project urls.py:
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('catApp/', include('catApp.urls')),  # where to find the App url's
    path('admin/', admin.site.urls),  # the project-wide admin url
]

urlpatterns is just a list. An item with an include() has the root for that App's urls, then a path to its urls file (the command to add an App will create the line including it).

In the App urls.py's each 3-part item deals with a url or url pattern:

from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='indexPage'),  # a traditional index
    path('allCats/', views.showAllCats, name='listOfAllCats'),
    path('breedDetails/<int:breedNum>', views.showaBreed, name='detailsOfOneBreed'),
]

The 3 parts of each line are: 1) the incoming URL. They start from your App's root, so "allCats/" is actually /catApp/allCats and "" is /catApp/, 2) the name of your Python function handling it, and 3) an alias for use within one of your HTML templates.

Url's with <>'s in them accept patterns: int and slug match numbers and this-is-a-slug strings. The identifier after the colon is how Python gets them. So breedDetails/<int:breedNum> will accept url breedDetails/4/ passing along the 4 as breedNum (your Python handler must have that variable).

The third slot, name= is an alias. It's mostly used inside one of your templates, like {% url "listOfAllCats" %}. Looking that up, it's the alias for allCats/. That seems a bit silly, but your pages might move around -- this ensures they're always properly tracked.

A little more on patterns. The <>'s can be anywhere, as many as you want. Since they only go between slashes, matching isn't complicated. <int:x><int:y>/99/<slug:z> would match catApp/6/52/99/arm-parts/ or catApp/0/0/99/z. Later, your python function will have easy acccess to the parts: x would be 6, y is 52 and z is arm-parts.

namespaces

At some point you may want to put your App in a django namespace. It's 2 steps: add app_name="myAppName" to the App's urls.py file, then, up in the project's urls.py add namespace="myAppName" as an argument to that App's include:

# project urls.py:
  path('catApp/', include('catApp.urls', namespace="catApp")),
  # namespace= matches the app_name, below

# App urls.py:
app_name="catApp"  # pick any string as the namespace

urlpatterns = [ ...

Once you have a namespace, alias shortcuts must use it. It goes in front with a colon. {% url pawPage %} becomes {% url catApp:pawPage %}. Later we'll have Python redirection. Those also use the alias, so must also include any namespace.

The python url handlers

After a url is matched to a line, it goes to a Python function -- the one from the second slot. They're normally written in views.py but can be anywhere. They automatically have any GET or POST data as part of their first input. If they came from a pattern path, such as cats/<int:amt>, they have those additional inputs (more, later).

The output is the location of an html template, plus a dictionary of variables it will use. You'll write the template, so the variable communication is entirely under your control. This function uses the database API, once, to create a list of (cat,breed) tuples in AllCats, then sends it to allCatTemplate.html:

# recall that urls.py send url "allCats/" to views.showAllCats

# views.py:

def showAllCats(req):
  C=Cat.objects.all()
  # creating list of [(name, breedName), ... ]
  AllCats=[]
  foreach c in C: AllCats.add( (c.name, c.breed.name) )
  D={"cats":L}  # this goes to our template 
  
  return render(req, 'catApp/allCatTemplate.html', D)
  # points to the template and sends the vars

Of course, this is backwards from the real process. For real we decide we want a page with cats and breeds and design the html template complete with how it wants the variables sent. Then the Python code's job is clearly to create those variables and call that template.

GET and POST data is bundled into the first parameter, dictionary-like:

# Exepects urls ending with: ?name="greta"&age="5"
def usingGETsexample(req):
  catName=req.GET["name"]  # nothing special
  age=req.GET['age']
  
  # if we choose to pass this to our template:
  D={"catName":name, "age":age }
  ...

Data included as part of the url pattern is sent as additional parameters. The names must match. For example, this expects a string (sort of) and an int. It will match /the-brown-one/C/6:

# urls.py:
  path("/C/", views.handleC, ... )

The function it runs, handleC, must have parameters named cow and hours, which are automatically filled with the-brown-one and 6:

# views.py
def handleC(request, cow, hours):
  # find the cow named "the-brown-one":
  wantedCowList = Cow.objects.filter(name=cow)
  ...

What's the point? Well, some people prefer cow/6 over cow?id=6 as the url style. We may as well auto-copy 6 into id in that first version.

The scheme may seem fragile at first -- maybe url's without the correct parms can crash us. But the url only matches if the patterns is exactly right, which ensures the parameters will be exactly right. The whole thing won't even compile if the names in the function don't match the names in the url pattern. So it's all fairly safe.

Redirects

Redirects in response to saving data hurt my head. When a server gets a url it assumes you want to see that page, but that's not true for /addCow?name="bessy". Being on that page makes no sense. We want the server to make the cow and then put us on any other reasonable page.

One solution is have the "bad" page empty except for a button leading to the page they should be on. That's a client-side redirect. A nicer solution is to invent a special reply code, 301, that lets the browser know you're skipping ahead. The browser understands the url it asked for is gone, deleted from history, replaced by the new one supplied along with the 301. That's a server-side redirect.

Django's Python handlers allow server-side redirects with the HttpResponseRedirect command. The input is the new url, and that's it. Your Python function is done. The secret is that Django puts that new url through the system again, starting with urls.py. "You were redirected here" is added to the headers of the new page.

The redirected path can be a normal url. For example:

# views.py
from django.http import HttpResponseRedirect
from django.urls import reverse
...

def handleCatCreate(req):
   ...
  return HttpResponseRedirect('/catApp/allCats/')

It's a long way of simply calling the Python function that handles allCats. The difference is that we're also sending the redirect code and supplying the new url the client should display.

We can, and probably should, use a django path alias. The reverse command performs that look-up. The line would be:

  
  return HttpResponseRedirect(reverse('allTheCats'))

It searches for the url alias 'allTheCats' (the 3rd item in each url entry). We already know the answer: reverse('allTheCats') is the real url /catApp/allCats/. If catApp was in a namespace then alias's need to use it. We'd need: reverse('catApp:allTheCats').

The html templates

Your python code will generally pass control to a django HTML template. It's PHP-style: the text is used as-is except for magic django inserts inside of {{ }} (variables) and {% %} (commands). It might look like this:

<html><body>
<h1>Info about {{animal}}</h1>

<p>There are many types of cats, including:
{% for w in catList %}
 the {{w}} cat
{% endfor %}
...with more are discovered every day.</p>
</body><html>

Django searches for templates in any App subdirectory named template. You'll have to make those yourself. It smushes them all together -- if two Apps have two welcome.html's, each in its own template directory, django can't tell them apart. It's considered safer to create a sub-sub-directory. The usual setup for template locations:

# project directory structure showing template location:
catProj
  catApp
    templates  # "APP_DIRS: True" looks for this
      catApp  # an extra folder, not required
        allCatTemplate.html  # the template
        catDetailsTemplate.html # another

Your Python would then use catApp/allCatTemplate.html.

The actual list of possible template locations is in settings.py (a Project file), in the variable TEMPLATES. Setting 'APP_DIRS' : True enables searching template subidirectories (it starts as True). Beyond that, DIRS is a path list of anywhere else to look:

        # search in these places for html templates:
        'DIRS': [],  # additional places to search
        'APP_DIRS': True,  # automatically include templates dir form every app 

        # alterately: template files may now be on your Desktop:
        'DIRS': [r'C:\Users\Owner\Desktop'] 

The embedded python-like language

Double-curly-braces evaluates a variable: {{varName}}. Python-style, the keys to the dictionary you received are your variables. If you received { "x":3, "cow":"Moo" }, then {{cow}} prints out Moo.

Structural code is wrapped in {% %}'s. Basic commands include a loop, an if, and a URL alias. A loop example:

# basic loop:
{% for n in L %}
test to repeat goes here
{% endfor %}

# tuple desassembly is allowed
{% for name, age in Cats %}

Plain text in the loop is copied each time. As a special bonus you get the variable {{forloop.counter}}, which starts at 1. You can only go through a list (or dictionary or tuple). There's no range(1,10) in this mini-language.

If's use the python-style elif/else. They're allowed the usual compares: ==, !=, > ... :

{% if Cats %}
Some cats are
{% elif dogCount>0 %}
Some dogs to consider
{% else %}
No animals found
{% endif %}

The url alias command uses that 3rd entry in urls.py lines. Oddly, it needs to be in quotes. That's because you're allowed to combine it with variables, which are un-quoted. As an example, here are two paths from urls.py and two template {%url%}'s using them:

    # from urls.py:
    path('allCats/', views.showAllCats, name='listOfAllCats'),
    path('breedDetails/<int:breedNum>', views.showaBreed, name='detailsOfOneBreed'),
	

# in any template:
{% url "listOfAllCats" %}  # becomes: catApp/allCats/
{% url 'detailsOfOneBreed' breedNum %}  # becomes: catApp/breedDetails/6/

Notice how the second one uses only spaces. Django understands that it should add slashes. This is probably obvious, but there must be an exact match for number of parameters. {% url 'detailsOfOneBreed' %} (no breedNum integer) would be a server error as it attempted to build the template. A longer example of matching:

path('<int:count>/XY/<int:size>', ... , name='ratz'),

{% url num1 "ratz" 4 %}  # matches, with count=num1, size=4

"Static" include files

Finding your various dot-css and javascript includes looks similar, but works more the way templates are found. Your various "static" files, such as mainStyle.css, go in any directory the system can find, generally static/ in any App, which you have to create.

Include them in your web page with {% static "filename.css" %}. Or, more commonly, {% static "appName/filename.css" %}. But your template also needs an include at the top: {% load static %}. All together:

{% load static %}

<html><head><title>All Cats</title>
<link rel="stylesheet" href={% static "catApp/mainLook.css" %} />
  ...

The settings are in the Project's settings.py file. You don't need to enable APP_DIRS like you did for templates (there isn't one). The line for additional directories isn't there at all. You could add it:

# also look here for css and javascript (statics):
STATICFILES_DIRS = ["C:/Users/Owner/webfiles/"]

Finally, for fun: settings.py has a line: STATIC_URL = '/static/'. That does not control where you put these files. It's where the system lies and tells other people they are. If you set it to cows, clients see the fake path /cows/catApp/mainLook.css.

expressions

Math and member functions use a bizzarre syntax. A dot is any sort of lookup: list or dictionary index, or class attribute:

{{ n.0 }}  # first item in list or tuple n
{{ cow.feet }}  # feet attribute of class cow

A verticle bar is a filter (a function call). Filter parameters go after colons. Ex of common filters:

n|add:2  # n+2
n|sub:2  #n-1
L|first  L|last  # shortcuts (note these are filters)

L|length

w|lower  w|upper

These can be chained, but there's no "nesting". L.0|upper is fine, but the parser can't handle L.(n|add:1). There are more than these, and the system can be extended by writing your own (in Python).

An odd and fun rule. like Python, w1|add:w2 will add numbers or concatenate strings. But, this is the odd part, it will attempt to convert to numbers first. So it's not totally reliable for adding strings, unless you know for sure that at least one isn't a number.

A thing to remember: if you don't want to do any of this, you don't have to. Have your Python code pre-compute whatever you need, possibly sent over as tuples.

Template inheritance, includes

Django has 2 methods to assemble a template prior to evaluating. {% include "otherFile.html" %} is straight-forward. It inserts the other file:

<html><head> ...
<body> ...

{% include 'catApp/disclaimer.html' %}

</body></html>

It's done before the template is evaluated, which means the included file has access to your variables and can run {{}}'s and {%%}'s. Here disclaimer.html assumes we have an object variable:

#  disclaimer.html
<p>The cat named {{ object.name|lower }} is completely made up</p>

There's another system where sub-templates plug content into master templates. Sub-templates are only a series of blocks, marked like {% block BLOCKNAME %} content {% endblock %}. This one supplies 3 blocks named TTL, H1, and BODY:

{% extends "catApp/detsBase.shtml" %}

{% block TTL %}Info for cat {{ object.name }}{% endblock %}

{% block H1 %}cat{% endblock %}

{% block BODY %}
<p>ID:{{ object.id }} Name: {{ object.name }} Breed: {{ object.breed.name }}</p>
<a href="{% url 'allTheCats'%}">Back</a>
{% endblock %}

At the top, extends names the master template this uses, found using the same searching rules as regular templates (in other words, even if it's in the same directory, you still need to give the full path). Then is simply describes 3 blocks.

Master templates also use block/endblock to mark the insertion points. "block TTL" in a child matches "block TTL" in the parent. Here's detsBase.html, which our sample child plugs into. It marks blocks TTL, H1, and BODY:

# detBase.shtml
<html><head>
  <title>
  {% block TTL %}No title was provided{% endblock %}
  </title>
</head><body>
<h1 style="color:#00FF00">
  Details for {% block H1 %} {% endblock %}</h1>

<div>{% block BODY %} {% endblock %}
</div>
</body></html>

Children are free to skip supplying a block, in which case the contents of the master block are used (normally they're completely overwritten). As we can see, empty blocks in the master are fine.

Child templates can only have blocks. Content outside of blocks is ignored. It makes no sense, since we wouldn't have a reasonable place to put it.

This system can be extended to several levels. Children may have block/endblocks inside of their blocks, filled in by their children. The Python function would render the lowest level child. Here's a working master, child and sub-child:

<html> ...
<p>I'm the parent for {{ object.name }}</p>
{% block B1 %}{% endblock %}
</html>

{% extends "master1.html" %}
{% block B1 %}
I'm the 1st child for {{ object.name|upper }}
  {% block B2 %}{% endblock %}
{% endblock %}

{% extends "child1.html" %}
{% block B2 %}I'm the child of child1 for {{ object.name }}{% endblock %}

Your Python would render child2.html, which plugs its B2 block into child1.html, which plugs its modified B1 block into master1.html. That seems like a lot of work. It's one of those things that's there if you need it. Otherwise, yikes!

All together run-through

Putting it together, here's everything to make a page showing details of a single cat. The url will look like /catApp/oneCat/<catID>/. Here's the line for it in urls.py. When a client asks for catApp/oneCat/5, or when our code request alias "oneCatDesc" cNum, we'll run Python fucntion views.catDetails:

# in catApp/urls.py:
  path("oneCat/<int:catNum>", views.catDetails, name="oneCatDesc"),
  path(  ...  ,  ...  , name="allTheCats"),  # we need this for the "back" button

The only funny thing is how the number after the final slash is put into catNum. Next this Python function looks in the database for that cat-ID, gets the details for it, and passes them to its html template:

# in views.py
def catDetails(req, catNum): # catNum is auto-pulled from oneCat/3/
  # set up dict with "cNum", "name" and "breed"
  Vars={"cNum":catNum}
  # look up name and breed-name from the cat with that id:
  theCat=Cat.objects.get(pk=catNum)
  Vars["name"]=theCat.name
  Vars["breed"]=theCat.breed.name  # reach through foreign key
  return render(req, "catApp/catDets.shtml", Vars)

The final render causes the system to look in catApp/templates to find catApp/catDets.shtml. Our dictionary, Vars, provided it with cNum, name, and breed. It copies those into the page using {{ }}:

# catDets.shtml
<html><head><title>All Cats</title>
<h1>Info for cat #{{cNum}}</h1>
<p>Name: {{name}}, Breed: {{breed}}</p>

<p>Back to <a href="{% url 'allTheCats' %}">all cats</a></p>
<!-- NOTE: with a namespace, this is catApp:allTheCats -->
</body></html>

The back button uses the url alias trick. {% url 'allTheCats' %} finds 'allTheCats' at the end of the first line in urls.py, way up above, then translates it to the real path, at the start. And that's all of how django produces a cat details page.

The next page will show the list of all cats. It's got more Python, more template code, plus it creates a new cat complete with redirect. We'll start back at urls.py. Our new url is allCatsList/:

# in catApp/urls.py
  path("allCatsList/", views.catAllTable, name="allTheCats"),
  path("addNewCat",  ...  , name="addNewCat"),

Asking for that url sends the server to views.catAllTable. Its template will need a list of every cat, and a list of every breed (why do we need breeds? For real we design the template first, and know why we need it). We'll simply create those lists, then run the template:

# in views.py:
def catAllTable(req):
  # simle list of cat names:
  allCats=[]
  for c in Cat.objects.all(): allCats.append(c.name)
   
  # list of breed names, for the create-a-cat dropdown:
  allBreeds=[]
  for b in Breed.objects.all(): allBreeds.append(b.name)

  return render(req, "catApp/allCats.shtml", {"Cats":allCats, "Breeds":allBreeds })

The part of the template making the all-cats-list uses a basic (template-style) loop. Notice how the inside is a plain old table row:

# relevant part of allCats.shtml:
<table>
{% for c in Cats %}
<tr><td>{{c}}</td>
<td><input type="button" value="details"
   onClick="window.location.href=
     '{% url "oneCatDesc" forloop.counter %}'"></td>
</tr>
{% endfor %}
</table>

That plain old tr/td is copied once for each cat, with the name filled in. Each cat also has a Details button, which is more complicated. {% url "oneCatDesc" forloop.counter %} uses the alias to create links like oneCat/3. We're using it inside quotes, inside of javascript, which is fine -- the template processor expands {{'s and {%'s in place where-ever it finds them. Note my idea is junk -- I'm making the horrible assumption that cat-ID's are sequential from 1.

The add-new-cat part will use a Form. A loop fills the dropdown with every breed (it's a normal loop, but using it for a dropdown is pretty cool). The relevant part:

<form action="/catApp/addNewCat">
<input type="text" name="name" size="20">
<select name="breed">
{% for b in Breeds %}
<option value={{forloop.counter}}>{{b}}</option>
{% endfor %}}
</select>

<input type="submit" value="add this cat">
</form>

Notice the action uses the real path, not {%url%}, just to show that we can.

The Python code to add their cat to the database starts out simple enough: read from the GET, create the add, and put it in the database:

# views.py:
def addNewCat(req):
  newCatName=req.GET["name"]
  breedID=req.GET["breed"]
  
  # create and add that cat:
  brd=Breed.objects.get(pk=breedID)
  c=Cat(name=newCatName, breed=brd)  # or search by id: breed_id=breedID
  c.save()
  
  return HttpResponseRedirect(reverse('allTheCats'))

When we're done, we want them to be back on the all-the-cats page. That's a redirect, in the last line. I used the alias trick, but the more direct HttpResponseRedirect('/catApp/allCatsList/') would have worked. Also note that if we had a namespace then reverse('catApp:allTheCats') would have been required.

The Auto-pages

Admin

Django has a built-in set of Admin pages. The official Intro covers them pretty well. You get usernames and passwords, a page for adding users, and, most useful, simple table management. It will makes textboxes for your CharFields, populated drop-downs for your Foreign keys, and even multi-select boxes for your special ManyMany fields.

Each App tells it which tables to display. It goes in the App's admin.py file (which should be pre-made). Lines enabling tables look like this:

# admin.py, enabling these tables for the Admin page:
from .models import Breed, Cat, Friend

admin.site.register(Breed)
admin.site.register(Cat)
admin.site.register(Friend)

That should get us a simple display, add, delete, update for those three tables. Not fancy, but it works. The special ManyToMany shortcut is rough. Only one end (the class with the ManyToMany field) has it. It displays a nice multi-select-panel (which requires you select at least 1, unless you remembered blank=True). But the other side shows nothing. For the Birds and States many-to-many, birds won't list their states. If a new bird lives in 5 states, you'll need to go to each state, adding that bird.

Generics for the Python handlers

Django has an awkward system which automates common tasks of the Python functions. You still have to make the templates. As an example, putting this into your views.py will fetch one Cat:

class oneCatMaker(DetailView):
  model=Cat
  template_name="catApp/cd.html"

Note that it's a class, not a function like normal. The superclass, DetailView, is a built-in. It's purpose is to automate "here's a key, find the item and send it to a template". as_view() returns a function doing that. This is what it creates:

def _oneCatMaker(req, pk):
  c = Cat.objects.get(pk=pk)
  return render(req, "catApp/cd.html", {"object":c} )

Put another way, we wrote a 2-line class to avoid writing a 2-line function. We also just have to know the input is named pk and the output is 1 variable named object (the result of looking up pk in that table).

template_name is optional. All it does it rename the template file from the default. But no matter what, we have to write the template outselves.

We'll also need to modify the entry in urls.py. The incoming id must be renamed pk, and we need as_view() to extract the function:

  path("oneCat/<int:pk>", views.oneCatMaker.as_view(), name="oneCatDesc"),

Besides DetailView there's a ListView and more. ListView grabs all items of a table. Each has their own rules for use and places to override.

The system seems like a lot of work for no gain. But if you like descriptive systems, and don't know Python, and for whatever reason are using django, I suppose it's fine. The official name is generics.