Basics of Models in Django
Last updated on July 27, 2020
We have learned quite a lot of things in the last few chapters. At this point, you should have a pretty good understanding of what Templates and Views are. In this chapter, we will learn about the Model part of the Django MTV architecture.
The Database and Model #
Today most modern web applications are database driven. And SQL is the de facto language for accessing databases. If you have already done some Web programming then you may probably know that in languages like PHP, it is very common to mix PHP code and SQL code. Apart from being messy, this method does not encourage separation of concerns. With Django, we don't that, instead of executing raw SQL queries to access database we use Django ORM, but before we do that, we have to define our data using Models.
So What is a Model?
A Model defines fields and behaviors of the data you are storing. Django models roughly map to a database table. Once the models are in place, Django provides an automatically-generated API (also known as Django ORM) to access the database.
Don't worry if all this passing over your head. In the next few sections, we are discussing everything in great depth.
Features Checklist #
As you know, in this tutorial we will be building a code-sharing app called djangobin. You can see a live example of such a site at https://djangosnippets.org/. Unlike djangosnippets.org, however, we will allow users to create snippets for a variety of languages. As with any new application, let's compile a list of features that we would like to implement in our app. Here is the complete feature list for your reference.
- Ability to create snippets by selecting language, exposure and expiration time, along with optional title and tags.
- Organize snippets by tags.
- List of trending snippets.
- List recent snippets.
- Syntax-aware code highlighting.
- List of snippets by language.
- Track snippets visit count.
- Download snippets, view raw code and delete snippets.
- Searching snippets by keywords.
- Login users.
- Reset Password.
- Update Password.
- Change account preferences.
- View account details
- Contact Page.
What data do we need? #
To implement the features outlined in the previous section, we would need to store information about the four entities: author, language, snippet and tag.
The author entity represents information about an author. An author has attributes like name, email, creation date and so on. Similarly, language, snippet and tag entities represent the information about the programming language, code snippet and the tag itself and each has their own set of attributes. The following is a list of attributes that we would like to store in the database.
Author:
- name - the name of the author
- email - email of the author
- active - a boolean field which designates whether the user can login or not
- creation date - creation date
- last login date - last logged in date
Language:
- name - the name of the language.
- language code - a language code to load the appropriate Pygment's* lexer.
- slug - a unique identifier for the language.
- mime - mime to use to send the snippet file.
- file_extension - file extension to use while downloading the snippet.
- publication date - date of creation
Snippet:
- title - title of the snippet
- slug - a unique identifier for the snippet
- original_code - code entered by the user
- highlighted_code - syntax-highlighted HTML version of the code
- expiration - expiration time of snippet (i.e never, 1 month, 6 months, 1 year)
- exposure - Snippet exposure (i.e public, private and unlisted)*.
- hits - the number of visits
- tag - a list of tags
Tag:
- name - the name of the tag
- slug - a unique identifier for the tag.
Note: Pygments is a library to highlight code snippets. To learn more about it visit this Pygments tutorial.
Note: So What is Public, Private and Unlisted in the context of Snippet? Public snippets are visible to everyone whereas private snippets are only visible to you. To create private snippet you have to login using username and password. Further, the public snippet will appear in the search, archive pages and recent snippet list throughout the site. You can also create Unlisted snippets, these items will not appear in search, archive pages and recent snippet list. In other words, Unlisted snippets will be invisible to the user unless you share the snippet URL. You don't need to login to create an Unlisted snippet. There are many sites which uses such a system, one popular example is pastebin.com.
In the context of Database, each of these entities corresponds to a table and each attribute corresponds to a column. And in the context of Django, each entity is a model and each attribute is a field.
By default, Django automatically adds a primary key field called id
to all models. You can refer to it using id
or pk
. The key automatically increments every time a new record is added.
You might be wondering what the slug means. A slug is part of a URL which identifies a page. A slug can only consist of alphabets, numbers, underscore or hyphens. For example, in the URL http://example.com/very-important-post/
, the slug is very-important-post
. As we will see, Django can automatically generate slug from the title.
Now let's take a look at what type of relationship exists between these entities.
An author can create one or more snippets but a snippet can only belong to one author. So there exists a one-to-many relationship between Author and Snippet.
An author can create one or more snippets of the same language but a snippet can only belong to one language. So again there exists a one-to-many relationship between Language and Snippet.
A snippet can be can tagged with one or more tags. Similarly, a tag can belong to one or more snippets. So there exists a many-to-many relationship between Snippet and Tag.
All in all, we have 2 one-to-many relationships and 1 many-to-many relationship.
We will see later how to define these relationships inside our models. For now, just remember these relationships exists between our entities.
Here is the updated list of attributes of each entity along with the relationship between them.
Author:
- name
- active
- creation date
- last login date
Language:
- name
- language code
- slug
- mime
- file_extension
- publication date
Snippet:
- title
- slug
- original_code
- highlighted_code
- expiration
- exposure
- hits
- tag
- Foreign key to author
- Foreign key to language
Tag:
- name
- slug
Junction Table (representing a many-to-many relationship between snippets and tags)
- snippets and tag
Creating Models #
Open models.py
file inside the djangobin app directory. It should look like this:
djangobin/django_project/djangobin/models.py
1 2 3 | from django.db import models
# Create your models here.
|
Let's start by creating an Author
model. A model is just a Python class which inherits from models.Model
class. Add the following code to models.py
file.
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 | from django.db import models
# Create your models here.
class Author(models.Model):
pass
|
Syntactically, Author
is a valid model, although it doesn't contain any fields (or attributes), it is still valid. Generally, when we define model we specify the all the fields along with their types. As already said, Django automatically adds a primary key field called id
for all models, so you don't need to define it manually. For Author
model we want following fields:
- name
- active
- account creation date
- last_logged_in
Here is the code to add these fields:
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 | from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
active = models.BooleanField(default=False)
created_on = models.DateTimeField()
last_logged_in = models.DateTimeField()
|
Let's step through the code we have added.
In line 7, we are defining name
field of type CharField
, think of CharField
type as VARCHAR
in context of the database. The max_length
argument specifies the maximum number of characters this field can store.
In line 8, here we are defining email
field of type EmailField
. The unique=True
means that value of this field must be unique throughout the table. We can pass unique
and max_length
arguments to almost all types of fields.
In line 9, we are defining active
field which designates whether the user is active or not. The type of this field is BooleanField
. A BooleanField
field can only take two values True
or False
. You can also pass 1
and 0
to it. The default
is a special argument and is used to set the default value for the column. This default value is used in the case when the value of the column is not supplied while creating a new record.
In line 10 and 11, we are defining two fields, created_on
and last_logged_in
of type DateTimeField
. A DateTimeField
is used to store date and time and it corresponds to datetime.datetime
object in Python.
At this point, you might be thinking "Database already provides various types of data types to store data". So what's the purpose of redefining them in Django?
They serve two important purposes:
- They validate data before it is saved into the database.
- They also control how to display data in the form using Widgets.
Widget refers to HTML representation of a field. Every field has a default widget. For example, the default widget for Charfield
is <input type="text" >
. The utility of widgets come into play when we create forms. We will discuss how to do that in future lessons.
Note that both models.Model
and model fields (i.e models.CharField
, models.EmailField
etc) are Python classes. In the context of Python CharField
is a string. In the context of database CharField
is VARCHAR
. The CharField
is very special field because it is the basis for all the other string-based fields. Django doesn't validate contents of the field of type CharField
. In other words, Django doesn't check the values provided to the CharField
field. It makes sense if you think about it. As CharField
is essentially a string field, it can take almost any type of data. So, 1
, mymail@example.com
, 100e100
, DJANG0
, %^%*&^%$#
all are valid examples of data.
However, the fields which inherit from CharField
do perform Validation. For example, the EmailField
. The EmailField
is commonly used to store email, if you try to store anything else in it, it will raise an exception.
Django provides many other fields to store different types of data. The following table lists some common fields:
Field Name | What it is used for? |
---|---|
CharField |
A Field for storing small to medium-sized string. It acts as a base for other string-based fields. It doesn't provide any validation |
SlugField |
A CharField to store slug. It only accepts alphabets, numbers, underscores and hyphens. It provides validation. |
DateField |
A Field to store date. It corresponds to Python's datetime.date instance. It doesn't inherit from CharField . It provides validation. |
DateTimeField |
A Field to store date and time. It corresponds to Python's datetime.datetime instance. It provides validation. |
EmailField |
A CharField to store Email address. It verifies whether value entered value is valid email or not |
URLField |
A CharField used to store URL. It provides validation. Note that it is different from SlugField . The SlugField only contains a part of the URL, not the URL itself. |
TextField |
A field to store a large amount of text. Use this field to store the content of blog post, news, stories etc. In Python, this field translates to string. In databases, it translates to TEXT field. Like CharField this field doesn't provide any validation |
BooleanField |
A field to store True or False . In the context of Python, it translates to boolean True and False . In the context of the database (as we are using SQLite) it is stored as an integer (1 )True and (0 ) False . Remember SQLite has integer type to handle integers. |
IntegerField |
A field to store integer values from -2147483648 to 2147483647 . It checks whether the value entered is number or not. |
DecimalField |
A field to store decimal number. |
ForeignKey |
A Field to define a one-to-many relationship. |
ManyToManyField |
A Field to define a many-to-many relationship. |
OneToOneField |
A field to define one-to-one relationship. |
Optional Field Parameters #
The following are some optional field parameters available to all types of field. To use these parameters pass them as keyword arguments to the model field class.
blank #
As we will see, Django can automatically generate forms by inferring the field types from the model class. Further, Django expects you to enter data in every field. If you try to submit a field without entering any data then Django will show validation errors. To make a field optional set blank=True
while defining it in the model. The default value of this parameter is False
. After setting blank=True
, if you don't provide any value to the field, Django will automatically insert an empty string into the field.
null #
If set to True
, Django will store empty values as NULL
in the database. The default is False
. Django doesn't recommend setting null=True
for string-based fields. Because if you don't provide any value to the field then Django will automatically insert an empty string into the field. If you still add null=True
for string-based field then you will end up with two possible values for "no data": NULL
, and the empty string. In such a case, Django will insert NULL
instead of an empty string. However, there are some occasions where setting null=True
in string-based fields can be useful. We will see an example of such a case in the next section.
Beginners often find blank
and null
confusing and end up and using them inappropriately. If you are still confused, Just remember, the utility of blank
parameter is only restricted to the validation mechanism, not to your database. Whereas null
controls whether a field can be NULL
or not at the database level.
default #
You can pass this parameter to set the default value of a Field. The default value will be used when the value for the field is not provided at the time of inserting new records in the table.
unique #
If set to True
then the value in the field will be unique throughout the table. The default value is False
.
help_text #
This parameter is used to specify a help string. A help string describes how to use a field or some other important information regarding the field.
db_index #
If set to True
creates an indexed column. It defaults to False
.
db_column #
This parameter is used to specify the name of the column in the database. If you don't provide this option, Django will use the field name in the model.
choices #
This parameter specifies predefined values for a field as tuples of tuples. For example:
1 2 3 4 5 6 7 8 | STATUS = (
('pen', 'Pending'),
('pub', 'Published'),
('tra', 'Trash'),
)
class Post(models.Model):
status = models.CharField(choices=STATUS)
|
If this parameter is specified the default widget will be a <select>
box instead of
<input type="text">
. The first element in each tuple is what we assign to the model instance, and the value of the second element will be displayed to the user.
There are many other optional parameters, to view the full list checkout the documentation.
Getting Acquainted #
Don't get overwhelmed by various field types and their parameters. You don't need to remember all of them. Let's say you want to use URLField
and want to know it's parameter. Open your browser and visit https://docs.djangoproject.com/en/1.11/ref/models/fields/. This page contains documentation about every field type and their available options(parameters) in Django 1.11. Search the page for URLField
keyword. Scroll down halfway through the page until you find the section describing URLField
.
The URLField
documentation tells you following things:
The first line specifies the class signature. In other words, what arguments should be passed to create an instance of
URLField
.It is
CharField
field means it inherits fromCharField
.The default form widget for this field is a
TextInput
. TheTextInput
is the name of the widget class which corresponds to<input type="text">
element. In other words,URLField
will be displayed using<input type="text">
tag. We will learn more about widgets and how to override them in Django Form Basics chapter.The next line tells us we can also pass an optional
max_length
parameter toURLField
to limit the number of characters this field can take. If you don't specifymax_length
then a default value of200
will be used.
An astute reader might say but what about the second argument in class signature i.e
**options
?
The **options
refers to the optional keyword arguments that all of the fields can take. We have covered some of them in the previous section.
Let's say you want to create unique URLField
field of length 100
. Here is the code to do that:
url = models.URLField(max_length=100, unique=True)
By default, all fields are required. If you don't specify data to a required field Django will display validation errors. To make the URL field optional add blank=True
.
url = models.URLField(max_length=100, unique=True, blank=True)
Did you notice something odd?
Here we have made url
field optional. This means if you don't enter provide any value to it Django will automatically insert an empty string. But we have also set unique
to True
that means no two columns can have the same values. To resolve the conflict you must add null=True
to url
field definition:
url = models.URLField(max_length=100, unique=True, blank=True, null=True)
Completing other Models #
Let's add the code for the remaining models.
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | from django.db import models
from .utils import Preference as Pref
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
active = models.BooleanField(default=False)
created_on = models.DateTimeField(auto_now_add=True)
last_logged_in = models.DateTimeField(auto_now=True)
class Language(models.Model):
name = models.CharField(max_length=100)
lang_code = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
mime = models.CharField(max_length=100, help_text='MIME to use when sending snippet as file.')
file_extension = models.CharField(max_length=10)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField()
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
hits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.CharField(max_length=200, unique=True)
|
There is nothing new in above code except for auto_now
and auto_now_add
parameters passed to DateTimeField
constructor. The auto_now
parameter when set to True
updates the value of the field automatically to current date and time every time an object (i.e an instance of Model
) is saved. We use this parameter to commonly store last-modified timestamps. The important thing to note about this parameter is that it only updates the field when Model.save()
method is called on the model instance. At this point, we haven't covered the save()
method, so just keep this statement in mind, it will make more sense when we will discuss the save()
method in Django ORM Basics lesson.
The auto_now_add
parameter, when set to True
automatically, sets the field value to current date and time when the object is first created.
You can also pass these two parameters to the DateField
. In that case, auto_now
updates the value of the field automatically to current date every time object is saved. And auto_now_add
parameter when set to True
automatically sets the field value to the current date when the object is first created.
The choices for expiration
and exposure
fields in the Snippet
model is coming from the Preference
class defined in utils.py
file as follows:
djangobin/django_project/djangobin/utils.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Preference:
SNIPPET_EXPIRE_NEVER = 'never'
SNIPPET_EXPIRE_1WEEK = '1 week'
SNIPPET_EXPIRE_1MONTH = '1 month'
SNIPPET_EXPIRE_6MONTH = '6 month'
SNIPPET_EXPIRE_1YEAR = '1 year'
expiration_choices = (
(SNIPPET_EXPIRE_NEVER, 'Never'),
(SNIPPET_EXPIRE_1WEEK, '1 week'),
(SNIPPET_EXPIRE_1MONTH, '1 month'),
(SNIPPET_EXPIRE_6MONTH, '6 month'),
(SNIPPET_EXPIRE_1YEAR, '1 year'),
)
SNIPPET_EXPOSURE_PUBLIC = 'public'
SNIPPET_EXPOSURE_UNLIST = 'unlisted'
SNIPPET_EXPOSURE_PRIVATE = 'private'
exposure_choices = (
(SNIPPET_EXPOSURE_PUBLIC, 'Public'),
(SNIPPET_EXPOSURE_UNLIST, 'Unlisted'),
(SNIPPET_EXPOSURE_PRIVATE, 'Private'),
)
|
We are defining choices in a separate class so that the other modules of the application can access them easily.
Adding relationships to our models #
In this section, we will learn how to add relationships between our models:
Recall that we have outlined the following the relationships between our models.
- A one-to-many relationship between Author and Snippet models.
- A one-to-many relationship between Language and Snippet models.
- A many-to-many relationship between Snippet and Tag models.
Creating one-to-many relationship is simple, just add ForeignKey
field on the many side of the relationship. The ForeignKey
constructor requires two parameters: the name of the class to which the model is related and on_delete
option. The on_delete
option specifies how Django treats rows in the child table when a row in the parent table is deleted. The following table lists possible values of on_delete
option.
Option | Description |
---|---|
CASCADE |
Delete the row or rows in the child table automatically when a row in the parent table is deleted. |
PROTECT |
Restrict deletion of a row in the parent table if there are related rows in the child table. |
SET_NULL |
Set the Foreign key column or columns in the child table to NULL and delete the row from the parent table. |
SET_DEFAULT |
Set the Foreign key column or columns in the child table to some default value. To use this option you must set some default value using the default parameter. |
DO_NOTHING |
No action is taken in the child table if a record in the parent table is deleted. If your database enforces referential integrity then this will generate an error. |
SET() |
Set a value to the Foreign key column through a callable. This is not the part of SQL standard. |
Let's now add ForeignKey
field to our models. Open models.py
file and add two ForeignKey
fields to Snippet
models as follows:
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #...
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField()
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
hits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
|
Here we have added the 2 one-to-many relationships:
- The first is between
Language
andSnippet
. - The second is between
Author
andSnippet
.
Similarly, creating a many-to-many relationship is easy. In our case, we have a many-to-many relationship between Snippet
and Tag
models. To create many-to-many relationship add ManyToManyField
field to any one side of the relationship. In other words, you can add ManyToManyField
either on the Snippet
model or Tag
model. Just like ForeignKey
field, it requires you to pass the name of the model you want to connect to.
Here is the code to add a many-to-many relationship between Snippet
and Tag
models.
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #...
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField()
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
hits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.CharField(max_length=200, unique=True)
#...
|
Notice that, in line 15, I am passing the name of the model as a string instead of the class object, this is because the definition of the Tag
model comes below the Snippet
model. You can do the same thing with the ForeignKey
field. At this point, models.py
file should look like this:
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | from django.db import models
from .utils import Preference as Pref
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
active = models.BooleanField(default=False)
created_on = models.DateTimeField(auto_now_add=True)
last_logged_in = models.DateTimeField(auto_now=True)
class Language(models.Model):
name = models.CharField(max_length=100)
lang_code = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
mime = models.CharField(max_length=100, help_text='MIME to use when sending snippet as file.')
file_extension = models.CharField(max_length=10)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class Snippet(models.Model):
title = models.CharField(max_length=200, blank=True)
original_code = models.TextField()
highlighted_code = models.TextField()
expiration = models.CharField(max_length=10, choices=Pref.expiration_choices)
exposure = models.CharField(max_length=10, choices=Pref.exposure_choices)
hits = models.IntegerField(default=0)
slug = models.SlugField()
created_on = models.DateTimeField(auto_now_add=True)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag')
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.CharField(max_length=200, unique=True)
|
Adding URL patterns and Views #
Currently, our app djangobin's urls.py
file contains 6 URL patterns:
djangobin/django_project/djangobin/urls.py
1 2 3 4 5 6 7 8 9 10 11 12 13 | from django.conf.urls import url
from . import views
# app_name = 'djangobin'
urlpatterns = [
url(r'^time/$', views.today_is, name='time'),
url(r'^$', views.index, name='index'),
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile'),
url(r'^books/$', views.book_category, name='book_category'),
url(r'^books/(?P<category>[\w-]+)/$', views.book_category, name='book_category'),
url(r'^extra/$', views.extra_args, name='extra_args'),
]
|
We are done working with the first and last three URL patterns and their corresponding view function. So you can safely delete them.
Next, we will add 4 new URL patterns. The following table describes the role of each URL pattern:
URL Path | Description |
---|---|
/trending/ |
show a list of trending (most viewed) snippets. |
/trending/<language> |
show a list of trending snippets from a particular language |
/<snippet_id>/ |
show the highlighted snippet |
/tag/<tag>/ |
show a list of snippets tagged with a certain tag. |
Open urls.py
add update it to include the new URL patterns as follows:
djangobin/django_project/djangobin/urls.py
1 2 3 4 5 6 7 8 9 10 | #...
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^user/(?P<username>[A-Za-z0-9]+)/$', views.profile, name='profile'),
url('^trending/$', views.trending_snippets, name='trending_snippets'),
url('^trending/(?P<language_slug>[\w]+)/$', views.trending_snippets, name='trending_snippets'),
url('^(?P<snippet_slug>[\d]+)/$', views.snippet_detail, name='snippet_detail'),
url('^tag/(?P<tag>[\w-]+)/$', views.tag_list, name='tag_list'),
]
|
Now add 3 new view function after index()
view in views.py
as follows:
djangobin/django_project/djangobin/views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from django.shortcuts import HttpResponse, render
def index(request):
return HttpResponse("<p>Hello Django</p>")
def snippet_detail(request, snippet_slug):
return HttpResponse('viewing snippet #{}'.format(snippet_slug))
def trending_snippets(request, language_slug=''):
return HttpResponse("trending {} snippets".format(language_slug if language_slug else ""))
def tag_list(request, tag):
return HttpResponse('viewing tag #{}'.format(tag))
def profile(request, username):
return HttpResponse("<p>Profile page of #{}</p>".format(username))
|
Right now, view functions are just stub displaying simple output. We will update then as we move along.
Adding Model Methods #
Django models are just ordinary Python classes which inherit from models.Model
class. That means we can define normal Python methods in our model class too.
To implement syntax highlighting using Pygments, we have to add some helper methods to the Language
and Snippet
models. But before we do that, let's first install Pygments by typing:
$ pip install pygments
The first step in highlighting a code snippet is to determine the name of the lexer. The lexer is a program which analyzes the syntax of the programming language. Pygments ships with lexers for a large set of languages, each one is given a unique name. We can get the lexer using the get_lexer_by_name()
function of the pygments.lexers
package.
Let's add a method to the Language
model to return the appropriate lexer for a given language:
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from django.db import models
from pygments import lexers, highlight
from pygments.formatters import HtmlFormatter, ClassNotFound
from .utils import Preference as Pref
#...
class Language(models.Model):
#...
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
def get_lexer(self):
return lexers.get_lexer_by_name(self.lang_code)
|
Given a Language
instance l1
, we can now get the lexer by calling get_lexer()
method as follows:
l1.get_lexer()
The next step is to add a method to the Snippet
model which knows how to highlight the snippet. Open models.py
file and add highlight()
method to the Snippet
model as follows:
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 | #...
class Snippet(models.Model):
#...
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField('Tag')
def highlight(self):
formatter = HtmlFormatter(linenos=True)
return highlight(self.original_code, self.language.get_lexer(), formatter)
#...
|
To highlight the code the Pygments provides highlight()
function. It takes three arguments, the code to highlight, lexer and the formatter. And returns the highlighted code. You can learn more about highlight()
function as it's parameters here.
Now, our models have the ability to highlight snippets but we won't be testing it until chapter 16.
To make working with model instances easier, Django advocates implementing the following two methods in the model class.
__str__()
methodget_absolute_url()
method
Let's start with the __str__()
method.
str() method #
The __str__()
method tells Python how to display an object in the human-readable form. If you don't define __str__()
method then its default implementation will print the object like this:
<Snippet: Snippet object>
This representation isn't very helpful because apart from the Model name it doesn't give any other information about the object.
Similarly, a list of objects will look like this:
[<Snippet: Snippet object>, <Snippet: Snippet object>, <Snippet: Snippet object>]
As you can see, objects in the list are practically indistinguishable from each other.
We can solve these issues by simply defining a __str__()
method in the model class. Open models.py
file and define __str__()
in every model class as follows:
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #...
class Author(models.Model):
#...
last_logged_in = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name + " : " + self.email
class Language(models.Model):
#...
updated_on = models.DateTimeField(auto_now=True)
def get_lexer(self):
return lexers.get_lexer_by_name(self.lang_code)
def __str__(self):
return self.name
class Snippet(models.Model):
#...
created_on = models.DateTimeField(auto_now_add=True)
def highlight(self):
formatter = HtmlFormatter(linenos=True)
return highlight(self.original_code, self.language.get_lexer(), formatter)
def __str__(self):
return (self.title if self.title else "Untitled") + " - " + self.language.name
class Tag(models.Model):
#...
slug = models.CharField(max_length=200, unique=True)
def __str__(self):
return self.name
|
get_absolute_url() method #
The get_absolute_url()
method returns the canonical URL of an object. Eventually, most of our model instances will be displayed in its unique URL. The job of the get_absolute_url()
method is to return this unique URL.
What makes get_absolute_url()
special is that unlike the {% url %}
tag and reverse()
function, we can use get_absolute_url()
in our Python code as well as in templates.
In addition to that, Django admin site (which we will discuss in lesson Django Admin app) uses get_absolute_url()
method to create a "VIEW ON SITE" link in the object-editing page that points to the public view of the object.
Above all, the most important benefit we get from defining get_absolute_url()
method is that, if we need to change the structure of the URL, then we just need to modify get_absolute_url()
method and all the templates and Python modules will pick up the changes automatically. It also means that you don't have to remember whether Snippet or Tag object takes id or any other argument.
Let's define get_absolute_url()
in all our model classes. Open models.py
and modify it as follows:
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #...
from django.shortcuts import reverse
from .utils import Preference as Pref
#...
class Language(models.Model):
#...
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('djangobin:trending_snippets', args=[self.slug])
class Snippet(models.Model):
#...
def __str__(self):
return (self.title if self.title else "Untitled") + " - " + self.language.name
def get_absolute_url(self):
return reverse('djangobin:snippet_detail', args=[self.slug])
class Tag(models.Model):
#...
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('djangobin:tag_list', args=[self.slug])
|
Customizing Model Behavior using inner Meta class #
The Meta
class deals with the options that are not specific to any fields. For example, the name of the database table, default sort order when querying the database, adding composite indexes and so on. Adding Meta
class is completely optional. We define Meta
class as a nested class in the model class.
Here are some common options that we can specify in the Meta
class.
ordering #
This option specifies the default ordering scheme for the object. If specified, this ordering scheme will be used every time we access data using Django ORM (We will learn what is Django ORM in the upcoming lessons.). It accepts a list or tuple of field names. For example:
1 2 3 4 5 6 7 | class Post
title = models.CharField()
content = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-pub_date']
|
This example sets the default ordering of Post
objects to pub_date
in descending order. If we now list Post
objects, Django will automatically return them in reverse chronological order (latest first).
unique_together #
This option specifies the unique constraint on two or more field names when taken together. It accepts two or more field names as a tuple of tuple. For example:
1 2 3 4 5 6 | class Author:
name = models.CharField()
email = models.EmailField()
class Meta:
unique_together = (('name', 'email'),)
|
This example adds UNIQUE
constraint spanning the two columns, name
and email
. In other words, name
and email
values, when taken together must be unique
indexes #
This option is used to create indexes on one or more fields. It accepts a list of models.Index
instances. The models.Index
takes two arguments: fields
and an optional name
. The fields
refers to a list of model fields where you want to add the index and name
is the name of the index. If name
isn't specified Django will auto-generate a name. Specifying more than one fields in the fields
list, will create a composite index ( i.e. an index on more than one column). Here is an example:
1 2 3 4 5 6 7 8 9 10 | class Employee
first_name = models.CharField()
last_name = models.CharField()
address = models.CharField()
class Meta:
indexes = [
models.Index(fields=['last_name', 'first_name']),
models.Index(fields=['address'], name='address_idx'),
]
|
This example creates two indexes: a composite index on last_name
and first_name
fields and a regular index on the address
field.
verbose_name #
This option specifies the singular name of the model. It accepts a string. For example:
1 2 3 4 5 | class JusticeLeague
#...
class Meta:
verbose_name = 'JusticeLeague'
|
The value of verbose_name
option is used in a number of places in Django admin site, for example, object-editing page, object list page and so on. if you don't define this option then Django will generate a munged version of the class name i.e JusticeLeague
will become justice league
.
verbose_name_plural #
This option specify the pluralized name of the model. It accepts a string. For example:
1 2 3 4 | class Category(models.Model):
#...
class Meta:
verbose_plural_name = "Category"
|
Just like verbose_name
option, the value of verbose_name_plural
is used in many pages of the Django admin site. If you don't define this option Django will use verbose_name + s
.
db_table #
By default, Django automatically creates table names for us. For example, If we have an app called forum and a model class named Bounty
, then the table name would be forum_bounty
. If for some reason you want to override this naming convention use db_table
option. It accepts a string as an argument. For example:
1 2 3 4 5 | class Bounty
#...
class Meta:
db_table = "bounties"
|
For a full list of Meta
options visit the Django documentation.
Let's now update our models.py
to add some Meta options to Language
, Snippet
and Tag
models as follows:
djangobin/django_project/djangobin/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #...
class Language(models.Model):
#...
updated_on = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
#...
class Snippet(models.Model):
#...
tags = models.ManyToManyField('Tag')
class Meta:
ordering = ['-created_on']
#...
class Tag(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
class Meta:
ordering = ['name']
#...
|
We will continue adding missing elements to models.py
file it but for now, it's enough.
We have now created some models. So what's next?
The next step is to create database tables from models. To do so, we use something called migration.
Load Comments