Announcements

Web Development with
Python & Django IV

Models

Our Example Project

We're playing the role of a cheese enthusiast
who wants to build a site to:

  • Make our database of cheeses available to visitors
  • Allow visitors to rate cheeses they have tried

We created three views + templates:

  1. A view for the homepage
  2. A view listing cheeses we had data for
  3. A detail page with the name, country of origin and a description for individual cheeses

Django Models

Remember this?

The class is not the thing. It's a blueprint for making the thing.
Or making many things.

A class is a blueprint for mapping a database table
to a Python object.

It's a bidirectional mapping:

  • A Python object can be mapped to a table.
  • A table can be mapped to Python objects.

    A database consists of tables

    • Tables have rows and columns
    • Columns have a type (e.g. number, string, datetime, bool)
    • Similar to a Pandas DataFrames
    • They are queried with SQL (Structured Query Language)

Example SQL Query


        SELECT slug, last_updated, description
        FROM catalog_cheese
        WHERE fat_content >= 20
            AND country_of_origin IN ('France', 'Italy')
    
  • Django models map your data between Python and a database without you having to know SQL
  • You can define a database table in Python as a class
  • This is what a model is: a Python class that maps to a table.
  • Django can create a database table from this model class
  • You can create an instance of the class and ask Django to store that as a row in the database table
  • You can query the database for one or more rows and Django will convert each row to an instance of the class
  • This is object-relational mapping (ORM)

How do we tell a model class how
to map to a database table's columns?

We define columns as properties of
the class of the Field type.


from django.db import models

class Cheese(models.Model):
    name = models.CharField(max_length=200)
    country_of_origin = models.CharField(max_length=200)
    fat_content = models.FloatField()
    last_updated = models.DateField()
    

Django has many field types:

Field Subclass Database Type Python Type
BooleanFieldbooleanbool()
CharFieldtextstr()
DateFielddatedatetime.date()
FloatFieldfloatfloat()
IntegerFieldintegerint()

In Summary:

Python Concept Database Concept
classtable
property (if Field)column
instancerow

An Example Model


from django.db import models

class Cheese(models.Model):
    name = models.CharField(max_length=200)
    country_of_origin = models.CharField(max_length=200)
    fat_content = models.FloatField()
    last_updated = models.DateField()
    

class Cheese(models.Model):
    name = models.CharField(max_length=200)
    country_of_origin = models.CharField(max_length=200)
    fat_content = models.FloatField()
    last_updated = models.DateField()
    

↑ Notice the model doesn't have an "id" column
but the database does ↓

What is a primary key?

  • Database tables can have a column with a value that uniquely identifies each row
  • This is called the primary key
  • Django requires each table to have a primary key
  • If you don't define this on your model, Django creates an automatic primary key field
    • The field looks like this:
    • id = models.AutoField(primary_key=True)
  • Rows in the database will start with one and increment from there.
  • Primary keys can be explicit rather than implicit.
  • You can use primary_key=True on a column of your model if you want to supply your own primary key.

Improving the model


from django.db import models

class Cheese(models.Model):
    slug = models.SlugField(primary_key=True, max_length=200)
    name = models.CharField(unique=True, max_length=200)
    country_of_origin = models.CharField(max_length=200)
    fat_content = models.FloatField(null=True)
    last_updated = models.DateField(auto_now=True)
    

Model methods


from django.db import models

class Cheese(models.Model):
    slug = models.SlugField(primary_key=True, max_length=200)
    name = models.CharField(unique=True, max_length=200)
    country_of_origin = models.CharField(max_length=200)
    fat_content = models.FloatField(null=True)
    last_updated = models.DateField(auto_now=True)

    def __str__(self):
        return self.name

    def is_high_fat(self):
        if self.fat_content is None:
            return False
        return self.fat_content >= 0.5
    

Querying


# Get a list of every cheese in the table.
Cheese.objects.all()

# Get a specific cheese
Cheese.objects.get(slug="brie")

# Get a cheese that may or may not exist
try:
    cheese = Cheese.objects.get(slug="apple")
except Cheese.DoesNotExist:
    cheese = None
    

A quick detour: Exceptions

  • You can catch a specific exception using a try block.
  • This allows you to recover from some failure and continue running the program.

try:
    result = numerator / denominator
except ZeroDivisionError:
    print("Cannot divide by zero.")
    

try:
    with open("nonexistent_file.txt", "r") as fh:
        content = fh.read()
except FileNotFoundError:
        content = None
    

Django exceptions

  • Exceptions make sense here, where we want something from a database but we may not know what's actually in the DB
  • DB queries can be "slow" so we should avoid doing two queries when we can get by with one

# Get a cheese that may or may not exist
try:
    cheese = Cheese.objects.get(slug="apple")
except Cheese.DoesNotExist:
    cheese = None

# Same thing, but makes 2 DB queries
cheese_exists = Cheese.objects.filter(slug="apple").exists()
if cheese_exists:
        cheese = Cheese.objects.get(slug="apple")
else:
        cheese = None
    

Field lookups

  • You can specify how a field should be queried for by adding to its name when specifying arguments to .filter()
  • The follow the format "{field name}__{lookup}" (double underscore)
  • Example lookups:
    • __lt, __gt, __lte, __gte, which are <, >, <= and >=
    • __in: is the column value in a list?
    • __startswith/__istartswith: Does a column value start with a string?
    • __range: is a value between a start and end value?
  • There are many of these

Querying


# From cheeses from a list of countries
Cheese.objects.filter(
        country_of_origin__in=["France", "Greece", "Italy"]
)

# Cheeses whose name starts with "Brie"
Cheese.objects.filter(name__startswith="Brie")

# High-fat cheeses that are not from France.
Cheese.objects
        .filter(fat_content__gte=0.5)
        .exclude(country_of_origin="France")
    

Model Meta

  • Sometimes you want to specify additional metadata about your model
  • Examples:
    • How you data should be sorted by default
    • What the underlying DB table should be
    • If there is a date field that specifies temporal ordering
    • If some fields must be unique together
  • Model metadata is “anything that’s not a field”
  • You specify it using an inner `Meta` class for your model

Model Meta Example


from django.db import models

class Cheese(models.Model):
    slug = models.SlugField(primary_key=True, max_length=200)
    name = models.CharField(unique=True, max_length=200)
    country_of_origin = models.CharField(max_length=200)
    fat_content = models.FloatField(null=True)
    last_updated = models.DateField(auto_now=True)

    class Meta
        ordering = ["name"]
        get_latest_by = "last_updated"
    

Model Meta Usage


# Will be alphabetically ordered by the cheese name.
Cheese.objects.all()

# The cheese with the most
# recent "last_updated" date.
Cheese.objects.latest()

# The cheese with the
# earliest "last_updated" date.
 Cheese.objects.earliest()