Introduction
Django is one of today's most widely-used Python web frameworks. For now, we will not focus on the technical definition of a web framework - the guide includes a section dedicated to explaining what frameworks are and what they do - but rather consider the literal meaning of the word, as it does not deviate much in this context.
Django's role is to provide structure (as this is what a framework does) for Python applications designed for the web (hence the web framework). Additionally, Django offers utility functions and modules that encapsulate and streamline the tedious details of web development, creating a second layer of abstraction on top of Python.
In this guide, we will explore the workings of Django and become familiar with its architecture, features, and use cases. We will also touch on how the web and web frameworks function while explaining Django's place in this system.
Note: Before diving into Django, we strongly recommend familiarizing yourself with certain technologies/concepts.
The first requirement to start programming with Django is having basic Python knowledge, given that Django itself is built on Python. However, if you have experience with any Object-Oriented language, you should be able to follow this particular guide.
The second requirement is having HTML knowledge, as it is the most common way to structure web pages.
The final requirement is understanding basic relational database theory - comprehending how databases are structured and how pieces of data relate to one another. (SQL knowledge is also quite helpful, but not necessary.)
How does the web work?
The Internet defines the means by which we exchange various types of data between numerous computers. This exchange has two endpoints: a client and a server. A client refers to the computer that is on the receiving end of this data exchange, while a server is the keeper and handler of the content to be distributed.
This content, in the early days of the Internet, was a static file sitting somewhere on the server's file system. A URL, then, defined the literal path to this static file, and when input into a browser, it displayed the exact same page at any point in time. Today, our web pages are dynamic and in constant interaction with their users. The server is no longer simply a storage unit for static files; it encompasses the business logic that renders the webpage along with the webpage's state at any particular moment in time.
The interaction between the client and the server is conducted via HTTP (Hypertext Transfer Protocol). Any time a web address is input into a browser, the browser sends an HTTP request to the server, asking for the webpage at the given address. The server then analyzes this request and returns an HTTP response. Although there are certain technical details to both, in essence, a request encodes an attempt to interact with a given webpage, and a response tells the browser what this attempt resulted in on the server-side.
A request carries within it a method along with an identifier as to where in the specified domain the method would be applied. For you to see this article, for example, your browser sent a GET
request (the method) to /introduction-to-django
(the identifier) on stackabuse.com
(the domain). There are numerous HTTP request methods to accommodate the browser's attempts to fetch information and possibly add or manipulate data (GET
, POST
, PUT
, DELETE
, TRACE
, etc.). The response, on the other hand, includes a 3-digit status code and the source code of the webpage to be rendered. Various status codes indicate different types of error, success, and redirection (i.e., the code 200
means the request was successful, 201
means the request was successful and it created a new resource).
Django's Role in This System
We label the processes that run on two different ends of the communication discussed above as frontend and backend. Frontend refers to the client-side processes, while backend covers the server-side.
Frontend development concerns itself with how the data is rendered and displayed after it is received on the client's end. Backend development, on the other hand, focuses on serving the website, accomplishing its dynamic characteristics, and calculating and managing its ever-changing state.
Django is a full-stack framework, meaning that it provides the tools to handle both the frontend and backend of a web application. On the frontend, Django defines the presentation logic. It employs keywords and methods to attach data to certain places, modify their appearance in specific ways, and move them around if necessary.
On the backend, Django handles the request/response cycle. Django processes the requests when they reach the server and renders them according to the logic defined for that particular website. It then manipulates the database if necessary, applies the business logic, and creates the context to be returned to the frontend (aka the response).
What is a Web Framework?
We have Python, a general-purpose programming language whose job is to construct behavior that can be executed by a computer. Building command-line interface applications, creating GUIs (graphical user interfaces), games, or web apps are all included in Python's job description, and you most certainly can accomplish these tasks with raw Python. Then, why do we need Django, an additional technology on top of Python? Why should we even bother learning it?
Well, Django itself is pure Python code. It actually includes most of the code you probably would have written yourself for a raw Python web application. That's what web frameworks do; they program the machinery that carries out typical web-application behavior, so the developers themselves don't have to.
Initially, Django will create the code infrastructure for our web application - the groundwork will be laid down by Django itself, and we will later code for additional behavior. Django will also provide shortcuts and conventions for the additional behavior and pack the time-consuming, boilerplate code into high-level abstractions.
Note: A framework and a library are both codebases; in general, a framework is a larger codebase. However, a framework differs from a library mainly in the sense that when a programmer uses a library, they pull the library into the project and call the behavior defined. When using a framework, there is an inversion of control; the framework itself defines and manages the project, and what the programmer does is to tweak or add on to the behavior defined by the framework.
Using a web framework saves programmers a lot of effort and time. In addition to that, there is also the benefit of frameworks being used by large communities. Django has been around for over 15 years, and many popular websites currently run on Django. It is used, tested, approved, criticized, and improved upon (given that it is an open-source project) by many. It is more stable and secure than what any one person can come up with on a random afternoon.
Django's Structure
Further down the guide, we will see how to install Django and how to set up our project. Before diving into the code, however, let's take a gander at how Django projects are structured, and thus, how we are supposed to structure our code.
Django's project structure may seem slightly convoluted at first glance. When we initiate our project and create our first app, we will be presented with an admittedly overwhelming number of files. Each of these files contains a certain functionality or code for a specific aspect of the project. The web application at the end will result from the combined effort of these various pieces.
We have mentioned that Django, on the server-side, takes in requests, applies the logic defined for the specific webpage, interacts with the database if necessary (if the user is trying to delete, add, or edit content), and sends back a response. On the user's end, the response arrives carrying a message and the source code of the web page to be rendered. The rendering of the webpage and the presentation of the data is also part of Django's job.
Django divides this labor among three components: views, models, and templates.
-
Views make up the business logic. Each view is connected to a URL, and they define the behavior of the webpage located at that URL. The view processes requests that access the webpage, applies specific logic, and prepares the appropriate responses.
-
Models create the database layer. They determine how data will be stored and accessed.
-
Templates establish the presentation logic. They decide the placement of data on the page and manage its appearance.
Each of these layers will be defined in their own files and will be as loosely coupled as possible with one another. Modifications to any of these layers won't affect the others, and people working on one aspect won't necessarily need to know about the technical details of the other ones.
Think of the Django app as a small business, a scented soap shop. The shop has an owner; they make the executive decisions, choose whom to hire, which soaps to sell, and also pay the bills, the rent, and the taxes. This is our view. The salesperson the owner employs is responsible for selling the soaps, keeping the shop clean and neat, and interacting with the customers. The executive decisions are not their concern, and since they are taking care of customer interactions, this load is off the owner's shoulders. Our salespeople are templates. The soaps the shop sells come from a supplier. The supplier is responsible for making the soaps, storing them, packing them, and shipping them. The owner only concerns themselves with which soaps to order, not with any single detail about how these soaps are made or how they end up at the shop. The supplier takes care of all this, and this is our model.
Django Apps
Django's convention is to group a project's different concerns in separate apps (or applications). An app in Django terms is a bundle of code devoted to performing a single task for the website (an ecommerce website's product listing, buyer, and seller portals can each define an app). Each app can have its own models, views, and templates, but it does not necessarily have to employ any one of them. Apps are meant to be relatively self-contained and potentially portable (so that they can be plugged into different projects).
If we were to follow the small business analogy, a Django project would be a shopping mall that houses many small businesses - the apps.
Dividing a Django project into apps is an individual process; there is no right or wrong way of doing it. A programmer may choose to create fifteen apps or only one app. They may opt to write them all from scratch or incorporate third-party apps to carry out certain functionalities. Although it may not be the wisest decision, a programmer can also go rogue and decide not to use any apps.
Note: There is only one hard-set rule regarding the app convention: models can only be defined and used within apps.
We will follow the convention and the default structure Django provides for us. We will be coding for a small section of a puppy adoption website to demonstrate the workings of views, templates, and models.
If at this moment, you feel a little flustered and don't quite understand where each piece goes, don't worry. We will apply a second coat and refine these concepts. In the end, hopefully, it will all be quite clear.
Django Set Up
Installing Python
Django is written in Python, so the first requirement to start coding with Django is having Python installed.
Let's go to the official website to download the Python installer. Once we hover the cursor over the 'Downloads' tab in the navigation bar, the website will provide us with the appropriate installer for our system.
Click the version suggested and open the installer once the download is complete.
On Mac and Linux, you can click 'Install Now' and follow through with the installer without changing any default settings. On Windows systems, however, before hitting "Install Now", we need to check the box to "Add Python to PATH".
Virtual Environment Setup
Python has a somewhat inconvenient approach towards third-party packages. All third-party Python libraries, regardless of the project they are intended for, are stored in the same location by default without proper organization. If you work with Python long enough, this characteristic can lead to chaos.
To resolve this issue, we use virtual environments for Python projects. We create an isolated Python environment for each individual project and install the project's dependencies in that environment. This not only helps categorize third-party packages but also prevents projects' dependencies from colliding with each other.
Since Django is also a third-party Python package, we will be installing it within a virtual environment. Let's go ahead and start by creating a new directory to place our virtual environment.
$ mkdir django-intro
You can name this directory whatever you want, though it is important that the path to your virtual environment does not include any spaces, as this may cause errors while executing certain commands.
Inside the directory we've just created, run:
# Switch directory to django-intro
$ cd django_intro
# Create the virtual environment on Windows
$ python -m venv ./myenv
# Create the virtual environment on Linux/Mac
$ python3 -m venv ./myenv
Note: On Mac and Linux systems, there might already be an older version of Python installed. Because of this, when creating the virtual environment, you have to specify which Python version to use (hence python3 -m venv ./myenv
). After creating the virtual environment, you can simply use the keyword python
since the environment is built on, and therefore only knows of, one specific version of Python.
Now that our virtual environment has been created, all that's left to do is activate it by running the activate
script:
# on Windows command prompt
$ .\myenv\Scripts\activate.bat
# on Linux
$ source myenv/Scripts/activate
# on MacOS
$ source myenv/bin/activate
Once the environment is activated, if we install dependencies, they will only apply to that environment and won't conflict with other environments, including the system environment.
Note: You can use the command deactivate
to leave the virtual environment.
Here, we can install this environment's first package, Django, via pip
:
$(myenv) pip install "Django==3.0.*"
Starting Out with Django
Now that we have Django installed, we can create our first Django project. While the virtual environment is still active ((myenv)
next to the shell prompt indicates that it is), from the terminal, run:
$(myenv) django-admin startproject puppypound
This command will instruct Django to create an initial structure for our project inside the directory we are working in. Let's open django-intro/
with a text editor and take a look at its contents.
There are several text editors that are quite popular among programmers; you can perform a quick web search to find one you'd like. Visual Studio Code is one of them. It is free, easy to use, and provides nice visuals and certain functionalities to help with coding, testing, and debugging. You can download it through the official website and simply follow the installer to start coding with it.
# To open the current working directory with VS Code
$ code .
We will see that inside, along with the venv
, a puppypound
directory is created, the contents of which can be seen below.
puppypound/
puppypound/
__init__.py
asgi.py
settings.py
urls.py
wsgi.py
manage.py
Within the puppypound
, we have another directory with the same name, as well as the manage.py
file. The manage.py
file marks our project's root and provides us with a set of commands to interact with the project.
Inside our second puppypound
directory, there are several files listed; let's examine them.
__init__.py
is an empty file whose sole purpose is to indicate that the folder it resides in is a Python module. This file will not concern us, nor will we need to modify it in any way.asgi.py
andwsgi.py
files are related to deployment. Since this is an introductory guide, these files will not be of concern to us at the moment.settings.py
holds the configurations for our project, which we may modify or adjust from time to time.urls.py
file defines the navigation system for our project. It lists the URLs of our website and associates each of them with a view.
If you think of a Django project as an electric circuit, the Django installation becomes the battery - the power that feeds the entire project. settings.py
in this analogy is our copper wire connecting two different terminals of the battery. As long as the wire is stretched between the two terminals, there is current running, and technically we have an electric circuit. The same requirements need to be fulfilled to consider any Python package as a Django project: a wire and a battery - Django installation, and a settings file. And just like adding elements to the circuit by attaching them to the wire, we include the components that make up our project in the settings.py
.
The settings.py
file holds information regarding the location of our project, the file defining our URL configuration (by default, it is the urls.py
as we just discussed - but you can easily change that), the locations of our templates or static files, the configured database for the project, and so on. One important section of this file is:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
We have mentioned before that conventionally, a Django project consists of various apps. These apps might be created from scratch, they may be third-party apps, or they can be brought in from Django's own contrib
package. Regardless of the origin of their creators, to be a part of the project, each app should be listed in INSTALLED_APPS
. As you can see above, the startproject
command has already brought in some apps from django.contrib
.
django.contrib
Django's codebase includes a contrib
package; within this package, Django defines various add-ons to the core framework.
These sub-packages come bundled with Django to provide certain additional functionalities, though no part of the core framework is dependent on them (yet they may be dependent on each other).
The contrib
package comprises 13 apps (at the time of writing):
-
admin
: an administrative interface -
auth
: user authentication system -
contenttypes
: a high-level interface that manages models as content types -
flatpages
: Manages one-off static pages in the database -
gis
: Django's Geographic Information Systems support -
humanize
: Defines various template filters to humanize data -
messages
: Enables cookie- and session-based messaging -
postgres
: Provides additional PostgreSQL support -
redirects
: Manages redirects -
sessions
: Django's session framework -
sites
: Connects multiple websites to a single database and Django configuration -
sitemaps
: Generates sitemap XML files -
syndication
: Generates syndication feeds
Some of these apps serve the basic needs of a website, such as authenticating users or managing content. However, some have rather cryptic descriptions. Each app requires a different level of knowledge and caters to various needs.
In this guide, we will only experiment with the admin
app and leave the rest of the contrib
apps for further guides to explore.
Initiating an App
Before creating our first app, we will go ahead and start Django's development server to view our bare-bones website.
From the root, run the command:
$(myenv) python manage.py runserver
In the terminal, you should see the output:
System check identified no issues (0 silenced).
Django version 3.0.14, using settings 'puppypound.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Note: This terminal is now dedicated to handling the development server. We will keep the server running here and open a new terminal to execute commands.
If we go ahead and enter http://127.0.0.1:8000/
into a browser, we'll see Django's welcome page.
Note: If we return to our editor now, we will see that a new file has appeared inside our root directory: db.sqlite3
. This is the database file Django has created according to our settings.py
. By default, Django projects are configured to use SQLite, a lightweight mock-up database. However, SQLite is not intended for production. We will be using it in this guide for educational purposes.
At this point, we have a working website - theoretically. Let's add some functionality to it. We will create an app of our own within which we will code some machinery. Inside our new terminal, we activate the virtual environment, switch into our project directory (puppypound
), and run:
$(myenv) django-admin startapp listing
This command will create a new folder named listing
in our project's root. Within listing/
, we see Django's default app structure created:
listing/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
apps.py
is the configuration file for our application - we will not change its default state; we will accept it as is throughout this guide. For the rest of the files (except tests.py
, which is where we are supposed to create tests for our app), we will provide explanations further down.
For now, let's register our app in the puppypound/settings.py
file by adding it to the list of INSTALLED_APPS
:
INSTALLED_APPS = [
'listing',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Awesome!
Models
We previously mentioned that Django applications' data access layers are built from models. A model is a Python object that extends the django.db.models.Model
class. Within this class, Django defines mechanisms to interact with the database and provides various methods that enable other parts of the application to communicate with the Model class. These capabilities are inherited by each child model, and all that is left for the programmer to do is define attributes to form an abstract data schema.
In the background, Django maps each model to a database table. The attributes of these models are reflected as database columns, and the model fields assigned to these attributes determine the column types.
Model fields encode translations to SQL (Django creates appropriate SQL CREATE TABLE
statements for each model), but this is not their only function. They define how the data will be represented in various parts of the application; we will see that soon enough.
Let's go ahead and start defining the data schema for our project. We will create two models: a Puppy
and a Shelter
model. We will record the shelter's name and zip code information so that (even though we will not create this functionality in this guide) we can show visitors listings from shelters located in their region. Our Puppy
model will hold the information we want to display in our listings for each puppy: the puppy's name, age, gender, and breed along with its adoption status, the date it was put up for adoption, and the shelter that posted the listing.
The models.py
file of our app will look like this:
from django.db import models
from django.utils import timezone
class Shelter(models.Model):
name = models.CharField(max_length=100)
zip_code = models.CharField(max_length=10)
class Puppy(models.Model):
STATUS_CHOICES = (
('UFA', 'Up For Adoption'),
('ONHOLD', 'On Hold'),
)
GENDER_CHOICES = (
('F', 'Female'),
('M', 'Male'),
)
name = models.CharField(max_length=100)
age = models.CharField(max_length=2)
breed = models.CharField(max_length=100)
gender = models.CharField(max_length=1,
choices=GENDER_CHOICES)
status = models.CharField(max_length=10,
choices=STATUS_CHOICES,
default='UFA')
shelter = models.ForeignKey(Shelter,
on_delete=models.CASCADE)
publish = models.DateTimeField(default=timezone.now)
class Meta:
ordering = ('-publish',)
Let's break this down, line by line:
-
We start by importing the
models
module fromdjango.db
. This module defines Django'sModel
object, which will be extended by each model we create. -
We also import the
timezone
object, which we will use to determine the time and date each listing was posted in a time zone-aware manner. -
Our
Shelter
model extendsmodels.Model
and has two attributes:name
andzip_code
, both of which areCharField
s.CharField
corresponds to a VARCHAR field in the database and requires itsmax_length
attribute to be defined. We useCharField
s for relatively small text; the shelter'sname
will consist of a maximum of 100 characters, while thezip_code
is limited to 10.
For the Shelter
model, Django will create a database table like this:
CREATE TABLE listing_shelter (
"id" serial NOT NULL PRIMARY KEY,
"name" varchar(100) NOT NULL,
"zip_code" varchar(10) NOT NULL
);
The name of the table is assigned by Django automatically, but it can be overridden. The id
field is also assigned by Django. For each of the models defined, Django will create an auto-incrementing integer primary key field with the name id
.
Note: This is an example syntax; Django will match the format of the SQL to the database backend defined in the project configurations.
- Our
Puppy
model begins by declaring two sets of choices, one for the adoption status and one for the puppy's gender.STATUS_CHOICES
will be assigned to thestatus
field and restrict thisCharField
to the two values it holds, whileGENDER_CHOICES
will do the same for thegender
field.
The
choices
field option expects a sequence consisting of 2-tuples. The first element in the tuple is assigned to the attribute in question when selected, while the second value defines the human-readable name for display.
-
The
name
,breed
, andage
fields are allCharField
s, and they are restricted to a certain number of characters using themax_length
option. -
The
gender
field is also aCharField
, but it cannot be filled with random values; it can either be assignedF
orM
, which will be displayed asFemale
andMale
, respectively. -
The
status
field, while also being restricted to a set of values, is additionally assigned a default value:UFA
.
The default
field option is applicable to all field types. There are numerous field options available; the full list can be found in Django's model field reference documentation.
-
The
Puppy
model'sshelter
field defines a relationship. Since each shelter can have multiple listings and each listing belongs to one shelter, we have a one-to-many relationship here, defined by aForeignKey
field.Note: Django defines model fields to indicate the three most common database relationships, namely: many-to-many, one-to-many, and one-to-one. All of these fields require their first argument to be a model class (the one that the model in question is relating to).
:::
Along with the name of the model to be connected (Shelter
), our ForeignKey
field holds one other argument. The on_delete
option is a required attribute; it tells Django what to do with a Puppy
object if the connected Shelter
is deleted. We have assigned CASCADE
to on_delete
; CASCADE
will delete every instance of the object in the case where the referenced object is deleted (meaning that Puppy
objects will be deleted from the database if their connected Shelter
gets deleted).
-
The
publish
field is aDateTimeField
with a default value oftimezone.now
, which indicates the date and time thePuppy
object is first created. -
The inner class
Meta
defines the metadata for the model. Here, we have declared the default order to be descending according to publish time (-publish
). When querying thePuppy
objects, thePuppy
s that were put up for adoption most recently will be listed first.
Now that we have created our data schema in code, we need to tell Django that we want these changes applied to the database. Django uses migrations to record these changes and later on applies these migrations to the database to sculpt it as defined in code.
First, we will run the following command from the terminal:
$(myenv) python manage.py makemigrations
You should see the output:
Migrations for 'listing':
listing\migrations\0001_initial.py
- Create model Shelter
- Create model Puppy
This indicates that Django has noticed the changes we made in our models.py
file and created migrations for our two new models.
Now we will run:
$(myenv) python manage.py migrate
This command will apply migrations for all the apps listed in INSTALLED_APPS
.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Operations to perform:
Apply all migrations: admin, auth, contenttypes, listing, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying listing.0001_initial... OK
Applying sessions.0001_initial... OK
Note: Any changes to the models.py
require us to run makemigrations
and migrate
commands to be reflected in the database.
Admin
Django ships with a built-in admin interface. The admin handles user authentication, creates appropriate forms for data entry according to the models' structure, and validates data input based on field types. To manage the models through the admin interface, simply register them in the app's admin.py
file.
The startapp
command integrates Django admin into our project's structure, along with other apps and middleware the admin depends on. It also loads the URL patterns of the admin application into our project by default.
If we open up our project's urls.py
file, we will see:
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
The admin module is already imported, and the admin's URLs are appended to our project's domain with the 'admin/' prefix (path('admin/', admin.site.urls)
).
Let's go ahead and register our models in admin.py
.
from django.contrib import admin
from .models import Shelter, Puppy
admin.site.register(Shelter)
admin.site.register(Puppy)
We have imported our models from the same directory and registered each of them on the admin site. Now let's create a superuser to be able to log in to the site, with the command:
$(myenv) python manage.py createsuperuser
The terminal will instruct us to fill in some information.
Username (leave blank to use 'username'):
Email address:
Password:
Password (again):
After creating the superuser, if we go to http://127.0.0.1:8000/admin
and log in with our superuser's credentials, we will see that Puppies
and Shelters
are listed under the name Listing (our app's name).
If we click on Puppies
or Shelters
, we will be presented with empty lists since we haven't populated the database yet.
Let's go ahead and click the green +
icon next to Puppies
.
We will be presented with an input form, each field of which is shaped and named according to the Puppy
model we have defined. You can see that most of the CharField
s are assigned small text widgets, while the ones with choices
defined have drop-down menus. The Status selection is prefixed with "Up For Adoption" - the assigned default
value. The JavaScript date-time picker corresponding to the publish
field is also filled with the current date and time, as we declared in the model definition.
Django has inspected our models and created a beautiful, easy-to-use interface for adding, deleting, or manipulating data, and we had to write exactly three lines of code to trigger this behavior.
Interacting with Models
Let's return to our terminal to see how we can add, access, and manipulate data in code.
We will first open up the Python shell with the command:
$(myenv) python manage.py shell
We are once again using the powers of
manage.py
here. This command not only provides us with an environment to execute Python code, but it also loads our project's settings; the Python shell that is triggered bymanage.py
is configured for our project.
Let's go ahead and import the listing
app's models into the terminal and start creating new instances for them.
>>> from listing.models import Puppy, Shelter
>>> shelter = Shelter(name='Senior Dog Rescue Center', zip_code=12345)
>>> shelter.save()
We have created a new instance of Shelter
and assigned it to the shelter
variable. This does not affect the database just yet; this is simply a Python object stored in a variable. To transfer this piece of data to the database, we use the save()
method on it. You can go ahead and reload http://127.0.0.1:8000/admin/listing/shelter/
to see our new Shelter listed there.
Let's create another instance of Shelter, but this time, let's omit the middleman.
>>> Shelter(name='Pupper Pound', zip_code=2344).save()
We can also use the model's manager to create new instances. Each model is assigned a manager by default, and by default, this manager's name is objects. Managers' job is to manage the program's interactions with the specific database table.
Managers define a set of methods to retrieve, add, delete, or manipulate data. To trigger those methods, we use the syntax:
model_name.manager_name.manager_method(method_arguments)
Note: Django also gives programmers the option to assign custom managers to the models. These managers can have slightly modified ways of accessing, editing, and deleting data.
To add a new Shelter object through the Shelter's default manager, we execute:
>>> Shelter.objects.create(name='Adoption Center', zip_code=1111)
We now have three Shelters on our list. Let's view them in the terminal.
>>> Shelter.objects.all()
The all()
method of objects
lists all the elements of the related database table.
<QuerySet [<Shelter: Senior Dog Rescue Center>, <Shelter: Pupper Pound>, <Shelter: Adoption Center>]>
Now that we have some Shelters defined, let's go ahead and add some Puppies for adoption.
Since each Puppy is related to a Shelter, we will first retrieve the desired Shelter from the database, store it in a variable, and assign that variable to the shelter
attribute of the new Puppy
instance.
We use the get()
method of the manager to retrieve a single object and, to look up the desired instance, we feed it a value to match one of the model's attributes.
Note: The get()
method is meant to fetch a single object from the database. If there is more than one value with the desired attribute, or none exists, the method will throw an error.
We will use the name attribute to retrieve a Shelter from the database.
>>> s1 = Shelter.objects.get(name='Pupper Pound')
>>> Puppy.objects.create(name='Diana', age=2, breed='Great Dane', gender='F', shelter=s1)
>>> Puppy.objects.create(name='Tyson', age=3, breed='Pitbull Terrier', gender='M', shelter=s1)
We will create a few more Puppy instances and assign them to the rest of the shelters in the database.
>>> s2 = Shelter.objects.get(name='Adoption Center')
>>> Puppy.objects.create(name='Toraman', age=3, breed='Anatolian Shepherd', gender='M', shelter=s2)
>>> Puppy.objects.create(name='Marquis', age=7, breed='Pitbull Terrier', gender='M', shelter=s2)
>>> s3 = Shelter.objects.get(name='Senior Dog Rescue Center')
>>> Puppy.objects.create(name='Roxanne', age=11, breed='Husky', gender='M', shelter=s3)
While creating the Puppy
instances, we ignored the fields status
and publish
since both of them have default values, and at this moment, the default values are appropriate.
Note: We covered the most basic ways to query the database, but there are numerous other methods. You can find them in the making queries documentation.
URLs
We have built the bridge between our database and our program; now, we will create a webpage that utilizes this. Before creating the webpage, though, we need to define the URL it will reside at.
Django's convention is to create the URL patterns at the app level and later include them in the project-level urls.py
file (keeping the apps self-contained and portable if necessary).
Let's take another look at this file's contents:
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
To view the admin page, we visited http://127.0.0.1:8000/admin
in our browser. This is the only pattern defined in this file: the path admin/
. We took a walk-through of the admin page, and when we tried to see the list of puppies, the browser directed us to /admin/listing/puppy/
. When we wanted to add a puppy, we navigated to /admin/listing/puppy/add/
. These are not explicitly defined at the project level. What the project level urls.py
indicates is that there is a set of URLs in this domain that starts with admin/
, and admin.site.url
will append the URLs of its various pages to this prefix. The listing/puppy
and listing/puppy/add
portions of the URLs are defined within the admin app.
We will adhere to this convention and create a urls.py
file inside our listing
folder to hold our app-specific URL patterns.
Each URL pattern defined inside this file connects a view to a URL. When a browser requests a web page at one of these URLs, Django goes through the project's URL patterns, finds the assigned view, and instructs it to handle the incoming request. The view then processes the request and generates an appropriate response.
First, let's begin by registering our app-level URLs in the project's urls.py
file.
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('listing/', include(listing.urls)),
]
We have imported include
from django.urls
and used this method inside path()
to append all our app-level URLs to listing/
.
path('admin/', admin.site.urls)
is unique; to import all the other apps' URLs, we need to use the methodinclude()
.
Inside listing/urls.py
, type in:
from django.urls import path
from . import views
urlpatterns = [
path('', views.puppy_list),
]
Here we have imported path
from django.urls
, and the contents of our views.py
file.
In our list of urlpatterns
, we have defined a single URL: an empty string meaning that nothing will be appended to listing/
. The puppy_list
view (which we haven't created yet) will be triggered to render the home page of our app.
Views
A view is a function (there are class-based views, but this is not our concern at the moment) that takes in a request, executes some arbitrary logic, and returns a response.
Let's go inside the views.py
file of our app to try and define a view:
from django.shortcuts import render
from django.http import HttpResponse
def puppy_list(request):
return HttpResponse('Puppies')
The render()
method is imported by default at the app's creation. In addition to that, we have imported the HttpResponse
class.
We have created a standard Python function named puppy_list
. What makes this function a view is that its first argument is a request
object (naming this argument 'request' is just the convention; there are no hard-set rules. You can name it 'potatoes' if you want; Django knows this function is a view - it is recorded in the URL patterns.) and it returns an HttpResponse
.
A view is an abstract concept in Django; it is just a name conventionally used for the functions that handle the request-response cycle for a given URL. You are not bound to create your views inside the
views.py
file either; you can create them inpotatoes.py
. As long as you import them intourls.py
and assign them to a path, Django will know what they are meant to do. Notice that this was not the case for the models. Models needed to extend Django'sModel
class and they had to be defined inside themodels.py
for Django to be able to keep track of them.
The puppy_list
view we have written is as simple as a view can get. We haven't incorporated any logic or database interactions - we have just passed a string to the HttpResponse()
constructor.
If we go to http://127.0.0.1:8000/listing/
, we will see this string sitting alone at the top of the otherwise blank page.
Note: If we go to http://127.0.0.1:8000/
from the browser, we will no longer see the welcome page. We will encounter an error trying to reach the URL. The reason for this is that the welcome page is meant for newly created, empty projects. After we create a URL of our own, Django removes it, and since we haven't defined a URL pattern for this particular URL, we get an error trying to access it.
Our intention with this page was to list the Puppy objects from our database. Let's go ahead and add logic to our view.
from .models import Puppy
def puppy_list(request):
puppies = Puppy.objects.all()
output = ", ".join([p.name + ' ' + p.age for p in puppies])
return HttpResponse(output)
We started by importing our Puppy
model. Then, we used its manager's method all()
to retrieve all instances of Puppy from the database. This query set is stored in puppies
.
We used Python's join()
to iterate over puppies
and join each Puppy's information (p.name + ' ' + p.age
) into a string with commas in between. We then passed this string into HttpResponse()
.
When we reload our page on the browser, we'll see that the text 'Puppies' is replaced with:
Roxanne 11, Marquis 7, Toraman 3, Tyson 3, Diana 2
This is more or less as far as we can go with a plain string. We generally use HTML in our response to help us structure the webpage on the frontend.
We can define the HTML right here, assign it to a variable, and then return it as our response, but this might not be the best idea.
We will use templates instead to separate the presentation logic into another file.
Note: We will be using Django's template language to create HTML, but it is capable of generating any text-based document (HTML, XML, CSV, etc.).
Templates
Within our view, we have retrieved the data we want to return to the frontend. Now, we will fill this data into a template and render this template as an HTTP response.
Django, by default, expects to find the templates of each app in a folder named templates within that app. Let's go ahead and create this folder for our listing
app.
Under listing/templates
, we will create a list.html
file and fill it with the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Puppy Pound</title>
</head>
<body>
<h1>Puppies</h1>
{% for puppy in puppies|slice:":3" %}
<h2>
{{ puppy.name | upper }}
</h2>
<h4>
{{ puppy.age }} years old,
{{ puppy.breed }},
{% if puppy.gender == 'F' %}
Female
{% else %}
Male
{% endif %}
</h4>
<p>
posted by {{ puppy.shelter.name }}, on {{ puppy.publish|date:"d M" }} {{ puppy.publish|time:"H:i"}}
</p>
{% endfor %}
</body>
</html>
So we have our regular HTML sitting in this file. Along with that, though, we have some peculiar expressions sprinkled in.
You may have noticed a theme - there are three types of unusual syntax here:
- First, we have
{{ potatoes }}
. Anything within two sets of curly braces defines a template variable. These are placeholders; they mark where data is supposed to go. Each template variable gets evaluated according to the data that is passed into the template and gets replaced with an actual value. - The ones with the structure
{% do something %}
are template tags, and they have a rather broad definition: they do something; they trigger some arbitrary behavior. - Lastly, we have pipe characters (
|
) appended to some variable names. This is Django's way of providing various alterations to the data's presentation.{{ variable | filter }}
syntax evaluates thevariable
and modifies the data in the wayfilter
defines.
Note: Even though Django template language has certain characteristics that resemble those of Python's, Django template language is not Python merged with text. In fact, Python code is not welcome in templates. Python expressions will not get evaluated, and Python functions won't get executed inside a template.
Let's go ahead and step through our code:
- We created the regular HTML structure (if you are using VS Code, typing
html:5
and hitting enter will generate the boilerplate for you) and set 'Puppy Pound' as the<title>
.
After creating the <h1>
element 'Puppies', we began looping over a list. We defined what would happen at each iteration between {% for %}
and {% endfor %}
tags.
{% for puppy in puppies|slice:":3" %}
includes a template filter |slice:"3"
, which is a template filter that accepts an argument. |slice:"X"
shortens the list in question to its first X elements, meaning that we will only loop over the first three puppy
elements in puppies
.
Notice that we have not passed any data into this template yet. We assumed that we would be getting a list of Puppy
objects and defined the presentation based on this assumption. We have named this hypothetical list puppies
simply because it reflects what the list will contain. This is not the same puppies
list we have defined in the puppy_list
view.
For each puppy
, we first created an <h2>
tag that holds {{ puppy.name | upper }}
. The filter |upper
will apply to the puppy
's name and turn it into all uppercase.
The <h4>
tag we created next is meant to hold three different attributes of the puppy
: its age, its breed, and its gender. We used an if-else clause to present the 'F' value as 'Female' and the 'M' as 'Male'. The end of the if-else clause is marked with {% endif %}
.
Lastly, within a <p>
tag, we placed the information regarding when the listing was put up and by which shelter. We used template filters to modify how the date and time will be displayed. In {{ puppy.publish|date:"d M" }}
, |date:"d M"
takes the date of our date-time field and displays its day (d
) and month (M
), while {{ puppy.publish|time:"H:i"}}
formats the time of publish
as hour:minute
.
Note: You can look up all the other ways you can format date and time at Django's official website.
Now all that is left to do is render this template as our webpage instead of a simple string.
Let's modify our views.py
:
def puppy_list(request):
puppies = Puppy.objects.all()
return render(request,
'list.html',
{'puppies': puppies})
All we changed about puppy_list
is what is returned from it. We used the render()
method that is imported into this file by default.
render()
takes in three arguments: the request object (if you have named the first argument of your view potatoes
, you should place that in here), the path to the template to be rendered (since Django knows our templates are under the listing/templates/
, we just put in 'list.html'
), and the context in the form of a dictionary. The keys in this dictionary define the names the template will use for the data (we put 'puppies' here because we assumed this would be the name when creating the template), and the values matched to these keys get passed into the template under these names.
Now, if we go to http://127.0.0.1:8000/listing/
, our browser will send a request to this URL. Django will go through our URL patterns, find the URL the browser is looking for, and trigger the view function assigned to it. Our view, puppy_list
, will interact with the database and retrieve all the Puppy
instances into the variable puppies
. It then will proceed to render list.html
, filling it with the list of puppies. The template will place each puppy in its appropriate place, and at the end, the render()
function will wrap it all up as an HttpResponse
object and send it back to our browser.
Within our browser, we will see something like this:
Conclusion
In this guide, we have learned about Django: what it is, what it does, and how it works. We have observed that a Django project consists of various moving parts, and we examined the workings of each of them. However, this is an introductory guide, and its primary purpose is to familiarize you with Django's unique structure. There is still much to discover about each individual component.
If you want to explore Django further, Django 3 By Example, by Antonio Melé, is an excellent book. For a deeper understanding of why Django works the way it does, you can also read The Definitive Guide to Django: Web Development Done Right (preferably the second edition) by Adrian Holovaty and Jacob Kaplan-Moss, two of the co-creators of Django. Although the Django version used in the book is deprecated (from years ago), it provides valuable insights into Django's structure.
If you are looking for free resources, you can refer to Django's online documentation, which is well-written. You can also stay updated with the articles we post; there are already dozens of them on the website, and we are actively creating new ones.