Fixed #24732 -- Reordered tutorial to cover basics before bells and whistles.
@ -41,12 +41,13 @@ Are you new to Django or to programming? This is the place to start!
|
|||||||
:doc:`Installation <intro/install>`
|
:doc:`Installation <intro/install>`
|
||||||
|
|
||||||
* **Tutorial:**
|
* **Tutorial:**
|
||||||
:doc:`Part 1: Models <intro/tutorial01>` |
|
:doc:`Part 1: Requests and responses <intro/tutorial01>` |
|
||||||
:doc:`Part 2: The admin site <intro/tutorial02>` |
|
:doc:`Part 2: Models and the admin site <intro/tutorial02>` |
|
||||||
:doc:`Part 3: Views and templates <intro/tutorial03>` |
|
:doc:`Part 3: Views and templates <intro/tutorial03>` |
|
||||||
:doc:`Part 4: Forms and generic views <intro/tutorial04>` |
|
:doc:`Part 4: Forms and generic views <intro/tutorial04>` |
|
||||||
:doc:`Part 5: Testing <intro/tutorial05>` |
|
:doc:`Part 5: Testing <intro/tutorial05>` |
|
||||||
:doc:`Part 6: Static files <intro/tutorial06>`
|
:doc:`Part 6: Static files <intro/tutorial06>` |
|
||||||
|
:doc:`Part 7: Customizing the admin site <intro/tutorial07>`
|
||||||
|
|
||||||
* **Advanced Tutorials:**
|
* **Advanced Tutorials:**
|
||||||
:doc:`How to write reusable apps <intro/reusable-apps>` |
|
:doc:`How to write reusable apps <intro/reusable-apps>` |
|
||||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB |
BIN
docs/intro/_images/admin10t.png
Normal file
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB |
@ -15,6 +15,7 @@ place: read this material to quickly get up and running.
|
|||||||
tutorial04
|
tutorial04
|
||||||
tutorial05
|
tutorial05
|
||||||
tutorial06
|
tutorial06
|
||||||
|
tutorial07
|
||||||
reusable-apps
|
reusable-apps
|
||||||
whatsnext
|
whatsnext
|
||||||
contributing
|
contributing
|
||||||
|
@ -10,7 +10,7 @@ poll application.
|
|||||||
It'll consist of two parts:
|
It'll consist of two parts:
|
||||||
|
|
||||||
* A public site that lets people view polls and vote in them.
|
* A public site that lets people view polls and vote in them.
|
||||||
* An admin site that lets you add, change and delete polls.
|
* An admin site that lets you add, change, and delete polls.
|
||||||
|
|
||||||
We'll assume you have :doc:`Django installed </intro/install>` already. You can
|
We'll assume you have :doc:`Django installed </intro/install>` already. You can
|
||||||
tell Django is installed and which version by running the following command:
|
tell Django is installed and which version by running the following command:
|
||||||
@ -118,103 +118,8 @@ These files are:
|
|||||||
|
|
||||||
.. _more about packages: https://docs.python.org/tutorial/modules.html#packages
|
.. _more about packages: https://docs.python.org/tutorial/modules.html#packages
|
||||||
|
|
||||||
Database setup
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Now, open up :file:`mysite/settings.py`. It's a normal Python module with
|
|
||||||
module-level variables representing Django settings.
|
|
||||||
|
|
||||||
By default, the configuration uses SQLite. If you're new to databases, or
|
|
||||||
you're just interested in trying Django, this is the easiest choice. SQLite is
|
|
||||||
included in Python, so you won't need to install anything else to support your
|
|
||||||
database. When starting your first real project, however, you may want to use a
|
|
||||||
more robust database like PostgreSQL, to avoid database-switching headaches
|
|
||||||
down the road.
|
|
||||||
|
|
||||||
If you wish to use another database, install the appropriate :ref:`database
|
|
||||||
bindings <database-installation>`, and change the following keys in the
|
|
||||||
:setting:`DATABASES` ``'default'`` item to match your database connection
|
|
||||||
settings:
|
|
||||||
|
|
||||||
* :setting:`ENGINE <DATABASE-ENGINE>` -- Either
|
|
||||||
``'django.db.backends.sqlite3'``,
|
|
||||||
``'django.db.backends.postgresql_psycopg2'``,
|
|
||||||
``'django.db.backends.mysql'``, or
|
|
||||||
``'django.db.backends.oracle'``. Other backends are :ref:`also available
|
|
||||||
<third-party-notes>`.
|
|
||||||
|
|
||||||
* :setting:`NAME` -- The name of your database. If you're using SQLite, the
|
|
||||||
database will be a file on your computer; in that case, :setting:`NAME`
|
|
||||||
should be the full absolute path, including filename, of that file. The
|
|
||||||
default value, ``os.path.join(BASE_DIR, 'db.sqlite3')``, will store the file
|
|
||||||
in your project directory.
|
|
||||||
|
|
||||||
If you are not using SQLite as your database, additional settings such as :setting:`USER`, :setting:`PASSWORD`, :setting:`HOST` must be added.
|
|
||||||
For more details, see the reference documentation for :setting:`DATABASES`.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you're using PostgreSQL or MySQL, make sure you've created a database by
|
|
||||||
this point. Do that with "``CREATE DATABASE database_name;``" within your
|
|
||||||
database's interactive prompt.
|
|
||||||
|
|
||||||
If you're using SQLite, you don't need to create anything beforehand - the
|
|
||||||
database file will be created automatically when it is needed.
|
|
||||||
|
|
||||||
While you're editing :file:`mysite/settings.py`, set :setting:`TIME_ZONE` to
|
|
||||||
your time zone.
|
|
||||||
|
|
||||||
Also, note the :setting:`INSTALLED_APPS` setting at the top of the file. That
|
|
||||||
holds the names of all Django applications that are activated in this Django
|
|
||||||
instance. Apps can be used in multiple projects, and you can package and
|
|
||||||
distribute them for use by others in their projects.
|
|
||||||
|
|
||||||
By default, :setting:`INSTALLED_APPS` contains the following apps, all of which
|
|
||||||
come with Django:
|
|
||||||
|
|
||||||
* :mod:`django.contrib.admin` -- The admin site. You'll use it in :doc:`part 2
|
|
||||||
of this tutorial </intro/tutorial02>`.
|
|
||||||
|
|
||||||
* :mod:`django.contrib.auth` -- An authentication system.
|
|
||||||
|
|
||||||
* :mod:`django.contrib.contenttypes` -- A framework for content types.
|
|
||||||
|
|
||||||
* :mod:`django.contrib.sessions` -- A session framework.
|
|
||||||
|
|
||||||
* :mod:`django.contrib.messages` -- A messaging framework.
|
|
||||||
|
|
||||||
* :mod:`django.contrib.staticfiles` -- A framework for managing
|
|
||||||
static files.
|
|
||||||
|
|
||||||
These applications are included by default as a convenience for the common case.
|
|
||||||
|
|
||||||
Some of these applications make use of at least one database table, though,
|
|
||||||
so we need to create the tables in the database before we can use them. To do
|
|
||||||
that, run the following command:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ python manage.py migrate
|
|
||||||
|
|
||||||
The :djadmin:`migrate` command looks at the :setting:`INSTALLED_APPS` setting
|
|
||||||
and creates any necessary database tables according to the database settings
|
|
||||||
in your :file:`mysite/settings.py` file and the database migrations shipped
|
|
||||||
with the app (we'll cover those later). You'll see a message for each
|
|
||||||
migration it applies. If you're interested, run the command-line client for your
|
|
||||||
database and type ``\dt`` (PostgreSQL), ``SHOW TABLES;`` (MySQL), or
|
|
||||||
``.schema`` (SQLite) to display the tables Django created.
|
|
||||||
|
|
||||||
.. admonition:: For the minimalists
|
|
||||||
|
|
||||||
Like we said above, the default applications are included for the common
|
|
||||||
case, but not everybody needs them. If you don't need any or all of them,
|
|
||||||
feel free to comment-out or delete the appropriate line(s) from
|
|
||||||
:setting:`INSTALLED_APPS` before running :djadmin:`migrate`. The
|
|
||||||
:djadmin:`migrate` command will only run migrations for apps in
|
|
||||||
:setting:`INSTALLED_APPS`.
|
|
||||||
|
|
||||||
The development server
|
The development server
|
||||||
----------------------
|
======================
|
||||||
|
|
||||||
Let's verify your Django project works. Change into the outer :file:`mysite` directory, if
|
Let's verify your Django project works. Change into the outer :file:`mysite` directory, if
|
||||||
you haven't already, and run the following commands:
|
you haven't already, and run the following commands:
|
||||||
@ -229,12 +134,20 @@ You'll see the following output on the command line:
|
|||||||
|
|
||||||
Performing system checks...
|
Performing system checks...
|
||||||
|
|
||||||
0 errors found
|
System check identified no issues (0 silenced).
|
||||||
|
|
||||||
|
You have unapplied migrations; your app may not work properly until they are applied.
|
||||||
|
Run 'python manage.py migrate' to apply them.
|
||||||
|
|
||||||
|today| - 15:50:53
|
|today| - 15:50:53
|
||||||
Django version |version|, using settings 'mysite.settings'
|
Django version |version|, using settings 'mysite.settings'
|
||||||
Starting development server at http://127.0.0.1:8000/
|
Starting development server at http://127.0.0.1:8000/
|
||||||
Quit the server with CONTROL-C.
|
Quit the server with CONTROL-C.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Ignore the warning about unapplied database migrations for now; we'll deal
|
||||||
|
with the database shortly.
|
||||||
|
|
||||||
You've started the Django development server, a lightweight Web server written
|
You've started the Django development server, a lightweight Web server written
|
||||||
purely in Python. We've included this with Django so you can develop things
|
purely in Python. We've included this with Django so you can develop things
|
||||||
rapidly, without having to deal with configuring a production server -- such as
|
rapidly, without having to deal with configuring a production server -- such as
|
||||||
@ -279,10 +192,8 @@ It worked!
|
|||||||
effect. However, some actions like adding files don't trigger a restart,
|
effect. However, some actions like adding files don't trigger a restart,
|
||||||
so you'll have to restart the server in these cases.
|
so you'll have to restart the server in these cases.
|
||||||
|
|
||||||
.. _creating-models:
|
Creating the Polls app
|
||||||
|
======================
|
||||||
Creating models
|
|
||||||
===============
|
|
||||||
|
|
||||||
Now that your environment -- a "project" -- is set up, you're set to start
|
Now that your environment -- a "project" -- is set up, you're set to start
|
||||||
doing work.
|
doing work.
|
||||||
@ -324,487 +235,138 @@ That'll create a directory :file:`polls`, which is laid out like this::
|
|||||||
|
|
||||||
This directory structure will house the poll application.
|
This directory structure will house the poll application.
|
||||||
|
|
||||||
The first step in writing a database Web app in Django is to define your models
|
|
||||||
-- essentially, your database layout, with additional metadata.
|
|
||||||
|
|
||||||
.. admonition:: Philosophy
|
|
||||||
|
|
||||||
A model is the single, definitive source of truth about your data. It contains
|
|
||||||
the essential fields and behaviors of the data you're storing. Django follows
|
|
||||||
the :ref:`DRY Principle <dry>`. The goal is to define your data model in one
|
|
||||||
place and automatically derive things from it.
|
|
||||||
|
|
||||||
This includes the migrations - unlike in Ruby On Rails, for example, migrations
|
|
||||||
are entirely derived from your models file, and are essentially just a
|
|
||||||
history that Django can roll through to update your database schema to
|
|
||||||
match your current models.
|
|
||||||
|
|
||||||
In our simple poll app, we'll create two models: ``Question`` and ``Choice``.
|
|
||||||
A ``Question`` has a question and a publication date. A ``Choice`` has two fields:
|
|
||||||
the text of the choice and a vote tally. Each ``Choice`` is associated with a
|
|
||||||
``Question``.
|
|
||||||
|
|
||||||
These concepts are represented by simple Python classes. Edit the
|
|
||||||
:file:`polls/models.py` file so it looks like this:
|
|
||||||
|
|
||||||
.. snippet::
|
|
||||||
:filename: polls/models.py
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Question(models.Model):
|
|
||||||
question_text = models.CharField(max_length=200)
|
|
||||||
pub_date = models.DateTimeField('date published')
|
|
||||||
|
|
||||||
|
|
||||||
class Choice(models.Model):
|
|
||||||
question = models.ForeignKey(Question)
|
|
||||||
choice_text = models.CharField(max_length=200)
|
|
||||||
votes = models.IntegerField(default=0)
|
|
||||||
|
|
||||||
The code is straightforward. Each model is represented by a class that
|
|
||||||
subclasses :class:`django.db.models.Model`. Each model has a number of class
|
|
||||||
variables, each of which represents a database field in the model.
|
|
||||||
|
|
||||||
Each field is represented by an instance of a :class:`~django.db.models.Field`
|
|
||||||
class -- e.g., :class:`~django.db.models.CharField` for character fields and
|
|
||||||
:class:`~django.db.models.DateTimeField` for datetimes. This tells Django what
|
|
||||||
type of data each field holds.
|
|
||||||
|
|
||||||
The name of each :class:`~django.db.models.Field` instance (e.g. ``question_text`` or
|
|
||||||
``pub_date``) is the field's name, in machine-friendly format. You'll use this
|
|
||||||
value in your Python code, and your database will use it as the column name.
|
|
||||||
|
|
||||||
You can use an optional first positional argument to a
|
|
||||||
:class:`~django.db.models.Field` to designate a human-readable name. That's used
|
|
||||||
in a couple of introspective parts of Django, and it doubles as documentation.
|
|
||||||
If this field isn't provided, Django will use the machine-readable name. In this
|
|
||||||
example, we've only defined a human-readable name for ``Question.pub_date``. For all
|
|
||||||
other fields in this model, the field's machine-readable name will suffice as
|
|
||||||
its human-readable name.
|
|
||||||
|
|
||||||
Some :class:`~django.db.models.Field` classes have required arguments.
|
|
||||||
:class:`~django.db.models.CharField`, for example, requires that you give it a
|
|
||||||
:attr:`~django.db.models.CharField.max_length`. That's used not only in the
|
|
||||||
database schema, but in validation, as we'll soon see.
|
|
||||||
|
|
||||||
A :class:`~django.db.models.Field` can also have various optional arguments; in
|
|
||||||
this case, we've set the :attr:`~django.db.models.Field.default` value of
|
|
||||||
``votes`` to 0.
|
|
||||||
|
|
||||||
Finally, note a relationship is defined, using
|
|
||||||
:class:`~django.db.models.ForeignKey`. That tells Django each ``Choice`` is related
|
|
||||||
to a single ``Question``. Django supports all the common database relationships:
|
|
||||||
many-to-one, many-to-many and one-to-one.
|
|
||||||
|
|
||||||
.. _`Python path`: https://docs.python.org/tutorial/modules.html#the-module-search-path
|
.. _`Python path`: https://docs.python.org/tutorial/modules.html#the-module-search-path
|
||||||
|
|
||||||
Activating models
|
Write your first view
|
||||||
=================
|
=====================
|
||||||
|
|
||||||
That small bit of model code gives Django a lot of information. With it, Django
|
Let's write the first view. Open the file ``polls/views.py``
|
||||||
is able to:
|
and put the following Python code in it:
|
||||||
|
|
||||||
* Create a database schema (``CREATE TABLE`` statements) for this app.
|
|
||||||
* Create a Python database-access API for accessing ``Question`` and ``Choice`` objects.
|
|
||||||
|
|
||||||
But first we need to tell our project that the ``polls`` app is installed.
|
|
||||||
|
|
||||||
.. admonition:: Philosophy
|
|
||||||
|
|
||||||
Django apps are "pluggable": You can use an app in multiple projects, and
|
|
||||||
you can distribute apps, because they don't have to be tied to a given
|
|
||||||
Django installation.
|
|
||||||
|
|
||||||
Edit the :file:`mysite/settings.py` file again, and change the
|
|
||||||
:setting:`INSTALLED_APPS` setting to include the string ``'polls'``. So it'll
|
|
||||||
look like this:
|
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: mysite/settings.py
|
:filename: polls/views.py
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
from django.http import HttpResponse
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
def index(request):
|
||||||
'django.contrib.sessions',
|
return HttpResponse("Hello, world. You're at the polls index.")
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
This is the simplest view possible in Django. To call the view, we need to map
|
||||||
'polls',
|
it to a URL - and for this we need a URLconf.
|
||||||
|
|
||||||
|
To create a URLconf in the polls directory, create a file called ``urls.py``.
|
||||||
|
Your app directory should now look like::
|
||||||
|
|
||||||
|
polls/
|
||||||
|
__init__.py
|
||||||
|
admin.py
|
||||||
|
models.py
|
||||||
|
tests.py
|
||||||
|
urls.py
|
||||||
|
views.py
|
||||||
|
|
||||||
|
In the ``polls/urls.py`` file include the following code:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/urls.py
|
||||||
|
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^$', views.index, name='index'),
|
||||||
]
|
]
|
||||||
|
|
||||||
Now Django knows to include the ``polls`` app. Let's run another command:
|
The next step is to point the root URLconf at the ``polls.urls`` module. In
|
||||||
|
``mysite/urls.py`` insert an :func:`~django.conf.urls.include`, leaving you
|
||||||
.. code-block:: console
|
with:
|
||||||
|
|
||||||
$ python manage.py makemigrations polls
|
|
||||||
|
|
||||||
You should see something similar to the following:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
Migrations for 'polls':
|
|
||||||
0001_initial.py:
|
|
||||||
- Create model Choice
|
|
||||||
- Create model Question
|
|
||||||
- Add field question to choice
|
|
||||||
|
|
||||||
By running ``makemigrations``, you're telling Django that you've made
|
|
||||||
some changes to your models (in this case, you've made new ones) and that
|
|
||||||
you'd like the changes to be stored as a *migration*.
|
|
||||||
|
|
||||||
Migrations are how Django stores changes to your models (and thus your
|
|
||||||
database schema) - they're just files on disk. You can read the migration
|
|
||||||
for your new model if you like; it's the file
|
|
||||||
``polls/migrations/0001_initial.py``. Don't worry, you're not expected to read
|
|
||||||
them every time Django makes one, but they're designed to be human-editable
|
|
||||||
in case you want to manually tweak how Django changes things.
|
|
||||||
|
|
||||||
There's a command that will run the migrations for you and manage your database
|
|
||||||
schema automatically - that's called :djadmin:`migrate`, and we'll come to it in a
|
|
||||||
moment - but first, let's see what SQL that migration would run. The
|
|
||||||
:djadmin:`sqlmigrate` command takes migration names and returns their SQL:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ python manage.py sqlmigrate polls 0001
|
|
||||||
|
|
||||||
You should see something similar to the following (we've reformatted it for
|
|
||||||
readability):
|
|
||||||
|
|
||||||
.. code-block:: sql
|
|
||||||
|
|
||||||
BEGIN;
|
|
||||||
--
|
|
||||||
-- Create model Choice
|
|
||||||
--
|
|
||||||
CREATE TABLE "polls_choice" (
|
|
||||||
"id" serial NOT NULL PRIMARY KEY,
|
|
||||||
"choice_text" varchar(200) NOT NULL,
|
|
||||||
"votes" integer NOT NULL
|
|
||||||
);
|
|
||||||
--
|
|
||||||
-- Create model Question
|
|
||||||
--
|
|
||||||
CREATE TABLE "polls_question" (
|
|
||||||
"id" serial NOT NULL PRIMARY KEY,
|
|
||||||
"question_text" varchar(200) NOT NULL,
|
|
||||||
"pub_date" timestamp with time zone NOT NULL
|
|
||||||
);
|
|
||||||
--
|
|
||||||
-- Add field question to choice
|
|
||||||
--
|
|
||||||
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
|
|
||||||
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
|
|
||||||
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
|
|
||||||
ALTER TABLE "polls_choice"
|
|
||||||
ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
|
|
||||||
FOREIGN KEY ("question_id")
|
|
||||||
REFERENCES "polls_question" ("id")
|
|
||||||
DEFERRABLE INITIALLY DEFERRED;
|
|
||||||
|
|
||||||
COMMIT;
|
|
||||||
|
|
||||||
Note the following:
|
|
||||||
|
|
||||||
* The exact output will vary depending on the database you are using. The
|
|
||||||
example above is generated for PostgreSQL.
|
|
||||||
|
|
||||||
* Table names are automatically generated by combining the name of the app
|
|
||||||
(``polls``) and the lowercase name of the model -- ``question`` and
|
|
||||||
``choice``. (You can override this behavior.)
|
|
||||||
|
|
||||||
* Primary keys (IDs) are added automatically. (You can override this, too.)
|
|
||||||
|
|
||||||
* By convention, Django appends ``"_id"`` to the foreign key field name.
|
|
||||||
(Yes, you can override this, as well.)
|
|
||||||
|
|
||||||
* The foreign key relationship is made explicit by a ``FOREIGN KEY``
|
|
||||||
constraint. Don't worry about the ``DEFERRABLE`` parts; that's just telling
|
|
||||||
PostgreSQL to not enforce the foreign key until the end of the transaction.
|
|
||||||
|
|
||||||
* It's tailored to the database you're using, so database-specific field types
|
|
||||||
such as ``auto_increment`` (MySQL), ``serial`` (PostgreSQL), or ``integer
|
|
||||||
primary key autoincrement`` (SQLite) are handled for you automatically. Same
|
|
||||||
goes for the quoting of field names -- e.g., using double quotes or
|
|
||||||
single quotes.
|
|
||||||
|
|
||||||
* The :djadmin:`sqlmigrate` command doesn't actually run the migration on your
|
|
||||||
database - it just prints it to the screen so that you can see what SQL
|
|
||||||
Django thinks is required. It's useful for checking what Django is going to
|
|
||||||
do or if you have database administrators who require SQL scripts for
|
|
||||||
changes.
|
|
||||||
|
|
||||||
If you're interested, you can also run
|
|
||||||
:djadmin:`python manage.py check <check>`; this checks for any problems in
|
|
||||||
your project without making migrations or touching the database.
|
|
||||||
|
|
||||||
Now, run :djadmin:`migrate` again to create those model tables in your database:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ python manage.py migrate
|
|
||||||
Operations to perform:
|
|
||||||
Apply all migrations: admin, contenttypes, polls, auth, sessions
|
|
||||||
Running migrations:
|
|
||||||
Rendering model states... DONE
|
|
||||||
...
|
|
||||||
Applying polls.0001_initial... OK
|
|
||||||
...
|
|
||||||
|
|
||||||
The :djadmin:`migrate` command takes all the migrations that haven't been
|
|
||||||
applied (Django tracks which ones are applied using a special table in your
|
|
||||||
database called ``django_migrations``) and runs them against your database -
|
|
||||||
essentially, synchronizing the changes you made to your models with the schema
|
|
||||||
in the database.
|
|
||||||
|
|
||||||
Migrations are very powerful and let you change your models over time, as you
|
|
||||||
develop your project, without the need to delete your database or tables and
|
|
||||||
make new ones - it specializes in upgrading your database live, without
|
|
||||||
losing data. We'll cover them in more depth in a later part of the tutorial,
|
|
||||||
but for now, remember the three-step guide to making model changes:
|
|
||||||
|
|
||||||
* Change your models (in ``models.py``).
|
|
||||||
* Run :djadmin:`python manage.py makemigrations <makemigrations>` to create
|
|
||||||
migrations for those changes
|
|
||||||
* Run :djadmin:`python manage.py migrate <migrate>` to apply those changes to
|
|
||||||
the database.
|
|
||||||
|
|
||||||
The reason that there are separate commands to make and apply migrations is
|
|
||||||
because you'll commit migrations to your version control system and ship them
|
|
||||||
with your app; they not only make your development easier, they're also
|
|
||||||
useable by other developers and in production.
|
|
||||||
|
|
||||||
Read the :doc:`django-admin documentation </ref/django-admin>` for full
|
|
||||||
information on what the ``manage.py`` utility can do.
|
|
||||||
|
|
||||||
Playing with the API
|
|
||||||
====================
|
|
||||||
|
|
||||||
Now, let's hop into the interactive Python shell and play around with the free
|
|
||||||
API Django gives you. To invoke the Python shell, use this command:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
$ python manage.py shell
|
|
||||||
|
|
||||||
We're using this instead of simply typing "python", because :file:`manage.py`
|
|
||||||
sets the ``DJANGO_SETTINGS_MODULE`` environment variable, which gives Django
|
|
||||||
the Python import path to your :file:`mysite/settings.py` file.
|
|
||||||
|
|
||||||
.. admonition:: Bypassing manage.py
|
|
||||||
|
|
||||||
If you'd rather not use :file:`manage.py`, no problem. Just set the
|
|
||||||
:envvar:`DJANGO_SETTINGS_MODULE` environment variable to
|
|
||||||
``mysite.settings``, start a plain Python shell, and set up Django:
|
|
||||||
|
|
||||||
.. code-block:: pycon
|
|
||||||
|
|
||||||
>>> import django
|
|
||||||
>>> django.setup()
|
|
||||||
|
|
||||||
If this raises an :exc:`AttributeError`, you're probably using
|
|
||||||
a version of Django that doesn't match this tutorial version. You'll want
|
|
||||||
to either switch to the older tutorial or the newer Django version.
|
|
||||||
|
|
||||||
You must run ``python`` from the same directory :file:`manage.py` is in,
|
|
||||||
or ensure that directory is on the Python path, so that ``import mysite``
|
|
||||||
works.
|
|
||||||
|
|
||||||
For more information on all of this, see the :doc:`django-admin
|
|
||||||
documentation </ref/django-admin>`.
|
|
||||||
|
|
||||||
Once you're in the shell, explore the :doc:`database API </topics/db/queries>`::
|
|
||||||
|
|
||||||
>>> from polls.models import Question, Choice # Import the model classes we just wrote.
|
|
||||||
|
|
||||||
# No questions are in the system yet.
|
|
||||||
>>> Question.objects.all()
|
|
||||||
[]
|
|
||||||
|
|
||||||
# Create a new Question.
|
|
||||||
# Support for time zones is enabled in the default settings file, so
|
|
||||||
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
|
|
||||||
# instead of datetime.datetime.now() and it will do the right thing.
|
|
||||||
>>> from django.utils import timezone
|
|
||||||
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
|
|
||||||
|
|
||||||
# Save the object into the database. You have to call save() explicitly.
|
|
||||||
>>> q.save()
|
|
||||||
|
|
||||||
# Now it has an ID. Note that this might say "1L" instead of "1", depending
|
|
||||||
# on which database you're using. That's no biggie; it just means your
|
|
||||||
# database backend prefers to return integers as Python long integer
|
|
||||||
# objects.
|
|
||||||
>>> q.id
|
|
||||||
1
|
|
||||||
|
|
||||||
# Access model field values via Python attributes.
|
|
||||||
>>> q.question_text
|
|
||||||
"What's new?"
|
|
||||||
>>> q.pub_date
|
|
||||||
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
|
|
||||||
|
|
||||||
# Change values by changing the attributes, then calling save().
|
|
||||||
>>> q.question_text = "What's up?"
|
|
||||||
>>> q.save()
|
|
||||||
|
|
||||||
# objects.all() displays all the questions in the database.
|
|
||||||
>>> Question.objects.all()
|
|
||||||
[<Question: Question object>]
|
|
||||||
|
|
||||||
|
|
||||||
Wait a minute. ``<Question: Question object>`` is, utterly, an unhelpful representation
|
|
||||||
of this object. Let's fix that by editing the ``Question`` model (in the
|
|
||||||
``polls/models.py`` file) and adding a
|
|
||||||
:meth:`~django.db.models.Model.__str__` method to both ``Question`` and
|
|
||||||
``Choice``:
|
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
:filename: polls/models.py
|
:filename: mysite/urls.py
|
||||||
|
|
||||||
from django.db import models
|
from django.conf.urls import include, url
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
class Question(models.Model):
|
urlpatterns = [
|
||||||
# ...
|
url(r'^polls/', include('polls.urls')),
|
||||||
def __str__(self): # __unicode__ on Python 2
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
return self.question_text
|
]
|
||||||
|
|
||||||
class Choice(models.Model):
|
.. admonition:: Doesn't match what you see?
|
||||||
# ...
|
|
||||||
def __str__(self): # __unicode__ on Python 2
|
|
||||||
return self.choice_text
|
|
||||||
|
|
||||||
It's important to add :meth:`~django.db.models.Model.__str__` methods to your
|
If you're seeing ``admin.autodiscover()`` before the definition of
|
||||||
models, not only for your own convenience when dealing with the interactive
|
``urlpatterns``, you're probably using a version of Django that doesn't
|
||||||
prompt, but also because objects' representations are used throughout Django's
|
match this tutorial version. You'll want to either switch to the older
|
||||||
automatically-generated admin.
|
tutorial or the newer Django version.
|
||||||
|
|
||||||
.. admonition:: ``__str__`` or ``__unicode__``?
|
You have now wired an ``index`` view into the URLconf. Lets verify it's
|
||||||
|
working, run the following command:
|
||||||
|
|
||||||
On Python 3, it's easy, just use
|
.. code-block:: console
|
||||||
:meth:`~django.db.models.Model.__str__`.
|
|
||||||
|
|
||||||
On Python 2, you should define :meth:`~django.db.models.Model.__unicode__`
|
$ python manage.py runserver
|
||||||
methods returning ``unicode`` values instead. Django models have a default
|
|
||||||
:meth:`~django.db.models.Model.__str__` method that calls
|
|
||||||
:meth:`~django.db.models.Model.__unicode__` and converts the result to a
|
|
||||||
UTF-8 bytestring. This means that ``unicode(p)`` will return a Unicode
|
|
||||||
string, and ``str(p)`` will return a bytestring, with characters encoded
|
|
||||||
as UTF-8. Python does the opposite: ``object`` has a ``__unicode__``
|
|
||||||
method that calls ``__str__`` and interprets the result as an ASCII
|
|
||||||
bytestring. This difference can create confusion.
|
|
||||||
|
|
||||||
If all of this is gibberish to you, just use Python 3.
|
Go to http://localhost:8000/polls/ in your browser, and you should see the
|
||||||
|
text "*Hello, world. You're at the polls index.*", which you defined in the
|
||||||
|
``index`` view.
|
||||||
|
|
||||||
Note these are normal Python methods. Let's add a custom method, just for
|
The :func:`~django.conf.urls.url` function is passed four arguments, two
|
||||||
demonstration:
|
required: ``regex`` and ``view``, and two optional: ``kwargs``, and ``name``.
|
||||||
|
At this point, it's worth reviewing what these arguments are for.
|
||||||
|
|
||||||
.. snippet::
|
:func:`~django.conf.urls.url` argument: regex
|
||||||
:filename: polls/models.py
|
---------------------------------------------
|
||||||
|
|
||||||
import datetime
|
The term "regex" is a commonly used short form meaning "regular expression",
|
||||||
|
which is a syntax for matching patterns in strings, or in this case, url
|
||||||
|
patterns. Django starts at the first regular expression and makes its way down
|
||||||
|
the list, comparing the requested URL against each regular expression until it
|
||||||
|
finds one that matches.
|
||||||
|
|
||||||
from django.db import models
|
Note that these regular expressions do not search GET and POST parameters, or
|
||||||
from django.utils import timezone
|
the domain name. For example, in a request to
|
||||||
|
``http://www.example.com/myapp/``, the URLconf will look for ``myapp/``. In a
|
||||||
|
request to ``http://www.example.com/myapp/?page=3``, the URLconf will also
|
||||||
|
look for ``myapp/``.
|
||||||
|
|
||||||
|
If you need help with regular expressions, see `Wikipedia's entry`_ and the
|
||||||
|
documentation of the :mod:`re` module. Also, the O'Reilly book "Mastering
|
||||||
|
Regular Expressions" by Jeffrey Friedl is fantastic. In practice, however,
|
||||||
|
you don't need to be an expert on regular expressions, as you really only need
|
||||||
|
to know how to capture simple patterns. In fact, complex regexes can have poor
|
||||||
|
lookup performance, so you probably shouldn't rely on the full power of regexes.
|
||||||
|
|
||||||
class Question(models.Model):
|
Finally, a performance note: these regular expressions are compiled the first
|
||||||
# ...
|
time the URLconf module is loaded. They're super fast (as long as the lookups
|
||||||
def was_published_recently(self):
|
aren't too complex as noted above).
|
||||||
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
|
|
||||||
|
|
||||||
Note the addition of ``import datetime`` and ``from django.utils import
|
.. _Wikipedia's entry: http://en.wikipedia.org/wiki/Regular_expression
|
||||||
timezone``, to reference Python's standard :mod:`datetime` module and Django's
|
|
||||||
time-zone-related utilities in :mod:`django.utils.timezone`, respectively. If
|
|
||||||
you aren't familiar with time zone handling in Python, you can learn more in
|
|
||||||
the :doc:`time zone support docs </topics/i18n/timezones>`.
|
|
||||||
|
|
||||||
Save these changes and start a new Python interactive shell by running
|
:func:`~django.conf.urls.url` argument: view
|
||||||
``python manage.py shell`` again::
|
--------------------------------------------
|
||||||
|
|
||||||
>>> from polls.models import Question, Choice
|
When Django finds a regular expression match, Django calls the specified view
|
||||||
|
function, with an :class:`~django.http.HttpRequest` object as the first
|
||||||
|
argument and any “captured” values from the regular expression as other
|
||||||
|
arguments. If the regex uses simple captures, values are passed as positional
|
||||||
|
arguments; if it uses named captures, values are passed as keyword arguments.
|
||||||
|
We'll give an example of this in a bit.
|
||||||
|
|
||||||
# Make sure our __str__() addition worked.
|
:func:`~django.conf.urls.url` argument: kwargs
|
||||||
>>> Question.objects.all()
|
----------------------------------------------
|
||||||
[<Question: What's up?>]
|
|
||||||
|
|
||||||
# Django provides a rich database lookup API that's entirely driven by
|
Arbitrary keyword arguments can be passed in a dictionary to the target view. We
|
||||||
# keyword arguments.
|
aren't going to use this feature of Django in the tutorial.
|
||||||
>>> Question.objects.filter(id=1)
|
|
||||||
[<Question: What's up?>]
|
|
||||||
>>> Question.objects.filter(question_text__startswith='What')
|
|
||||||
[<Question: What's up?>]
|
|
||||||
|
|
||||||
# Get the question that was published this year.
|
:func:`~django.conf.urls.url` argument: name
|
||||||
>>> from django.utils import timezone
|
---------------------------------------------
|
||||||
>>> current_year = timezone.now().year
|
|
||||||
>>> Question.objects.get(pub_date__year=current_year)
|
|
||||||
<Question: What's up?>
|
|
||||||
|
|
||||||
# Request an ID that doesn't exist, this will raise an exception.
|
Naming your URL lets you refer to it unambiguously from elsewhere in Django
|
||||||
>>> Question.objects.get(id=2)
|
especially templates. This powerful feature allows you to make global changes
|
||||||
Traceback (most recent call last):
|
to the url patterns of your project while only touching a single file.
|
||||||
...
|
|
||||||
DoesNotExist: Question matching query does not exist.
|
|
||||||
|
|
||||||
# Lookup by a primary key is the most common case, so Django provides a
|
When you're comfortable with the basic request and response flow, read
|
||||||
# shortcut for primary-key exact lookups.
|
:doc:`part 2 of this tutorial </intro/tutorial02>` to start working with the
|
||||||
# The following is identical to Question.objects.get(id=1).
|
database.
|
||||||
>>> Question.objects.get(pk=1)
|
|
||||||
<Question: What's up?>
|
|
||||||
|
|
||||||
# Make sure our custom method worked.
|
|
||||||
>>> q = Question.objects.get(pk=1)
|
|
||||||
>>> q.was_published_recently()
|
|
||||||
True
|
|
||||||
|
|
||||||
# Give the Question a couple of Choices. The create call constructs a new
|
|
||||||
# Choice object, does the INSERT statement, adds the choice to the set
|
|
||||||
# of available choices and returns the new Choice object. Django creates
|
|
||||||
# a set to hold the "other side" of a ForeignKey relation
|
|
||||||
# (e.g. a question's choice) which can be accessed via the API.
|
|
||||||
>>> q = Question.objects.get(pk=1)
|
|
||||||
|
|
||||||
# Display any choices from the related object set -- none so far.
|
|
||||||
>>> q.choice_set.all()
|
|
||||||
[]
|
|
||||||
|
|
||||||
# Create three choices.
|
|
||||||
>>> q.choice_set.create(choice_text='Not much', votes=0)
|
|
||||||
<Choice: Not much>
|
|
||||||
>>> q.choice_set.create(choice_text='The sky', votes=0)
|
|
||||||
<Choice: The sky>
|
|
||||||
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
|
|
||||||
|
|
||||||
# Choice objects have API access to their related Question objects.
|
|
||||||
>>> c.question
|
|
||||||
<Question: What's up?>
|
|
||||||
|
|
||||||
# And vice versa: Question objects get access to Choice objects.
|
|
||||||
>>> q.choice_set.all()
|
|
||||||
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
|
|
||||||
>>> q.choice_set.count()
|
|
||||||
3
|
|
||||||
|
|
||||||
# The API automatically follows relationships as far as you need.
|
|
||||||
# Use double underscores to separate relationships.
|
|
||||||
# This works as many levels deep as you want; there's no limit.
|
|
||||||
# Find all Choices for any question whose pub_date is in this year
|
|
||||||
# (reusing the 'current_year' variable we created above).
|
|
||||||
>>> Choice.objects.filter(question__pub_date__year=current_year)
|
|
||||||
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
|
|
||||||
|
|
||||||
# Let's delete one of the choices. Use delete() for that.
|
|
||||||
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
|
|
||||||
>>> c.delete()
|
|
||||||
|
|
||||||
For more information on model relations, see :doc:`Accessing related objects
|
|
||||||
</ref/models/relations>`. For more on how to use double underscores to perform
|
|
||||||
field lookups via the API, see :ref:`Field lookups <field-lookups-intro>`. For
|
|
||||||
full details on the database API, see our :doc:`Database API reference
|
|
||||||
</topics/db/queries>`.
|
|
||||||
|
|
||||||
When you're comfortable with the API, read :doc:`part 2 of this tutorial
|
|
||||||
</intro/tutorial02>` to get Django's automatic admin working.
|
|
||||||
|
@ -6,8 +6,8 @@ This tutorial begins where :doc:`Tutorial 2 </intro/tutorial02>` left off. We're
|
|||||||
continuing the Web-poll application and will focus on creating the public
|
continuing the Web-poll application and will focus on creating the public
|
||||||
interface -- "views."
|
interface -- "views."
|
||||||
|
|
||||||
Philosophy
|
Overview
|
||||||
==========
|
========
|
||||||
|
|
||||||
A view is a "type" of Web page in your Django application that generally serves
|
A view is a "type" of Web page in your Django application that generally serves
|
||||||
a specific function and has a specific template. For example, in a blog
|
a specific function and has a specific template. For example, in a blog
|
||||||
@ -58,130 +58,6 @@ URLconf maps URL patterns (described as regular expressions) to views.
|
|||||||
This tutorial provides basic instruction in the use of URLconfs, and you can
|
This tutorial provides basic instruction in the use of URLconfs, and you can
|
||||||
refer to :mod:`django.core.urlresolvers` for more information.
|
refer to :mod:`django.core.urlresolvers` for more information.
|
||||||
|
|
||||||
Write your first view
|
|
||||||
=====================
|
|
||||||
|
|
||||||
Let's write the first view. Open the file ``polls/views.py``
|
|
||||||
and put the following Python code in it:
|
|
||||||
|
|
||||||
.. snippet::
|
|
||||||
:filename: polls/views.py
|
|
||||||
|
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
return HttpResponse("Hello, world. You're at the polls index.")
|
|
||||||
|
|
||||||
This is the simplest view possible in Django. To call the view, we need to map
|
|
||||||
it to a URL - and for this we need a URLconf.
|
|
||||||
|
|
||||||
To create a URLconf in the polls directory, create a file called ``urls.py``.
|
|
||||||
Your app directory should now look like::
|
|
||||||
|
|
||||||
polls/
|
|
||||||
__init__.py
|
|
||||||
admin.py
|
|
||||||
models.py
|
|
||||||
tests.py
|
|
||||||
urls.py
|
|
||||||
views.py
|
|
||||||
|
|
||||||
In the ``polls/urls.py`` file include the following code:
|
|
||||||
|
|
||||||
.. snippet::
|
|
||||||
:filename: polls/urls.py
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^$', views.index, name='index'),
|
|
||||||
]
|
|
||||||
|
|
||||||
The next step is to point the root URLconf at the ``polls.urls`` module. In
|
|
||||||
``mysite/urls.py`` insert an :func:`~django.conf.urls.include`, leaving you
|
|
||||||
with:
|
|
||||||
|
|
||||||
.. snippet::
|
|
||||||
:filename: mysite/urls.py
|
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
url(r'^polls/', include('polls.urls')),
|
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
|
||||||
]
|
|
||||||
|
|
||||||
.. admonition:: Doesn't match what you see?
|
|
||||||
|
|
||||||
If you're seeing ``admin.autodiscover()`` before the definition of
|
|
||||||
``urlpatterns``, you're probably using a version of Django that doesn't
|
|
||||||
match this tutorial version. You'll want to either switch to the older
|
|
||||||
tutorial or the newer Django version.
|
|
||||||
|
|
||||||
You have now wired an ``index`` view into the URLconf. Go to
|
|
||||||
http://localhost:8000/polls/ in your browser, and you should see the text
|
|
||||||
"*Hello, world. You're at the polls index.*", which you defined in the
|
|
||||||
``index`` view.
|
|
||||||
|
|
||||||
The :func:`~django.conf.urls.url` function is passed four arguments, two
|
|
||||||
required: ``regex`` and ``view``, and two optional: ``kwargs``, and ``name``.
|
|
||||||
At this point, it's worth reviewing what these arguments are for.
|
|
||||||
|
|
||||||
:func:`~django.conf.urls.url` argument: regex
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
The term "regex" is a commonly used short form meaning "regular expression",
|
|
||||||
which is a syntax for matching patterns in strings, or in this case, url
|
|
||||||
patterns. Django starts at the first regular expression and makes its way down
|
|
||||||
the list, comparing the requested URL against each regular expression until it
|
|
||||||
finds one that matches.
|
|
||||||
|
|
||||||
Note that these regular expressions do not search GET and POST parameters, or
|
|
||||||
the domain name. For example, in a request to
|
|
||||||
``http://www.example.com/myapp/``, the URLconf will look for ``myapp/``. In a
|
|
||||||
request to ``http://www.example.com/myapp/?page=3``, the URLconf will also
|
|
||||||
look for ``myapp/``.
|
|
||||||
|
|
||||||
If you need help with regular expressions, see `Wikipedia's entry`_ and the
|
|
||||||
documentation of the :mod:`re` module. Also, the O'Reilly book "Mastering
|
|
||||||
Regular Expressions" by Jeffrey Friedl is fantastic. In practice, however,
|
|
||||||
you don't need to be an expert on regular expressions, as you really only need
|
|
||||||
to know how to capture simple patterns. In fact, complex regexes can have poor
|
|
||||||
lookup performance, so you probably shouldn't rely on the full power of regexes.
|
|
||||||
|
|
||||||
Finally, a performance note: these regular expressions are compiled the first
|
|
||||||
time the URLconf module is loaded. They're super fast (as long as the lookups
|
|
||||||
aren't too complex as noted above).
|
|
||||||
|
|
||||||
.. _Wikipedia's entry: http://en.wikipedia.org/wiki/Regular_expression
|
|
||||||
|
|
||||||
:func:`~django.conf.urls.url` argument: view
|
|
||||||
--------------------------------------------
|
|
||||||
|
|
||||||
When Django finds a regular expression match, Django calls the specified view
|
|
||||||
function, with an :class:`~django.http.HttpRequest` object as the first
|
|
||||||
argument and any “captured” values from the regular expression as other
|
|
||||||
arguments. If the regex uses simple captures, values are passed as positional
|
|
||||||
arguments; if it uses named captures, values are passed as keyword arguments.
|
|
||||||
We'll give an example of this in a bit.
|
|
||||||
|
|
||||||
:func:`~django.conf.urls.url` argument: kwargs
|
|
||||||
----------------------------------------------
|
|
||||||
|
|
||||||
Arbitrary keyword arguments can be passed in a dictionary to the target view. We
|
|
||||||
aren't going to use this feature of Django in the tutorial.
|
|
||||||
|
|
||||||
:func:`~django.conf.urls.url` argument: name
|
|
||||||
---------------------------------------------
|
|
||||||
|
|
||||||
Naming your URL lets you refer to it unambiguously from elsewhere in Django
|
|
||||||
especially templates. This powerful feature allows you to make global changes
|
|
||||||
to the url patterns of your project while only touching a single file.
|
|
||||||
|
|
||||||
Writing more views
|
Writing more views
|
||||||
==================
|
==================
|
||||||
|
|
||||||
@ -287,7 +163,7 @@ you want, using whatever Python libraries you want.
|
|||||||
All Django wants is that :class:`~django.http.HttpResponse`. Or an exception.
|
All Django wants is that :class:`~django.http.HttpResponse`. Or an exception.
|
||||||
|
|
||||||
Because it's convenient, let's use Django's own database API, which we covered
|
Because it's convenient, let's use Django's own database API, which we covered
|
||||||
in :doc:`Tutorial 1 </intro/tutorial01>`. Here's one stab at a new ``index()``
|
in :doc:`Tutorial 2 </intro/tutorial02>`. Here's one stab at a new ``index()``
|
||||||
view, which displays the latest 5 poll questions in the system, separated by
|
view, which displays the latest 5 poll questions in the system, separated by
|
||||||
commas, according to publication date:
|
commas, according to publication date:
|
||||||
|
|
||||||
@ -318,20 +194,7 @@ Your project's :setting:`TEMPLATES` setting describes how Django will load and
|
|||||||
render templates. The default settings file configures a ``DjangoTemplates``
|
render templates. The default settings file configures a ``DjangoTemplates``
|
||||||
backend whose :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` option is set to
|
backend whose :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` option is set to
|
||||||
``True``. By convention ``DjangoTemplates`` looks for a "templates"
|
``True``. By convention ``DjangoTemplates`` looks for a "templates"
|
||||||
subdirectory in each of the :setting:`INSTALLED_APPS`. This is how Django
|
subdirectory in each of the :setting:`INSTALLED_APPS`.
|
||||||
knows to find the polls templates even though we didn't modify the
|
|
||||||
:setting:`DIRS <TEMPLATES-DIRS>` option, as we did in :ref:`Tutorial 2
|
|
||||||
<ref-customizing-your-projects-templates>`.
|
|
||||||
|
|
||||||
.. admonition:: Organizing templates
|
|
||||||
|
|
||||||
We *could* have all our templates together, in one big templates directory,
|
|
||||||
and it would work perfectly well. However, this template belongs to the
|
|
||||||
polls application, so unlike the admin template we created in the previous
|
|
||||||
tutorial, we'll put this one in the application's template directory
|
|
||||||
(``polls/templates``) rather than the project's (``templates``). We'll
|
|
||||||
discuss in more detail in the :doc:`reusable apps tutorial
|
|
||||||
</intro/reusable-apps>` *why* we do this.
|
|
||||||
|
|
||||||
Within the ``templates`` directory you have just created, create another
|
Within the ``templates`` directory you have just created, create another
|
||||||
directory called ``polls``, and within that create a file called
|
directory called ``polls``, and within that create a file called
|
||||||
@ -390,8 +253,8 @@ context. The context is a dictionary mapping template variable names to Python
|
|||||||
objects.
|
objects.
|
||||||
|
|
||||||
Load the page by pointing your browser at "/polls/", and you should see a
|
Load the page by pointing your browser at "/polls/", and you should see a
|
||||||
bulleted-list containing the "What's up" question from Tutorial 1. The link points
|
bulleted-list containing the "What's up" question from :doc:`Tutorial 2
|
||||||
to the question's detail page.
|
</intro/tutorial02>`. The link points to the question's detail page.
|
||||||
|
|
||||||
A shortcut: :func:`~django.shortcuts.render`
|
A shortcut: :func:`~django.shortcuts.render`
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
@ -129,18 +129,19 @@ This code includes a few things we haven't covered yet in this tutorial:
|
|||||||
This function helps avoid having to hardcode a URL in the view function.
|
This function helps avoid having to hardcode a URL in the view function.
|
||||||
It is given the name of the view that we want to pass control to and the
|
It is given the name of the view that we want to pass control to and the
|
||||||
variable portion of the URL pattern that points to that view. In this
|
variable portion of the URL pattern that points to that view. In this
|
||||||
case, using the URLconf we set up in Tutorial 3, this
|
case, using the URLconf we set up in :doc:`Tutorial 3 </intro/tutorial03>`,
|
||||||
:func:`~django.core.urlresolvers.reverse` call will return a string like
|
this :func:`~django.core.urlresolvers.reverse` call will return a string like
|
||||||
::
|
::
|
||||||
|
|
||||||
'/polls/3/results/'
|
'/polls/3/results/'
|
||||||
|
|
||||||
... where the ``3`` is the value of ``p.id``. This redirected URL will
|
where the ``3`` is the value of ``p.id``. This redirected URL will
|
||||||
then call the ``'results'`` view to display the final page.
|
then call the ``'results'`` view to display the final page.
|
||||||
|
|
||||||
As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest`
|
As mentioned in :doc:`Tutorial 3 </intro/tutorial03>`, ``request`` is an
|
||||||
object. For more on :class:`~django.http.HttpRequest` objects, see the
|
:class:`~django.http.HttpRequest` object. For more on
|
||||||
:doc:`request and response documentation </ref/request-response>`.
|
:class:`~django.http.HttpRequest` objects, see the :doc:`request and
|
||||||
|
response documentation </ref/request-response>`.
|
||||||
|
|
||||||
After somebody votes in a question, the ``vote()`` view redirects to the results
|
After somebody votes in a question, the ``vote()`` view redirects to the results
|
||||||
page for the question. Let's write that view:
|
page for the question. Let's write that view:
|
||||||
@ -183,7 +184,7 @@ Use generic views: Less code is better
|
|||||||
|
|
||||||
The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) and ``results()``
|
The ``detail()`` (from :doc:`Tutorial 3 </intro/tutorial03>`) and ``results()``
|
||||||
views are very simple -- and, as mentioned above, redundant. The ``index()``
|
views are very simple -- and, as mentioned above, redundant. The ``index()``
|
||||||
view (also from Tutorial 3), which displays a list of polls, is similar.
|
view, which displays a list of polls, is similar.
|
||||||
|
|
||||||
These views represent a common case of basic Web development: getting data from
|
These views represent a common case of basic Web development: getting data from
|
||||||
the database according to a parameter passed in the URL, loading a template and
|
the database according to a parameter passed in the URL, loading a template and
|
||||||
@ -237,8 +238,6 @@ First, open the ``polls/urls.py`` URLconf and change it like so:
|
|||||||
Note that the name of the matched pattern in the regexes of the second and third
|
Note that the name of the matched pattern in the regexes of the second and third
|
||||||
patterns has changed from ``<question_id>`` to ``<pk>``.
|
patterns has changed from ``<question_id>`` to ``<pk>``.
|
||||||
|
|
||||||
.. _tutorial04-amend-views:
|
|
||||||
|
|
||||||
Amend views
|
Amend views
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ Testing operates at different levels. Some tests might apply to a tiny detail
|
|||||||
(*does a particular model method return values as expected?*) while others
|
(*does a particular model method return values as expected?*) while others
|
||||||
examine the overall operation of the software (*does a sequence of user inputs
|
examine the overall operation of the software (*does a sequence of user inputs
|
||||||
on the site produce the desired result?*). That's no different from the kind of
|
on the site produce the desired result?*). That's no different from the kind of
|
||||||
testing you did earlier in :doc:`Tutorial 1 </intro/tutorial01>`, using the
|
testing you did earlier in :doc:`Tutorial 2 </intro/tutorial02>`, using the
|
||||||
:djadmin:`shell` to examine the behavior of a method, or running the
|
:djadmin:`shell` to examine the behavior of a method, or running the
|
||||||
application and entering data to check how it behaves.
|
application and entering data to check how it behaves.
|
||||||
|
|
||||||
@ -134,10 +134,8 @@ right away: the ``Question.was_published_recently()`` method returns ``True`` if
|
|||||||
the ``Question`` was published within the last day (which is correct) but also if
|
the ``Question`` was published within the last day (which is correct) but also if
|
||||||
the ``Question``’s ``pub_date`` field is in the future (which certainly isn't).
|
the ``Question``’s ``pub_date`` field is in the future (which certainly isn't).
|
||||||
|
|
||||||
You can see this in the Admin; create a question whose date lies in the future;
|
To check if the bug really exists, using the Admin create a question whose date
|
||||||
you'll see that the ``Question`` change list claims it was published recently.
|
lies in the future and check the method using the :djadmin:`shell`::
|
||||||
|
|
||||||
You can also see this using the :djadmin:`shell`::
|
|
||||||
|
|
||||||
>>> import datetime
|
>>> import datetime
|
||||||
>>> from django.utils import timezone
|
>>> from django.utils import timezone
|
||||||
@ -393,7 +391,7 @@ Improving our view
|
|||||||
The list of polls shows polls that aren't published yet (i.e. those that have a
|
The list of polls shows polls that aren't published yet (i.e. those that have a
|
||||||
``pub_date`` in the future). Let's fix that.
|
``pub_date`` in the future). Let's fix that.
|
||||||
|
|
||||||
In :ref:`Tutorial 4 <tutorial04-amend-views>` we introduced a class-based view,
|
In :doc:`Tutorial 4 </intro/tutorial04>` we introduced a class-based view,
|
||||||
based on :class:`~django.views.generic.list.ListView`:
|
based on :class:`~django.views.generic.list.ListView`:
|
||||||
|
|
||||||
.. snippet::
|
.. snippet::
|
||||||
|
@ -116,13 +116,6 @@ with the framework see
|
|||||||
static files </howto/static-files/deployment>` discusses how to use static
|
static files </howto/static-files/deployment>` discusses how to use static
|
||||||
files on a real server.
|
files on a real server.
|
||||||
|
|
||||||
What's next?
|
When you're comfortable with the static files, read :doc:`part 7 of this
|
||||||
============
|
tutorial </intro/tutorial07>` to learn how to customize Django's
|
||||||
|
automatically-generated admin site.
|
||||||
The beginner tutorial ends here for the time being. In the meantime, you might
|
|
||||||
want to check out some pointers on :doc:`where to go from here
|
|
||||||
</intro/whatsnext>`.
|
|
||||||
|
|
||||||
If you are familiar with Python packaging and interested in learning how to
|
|
||||||
turn polls into a "reusable app", check out :doc:`Advanced tutorial: How to
|
|
||||||
write reusable apps</intro/reusable-apps>`.
|
|
||||||
|
421
docs/intro/tutorial07.txt
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
=====================================
|
||||||
|
Writing your first Django app, part 7
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
This tutorial begins where :doc:`Tutorial 6 </intro/tutorial06>` left off. We're
|
||||||
|
continuing the Web-poll application and will focus on customizing the Django's
|
||||||
|
automatically-generated admin site that we first explored in :doc:`Tutorial 2
|
||||||
|
</intro/tutorial02>`.
|
||||||
|
|
||||||
|
Customize the admin form
|
||||||
|
========================
|
||||||
|
|
||||||
|
By registering the ``Question`` model with ``admin.site.register(Question)``,
|
||||||
|
Django was able to construct a default form representation. Often, you'll want
|
||||||
|
to customize how the admin form looks and works. You'll do this by telling
|
||||||
|
Django the options you want when you register the object.
|
||||||
|
|
||||||
|
Let's see how this works by reordering the fields on the edit form. Replace
|
||||||
|
the ``admin.site.register(Question)`` line with:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/admin.py
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Question
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionAdmin(admin.ModelAdmin):
|
||||||
|
fields = ['pub_date', 'question_text']
|
||||||
|
|
||||||
|
admin.site.register(Question, QuestionAdmin)
|
||||||
|
|
||||||
|
You'll follow this pattern -- create a model admin class, then pass it as the
|
||||||
|
second argument to ``admin.site.register()`` -- any time you need to change the
|
||||||
|
admin options for an model.
|
||||||
|
|
||||||
|
This particular change above makes the "Publication date" come before the
|
||||||
|
"Question" field:
|
||||||
|
|
||||||
|
.. image:: _images/admin07.png
|
||||||
|
:alt: Fields have been reordered
|
||||||
|
|
||||||
|
This isn't impressive with only two fields, but for admin forms with dozens
|
||||||
|
of fields, choosing an intuitive order is an important usability detail.
|
||||||
|
|
||||||
|
And speaking of forms with dozens of fields, you might want to split the form
|
||||||
|
up into fieldsets:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/admin.py
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Question
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionAdmin(admin.ModelAdmin):
|
||||||
|
fieldsets = [
|
||||||
|
(None, {'fields': ['question_text']}),
|
||||||
|
('Date information', {'fields': ['pub_date']}),
|
||||||
|
]
|
||||||
|
|
||||||
|
admin.site.register(Question, QuestionAdmin)
|
||||||
|
|
||||||
|
The first element of each tuple in
|
||||||
|
:attr:`~django.contrib.admin.ModelAdmin.fieldsets` is the title of the fieldset.
|
||||||
|
Here's what our form looks like now:
|
||||||
|
|
||||||
|
.. image:: _images/admin08t.png
|
||||||
|
:alt: Form has fieldsets now
|
||||||
|
|
||||||
|
Adding related objects
|
||||||
|
======================
|
||||||
|
|
||||||
|
OK, we have our Question admin page, but a ``Question`` has multiple
|
||||||
|
``Choice``\s, and the admin page doesn't display choices.
|
||||||
|
|
||||||
|
Yet.
|
||||||
|
|
||||||
|
There are two ways to solve this problem. The first is to register ``Choice``
|
||||||
|
with the admin just as we did with ``Question``. That's easy:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/admin.py
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Choice, Question
|
||||||
|
# ...
|
||||||
|
admin.site.register(Choice)
|
||||||
|
|
||||||
|
Now "Choices" is an available option in the Django admin. The "Add choice" form
|
||||||
|
looks like this:
|
||||||
|
|
||||||
|
.. image:: _images/admin09.png
|
||||||
|
:alt: Choice admin page
|
||||||
|
|
||||||
|
In that form, the "Question" field is a select box containing every question in the
|
||||||
|
database. Django knows that a :class:`~django.db.models.ForeignKey` should be
|
||||||
|
represented in the admin as a ``<select>`` box. In our case, only one question
|
||||||
|
exists at this point.
|
||||||
|
|
||||||
|
Also note the "Add Another" link next to "Question." Every object with a
|
||||||
|
``ForeignKey`` relationship to another gets this for free. When you click "Add
|
||||||
|
Another", you'll get a popup window with the "Add question" form. If you add a question
|
||||||
|
in that window and click "Save", Django will save the question to the database and
|
||||||
|
dynamically add it as the selected choice on the "Add choice" form you're
|
||||||
|
looking at.
|
||||||
|
|
||||||
|
But, really, this is an inefficient way of adding ``Choice`` objects to the system.
|
||||||
|
It'd be better if you could add a bunch of Choices directly when you create the
|
||||||
|
``Question`` object. Let's make that happen.
|
||||||
|
|
||||||
|
Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Question``
|
||||||
|
registration code to read:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/admin.py
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Choice, Question
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceInline(admin.StackedInline):
|
||||||
|
model = Choice
|
||||||
|
extra = 3
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionAdmin(admin.ModelAdmin):
|
||||||
|
fieldsets = [
|
||||||
|
(None, {'fields': ['question_text']}),
|
||||||
|
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
|
||||||
|
]
|
||||||
|
inlines = [ChoiceInline]
|
||||||
|
|
||||||
|
admin.site.register(Question, QuestionAdmin)
|
||||||
|
|
||||||
|
This tells Django: "``Choice`` objects are edited on the ``Question`` admin page. By
|
||||||
|
default, provide enough fields for 3 choices."
|
||||||
|
|
||||||
|
Load the "Add question" page to see how that looks:
|
||||||
|
|
||||||
|
.. image:: _images/admin10t.png
|
||||||
|
:alt: Add question page now has choices on it
|
||||||
|
|
||||||
|
It works like this: There are three slots for related Choices -- as specified
|
||||||
|
by ``extra`` -- and each time you come back to the "Change" page for an
|
||||||
|
already-created object, you get another three extra slots.
|
||||||
|
|
||||||
|
At the end of the three current slots you will find an "Add another Choice"
|
||||||
|
link. If you click on it, a new slot will be added. If you want to remove the
|
||||||
|
added slot, you can click on the X to the top right of the added slot. Note
|
||||||
|
that you can't remove the original three slots. This image shows an added slot:
|
||||||
|
|
||||||
|
.. image:: _images/admin14t.png
|
||||||
|
:alt: Additional slot added dynamically
|
||||||
|
|
||||||
|
One small problem, though. It takes a lot of screen space to display all the
|
||||||
|
fields for entering related ``Choice`` objects. For that reason, Django offers a
|
||||||
|
tabular way of displaying inline related objects; you just need to change
|
||||||
|
the ``ChoiceInline`` declaration to read:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/admin.py
|
||||||
|
|
||||||
|
class ChoiceInline(admin.TabularInline):
|
||||||
|
#...
|
||||||
|
|
||||||
|
With that ``TabularInline`` (instead of ``StackedInline``), the
|
||||||
|
related objects are displayed in a more compact, table-based format:
|
||||||
|
|
||||||
|
.. image:: _images/admin11t.png
|
||||||
|
:alt: Add question page now has more compact choices
|
||||||
|
|
||||||
|
Note that there is an extra "Delete?" column that allows removing rows added
|
||||||
|
using the "Add Another Choice" button and rows that have already been saved.
|
||||||
|
|
||||||
|
Customize the admin change list
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Now that the Question admin page is looking good, let's make some tweaks to the
|
||||||
|
"change list" page -- the one that displays all the questions in the system.
|
||||||
|
|
||||||
|
Here's what it looks like at this point:
|
||||||
|
|
||||||
|
.. image:: _images/admin04t.png
|
||||||
|
:alt: Polls change list page
|
||||||
|
|
||||||
|
By default, Django displays the ``str()`` of each object. But sometimes it'd be
|
||||||
|
more helpful if we could display individual fields. To do that, use the
|
||||||
|
:attr:`~django.contrib.admin.ModelAdmin.list_display` admin option, which is a
|
||||||
|
tuple of field names to display, as columns, on the change list page for the
|
||||||
|
object:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/admin.py
|
||||||
|
|
||||||
|
class QuestionAdmin(admin.ModelAdmin):
|
||||||
|
# ...
|
||||||
|
list_display = ('question_text', 'pub_date')
|
||||||
|
|
||||||
|
Just for good measure, let's also include the ``was_published_recently()``
|
||||||
|
method from :doc:`Tutorial 2 </intro/tutorial02>`:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/admin.py
|
||||||
|
|
||||||
|
class QuestionAdmin(admin.ModelAdmin):
|
||||||
|
# ...
|
||||||
|
list_display = ('question_text', 'pub_date', 'was_published_recently')
|
||||||
|
|
||||||
|
Now the question change list page looks like this:
|
||||||
|
|
||||||
|
.. image:: _images/admin12t.png
|
||||||
|
:alt: Polls change list page, updated
|
||||||
|
|
||||||
|
You can click on the column headers to sort by those values -- except in the
|
||||||
|
case of the ``was_published_recently`` header, because sorting by the output
|
||||||
|
of an arbitrary method is not supported. Also note that the column header for
|
||||||
|
``was_published_recently`` is, by default, the name of the method (with
|
||||||
|
underscores replaced with spaces), and that each line contains the string
|
||||||
|
representation of the output.
|
||||||
|
|
||||||
|
You can improve that by giving that method (in :file:`polls/models.py`) a few
|
||||||
|
attributes, as follows:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: polls/models.py
|
||||||
|
|
||||||
|
class Question(models.Model):
|
||||||
|
# ...
|
||||||
|
def was_published_recently(self):
|
||||||
|
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
|
||||||
|
was_published_recently.admin_order_field = 'pub_date'
|
||||||
|
was_published_recently.boolean = True
|
||||||
|
was_published_recently.short_description = 'Published recently?'
|
||||||
|
|
||||||
|
For more information on these method properties, see
|
||||||
|
:attr:`~django.contrib.admin.ModelAdmin.list_display`.
|
||||||
|
|
||||||
|
Edit your :file:`polls/admin.py` file again and add an improvement to the
|
||||||
|
``Question`` change list page: filters using the
|
||||||
|
:attr:`~django.contrib.admin.ModelAdmin.list_filter`. Add the following line to
|
||||||
|
``QuestionAdmin``::
|
||||||
|
|
||||||
|
list_filter = ['pub_date']
|
||||||
|
|
||||||
|
That adds a "Filter" sidebar that lets people filter the change list by the
|
||||||
|
``pub_date`` field:
|
||||||
|
|
||||||
|
.. image:: _images/admin13t.png
|
||||||
|
:alt: Polls change list page, updated
|
||||||
|
|
||||||
|
The type of filter displayed depends on the type of field you're filtering on.
|
||||||
|
Because ``pub_date`` is a :class:`~django.db.models.DateTimeField`, Django
|
||||||
|
knows to give appropriate filter options: "Any date", "Today", "Past 7 days",
|
||||||
|
"This month", "This year".
|
||||||
|
|
||||||
|
This is shaping up well. Let's add some search capability::
|
||||||
|
|
||||||
|
search_fields = ['question_text']
|
||||||
|
|
||||||
|
That adds a search box at the top of the change list. When somebody enters
|
||||||
|
search terms, Django will search the ``question_text`` field. You can use as many
|
||||||
|
fields as you'd like -- although because it uses a ``LIKE`` query behind the
|
||||||
|
scenes, limiting the number of search fields to a reasonable number will make
|
||||||
|
it easier for your database to do the search.
|
||||||
|
|
||||||
|
Now's also a good time to note that change lists give you free pagination. The
|
||||||
|
default is to display 100 items per page. :attr:`Change list pagination
|
||||||
|
<django.contrib.admin.ModelAdmin.list_per_page>`, :attr:`search boxes
|
||||||
|
<django.contrib.admin.ModelAdmin.search_fields>`, :attr:`filters
|
||||||
|
<django.contrib.admin.ModelAdmin.list_filter>`, :attr:`date-hierarchies
|
||||||
|
<django.contrib.admin.ModelAdmin.date_hierarchy>`, and
|
||||||
|
:attr:`column-header-ordering <django.contrib.admin.ModelAdmin.list_display>`
|
||||||
|
all work together like you think they should.
|
||||||
|
|
||||||
|
Customize the admin look and feel
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Clearly, having "Django administration" at the top of each admin page is
|
||||||
|
ridiculous. It's just placeholder text.
|
||||||
|
|
||||||
|
That's easy to change, though, using Django's template system. The Django admin
|
||||||
|
is powered by Django itself, and its interfaces use Django's own template
|
||||||
|
system.
|
||||||
|
|
||||||
|
.. _ref-customizing-your-projects-templates:
|
||||||
|
|
||||||
|
Customizing your *project's* templates
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Create a ``templates`` directory in your project directory (the one that
|
||||||
|
contains ``manage.py``). Templates can live anywhere on your filesystem that
|
||||||
|
Django can access. (Django runs as whatever user your server runs.) However,
|
||||||
|
keeping your templates within the project is a good convention to follow.
|
||||||
|
|
||||||
|
Open your settings file (:file:`mysite/settings.py`, remember) and add a
|
||||||
|
:setting:`DIRS <TEMPLATES-DIRS>` option in the :setting:`TEMPLATES` setting:
|
||||||
|
|
||||||
|
.. snippet::
|
||||||
|
:filename: mysite/settings.py
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
:setting:`DIRS <TEMPLATES-DIRS>` is a list of filesystem directories to check
|
||||||
|
when loading Django templates; it's a search path.
|
||||||
|
|
||||||
|
.. admonition:: Organizing templates
|
||||||
|
|
||||||
|
Just like the static files, we *could* have all our templates together, in
|
||||||
|
one big templates directory, and it would work perfectly well. However,
|
||||||
|
templates that belongs to a particular application, we should put in the
|
||||||
|
application’s template directory (e.g. ``polls/templates``) rather than the
|
||||||
|
project’s (``templates``). We'll discuss in more detail in the
|
||||||
|
:doc:`reusable apps tutorial </intro/reusable-apps>` *why* we do this.
|
||||||
|
|
||||||
|
Now create a directory called ``admin`` inside ``templates``, and copy the
|
||||||
|
template ``admin/base_site.html`` from within the default Django admin
|
||||||
|
template directory in the source code of Django itself
|
||||||
|
(``django/contrib/admin/templates``) into that directory.
|
||||||
|
|
||||||
|
.. admonition:: Where are the Django source files?
|
||||||
|
|
||||||
|
If you have difficulty finding where the Django source files are located
|
||||||
|
on your system, run the following command:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ python -c "import django; print(django.__path__)"
|
||||||
|
|
||||||
|
Then, just edit the file and replace
|
||||||
|
``{{ site_header|default:_('Django administration') }}`` (including the curly
|
||||||
|
braces) with your own site's name as you see fit. You should end up with
|
||||||
|
a section of code like:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% block branding %}
|
||||||
|
<h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
We use this approach to teach you how to override templates. In an actual
|
||||||
|
project, you would probably use
|
||||||
|
the :attr:`django.contrib.admin.AdminSite.site_header` attribute to more easily
|
||||||
|
make this particular customization.
|
||||||
|
|
||||||
|
This template file contains lots of text like ``{% block branding %}``
|
||||||
|
and ``{{ title }}``. The ``{%`` and ``{{`` tags are part of Django's
|
||||||
|
template language. When Django renders ``admin/base_site.html``, this
|
||||||
|
template language will be evaluated to produce the final HTML page, just like
|
||||||
|
we saw in :doc:`Tutorial 3 </intro/tutorial03>`.
|
||||||
|
|
||||||
|
Note that any of Django's default admin templates can be overridden. To
|
||||||
|
override a template, just do the same thing you did with ``base_site.html`` --
|
||||||
|
copy it from the default directory into your custom directory, and make
|
||||||
|
changes.
|
||||||
|
|
||||||
|
Customizing your *application's* templates
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Astute readers will ask: But if :setting:`DIRS <TEMPLATES-DIRS>` was empty by
|
||||||
|
default, how was Django finding the default admin templates? The answer is
|
||||||
|
that, since :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` is set to ``True``,
|
||||||
|
Django automatically looks for a ``templates/`` subdirectory within each
|
||||||
|
application package, for use as a fallback (don't forget that
|
||||||
|
``django.contrib.admin`` is an application).
|
||||||
|
|
||||||
|
Our poll application is not very complex and doesn't need custom admin
|
||||||
|
templates. But if it grew more sophisticated and required modification of
|
||||||
|
Django's standard admin templates for some of its functionality, it would be
|
||||||
|
more sensible to modify the *application's* templates, rather than those in the
|
||||||
|
*project*. That way, you could include the polls application in any new project
|
||||||
|
and be assured that it would find the custom templates it needed.
|
||||||
|
|
||||||
|
See the :ref:`template loading documentation <template-loading>` for more
|
||||||
|
information about how Django finds its templates.
|
||||||
|
|
||||||
|
Customize the admin index page
|
||||||
|
==============================
|
||||||
|
|
||||||
|
On a similar note, you might want to customize the look and feel of the Django
|
||||||
|
admin index page.
|
||||||
|
|
||||||
|
By default, it displays all the apps in :setting:`INSTALLED_APPS` that have been
|
||||||
|
registered with the admin application, in alphabetical order. You may want to
|
||||||
|
make significant changes to the layout. After all, the index is probably the
|
||||||
|
most important page of the admin, and it should be easy to use.
|
||||||
|
|
||||||
|
The template to customize is ``admin/index.html``. (Do the same as with
|
||||||
|
``admin/base_site.html`` in the previous section -- copy it from the default
|
||||||
|
directory to your custom template directory). Edit the file, and you'll see it
|
||||||
|
uses a template variable called ``app_list``. That variable contains every
|
||||||
|
installed Django app. Instead of using that, you can hard-code links to
|
||||||
|
object-specific admin pages in whatever way you think is best.
|
||||||
|
|
||||||
|
What's next?
|
||||||
|
============
|
||||||
|
|
||||||
|
The beginner tutorial ends here. In the meantime, you might want to check out
|
||||||
|
some pointers on :doc:`where to go from here </intro/whatsnext>`.
|
||||||
|
|
||||||
|
If you are familiar with Python packaging and interested in learning how to
|
||||||
|
turn polls into a "reusable app", check out :doc:`Advanced tutorial: How to
|
||||||
|
write reusable apps</intro/reusable-apps>`.
|