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>`
|
||||
|
||||
* **Tutorial:**
|
||||
:doc:`Part 1: Models <intro/tutorial01>` |
|
||||
:doc:`Part 2: The admin site <intro/tutorial02>` |
|
||||
:doc:`Part 1: Requests and responses <intro/tutorial01>` |
|
||||
:doc:`Part 2: Models and the admin site <intro/tutorial02>` |
|
||||
:doc:`Part 3: Views and templates <intro/tutorial03>` |
|
||||
:doc:`Part 4: Forms and generic views <intro/tutorial04>` |
|
||||
: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:**
|
||||
: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 |
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
|
||||
tutorial05
|
||||
tutorial06
|
||||
tutorial07
|
||||
reusable-apps
|
||||
whatsnext
|
||||
contributing
|
||||
|
|
|
@ -10,7 +10,7 @@ poll application.
|
|||
It'll consist of two parts:
|
||||
|
||||
* 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
|
||||
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
|
||||
|
||||
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
|
||||
----------------------
|
||||
======================
|
||||
|
||||
Let's verify your Django project works. Change into the outer :file:`mysite` directory, if
|
||||
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...
|
||||
|
||||
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
|
||||
Django version |version|, using settings 'mysite.settings'
|
||||
Starting development server at http://127.0.0.1:8000/
|
||||
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
|
||||
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
|
||||
|
@ -279,10 +192,8 @@ It worked!
|
|||
effect. However, some actions like adding files don't trigger a restart,
|
||||
so you'll have to restart the server in these cases.
|
||||
|
||||
.. _creating-models:
|
||||
|
||||
Creating models
|
||||
===============
|
||||
Creating the Polls app
|
||||
======================
|
||||
|
||||
Now that your environment -- a "project" -- is set up, you're set to start
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Activating models
|
||||
=================
|
||||
Write your first view
|
||||
=====================
|
||||
|
||||
That small bit of model code gives Django a lot of information. With it, Django
|
||||
is able to:
|
||||
|
||||
* 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:
|
||||
Let's write the first view. Open the file ``polls/views.py``
|
||||
and put the following Python code in it:
|
||||
|
||||
.. snippet::
|
||||
:filename: mysite/settings.py
|
||||
:filename: polls/views.py
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'polls',
|
||||
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'),
|
||||
]
|
||||
|
||||
Now Django knows to include the ``polls`` app. Let's run another command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ 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``:
|
||||
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: 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):
|
||||
# ...
|
||||
def __str__(self): # __unicode__ on Python 2
|
||||
return self.question_text
|
||||
urlpatterns = [
|
||||
url(r'^polls/', include('polls.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
]
|
||||
|
||||
class Choice(models.Model):
|
||||
# ...
|
||||
def __str__(self): # __unicode__ on Python 2
|
||||
return self.choice_text
|
||||
.. admonition:: Doesn't match what you see?
|
||||
|
||||
It's important to add :meth:`~django.db.models.Model.__str__` methods to your
|
||||
models, not only for your own convenience when dealing with the interactive
|
||||
prompt, but also because objects' representations are used throughout Django's
|
||||
automatically-generated admin.
|
||||
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.
|
||||
|
||||
.. 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
|
||||
:meth:`~django.db.models.Model.__str__`.
|
||||
.. code-block:: console
|
||||
|
||||
On Python 2, you should define :meth:`~django.db.models.Model.__unicode__`
|
||||
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.
|
||||
$ python manage.py runserver
|
||||
|
||||
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
|
||||
demonstration:
|
||||
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.
|
||||
|
||||
.. snippet::
|
||||
:filename: polls/models.py
|
||||
:func:`~django.conf.urls.url` argument: regex
|
||||
---------------------------------------------
|
||||
|
||||
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
|
||||
from django.utils import timezone
|
||||
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.
|
||||
|
||||
class Question(models.Model):
|
||||
# ...
|
||||
def was_published_recently(self):
|
||||
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
|
||||
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).
|
||||
|
||||
Note the addition of ``import datetime`` and ``from django.utils import
|
||||
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>`.
|
||||
.. _Wikipedia's entry: http://en.wikipedia.org/wiki/Regular_expression
|
||||
|
||||
Save these changes and start a new Python interactive shell by running
|
||||
``python manage.py shell`` again::
|
||||
:func:`~django.conf.urls.url` argument: view
|
||||
--------------------------------------------
|
||||
|
||||
>>> 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.
|
||||
>>> Question.objects.all()
|
||||
[<Question: What's up?>]
|
||||
:func:`~django.conf.urls.url` argument: kwargs
|
||||
----------------------------------------------
|
||||
|
||||
# Django provides a rich database lookup API that's entirely driven by
|
||||
# keyword arguments.
|
||||
>>> Question.objects.filter(id=1)
|
||||
[<Question: What's up?>]
|
||||
>>> Question.objects.filter(question_text__startswith='What')
|
||||
[<Question: What's up?>]
|
||||
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.
|
||||
|
||||
# Get the question that was published this year.
|
||||
>>> from django.utils import timezone
|
||||
>>> current_year = timezone.now().year
|
||||
>>> Question.objects.get(pub_date__year=current_year)
|
||||
<Question: What's up?>
|
||||
:func:`~django.conf.urls.url` argument: name
|
||||
---------------------------------------------
|
||||
|
||||
# Request an ID that doesn't exist, this will raise an exception.
|
||||
>>> Question.objects.get(id=2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
DoesNotExist: Question matching query does not exist.
|
||||
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.
|
||||
|
||||
# Lookup by a primary key is the most common case, so Django provides a
|
||||
# shortcut for primary-key exact lookups.
|
||||
# The following is identical to Question.objects.get(id=1).
|
||||
>>> 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.
|
||||
When you're comfortable with the basic request and response flow, read
|
||||
:doc:`part 2 of this tutorial </intro/tutorial02>` to start working with the
|
||||
database.
|
||||
|
|
|
@ -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
|
||||
interface -- "views."
|
||||
|
||||
Philosophy
|
||||
==========
|
||||
Overview
|
||||
========
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
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
|
||||
==================
|
||||
|
||||
|
@ -287,7 +163,7 @@ you want, using whatever Python libraries you want.
|
|||
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
|
||||
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
|
||||
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``
|
||||
backend whose :setting:`APP_DIRS <TEMPLATES-APP_DIRS>` option is set to
|
||||
``True``. By convention ``DjangoTemplates`` looks for a "templates"
|
||||
subdirectory in each of the :setting:`INSTALLED_APPS`. This is how Django
|
||||
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.
|
||||
subdirectory in each of the :setting:`INSTALLED_APPS`.
|
||||
|
||||
Within the ``templates`` directory you have just created, create another
|
||||
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.
|
||||
|
||||
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
|
||||
to the question's detail page.
|
||||
bulleted-list containing the "What's up" question from :doc:`Tutorial 2
|
||||
</intro/tutorial02>`. The link points to the question's detail page.
|
||||
|
||||
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.
|
||||
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
|
||||
case, using the URLconf we set up in Tutorial 3, this
|
||||
:func:`~django.core.urlresolvers.reverse` call will return a string like
|
||||
case, using the URLconf we set up in :doc:`Tutorial 3 </intro/tutorial03>`,
|
||||
this :func:`~django.core.urlresolvers.reverse` call will return a string like
|
||||
::
|
||||
|
||||
'/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.
|
||||
|
||||
As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest`
|
||||
object. For more on :class:`~django.http.HttpRequest` objects, see the
|
||||
:doc:`request and response documentation </ref/request-response>`.
|
||||
As mentioned in :doc:`Tutorial 3 </intro/tutorial03>`, ``request`` is an
|
||||
:class:`~django.http.HttpRequest` object. For more on
|
||||
: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
|
||||
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()``
|
||||
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
|
||||
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
|
||||
patterns has changed from ``<question_id>`` to ``<pk>``.
|
||||
|
||||
.. _tutorial04-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
|
||||
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
|
||||
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
|
||||
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``’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;
|
||||
you'll see that the ``Question`` change list claims it was published recently.
|
||||
|
||||
You can also see this using the :djadmin:`shell`::
|
||||
To check if the bug really exists, using the Admin create a question whose date
|
||||
lies in the future and check the method using the :djadmin:`shell`::
|
||||
|
||||
>>> import datetime
|
||||
>>> 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
|
||||
``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`:
|
||||
|
||||
.. snippet::
|
||||
|
|
|
@ -116,13 +116,6 @@ with the framework see
|
|||
static files </howto/static-files/deployment>` discusses how to use static
|
||||
files on a real server.
|
||||
|
||||
What's next?
|
||||
============
|
||||
|
||||
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>`.
|
||||
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.
|
||||
|
|
|
@ -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>`.
|