Review

Yesterday I learned how to create a practice project with a main practice app, created an app called polls to live in it, and set up what django terms a url and view within the polls app which returned a basic text response. Next up was to create a db, which in this case was sqlite3, and ensure the app config for the db was correct for what was being used.

With that in place, I created models in the polls app and then registered the app by listing the it in the INSTALLED_APPS of the top level settings. The command python manage.py makemigrations <app_name> created a migration folder in the polls app with file (0001) detailing the changes.

Data Models Continued

To start off today I learned that I can get a spiffy preview of the sql commands that the migration will generate by running python manage.py sqlmigrate polls 0001.

python manage.py check will also run a check of the project without running any actual migrations.

It’s time to actually update the database! This is the final step, first create models, then make the migrations, and finally migrate.

python manage.py migrate

Just like rails, it runs all the migrations it can find, and applies them in order. The list of migrations and which are applied or not is maintained as an addtl table in the db.

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0001_initial... OK

Using the Shell

python manage.py shell invokes the python shell. It is important to remember to use manage.py.

We’re using this instead of simply typing “python”, because manage.py sets the DJANGO_SETTINGS_MODULE environment variable, which gives Django the Python import path to your app/settings.py file.

Once in the shell, it works similar to rails. I can import the model classes and interact with the db directly to perform crud operations.

Working with Models

String Method

Overriding string methods for properties on the model is a common practice. Not only can it make working with models much easier in the interactive shell, the admin feature will also represent those objects in their string form.

This example is from the docs. I am guessing in real life information such as the id might also be desired in the string representation of an model object.

class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text

Custom Methods

Defining custom methods is exactly as expected on the model class. This example from the docs shows a simple method to determine if something was published in the last 24 hours or not. Note the use of timezone for a proper start date object.

import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

More reading on timezones and django.

Querying Models and Relations

Django’s API automatically follows model relations when interacting with models in the shell. Once I’ve imported the models into the shell, querying them and accessing the relations is a breeze.

I’m sure I’ll be getting to know this page on referencing relations quite well very soon.

Django’s Admin Module

In order to start working with the admin module, a user must first be created. And not just any user, the first user is the superuser which can control the rest. python manage.py createsuperuser. It defaults to the computer name, so it’s recommended to choose something else. Since this is a practice project, I chose admin and admin@test.com. I also chose password for my password and was notified it was too common and asked to override security protocol. That level of security was very nice to encounter.

The admin panel was already enabled, now that there is a user created, we can access it by running the server and navigating to the local page. python manage.py runserver

Upon successful login the user is taken to a admin index page, which lists only the users and groups for the admin itself until models are added.

Registering Models on the Admin

Before you can actually interact with a model via the admin page, it has to be declared as something admin is allowed to update. Within the polls app, find the admin.py file and update it to this

from django.contrib import admin

from .models import Question

admin.site.register(Question)

Tada! Now when we look a the admin page it shows POLLS as an app, and within that the model Questions. Exploring the functionality shows me that I get quite a bit just through this auto-generation process. The question text is editable, and the datetime has been split into date and time components with the appropriate picker widgets. Once a change has been saved, it is viewable from the history feature on the item index page.

Note - I am not currently seeing the associated choices of my question. This is most likely just a configuration and something to keep in mind for later.

Views

In Django, the view is the method that returns a response to a request against a specific url. GET /polls would be expected to return an html index page listing the currently available polls, or at least the first set of results. But views can also be actions, they just have to perform an interaction and return something, even if just an OK. The docs say it pretty well:

In Django, web pages and other content are delivered by views. Each view is represented by a Python function (or method, in the case of class-based views). Django will choose a view by examining the URL that’s requested (to be precise, the part of the URL after the domain name).

Its the URLconf that handles getting from a specific URL to the correct view. It allows for URL patterns, and like rails, can handle parsing a URL for parameters (here they are called keyword arguments) to be passed into the view method. From there, the view method has two options. It can complete successfully and return an HttpResponse, or not. If not, it should throw an exception, and preferably one that actually informs the user.

Although it would still work to write out all of an html response within a view method, it gets bulky really fast. So like other modern frameworks, the code of how to display the information is separated out from the code to get that information.

Setting up Templates

Create a templates directory within the polls app. Django will automatically know to look here. This can be customized in the settings if needed. Otherwise, by default Django will always look for a templates dir. Next create a polls dir within templates, and add an index.html file. The full path for the html file should look like this /practice/polls/templates/polls/index.html.

NB The reason for this nested craziness is namespacing. Django will search through all templates files and go with the first match it finds. By using a named subdirectory it creates a protected namespace that prevents collisions.

This example helps demonstrate what using a template looks like, note the way a template is pulled from the entire pool, the namespace becomes critical:

from django.http import HttpResponse
from django.template import loader

from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    template = loader.get_template("polls/index.html")
    context = {
        "latest_question_list": latest_question_list,
    }
    return HttpResponse(template.render(context, request))

However, this was a little too verbose, so the Django team created some shortcuts:

from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    context = {"latest_question_list": latest_question_list}
    return render(request, "polls/index.html", context)

Here, render takes the request, template name, and context and does the rest for you.

Handling Error Responses

Generally, a user browsing a website can’t run into too many errors. But they can try to access a page or data that doesn’t exist. In that case, the convention is to return a response saying that it couldn’t be found. The standardized http response code for that is 404. Django has a few different ways of handling this.

Here is an example class method that returns a 404 if it can’t find the object, and it’s also great to see the syntax of how try-except works in python, which I am hoping works similar to try-catch in js.

from django.http import Http404
from django.shortcuts import render

from .models import Question

# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, "polls/detail.html", {"question": question})

But of course that is a bit annoying as well, so why not write a convenience method that will call the 404 for you if it can’t find the object within the class model specified using keyword arguments. There is also a get_list_or_404 that takes a filter.

from django.shortcuts import get_object_or_404, render

from .models import Question

# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/detail.html", {"question": question})