Pixies, part 7: Django Apps


Photos app

Django encourages to encapsulate the related things into so called apps. It provides several built-in, like an admin app we’ve already seen. And, of course you create your own. We will create one app called ‘photos’. Create a new directory inside the backend/pixies directory and add an empt file __init__.py which, if Python is not your forte, turns the directory into a module

mkdir pixies/photos
touch pixies/photos/__init__.py

The next step is to declare that this is Django app. Create the file pixies/photos/apps.py with the following content:


from django.apps import AppConfig

class PhotosConfig(AppConfig):
    name = "pixies.photos"

And, finally, add a new entry into the INSTALLED_APPS:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # new
    "pixies.photos.apps.PhotosConfig",
]

Ok, we have our photos app, let’s add a database model to it, so we can store entities related to this app in the database. But first, since we are going to use DateTime information, let’s be very explicit that all our DateTime are in UTC. Add this to the settings.py

TIME_ZONE = "UTC"
USE_TZ = True

Now, create the model

touch pixies/photos/models.py

And in this file:

import uuid

from django.db import models

class Photo(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True, editable=False)
    url = models.CharField(max_length=280)
    caption = models.CharField(max_length=280)
    details = models.CharField(max_length=280)

    def __str__(self):
        return self.caption

As you see, the photo will have an id, which is going to be of the type uuid which will guarantee uniqueness. There are also two DateTime fields created_at and updated_at which are going to store thet timestamp when the particular record was created and updated, respectively. Notice that we won’t need to update these fields manually, that is going to be taken care by Django. Finally, 3 fields which are needed for our application, url of the photo, its caption and a sub-caption (called details). Also we override the __str__ function, so the default display will be human-readable. Now we need to have the corresponding table in our Postgres database. Django help us with that by utilizing migrations which are little scripts which update the database schema. We don’t need to write migrations ourselves, Django analyzes the models in your app and creates migration by running:

python manage.py makemigrations photos

This will create a directory pixies/photos/migrations which will store the migrations for this particular Django app. for now it will contain only one file 0001_initial.py which has the initial migration. As you work on your Django app, yo may need to create new models, modify existing ones and so on. makemigrations will detect those changes and create new migration scripts in this directory. When you apply migrations to your database, Django will replay them one by one. As you see, it is quite flexible way to incrementally manage your database, even in production. If you want to see what SQL is being generated by a particular migration you can run:

python manage.py sqlmigrate photos 0001_initial

Once you satisfied your curiosity, let’s apply the migration to our database

python manage.py migrate photos

Ok, our database is updated. It would be nice if the admin interface picked our Photo model

Managing our models with the Django admin

let us add the file pixies/photos/admin.py with the following:

from django.contrib import admin
from pixies.photos.models import Photo

admin.site.register(Photo, admin.ModelAdmin)

That is all we need to do to get a default admin interface for the Photo model.

Run the app again and login to the admin UI at http://127.0.0.1:8000/admin/photos Yo will see that the new section photos has appeared. Feel free to add a couple of photos. Here are mine:

Caption Details Url
Pygmy Short-horned Lizard Phrynosoma douglasii https://photos.smugmug.com/Animals/Reptiles/Lizards/Pygmy-Horned-Lizard-2013/i-cdRKpxK/0/fe991315/X5/131005_ManastashRidge_693-X5.jpg
American Crocodile Crocodylus acutus https://photos.smugmug.com/Animals/Reptiles/Crocodilians/American-Crocodile/i-vkN5pCN/0/b92acda4/5K/151029_Everglades_729-5K.jpg
Bobcat Lynx rufus https://photos.smugmug.com/Animals/Mammals/Cats/Wild-Cats/Bobcat/i-kvxCNsm/0/8c16f74a/X5/190502_Backyard_258-X5.jpg
Yellow Warbler Setophaga petechia https://photos.smugmug.com/Animals/Birds/Warblers/Yellow-Warbler-2013/i-Tr5rQBN/0/f058a66f/X5/191130_Camino%20de%20grava%20entre%20el%20Corchito%20y%20Estero%20de%20Chicxulub_079-X5.jpg

Customizing the admin interface

By default the admin UI will only display the caption, thanks to our __str__ implementation on the model class. Django allows you customize you pretty much every aspect of the admin UI. To do that let’s derive from the default ModelAdmin:

from django.contrib import admin
from pixies.photos.models import Photo

class PhotosAdmin(admin.ModelAdmin):
    model = Photo

    list_display = (
        "caption",
        "details",
        "created_at",
        "updated_at",
        "url",
    )


admin.site.register(Photo, PhotosAdmin)

This will display more fields, including the created_at and updated_at fields.

One thing still bothers: the created_at and updated_at fields are presented in UTC format which is undoubtedly correct and formal, but at the same time it is hard to read. One needs to apply some mental gymnastics to understand how long ago this has happened. We can do better. Let’s install the popular package arrow which contains routines to display the data time in more human-friendly form.

pip install arrow

Now, go ahead to the admin.py and add two methods to the PhotosAdmin class:

#new
import arrow
class PhotosAdmin(admin.ModelAdmin):
    model = Photo

#new
    def created(self, obj):
        return arrow.get(obj.created_at).humanize()

#new
    def updated(self, obj):
        return arrow.get(obj.updated_at).humanize()
# as before...

Finally, replace the original created_at and updated_at fields we these:

    list_display = (
        "caption",
        "details",
        #modified
        "created",
        "updated",
        "url",
    )

Launch the app, go to the admin UI and check the list of photos, specifically the updated and created columns. Much better!

In the next module we are going to expose our photos' data as a REST API.


See also