2.1 Using models, part 1 - LiVanych/locallibrary GitHub Wiki
Data Models and Relationships Design
- What data we need to store and the relationships between the different objects.
- We need to store information about books (title, summary, author, written language, category, ISBN).
- We might have multiple copies available (with globally unique id, availability status, etc.
- Need to store more information about the author than just their name.
- We want to be able to sort information based on book title, author, written language, and category.
- It makes sense to have separate models for every "object" - book instances and authors.
- A drop down list of choices for the book genre and language.
We need to think about the relationships. Django allows you to define relationships that are one to one
(OneToOneField
), one to many (ForeignKey
) and many to many (ManyToManyField
).
The diagram also shows the relationships between the models, including their multiplicities.
Model Primer
Models are usually defined in an application's models.py
file. They are implemented as subclasses of
django.db.models.Model
, and can include fields, methods and metadata.
from django.db import models
class MyModelName(models.Model):
"""A typical class defining a model, derived from the Model class."""
# Fields
my_field_name = models.CharField(
max_length=20,
help_text='Enter field documentation'
)
...
# Metadata
class Meta:
ordering = ['-my_field_name']
# Methods
def get_absolute_url(self):
"""Returns the url to access a particular instance of MyModelName."""
return reverse('model-detail-view', args=[str(self.id)])
def __str__(self):
"""String for representing the MyModelName object (in Admin site etc.)."""
return self.my_field_name
Common field arguments
The following common arguments can be used when declaring many/most of the different field types:
help_text: Provides a text label for HTML forms (e.g. in the admin site), as described above.
verbose_name: A human-readable name for the field used in field labels. If not specified, Django will infer the default verbose name from the field name.
default: The default value for the field. This can be a value or a callable object, in which case the object will be called every time a new record is created.
null: If True, Django will store blank values as NULL in the database for fields where this is appropriate (a CharField will instead store an empty string). The default is False.
blank: If True, the field is allowed to be blank in your forms. The default is False, which means that Django's form validation will force you to enter a value. This is often used with null=True , because if you're going to allow blank values, you also want the database to be able to represent them appropriately.
choices: A group of choices for this field. If this is provided, the default corresponding form widget will be a select box with these choices instead of the standard text field.
primary_key: If True, sets the current field as the primary key for the model (A primary key is a special database column designated to uniquely identify all the different table records). If no field is specified as the primary key then Django will automatically add a field for this purpose.
There are many other options โ you can view the full list of field options here.
Common field types
The following list describes some of the more commonly used types of fields.
CharField is used to define short-to-mid sized fixed-length strings. You must specify the max_length of the data to be stored.
TextField is used for large arbitrary-length strings. You may specify a max_length for the field, but this is used only when the field is displayed in forms (it is not enforced at the database level).
IntegerField is a field for storing integer (whole number) values, and for validating entered values as integers in forms.
DateField and DateTimeField are used for storing/representing dates and date/time information
(as Python datetime.date
in and datetime.datetime
objects, respectively). These fields can
additionally declare the (mutually exclusive) parameters auto_now=True
(to set the field to
the current date every time the model is saved), auto_now_add
(to only set the date when the
model is first created) , and default
(to set a default date that can be overridden by the user).
EmailField is used to store and validate email addresses.
FileField and Imagefield are used to upload files and images respectively (the ImageField simply adds additional validation that the uploaded file is an image). These have parameters to define how and where the uploaded files are stored.
AutoField is a special type of IntegerField that automatically increments. A primary key of this type is automatically added to your model if you donโt explicitly specify one.
ForeignKey is used to specify a one-to-many relationship to another database model (e.g. a car has one manufacturer, but a manufacturer can make many cars). The "one" side of the relationship is the model that contains the key.
ManyToManyField is used to specify a many-to-many relationship (e.g. a book can have several
genres, and each genre can contain several books). In our library app we will use these very
similarly to ForeignKeys
, but they can be used in more complicated ways to describe the
relationships between groups. These have the parameter on_delete to define what happens when
the associated record is deleted (e.g. a value of models.SET_NULL would simply set the value
to NULL).
There are many other types of fields, including fields for different types of numbers (big integers, small integers, floats), booleans, URLs, slugs, unique ids, and other "time-related" information (duration, time, etc.). You can view the full list here.
Metadata
You can declare model-level metadata for your Model by declaring class Meta, as shown.
class Meta:
ordering = ['-my_field_name']
One of the most useful features of this metadata is to control the default ordering of records returned when you query the model type. You do this by specifying the match order in a list of field names to the ordering attribute, as shown above. The ordering will depend on the type of field (character fields are sorted alphabetically, while date fields are sorted in chronological order). As shown above, you can prefix the field name with a minus symbol (-) to reverse the sorting order.
So as an example, if we chose to sort books like this by default:
ordering = ['title', '-pubdate']
the books would be sorted alphabetically by title, from A-Z, and then by publication date inside each title, from newest to oldest.
Another common attribute is verbose_name, a verbose name for the class in singular and plural form:
verbose_name = 'BetterName'
Other useful attributes allow you to create and apply new "access permissions" for the model (default permissions are applied automatically), allow ordering based on another field, or to declare that the class is "abstract" (a base class that you cannot create records for, and will instead be derived from to create other models).
Many of the other metadata options control what database must be used for the model and how the data is stored (these are really only useful if you need to map a model to an existing database).
The full list of metadata options are available here: Model metadata options (Django docs).
Methods
A model can also have methods.
Minimally, in every model you should define the standard Python class method '__str__()'
to
return a human-readable string for each object. This string is used to represent individual
records in the administration site (and anywhere else you need to refer to a model instance).
Often this will return a title or name field from the model.
def __str__(self):
return self.field_name
Another common method to include in Django models is get_absolute_url(), which returns a URL for displaying individual model records on the website (if you define this method then Django will automatically add a "View on Site" button to the model's record editing screens in the Admin site). A typical pattern for get_absolute_url() is shown below.
def get_absolute_url(self):
"""Returns the url to access a particular instance of the model."""
return reverse('model-detail-view', args=[str(self.id)])
Note: Assuming you will use URLs like /myapplication/mymodelname/2
to display individual
records for your model (where "2" is the id for a particular record), you will need to
create a URL mapper to pass the response and id to a "model detail view" (which will do
the work required to display the record). The reverse()
function above is able to "reverse"
your url mapper (in the above case named 'model-detail-view'
) in order to create a URL of
the right format.
Of course to make this work you still have to write the URL mapping, view, and template!
You can also define any other methods you like, and call them from your code or templates (provided that they don't take any parameters).
Model management
Once you've defined your model classes you can use them to create, update, or delete records, and to run queries to get all records or particular subsets of records. We'll show you how to do that in the tutorial when we define our views, but here is a brief summary.
Creating and modifying records
To create a record you can define an instance of the model and then call save()
.
# Create a new record using the model's constructor.
record = MyModelName(my_field_name="Instance #1")
# Save the object into the database.
record.save()
Note: If you haven't declared any field as a primary_key, the new record will be given one automatically, with the field name id. You could query this field after saving the above record, and it would have a value of 1.
You can access the fields in this new record using the dot syntax, and change the values. You have to call save() to store modified values to the database.
# Access model field values using Python attributes.
print(record.id) # should return 1 for the first record.
print(record.my_field_name) # should print 'Instance #1'
# Change record by modifying the fields, then calling save().
record.my_field_name = "New Instance Name"
record.save()
Searching for records
You can search for records that match a certain criteria using the model's objects attribute (provided by the base class).
Note: Explaining how to search for records using "abstract" model and field names can be a little confusing. In the discussion below we'll refer to a Book model with title and genre fields, where genre is also a model with a single field name.
We can get all records for a model as a QuerySet
, using objects.all()
. The QuerySet
is
an iterable object, meaning that it contains a number of objects that we can
iterate/loop through.
all_books = Book.objects.all()
Django's filter()
method allows us to filter the returned QuerySet
to match a specified
text or numeric field against a particular criteria. For example, to filter for books
that contain "wild" in the title and then count them, we could do the following.
wild_books = Book.objects.filter(title__contains='wild')
number_wild_books = Book.objects.filter(title__contains='wild').count()
The fields to match and the type of match are defined in the filter parameter name,
using the format: field_name__match_type (note the double underscore between title
and contains above). Above we're filtering title with a case-sensitive match. There
are many other types of matches you can do: icontains
(case insensitive), iexact
(case-insensitive exact match), exact
(case-sensitive exact match) and in
, gt
(greater than), startswith
, etc. The full list is here.
In some cases you'll need to filter on a field that defines a one-to-many relationship
to another model (e.g. a ForeignKey
). In this case you can "index" to fields within
the related model with additional double underscores. So for example to filter for
books with a specific genre pattern, you will have to index to the name through the
genre field, as shown below:
# Will match on: Fiction, Science fiction, non-fiction etc.
books_containing_genre = Book.objects.filter(genre__name__icontains='fiction')
Note: You can use underscores (__
) to navigate as many levels of relationships
(ForeignKey/ManyToManyField
) as you like. For example, a Book that had different
types, defined using a further "cover" relationship might have a parameter name:
type__cover__name__exact='hard'.
There is a lot more you can do with queries, including backwards searches from related models, chaining filters, returning a smaller set of values etc. For more information see Making queries (Django Docs).