We're going to create a simple, locally-deployed Django 2.0 web site here. Django is a backend web application framework for Python and is deployed using WSGI - the Python Web Services Gateway Interface. The Django code is essentially the controller in an MVC-style web application in which the models and views are supplied by the developer.
The setup can seem a little complicated at first, so we'll focus on the basic scaffolding here
Links:
http://www.djangoproject.com
We'll be using Python 3 on Ubuntu.
Django is available on pypi as the django
package and can be installed using pip
. Let's create a virtual environment and install the package there.
$ python3 -m venv django
$ source django/bin/activate
(django) $ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
pip (9.0.1)
setuptools (38.5.1)
(django) $ pip install django
Collecting Django
Downloading Django-2.0.2-py3-none-any.whl (7.1MB)
...
Collecting pytz (from Django)
Downloading pytz-2018.3-py2.py3-none-any.whl (509kB)
...
Installing collected packages: pytz, Django
Successfully installed Django-2.0.2 pytz-2018.3
(django) $ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
Django (2.0.2)
pip (9.0.1)
pytz (2018.3)
setuptools (38.5.1)
The directory structure of our virtual environment (including a few important directories and files that will be mentioned later) looks like this:
django/
bin/
django-admin
...
lib/
python3.6/
site-packages/
django/
conf/
global_settings.py
contrib/
admin/
...
views/
debug.py
templates/
default_urlconf.html
...
...
A Django project is a collection of directories and files that serves as a container for one or more Django applications. A project is typically created using the django-admin
program and its startproject
subcommand. This program is located in the bin
directory of the virtual environment and it provides a -h|--help
option.
(django) $ django-admin -h
Type 'django-admin help <subcommand>' for help on a specific subcommand.
Available subcommands:
[django]
check
compilemessages
...
The functionality of django-admin
can also be accessed by using python3 -m django
.
Creating a project creates a base directory, and a project directory of the same name within the base directory. The name of the base directory isn't used by Django but the the name of the project directory is (historical note: prior to 1.6 the directory structure was flat and there was only a project directory).
Let's create a project myprj
(we haven't changed our working directory - it remains the directory above our virtual environment directory django
- but we could do this anywhere).
(django) $ django-admin startproject myprj
That's it. The directory structure looks like this:
myprj/
manage.py
myprj/
__init__.py
settings.py
urls.py
wsgi.py
settings.py
is the project settings module. Settings in this module override those in the global settings module site-packages/django/conf/global_settings.py
. There are a couple of settings in settings.py
that should at a minimum be changed after project creation: the TIME_ZONE = 'UTC'
setting should be modified to specify your timezone (e.g. 'Europe/Amsterdam'
), and the ALLOWED_HOSTS = []
setting should be modified to contain localhost
(e.g. ALLOWED_HOSTS = ['localhost']
).
In fact, the default DEBUG = True
setting, in addition to causing exception tracebacks to be displayed in the browser, disables enforcement of the ALLOWED_HOSTS
setting. Modifying ALLOWED_HOSTS
here is thus preventative - if DEBUG
is set to False
later and ALLOWED_HOSTS
is empty then any browser access to a Django application gives HTTP 400 Bad Request errors.
With only a project created, you can test a Django installation by starting the Django HTTP development server. This HTTP server is intended for testing during development and binds to 127.0.0.1:8000 by default. It watches for relevant file changes and restarts automatically. The server is started using the manage.py
program and its runserver
subcommand. This program is located in the base directory and it also provides a -h|--help
option.
(django) $ python3 manage.py -h
Type 'manage.py help <subcommand>' for help on a specific subcommand.
Available subcommands:
[auth]
changepassword
createsuperuser
...
Let's start the server (our working directory is the base directory). You can disregard the message concerning "unapplied migrations" - it has to do with database configuration and we'll cover that next.
(django) $ python3 manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
February 23, 2018 - 06:53:12
Django version 2.0.2, using settings 'myprj.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
If you now browse to <http://localhost:8000/
> you should see an it-works page, as well as output on the server console.
(django) $ python3 manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
February 23, 2018 - 07:13:00
Django version 2.0.2, using settings 'myprj.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Not Found: /favicon.ico
[23/Feb/2018 07:19:38] "GET /favicon.ico HTTP/1.1" 404 1971
Not Found: /favicon.ico
[23/Feb/2018 07:19:38] "GET /favicon.ico HTTP/1.1" 404 1971
[23/Feb/2018 07:19:41] "GET / HTTP/1.1" 200 16348
[23/Feb/2018 07:19:41] "GET /static/admin/css/fonts.css HTTP/1.1" 200 423
[23/Feb/2018 07:19:41] "GET /static/admin/fonts/Roboto-Regular-webfont.woff HTTP/1.1" 200 80304
[23/Feb/2018 07:19:41] "GET /static/admin/fonts/Roboto-Light-webfont.woff HTTP/1.1" 200 81348
[23/Feb/2018 07:19:41] "GET /static/admin/fonts/Roboto-Bold-webfont.woff HTTP/1.1" 200 82564
A couple of things to note:
myprj/
Ctrl-C
DEBUG = True
is in settings.py
and the only urlpattern
in myprj/myprj/urls.py
is admin/
(more later)manage.py
is in fact equivalent to django-admin
and thus to python3 -m django
, but it can do more because it both inserts the base directory explicitly by name into sys.path
(rather than just leaving '' i.e. the working directory in sys.path
) and sets the value of the DJANGO_SETTINGS_MODULE
environment variable to the project settings module settings.py
. Thus manage.py
is different for each project. This can be verified by starting a Python shell via manage.py shell
.
(django) $ python3 manage.py shell
Python 3.6.1 (default, Apr 24 2017, 11:44:31)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
>>> import sys
>>> sys.path
['/home/keith/dev/python/django/myprj', ...]
>>>
>>> import os
>>> os.getenv("DJANGO_SETTINGS_MODULE")
'myprj.settings'
>>>
>>> quit()
(django) $
A Django project and its applications typically make use of a database. Even if you have no need of a database on an application level, a database is necessary in order to use certain Django infrastructure-oriented features such as user authentication and sessions (although these features can be disabled too if desired). In either case, one way to get started is to use a SQLite database. SQLite is a lightweight, open source database library and shell that provides an RDBMS-like interface. It's part of the Python standard library (the sqlite3
module; no driver needs to be installed separately).
Database settings must be specified using the DATABASES
setting in settings.py
, but if you use SQLite then there is nothing to configure, because the default value is:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Another convenience is that Django creates the SQLite database automatically when it first tries to access it. In fact, this happened when we started the HTTP development server. The database is the db.sqlite3
file in the base directory.
myprj/
db.sqlite3
manage.py
myprj/
__init__.py
settings.py
urls.py
wsgi.py
Django also supports MySQL, PostgreSQL, etc. through a Python DB API 2.0-compliant database connector such as MySQLdb, Mysql Connector/Python, Psycopg, etc. that must be installed separately. In this case, the database must be created outside of Django (more later). We'll stay with SQLite for now.
Database objects are usually created in Django using the manage.py
program and its migrate
subcommand (although they may be created manually).
A migration when using an RDBMS is essentially a set of one or more SQL statements that changes the database schema in some way. manage.py migrate
applies migrations. Creating a project creates a set of initial migrations related to administration and authentication that should be applied immediately. We can see these with manage.py showmigrations
.
(django) $ python3 manage.py showmigrations
admin
[ ] 0001_initial
[ ] 0002_logentry_remove_auto_add
auth
[ ] 0001_initial
[ ] 0002_alter_permission_name_max_length
[ ] 0003_alter_user_email_max_length
[ ] 0004_alter_user_username_opts
[ ] 0005_alter_user_last_login_null
[ ] 0006_require_contenttypes_0002
[ ] 0007_alter_validators_add_error_messages
[ ] 0008_alter_user_username_max_length
[ ] 0009_alter_user_last_name_max_length
contenttypes
[ ] 0001_initial
[ ] 0002_remove_content_type_name
sessions
[ ] 0001_initial
To get a feel for what a migration is, use manage.py sqlmigrate «app_label» «migration_name»
to display one of the pending migrations.
(django) $ python3 manage.py sqlmigrate admin 0001_initial
BEGIN;
--
-- Create model LogEntry
--
CREATE TABLE "django_admin_log" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "action_time" datetime NOT NULL, "object_id" text NULL, "object_repr" varchar(200) NOT NULL, "action_flag" smallint unsigned NOT NULL, "change_message" text NOT NULL, "content_type_id" integer NULL REFERENCES "django_content_type" ("id") DEFERRABLE INITIALLY DEFERRED, "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "django_admin_log_content_type_id_c4bce8eb" ON "django_admin_log" ("content_type_id");
CREATE INDEX "django_admin_log_user_id_c564eba6" ON "django_admin_log" ("user_id");
COMMIT;
Let's apply all of the initial migrations using manage.py migrate
. This will update the SQLite database.
(django) $ python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, 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 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 sessions.0001_initial... OK
(django) $ python3 manage.py showmigrations
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
[X] 0009_alter_user_last_name_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
sessions
[X] 0001_initial
If you're curious, run the SQLite CLI/shell with the database and see what's there (you'll need to install the SQLite CLI to do this; on Ubuntu that would be e.g. sudo apt-get install sqlite3
). Most of the tables are empty at this point.
(django) $ sqlite3 db.sqlite3
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
sqlite>
sqlite> .tables
auth_group auth_user_user_permissions
auth_group_permissions django_admin_log
auth_permission django_content_type
auth_user django_migrations
auth_user_groups django_session
sqlite>
sqlite> select * from django_migrations;
1|contenttypes|0001_initial|2018-02-27 07:24:16.953003
2|auth|0001_initial|2018-02-27 07:24:16.993802
3|admin|0001_initial|2018-02-27 07:24:17.020733
4|admin|0002_logentry_remove_auto_add|2018-02-27 07:24:17.048242
5|contenttypes|0002_remove_content_type_name|2018-02-27 07:24:17.089386
6|auth|0002_alter_permission_name_max_length|2018-02-27 07:24:17.104793
7|auth|0003_alter_user_email_max_length|2018-02-27 07:24:17.123304
8|auth|0004_alter_user_username_opts|2018-02-27 07:24:17.142049
9|auth|0005_alter_user_last_login_null|2018-02-27 07:24:17.160298
10|auth|0006_require_contenttypes_0002|2018-02-27 07:24:17.166242
11|auth|0007_alter_validators_add_error_messages|2018-02-27 07:24:17.187816
12|auth|0008_alter_user_username_max_length|2018-02-27 07:24:17.205524
13|auth|0009_alter_user_last_name_max_length|2018-02-27 07:24:17.224902
14|sessions|0001_initial|2018-02-27 07:24:17.235992
sqlite>
sqlite> .quit
(django) $
Infrastructure-oriented data is typically managed via the builtin admin
application located in site-packages/django/contrib/admin/
. This application provides a very full-featured interface to infrastructure-oriented data as well as any application-level data that you want to manage through Django. It's necessary to create a superuser if one wants to use the application. To create a superuser, use the manage.py
program and its createsuperuser
subcommand.
(django) $ python3 manage.py createsuperuser
Username (leave blank to use 'keith'): djangoadmin
Email address:
Password:
Password (again):
Superuser created successfully.
Let's check this in the database.
(django) $ sqlite3 db.sqlite3
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
sqlite>
sqlite> select * from auth_user;
1|pbkdf2_sha256$100000$vzzM470YY9rJ$aJ7bWZXxHdhZ+HbbWBNi4FPneAcjBxAMgPe/vE8GnHw=||1|djangoadmin|||1|1|2018-02-27 07:55:30.876765|
sqlite>
sqlite> .quit
(django) $
With a superuser created, you can now also browse to <http://localhost:8000/admin/
> and then login as the superuser to the admin application.
A Django application is a collection of directories and files that typically implements a web site. An application is typically created using the manage.py
program and its startapp
subcommand.
Creating an application creates an application directory.
Let's create an application myapp
(our working directory is the base directory).
(django) $ python3 manage.py startapp myapp
That's it. The directory structure now looks like this:
myprj/
db.sqlite3
manage.py
myapp/
migrations/
__init__.py
admin.py
apps.py
__init__.py
models.py
tests.py
views.py
myprj/
__init__.py
settings.py
urls.py
wsgi.py
Note that all __init__.py
files are zero-byte files.
Although it's not important for us at the moment, we'll now go ahead and register our application. In order for an application to be recognized by certain Django operations, it must be registered. Registration doesn't happen automatically. For example, manage.py makemigrations
will only make migrations for registered applications.
Registration of an application is performed by inserting the name of the application's configuration class in the list of installed applications as defined by the INSTALLED_APPS
setting in settings.py
. The default value of INSTALLED_APPS
is:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
An application's configuration class is defined by default in the application module apps.py
in the application directory. It's essentially a class that defines application metadata by means of class attributes (more later).
from django.apps import AppConfig
class MyappConfig(AppConfig):
name = 'myapp'
Let's register our application - we specify the name of its configuration class (before the existing applications) relative to the base directory.
INSTALLED_APPS = [
'myapp.apps.MyappConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
As usual with an MVC-style web application the input to the Django controller is a URL. A URL leads to a view - a function or method that typically manipulates a model (more later) and returns content by rendering a template.
In order for a URL to lead to a view, the URL must match a URL in one of the objects in the urlpatterns
list in a URL configuration module (aka a "URLconf"). The default URLconf is the urls.py
module in the project directory. This is determined by the ROOT_URLCONF = 'myprj.urls'
setting in settings.py
. The default urls.py
is:
"""myprj URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
Now we can see why the URL <http://localhost:8000/admin/
> worked earlier - there is a pattern or mapping or "route" for admin/
(the view that it's mapped to isn't important here). Note that there is no leading slash on the URL (it's implied), and that URLs in Django, by convention, end in a slash. Django will intelligently redirect slashless URLs to URLs with a trailing slash if APPEND_SLASH = True
(the default) in global_settings.py
.
Note also that, instead of the path
function, the re_path
function may be imported and used to do more sophisticated matching using regular expressions (this was always the approach in earlier versions of Django).
The URL <http://localhost:8000/
> also worked because the /
URL is hard-wired to map to site-packages/django/views/debug.py
when DEBUG = True
is in settings.py
.
Let's map the /
URL to a view of our own making. First, we'll create the view in the default views module views.py
in the application directory. The default views.py
is:
from django.shortcuts import render
# Create your views here.
A view must accept an HttpRequest
object (the parameter request
is traditionally used) and in some way return an HttpResponse
object that contains content. We'll create a function named index
and return content in the simplest way possible - by creating an HttpResponse
object and passing markup in the constructor call. The HttpResponse
class is imported from django.http
.
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse("<!doctype html><html><head></head><body><h1>It works!</h1><p>You're in the view function myapp.views.index.</p></body></html>")
We'll import this module in urls.py
and add a route to urlpatterns
that maps the URL ''
to views.index
- that's right, we don't use '/'
.
"""myprj URL Configuration
...
"""
from django.contrib import admin
from django.urls import path
from myapp import views
urlpatterns = [
path('', views.index),
path('admin/', admin.site.urls),
]
If we were to use /
we'd get a warning on the HTTP server console.
WARNINGS:
?: (urls.W002) Your URL pattern '/' [name='index'] has a route beginning with a '/'. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing '/'.
You should now see your custom it-works page by browsing to <http://localhost:8000/
.>
Note that the default Content-Type
header in an HttpReponse
object is Content-Type: text/html; charset=utf-8
based on settings in global_settings.py
.
# Default content type and charset to use for all HttpResponse objects, if a
# MIME type isn't manually specified. These are used to construct the
# Content-Type header.
DEFAULT_CONTENT_TYPE = 'text/html'
DEFAULT_CHARSET = 'utf-8'
Let's do this exercise again, this time by rendering a "template" (we won't get into the Django Template Language (DTL) yet). We'll create an application templates directory named templates
in the application directory and another myapp
directory in templates
(the reason for this directory structure has to do with the namespacing of templates; more later). We'll then create e.g. index.html
in myapp/templates/myapp
.
<!doctype html>
<html lang="en">
<head/>
<body>
<h1>It works!</h1>
<p>You're in the view function myapp.views.index.</p>
</body>
</html>
We now have:
myprj/
manage.py
myapp
migrations/
__init__.py
templates/
myapp/
index.html
admin.py
apps.py
__init__.py
models.py
tests.py
views.py
myprj/
__init__.py
settings.py
urls.py
wsgi.py
Instead of returning an HttpResponse
object directly, we'll return the result of a call to the render
function, passing it the request object and the name of our template relative to the application template directory templates
.
from django.shortcuts import render
# Create your views here.
def index(request):
return render(request, 'myapp/index.html')
Again, you should now see your custom it-works page by browsing to <http://localhost:8000/
.> Additional views could be added in the same way (although typically a separate application-specific "include URLconf" would be used for all of the URLs associated with a particular application; more later).
And there we are. A simple, locally-deployed Django 2.0 web site with just the basic scaffolding.
Share on Twitter Share on Facebook