Django Introduction Part 2 - Models
Django web applications access and manage data through Python objects referred to as models.
Models define the structure of stored data, including the field types and possibly also their maximum size, default values, selection list options, help text for documentation, label text for forms, etc.
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.
Designing the Models
Before you jump in and start coding the models, it’s worth taking a few minutes to think about what data we need to store and the relationships between the different objects.
Here, for example, we need to store information about movies.
Let us take the Aquaman for example,
1 | Title: Aquaman [海王] |
At the first time, we won’t include them all, coz we need to make the concepts more concise and the code more clear, we will take these attributes:
Title,poster,release date,director,summarygenre,language,rating_IMDB,url_IMDB
And Django allows you to define relationships that are one to one (OneToOneField), one to many (ForeignKey) and many to many (ManyToManyField).
The field name is used to refer to it in queries and templates.
Fields also have a label, which is either specified as an argument (
verbose_name) or inferred by capitalising the first letter of the field’s variable name and replacing any underscores with a space (for examplemy_field_namewould have a default label of My field name).
Edit the apps/models.py file so it looks like this:
1 | class Genre(models.Model): |
This model is used to store information about the movie genres — for example whether it is Action or Adventure, Crime, Fantasy, Historical, Horror, Romance,Science fiction or Animation, etc.
At the end of the model we declare a __str__() method, which simply returns the name of the genre defined by a particular record. No verbose name has been defined, so the field will be called Name in forms.
1 | from django.db import models |
The movie model represents all information about an available movie in a general sense.
The genre is a ManyToManyField, so that a movie can have multiple genres and a genre can have many books. The director and language are declared as ForeignKey, so each movie will only have one director and one language (in practice a movie might have many directors and many languages, but not in this implementation.)
The final method, get_absolute_url() returns a URL that can be used to access a detail record for this model
1 | import uuid # Required for unique movie watching status instances |
UUIDFieldis used for theidfield to set it as theprimary_keyfor this model. This type of field allocates a globally unique value for each instance (one for every user movie watching status you can find in the library).
1 | class Director(models.Model): |
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.
Note that this value is not HTML-escaped in automatically-generated forms. This lets you include HTML if you so desire. For example:
1
help_text="Please use the following format: <em>YYYY-MM-DD</em>."
-
verbose_name: A human-readable name for the field. If the verbose name isn’t given, Django will automatically create it using the field’s attribute name, converting underscores to spaces.
In this example, the verbose name is
"person's first name":1
first_name = models.CharField("person's first name", max_length=30)
-
validators: A list of validators to run for this field.
A validator is a callable that takes a value and raises a
ValidationErrorif it doesn’t meet some criteria. Validators can be useful for re-using validation logic between different types of fields.1
2
3
4
5
6
7
8
9
10
11from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
def validate_even(value):
if value % 2 != 0:
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
class MyModel(models.Model):
even_field = models.IntegerField(validators=[validate_even])See the validators documentation for more information.
-
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.
The default can’t be a mutable object (model instance,
list,set, etc.), as a reference to the same instance of that object would be used as the default value in all new model instances. Instead, wrap the desired default in a callable. For example, if you want to specify a defaultdictforJSONField, use a function:1
2
3
4def contact_default():
return {"email": "to1@example.com"}
contact_info = JSONField("ContactInfo", default=contact_default) -
null: If
True, Django will store blank values asNULLin the database for fields where this is appropriate (aCharFieldorTextFieldwill instead store an empty string). The default isFalse.When a
CharFieldhas bothunique=Trueandblank=Trueset. In this situation,null=Trueis required to avoid unique constraint violations when saving multiple objects with blank values. -
blank: For both string-based and non-string-based fields, you will need to set
blank=Trueif you wish to permit empty values in forms, as thenullparameter only affects database storage .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.The default is
False, which means that Django’s form validation will force you to enter a value, and the field value will be required. -
choices: An iterable (e.g., a list or tuple) consisting itself of iterables of exactly two items (e.g.
[(A, B),(A, B) ...]) to use as choices for this field.The first element in each tuple is the actual value to be set on the model, and the second element is the human-readable name.
For example:
1
2
3
4
5
6YEAR_IN_SCHOOL_CHOICES = (
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
)Unless
blank=Falseis set on the field along with adefaultthen a label containing"---------"will be rendered with the select box. To override this behavior, add a tuple tochoicescontainingNone; e.g.(None, 'Your String For Display'). Alternatively, you can use an empty string instead ofNonewhere this makes sense.Generally, it’s best to define choices inside a model class, and to define a suitably-named constant for each value:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from django.db import models
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
GRADUATE = 'GR'
YEAR_IN_SCHOOL_CHOICES = [
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
(GRADUATE, 'Graduate'),
]
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in {self.JUNIOR, self.SENIOR}In addition, Django provides enumeration types that you can subclass to define choices in a concise way:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22from django.utils.translation import gettext_lazy as _
class Student(models.Model):
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Sophomore')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in {
self.YearInSchool.JUNIOR,
self.YearInSchool.SENIOR,
} -
primary_key: If
True, this field is the primary key for the model.If you don’t specify
primary_key=Truefor any field in your model, Django will automatically add anAutoFieldto hold the primary key, so you don’t need to setprimary_key=Trueon any of your fields unless you want to override the default primary-key behavior.primary_key=Trueimpliesnull=Falseandunique=True. Only one primary key is allowed on an object. -
unique: If
True, this field must be unique throughout the table.This is enforced at the database level and by model validation. If you try to save a model with a duplicate value in a
uniquefield, adjango.db.IntegrityErrorwill be raised by the model’ssave()method.Note: This option is valid on all field types except
ManyToManyFieldandOneToOneField. WhenuniqueisTrue, you don’t need to specifydb_index, becauseuniqueimplies the creation of an index. -
unique_for_date/unique_for_month/unique_for_year:
Set this to the name of a
DateFieldorDateTimeFieldto require that this field be unique for the value of the date field.For example, if you have a field title that has
unique_for_date="pub_date", then Django wouldn’t allow the entry of two records with the same title and pub_date. -
db_column:The name of the database column to use for this field. If this isn’t given, Django will use the field’s name.
-
db_index: If
True, a database index will be created for this field. -
editable: If
False, the field will not be displayed in the admin or any otherModelForm. They are also skipped during model validation. Default isTrue. -
error_messages:
The
error_messagesargument lets you override the default messages that the field will raise. Pass in a dictionary with keys matching the error messages you want to override.Error message keys include
null,blank,invalid,invalid_choice,unique, andunique_for_date. Additional error message keys are specified for each field in the Field typessection below.These error messages often don’t propagate to forms. See Considerations regarding model’s error_messages
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.
-
CharFieldis used to define short-to-large-sized fixed-length strings. You must specify themax_lengthof the data to be stored.The default form widget for this field is a TextInput.
1
<input type="text" ...>
-
TextFieldis used for large arbitrary-length strings. You may specify amax_lengthfor the field, but this is used only when the field is displayed in forms ( However it is not enforced at the model or database level.).Renders as:
<textarea>...</textarea> -
IntegerFieldis a field for storing integer (whole number) values, and for validating entered values as integers in forms.Values from
-2147483648to2147483647are safe in all databases .1
2
3
4
5
6rating_movie = models.IntegerField(
default = 7,
validators=[
MaxValueValidator(10),
MinValueValidator(1)
])The default form widget for this field is a
NumberInputwhenlocalizeis False orTextInputotherwise. -
BigIntegerFieldis A 64-bit integer, much like anIntegerFieldexcept that it is guaranteed to fit numbers from-9223372036854775808to9223372036854775807.The default form widget for this field is a
TextInput -
DecimalFieldis A fixed-precision decimal number, represented in Python by aDecimalinstance.It has as two required arguments:
DecimalField.max_digitsThe maximum number of digits allowed in the number. Note that this number must be greater than or equal to
decimal_places.DecimalField.decimal_placesThe number of decimal places to store with the number.
For example, to store numbers up to
999with a resolution of 2 decimal places, you’d use:1
models.DecimalField(..., max_digits=5, decimal_places=2)
The default form widget for this field is a
NumberInputwhen localize is False or TextInput otherwise. -
DateFieldandDatetimeFieldare used for storing/representing dates and date/time information .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) , anddefault(to set a default date that can be overridden by the user).1
2
3
4
5
6
7
8
9
10from django.utils import timezone
import datetime
...
## As currently implemented, setting auto_now or auto_now_add to True will cause the field to have editable=False and blank=True set.
t_create = models.DateTimeField(verbose_name='Time Created', auto_now_add=True)
t_update = models.DateTimeField(verbose_name='Time Modified', auto_now=True)
## If you want to be able to modify this field, set the following instead
t_publish = models.DateTimeField(verbose_name='Time Published',default=timezone.now)
t_publish_date = models.DateField(verbose_name='Date Published',default=datetime.date.today())The default form widget for this field is a DateInput which Renders as <input type=“text” …>
-
EmailFieldis used to store and validate email addresses.Renders as:
<input type="email" ...> -
FileFieldandImageFieldare used to upload files and images respectively (theImageFieldsimply adds additional validation that the uploaded file is an image). These have parameters to define how and where the uploaded files are stored.It has two optional arguments:
-
FileField.upload_toThis attribute provides a way of setting the upload directory and file name, and can be set in two ways.
1
2
3
4
5
6class MyModel(models.Model):
# file will be uploaded to MEDIA_ROOT/uploads
upload = models.FileField(upload_to='uploads/')
# or...
# file will be saved to MEDIA_ROOT/uploads/2015/01/30
upload = models.FileField(upload_to='uploads/%Y/%m/%d/')You can also customize your the final destination path like this:
1
2
3
4
5
6
7
8def create_movie_path(instance, filename):
ext = filename.split('.')[-1]
filename = "%s_%s.%s" % (instance.user.id,instance.movie_name, ext)
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=create_movie_path) -
FileField.storageA storage object, or a callable which returns a storage object. This handles the storage and retrieval of your files.
say your
MEDIA_ROOTis set to'/home/media', andupload_tois set to'photos/%Y/%m/%d'. The'%Y/%m/%d'part ofupload_toisstrftime()formatting;'%Y'is the four-digit year,'%m'is the two-digit month and'%d'is the two-digit day. If you upload a file on Jan. 15, 2007, it will be saved in the directory/home/media/photos/2007/01/15.
-
-
SlugField is a short label for something, containing only letters, numbers, underscores or hyphens. They’re generally used in URLs.
Like a CharField, you can specify max_length (read the note about database portability and max_length in that section, too). If max_length is not specified, Django will use a default length of 50.
-
ForeignKeyis a one-to-many relationship.Requires two positional arguments: the class to which the model is related and the
on_deleteoption.(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.
To create a recursive relationship – an object that has a many-to-one relationship with itself – use
models.ForeignKey('self', on_delete=models.CASCADE).1
2
3
4
5from django.contrib.auth.models import User
class Post(models.Model):
...
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts') -
ManyToManyFieldis used to specify a many-to-many relationship (e.g. a book can have several genres, and each genre can contain several books).These have the parameter
on_deleteto define what happens when the associated record is deleted (e.g. a value ofmodels.SET_NULLwould simply set the value toNULL).
-
AutoFieldis a special type ofIntegerFieldthat automatically increments. A primary key of this type is automatically added to your model if you don’t explicitly specify one. -
BigAutoFieldis a 64-bit integer, much like anAutoFieldexcept that it is guaranteed to fit numbers from1to9223372036854775807.
You can find more model references on Django Models.
MetaData
You can declare model-level metadata for your Model by declaring class Meta, as shown.
1 | class Meta: |
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. 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 movies like this by default:
1 | ordering = ['-movie_release_date','movie_title'] |
Another common attribute is verbose_name, a verbose name for the class in singular and plural form:
1 | verbose_name = 'Mymovie' |
The attribute db_table is to set the name of the database table to use for the model:
1 | db_table = 'music_album' |
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.
1 | def __str__(self): |
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.
1 | def get_absolute_url(self): |
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.
REFERENCES
- https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-options
- https://docs.djangoproject.com/en/3.1/ref/models/fields/#field-types
- https://docs.djangoproject.com/en/3.1/ref/models/options/
- https://docs.djangoproject.com/en/3.1/ref/models/relations/
- https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_one/






