OverIQ.com

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.

  1. Ability to create snippets by selecting language, exposure and expiration time, along with optional title and tags.
  2. Organize snippets by tags.
  3. List of trending snippets.
  4. List recent snippets.
  5. Syntax-aware code highlighting.
  6. List of snippets by language.
  7. Track snippets visit count.
  8. Download snippets, view raw code and delete snippets.
  9. Searching snippets by keywords.
  10. Login users.
  11. Reset Password.
  12. Update Password.
  13. Change account preferences.
  14. View account details
  15. 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.

  1. 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.

  2. 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.

  3. 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
  • email
  • 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
  • email
  • 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:

  1. They validate data before it is saved into the database.
  2. 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:

  1. The first line specifies the class signature. In other words, what arguments should be passed to create an instance of URLField.

  2. It is CharField field means it inherits from CharField.

  3. The default form widget for this field is a TextInput. The TextInput 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.

  4. The next line tells us we can also pass an optional max_length parameter to URLField to limit the number of characters this field can take. If you don't specify max_length then a default value of 200 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.

  1. A one-to-many relationship between Author and Snippet models.
  2. A one-to-many relationship between Language and Snippet models.
  3. 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:

  1. The first is between Language and Snippet.
  2. The second is between Author and Snippet.

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.

  1. __str__() method
  2. get_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.