Basic Django¶
This tutorial is based on the official Django Getting Started. It add some explanation and hopefully it is easier to understand than the official document.
- Getting Started
- Database
- Model
- View
- Template
- Form
- Generic View
Getting Started¶
First, use python3 -m pip install django
to install the Django package.
Django comes with many tools such as django-admin
to make development simple and quick.
To create a project, run the following command: django-admin startproject my_site
.
Initial Project¶
The command creates a my_site
project folder with following folders and files:
manage.py
: it is a thin wrapper arounddjango-admin
. It uses the settings of the current project. You usedjango-admin
to create projects and apps, then usemanage.py
to perform the rest administration tasks.my_site
: it is the package folder for the project. It has the following files__init__.py
: it is an empty file to flag the parent folder as a Python package.asgi.py
: it is an entry point file for ASGI (Asynchronous Server Gateway Interface) web server. ASGI support asynchronous operations that might have better performance for certain applications at the cost of complex code and logic. Most Web applications don't use it.setting.py
: it contains settings (configurations) for this project. For example, you should change theTIME_ZONE = "America/Los_Angeles"
if your site is located iLos Angeles, CA, USA. You can find valid time zone names in Time Zone List.urls.py
: it configures the URL paths of the project.wsgi.py
: it is an entry point file for WSGI (Web Server Gateway Interface) web server. Most Python Web applications use it.
Dev Server¶
Django has a built-in WSGI web server for the development. You can run it with the initial project.
Inside my_site
project folder (not the nested my_site
package folder), run python3 manage.py runserver
, you can check the initial web site at http://127.0.0.1:8000/
or http://locahost:8000/
.
The built-in development server monitors file changes and rebuilt the project when there is a change in the project source code files.
To quit the server, type CONTROL-C
in the terminal.
Creating an App¶
A Django project has multiple Applications.
Each application is Python package that contains functions of subdomain the website.
An application consists of models, views, templates, static files, URLs, etc.
For example, Django creates a default admin
app during the project creation. Web administrators use the admin app to manage the web site.
To create an app named polls
, run the command: python3 manage.py startapp polls
.
The App Folder¶
The creating app command creates a polls
folder that has the following files:
__init__.py
the package flag file.admin.py
the placeholder for administration page file.apps.py
the configuration file for the app.migrations
folder is used to store database migration files.models.py
the data models used by the app.tests.py
the testing fileviews.py
the view code to process HTTP requests and return HTTP responses.
Application Configuration¶
The apps.py
inside the application package defines PollsConfig
class that is a subclass of AppConfig
.
The generated default Configure settings assign name
(the app name) and default_auto_field
(the auto generated database primary key) in the configuration class.
from django.apps import AppConfig
class PollsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'polls'
Add a New App¶
To let Django manage all apps' data models, add the polls
app to the INSTALLED_APPS
in the setting.py
file in the project's main package.∏
INSTALLED_APPS = [
"polls",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
What's a View¶
In Django, a view is associated with a URL pattern, i.e., Django maps URL patterns to views.
If a specific HTTP URL matches a view's URL pattern, the view usually performs the following functions:
- processes HTTP requests.
- fetches model data from a backend database.
- performs business logic based on request arguments.
- uses templates to render the response content.
- returns responses in different format such as HTML, JSON, or others.
Add the First View¶
As a tradition, you want to say "Hello World!" to an HTTP request. There are three basic steps:
- creating a view that return a message as an HTTP response. Because it returns a simple text message, it doesn't use a template file.
- creating a
urls.py
file that maps an app-specific URL to the view. - including the app-specific URL map in the root URL mapping.
If you run the dev server and access http://127.0.0.1:8000/polls/
, you should see the message "Hello World!".
# polls/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, World!")
# polls/urls.py
from django.urls import path
from . import views
urlpatterns = [path("", views.index, name="index"),]
# my_site/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("polls/", include("polls.urls")),
path("admin/", admin.site.urls),
]
The path()
and include()
Function¶
In polls/urls.py
file, the path()
function maps URLs to views or defines nested URL patterns. It has two required arguments and two optional arguments.
route
: is a string that contains a URL pattern.view
: is the view use to process the URL request.kwargs
: are arbitrary keyword arguments passed to the view.name
: is the name of this URL path. When this path is used elsewhere, you can use the name to refer the path.
The code path("", views.index, name="index")
maps the empty string to the views.index
view. The path has a name of index
.
The include()
Function and the ROOT_URLCONF
¶
In project's urls.py
file, the include()
function allows nested paths. You use include()
when you include other URL patterns from an app.
The path("polls/", include("polls.urls")),
puts all polls url patterns defined in polls/urls.py
under the polls/
path.
The ROOT_URLCONF = "my_site.urls"
in my_site/settings.py
specifies the URL configuration file for the root path. Django starts path matching from the this file.
For the configuration changes, you need to rerun python3 manage.py runserver
to see the changes.
Database¶
Django uses relational database to store application data that is defined by models.
A model defines a table schema.
Django provides built-in CRUD operations for data.
Django supports a number of relational database engines. You can change database setup DATABASES
in the project settings.py
file.
Database Setup¶
By default, Django uses the SQLite relational database to manage data. SQLite is a small, fast, and reliable relational database engine. It is an excellent choice for learning Django. It is also a good choice for small to medium web sites in production.
The my_site/settings.py
has two database configuration entries in the default
item in DATABASES
:
ENGINE
: default is'django.db.backends.sqlite3'
. Other popular choices are'django.db.backends.postgresql'
or'django.db.backends.mysql'
.NAME
: it is a database-specific setting. For SQLite, it is the database file path. For other databases, it is that database name that requires additional settings such asUSER
,PASSWORD
,HOST
,PORT
and so on to build the connection string.
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
Migrations¶
An app may have models whose data are stored in database.
Django uses migration
to manage database schema changes. A migration consists of SQL statements to create tables and data for an app. Whenever you change data models, Django can create a new migration for the changes.
Django provides following commands to manage migrations:
python3 manage.py showmigrations
: shows all migrations in a project.python3 manage.py makemigrations
: creates new migrations based on the changes detected to your models.python3 manage.py sqlmigrate app_label migration_name
: displays the SQL commands for the named migration.python3 manage.py migrate
: apply pending migrations to the database.
Built-in Apps¶
Django framework comes with a number of built-in apps that provide common services for a typical web site. The INSTALLED_APPS
item in settings.py
has the list of activated applications:
django.contrib.admin
: The administration site for this project.django.contrib.auth
: an app provides built-in support authentication.django.contrib.contenttypes
: an app provides built-in support for content types.django.contrib.sessions
: an app provides built-in support for session management.django.contrib.messages
: an app provides built-in support for messaging.django.contrib.staticfiles
: an app provides built-in support for managing static files.
Initialize Database for Built-in Apps¶
Django has built-in migrations for built-in apps thus there is no need to run create migrations for built-in apps.
To see the built-in migrations, run python3 manage.py showmigrations
To initialize database for built-in apps who use models, run python3 manage.py migrate
. The command applies migrations that create tables and initialization data for activated built-in apps.
For those who are familiar with database administration, you can use django-admin dbshell
or python3 manage.py dbshell
to run the command-line client for the specified database engine.
For SQLite, it actually runs sqlite3
command line client, also called a shell.
Model¶
To define a table schema and related database operations, you define a new subclass of models.Model
.
Django derives database migrations from model definition, thus there is only a single source definition of application data.
If you change a data model, Django can create updated migrations for the changes. You can even roll back changes in some cases.
Creating Models¶
In the polls app, there are two models: Question and Choice. Adding following definitions in polls/models.py
file.
- Each class inherits from
models.Model
class that provides many data access methods. - Each field is defined as a class attribute.
- Each field is a descriptor defined in
models
.- a defined class attribute is read/set as an instance attribute
- there are different descriptor types such as
CharField
,DataTimeField
, and so on.
- The
models.ForeignKey
defines a foreign key that links an instance ofChoice
to an instance ofQuestion
.- It is a one-to-many relationship: one instance of
Question
is mapped to more than one instances ofChoice
. - The
on_delete
means that if an instance ofQuestion
is deleted, all its associated instances ofChoice
are deleted automatically.
- It is a one-to-many relationship: one instance of
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
question_text = models.CharField(max_length=200)
publish_date = models.DateTimeField("date published")
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.publish_date >= timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
Create The Migrations¶
Django creates table schema and database access API based on app model definitions.
You need to add the new app to the list in the INSTALLED_APPS
item in the project setting file.
Then create migration from the app models using command python3 manage.py makemigrations polls
. It creates a migration file in polls/migrations/0001_initial.py
that has the migration code.
You can use the command python3 manage.py sqlmigrate polls 0001
to check the SQL statements that to be applied to the database.
BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question"
("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"question_text" varchar(200) NOT NULL,
"publish_date" datetime NOT NULL);
--
-- Create model Choice
--
CREATE TABLE "polls_choice"
("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"choice_text" varchar(200) NOT NULL,
"votes" integer NOT NULL, "question_id" bigint NOT NULL REFERENCES
"polls_question" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;
Apply the Migration¶
Finally, run python3 manage.py migrate
to apply the migration that creates the database tables.
In your database shell (python3 manage.py dbshell
), run .tables
to list all created tables. The two tables for polls
app are: polls_question
and polls_choice
, i.e., app name, underscore, and model class name, all in lower cases.
Django Data API¶
The models.Model
class provides database access methods for all model classes. The Django Making queries document has many examples.
Django provides a Python shell to interact with the project. Run python3 manage.py shell
to execute the project Python shell.
from polls.models import Choice, Question
from django.utils import timezone
# create an object, save to database, query its attributes
q = Question(question_text="How are you?", publish_date=timezone.now())
q.save()
q.id # 1
q.question_text # "How are you?"
# update the record with new question text
q.question_text = "How old are you?"
q.save()
# get all questions in the database
Question.objects.all() # <QuerySet [<How old are you?>]>
# query by id
q = Question.objects.get(pk=1)
q.was_published_recently() # True
# the choice table can be accessed from a question
q.choice_set.all() # <QuerySet []>
# create three choices
c1 = q.choice_set.create(choice_text="28", votes=0)
c2 = q.choice_set.create(choice_text="35", votes=0)
c3 = q.choice_set.create(choice_text="15", votes=0)
# query choice attribute
c3.question # <Question: How old are you?>
# query choices from its question
q.choice_set.all() # <QuerySet [<Choice: 28>, <Choice: 35>, <Choice: 15>]>
q.choice_set.count() # 3
# delete one, it returns the number of objects deleted and a dictionary with the number of deletions per object type.
c2.delete() # (1, {'polls.Choice': 1})
# how many left
q.choice_set.count() # 2
Django Admin¶
Django provides the ``django.contrib.admin` app to administrate the project. This is an internal site used by Web administrator.
You need to create a administrator account to use it.
Run python3 manage.py createsuperuser
to create an account by filling username, email address and password.
Then you can access the internal admin site using http://localhost:8000/admin
. By default, you can only manage users and groups after login.
Manage Models¶
To let administrator to manage your app models, you need to register the models.
In polls/admin.py
, add following lines and you can manage the registered models. Django provides simple (ugly?) pages to let you perform CRUD on the data models.
# polls/admin.py
from django.contrib import admin
from .models import Choice, Question
admin.site.register([Question, Choice])
View¶
A Django view processes requests and returns a HttpResponse
object or raises an exception. To generate response content, it may use a template system. Django comes with a default template engine but you can use other engines.
From a developer's point of view, a view is a function or class that create a Web page. Django transforms your code into a page consisting of HTML, CSS, and JavaScript.
Create Views¶
There are two steps to create a view: define the view function/class and configure an URL pattern for the view.
After adding the following code, run the command python3 manage.py runserver
to check that the URLs and views work.
# polls/view.py
def detail(request, question_id):
response = f"Detail view for {question_id}"
return HttpResponse(response)
def results(request, question_id):
response = f"Results view for {question_id}"
return HttpResponse(response)
def vote(request, question_id):
response = f"Vote view for {question_id}"
return HttpResponse(response)
# polls/urls.py
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path("", views.index, name="index"),
# ex: /polls/5/
path("<int:question_id>/", views.detail, name="detail"),
# ex: /polls/5/results/
path("<int:question_id>/results/", views.results, name="results"),
# ex: /polls/5/vote/
path("<int:question_id>/vote/", views.vote, name="vote"),
]
Django Template¶
A template has static content and placeholders or logic controls of dynamic data. A template uses special syntax for data display and logic controls.
By default, Django find templates in the templates
directory of each app. It is a best practice to put a template in a folder in the templates
directory because you may have different types of templates such as layouts and view templates.
It is a good idea for all views' templates into a folder that has the same name as the app. For example, the path for the index.html
template file of the polls
app is templates/polls/index.html
. You use the name polls/index.html
to load the template into a view.
Template Syntax¶
A template has special syntax to create view content. It is a small programming language by itself - so-called domain-specific language (DSL).
Django uses template tag {% %}
for rendering logic (conditions and loops).
It uses template expression {{ }}
for data output and The data is also called the context of the template. The templates/polls/index.html
uses a context named latest_question_list
to create the HTML content.
<!-- polls/templates/polls/index.html -->
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
The index
View¶
The index
view fetches data from database and render the template into HTML response page.
It uses Django's model API to fetch data and create a context for its template.
Then it loads a template and renders a page using the context and request data.
You can check the view output in http://localhost:8000/polls/
.
from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by("-publish_date")[:5]
template = loader.get_template("polls/index.html")
context = {
"latest_question_list": latest_question_list,
}
response = template.render(context, request)
return HttpResponse(response)
The render()
Shortcut¶
Python is famous for its expressiveness and simplicity. The previous code is good enough for many programming languages, but Python can do much better.
It is a common pattern to load a template file, apply a context and return an HttpResponse
object. Django provides a shortcut render()
function to apply a context to a template and return an HttpResponse
object.
The previous code could be simplified as the following:
from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by("-publish_date")[:5]
context = {"latest_question_list": latest_question_list}
return render(request, "polls/index.html", context)
The Detail View Template¶
The detail view uses a polls/detail.html
template whose context is, not a surprise, a question.
<!-- polls/templates/polls/detail.html -->
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
The Detail View Class¶
As usually, the view needs to fetch the model data and apply it to its template.
However, if Django couldn't find the question for the specified id, it makes sense to raise a 404 exception to let the user know that the requested object is not found.
The call get_object_or_404
throws an exception if it couldn't find the required data.
Try both http://localhost:8000/polls/1/
and http://localhost:8000/polls/2023/
.
# polls/views.py
from django.shortcuts import get_object_or_404, render
from .models import Question
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/detail.html", {"question": question})
Removing Hardcoded URLs¶
The polls/index.html
template has a hardcoded URL link for each question: <a href="/polls/{{ question.id }}/">{{ question.question_text }}</a>
.
The path pattern <int:question_id>/
is defined in polls.urls
module. The pattern may change during the development, for example, changed/<int:question_id>/whatever
. Then the hardcoded URL link in template will be broken.
To remove hardcoded URLs and to have a single source of path definition, you can use path name and {% url ... %}
template tag.
The link becomes <a href="{% url 'detail' question.id %}">{{ question.question_text }}</a>
URL Namespaces¶
The link such as "{% url 'detail' question.id %}"
works well if you only have one detail
view. If there are more than one detail
view in different apps, Django cannot differentiate them.
As a best practice, it is better to give each app an URL namespace. It is done by setting app_name
in an app's urls.py
file.
Adding app_name = "polls"
to the polls/urls.py
file before the definition of URL patterns.
With the new namespace, the link in polls/index.html
becomes <a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a>
Form¶
A Web browser is a client that send request to a Web server and displays an HTML page returned from the server. The client can send data to the backend server using a Web form.
The <form>...</form>
tag specifies a number of things to submit data:
- a URL: the target view for this data input.
- an action: an HTML action, default is
post
. - CSRF token: a unique token generated by the server to protect server from Cross Site Request Forgeries attack.
- One or more data fields and labels to let user input data.
- A submit button.
Create a Form¶
The polls app creates a form in detail
view to submit vote for a question.
It specifies the target path using the template tag {% url 'polls:vote' question.id %}
. The view is specified with a special syntax of 'polls:vote'
.
You can check the newly created form in http://localhost:8000/polls/1/
.
<!-- polls/templates/polls/detail.html -->
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend>
<h1>{{ question.question_text }}</h1>
</legend>
{% if error_message %}
<p><strong>{{ error_message }}</strong></p>
{% endif %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>
Handle Form Data¶
The detail
view defines a form submitted to the vote
view for a question.
Because the input field has a name "choice"
, you can fetch the value using this name request.POST["choice"]
.
It is a best practice to alway return an HttpResponseRedirect
after successfully dealing with posted data.
The HttpResponseRedirect
uses a redirect URL. When you need a URL from a view name, you can use reverse()
function. The reverse("polls:results", args=(question.id,))
# polls/views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Choice, Question
...
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
choice_key = request.POST["choice"]
selected_choice = question.choice_set.get(pk=choice_key)
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(
request,
"polls/detail.html",
{
"question": question,
"error_message": "You didn't select a choice.",
},
)
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
The results
Template¶
The results
template defines the content of the result view. Django template comes with a pluralize
filter that you can apply to an object to transform its value. It adds a plural suffix, often "s"
if the value is not 1
, "1"
, or an object of length 1
<!-- polls/templates/polls/results.html -->
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
The results
View¶
The results
shows the voted results for a question.
# polls/view.py
from django.shortcuts import get_object_or_404, render
...
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/results.html", {"question": question})
Generic View¶
Python is famous for its simplicity. Most view functions can be simplified with view classes that inherit common process logic from so-called generic view classes.
Two common patterns in development are
- displaying a list of objects
- showing the details of an object
Django abstract these into so-called generic views to remove redundant code.
To use generic views, you define view classes that inherit from an appropriate generic view. Django will do the heavy lifting and let you customize the view behavior.
View Classes¶
In the polls
app, the index
view displays a list of questions. You define a IndexView
that inherits from generic.ListView
.
The detail
view and results
view display the details of an object. You define DetailView
and ResultView
that inherit from generic.DetailView
.
Both generic views will retrieve the objects from database and apply them to the corresponding templates.
You can customize the generic views by define methods in your views. For example, the IndexView
fetches the list of objects in a specific order.
You need to update URL mappings to make them work.
# polls/views.py
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from .models import Choice, Question
class IndexView(generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by("-publish_date")[:5]
class DetailView(generic.DetailView):
model = Question
template_name = "polls/detail.html"
class ResultsView(generic.DetailView):
model = Question
template_name = "polls/results.html"
...
Amend URLs¶
The generic views have many naming conventions for templates and URL paths.
For example, the default template name for a model is <model_name>_list.html
. If you don't use the default name, you should set the template_name
class attribute.
By convention, Django uses pk
as the generic name as the parameter for object key. pk
is an abbreviation of Primary Key
that is an unique key in relational database table.
Each view class has an as_view()
method that create an instance of the class.
# polls/urls.py
from django.urls import path
from .views import IndexView, DetailView, ResultsView, vote
app_name = "polls"
urlpatterns = [
path("", IndexView.as_view(), name="index"),
path("<int:pk>/", DetailView.as_view(), name="detail"),
path("<int:pk>/results/", ResultsView.as_view(), name="results"),
path("<int:question_id>/vote/", vote, name="vote"),
]
Default Names¶
The generic views have a naming convention. If you follow the naming convention, the view class can be simplified to have the minimum configuration: a model name or a data query function.
For example, if you rename the index.html
to question_list.html
for a list of model data, rename the detail.html
to question_detail.html
for a model detail data, the corresponding view code becomes:
class IndexView(generic.ListView):
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by("-publish_date")[:5]
class DetailView(generic.DetailView):
model = Question
The Home URL¶
Django provides a default home page if you don't define any URLs.
When you define any URL, you should also define the home page URL.
The following code redirect the home URL to an app view.
from django.contrib import admin
from django.http import HttpResponseRedirect
from django.urls import include, path
def redirect_home(_request):
return HttpResponseRedirect("polls/")
urlpatterns = [
path("", redirect_home),
path("polls/", include("polls.urls")),
path("admin/", admin.site.urls),
]