summaryrefslogtreecommitdiff
path: root/docs/intro/tutorial02.txt
diff options
context:
space:
mode:
Diffstat (limited to 'docs/intro/tutorial02.txt')
-rw-r--r--docs/intro/tutorial02.txt995
1 files changed, 578 insertions, 417 deletions
diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt
index dbb1516428..8fef748a88 100644
--- a/docs/intro/tutorial02.txt
+++ b/docs/intro/tutorial02.txt
@@ -2,603 +2,764 @@
Writing your first Django app, part 2
=====================================
-This tutorial begins where :doc:`Tutorial 1 </intro/tutorial01>` left off. We're
-continuing the Web-poll application and will focus on Django's
-automatically-generated admin site.
+This tutorial begins where :doc:`Tutorial 1 </intro/tutorial01>` left off.
+We'll setup the database, create your first model, and get a quick introduction
+to Django's automatically-generated admin site.
-.. admonition:: Philosophy
+Database setup
+==============
- Generating admin sites for your staff or clients to add, change and delete
- content is tedious work that doesn't require much creativity. For that
- reason, Django entirely automates creation of admin interfaces for models.
+Now, open up :file:`mysite/settings.py`. It's a normal Python module with
+module-level variables representing Django settings.
- Django was written in a newsroom environment, with a very clear separation
- between "content publishers" and the "public" site. Site managers use the
- system to add news stories, events, sports scores, etc., and that content is
- displayed on the public site. Django solves the problem of creating a
- unified interface for site administrators to edit content.
+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.
- The admin isn't intended to be used by site visitors. It's for site
- managers.
+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:
-Creating an admin user
-======================
+* :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>`.
-First we'll need to create a user who can login to the admin site. Run the
-following command:
+* :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.
-.. code-block:: console
+If you are not using SQLite as your database, additional settings such as
+:setting:`USER`, :setting:`PASSWORD`, and :setting:`HOST` must be added.
+For more details, see the reference documentation for :setting:`DATABASES`.
- $ python manage.py createsuperuser
+.. note::
-Enter your desired username and press enter.
+ 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.
-.. code-block:: text
+ If you're using SQLite, you don't need to create anything beforehand - the
+ database file will be created automatically when it is needed.
- Username: admin
+While you're editing :file:`mysite/settings.py`, set :setting:`TIME_ZONE` to
+your time zone.
-You will then be prompted for your desired email address:
+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.
-.. code-block:: text
+By default, :setting:`INSTALLED_APPS` contains the following apps, all of which
+come with Django:
- Email address: admin@example.com
+* :mod:`django.contrib.admin` -- The admin site. You'll use it shortly.
-The final step is to enter your password. You will be asked to enter your
-password twice, the second time as a confirmation of the first.
+* :mod:`django.contrib.auth` -- An authentication system.
-.. code-block:: text
+* :mod:`django.contrib.contenttypes` -- A framework for content types.
- Password: **********
- Password (again): *********
- Superuser created successfully.
+* :mod:`django.contrib.sessions` -- A session framework.
-Start the development server
-============================
+* :mod:`django.contrib.messages` -- A messaging framework.
-The Django admin site is activated by default. Let's start the development
-server and explore it.
+* :mod:`django.contrib.staticfiles` -- A framework for managing
+ static files.
+
+These applications are included by default as a convenience for the common case.
-Recall from Tutorial 1 that you start the development server like so:
+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 runserver
+ $ python manage.py migrate
-Now, open a Web browser and go to "/admin/" on your local domain -- e.g.,
-http://127.0.0.1:8000/admin/. You should see the admin's login screen:
+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.
-.. image:: _images/admin01.png
- :alt: Django admin login screen
+.. admonition:: For the minimalists
-Since :doc:`translation </topics/i18n/translation>` is turned on by default,
-the login screen may be displayed in your own language, depending on your
-browser's settings and on whether Django has a translation for this language.
+ 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`.
-.. admonition:: Doesn't match what you see?
+.. _creating-models:
- If at this point, instead of the above login page, you get an error
- page reporting something like::
+Creating models
+===============
- ImportError at /admin/
- cannot import name patterns
- ...
+Now we'll define your models -- essentially, your database layout, with
+additional metadata.
- then 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:: Philosophy
-Enter the admin site
-====================
+ 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.
-Now, try logging in with the superuser account you created in the previous step.
-You should see the Django admin index page:
+ 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.
-.. image:: _images/admin02.png
- :alt: Django admin index page
+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``.
-You should see a few types of editable content: groups and users. They are
-provided by :mod:`django.contrib.auth`, the authentication framework shipped
-by Django.
+These concepts are represented by simple Python classes. Edit the
+:file:`polls/models.py` file so it looks like this:
-Make the poll app modifiable in the admin
-=========================================
+.. snippet::
+ :filename: polls/models.py
-But where's our poll app? It's not displayed on the admin index page.
+ from django.db import models
-Just one thing to do: we need to tell the admin that ``Question``
-objects have an admin interface. To do this, open the :file:`polls/admin.py`
-file, and edit it to look like this:
+
+ 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.
+
+Activating models
+=================
+
+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:
.. snippet::
- :filename: polls/admin.py
+ :filename: mysite/settings.py
- from django.contrib import admin
+ INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'polls',
+ ]
- from .models import Question
+Now Django knows to include the ``polls`` app. Let's run another command:
- admin.site.register(Question)
+.. code-block:: console
-Explore the free admin functionality
-====================================
+ $ python manage.py makemigrations polls
-Now that we've registered ``Question``, Django knows that it should be displayed on
-the admin index page:
+You should see something similar to the following:
-.. image:: _images/admin03t.png
- :alt: Django admin index page, now with polls displayed
+.. code-block:: text
-Click "Questions". Now you're at the "change list" page for questions. This page
-displays all the questions in the database and lets you choose one to change it.
-There's the "What's up?" question we created in the first tutorial:
+ Migrations for 'polls':
+ 0001_initial.py:
+ - Create model Choice
+ - Create model Question
+ - Add field question to choice
-.. image:: _images/admin04t.png
- :alt: Polls change list page
+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*.
-Click the "What's up?" question to edit it:
+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.
-.. image:: _images/admin05t.png
- :alt: Editing form for question object
+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:
-Things to note here:
+.. code-block:: console
-* The form is automatically generated from the ``Question`` model.
+ $ python manage.py sqlmigrate polls 0001
-* The different model field types (:class:`~django.db.models.DateTimeField`,
- :class:`~django.db.models.CharField`) correspond to the appropriate HTML
- input widget. Each type of field knows how to display itself in the Django
- admin.
+You should see something similar to the following (we've reformatted it for
+readability):
-* Each :class:`~django.db.models.DateTimeField` gets free JavaScript
- shortcuts. Dates get a "Today" shortcut and calendar popup, and times get
- a "Now" shortcut and a convenient popup that lists commonly entered times.
+.. code-block:: sql
-The bottom part of the page gives you a couple of options:
+ 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;
-* Save -- Saves changes and returns to the change-list page for this type of
- object.
+ COMMIT;
-* Save and continue editing -- Saves changes and reloads the admin page for
- this object.
+Note the following:
-* Save and add another -- Saves changes and loads a new, blank form for this
- type of object.
+* The exact output will vary depending on the database you are using. The
+ example above is generated for PostgreSQL.
-* Delete -- Displays a delete confirmation page.
+* 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.)
-If the value of "Date published" doesn't match the time when you created the
-question in Tutorial 1, it probably means you forgot to set the correct value for
-the :setting:`TIME_ZONE` setting. Change it, reload the page and check that
-the correct value appears.
+* Primary keys (IDs) are added automatically. (You can override this, too.)
-Change the "Date published" by clicking the "Today" and "Now" shortcuts. Then
-click "Save and continue editing." Then click "History" in the upper right.
-You'll see a page listing all changes made to this object via the Django admin,
-with the timestamp and username of the person who made the change:
+* By convention, Django appends ``"_id"`` to the foreign key field name.
+ (Yes, you can override this, as well.)
-.. image:: _images/admin06t.png
- :alt: History page for question object
+* 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.
-Customize the admin form
-========================
+* 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.
-Take a few minutes to marvel at all the code you didn't have to write. 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.
+* 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.
-Let's see how this works by re-ordering the fields on the edit form. Replace
-the ``admin.site.register(Question)`` line with:
+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.
-.. snippet::
- :filename: polls/admin.py
+Now, run :djadmin:`migrate` again to create those model tables in your database:
- from django.contrib import admin
+.. code-block:: console
- from .models import Question
+ $ 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.
- class QuestionAdmin(admin.ModelAdmin):
- fields = ['pub_date', 'question_text']
+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:
- admin.site.register(Question, QuestionAdmin)
+* 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.
-You'll follow this pattern -- create a model admin object, then pass it as the
-second argument to ``admin.site.register()`` -- any time you need to change the
-admin options for an object.
+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.
-This particular change above makes the "Publication date" come before the
-"Question" field:
+Read the :doc:`django-admin documentation </ref/django-admin>` for full
+information on what the ``manage.py`` utility can do.
-.. image:: _images/admin07.png
- :alt: Fields have been reordered
+Playing with the API
+====================
-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.
+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:
-And speaking of forms with dozens of fields, you might want to split the form
-up into fieldsets:
+.. code-block:: console
-.. snippet::
- :filename: polls/admin.py
+ $ python manage.py shell
- from django.contrib import admin
+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.
- from .models import Question
+.. 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:
- class QuestionAdmin(admin.ModelAdmin):
- fieldsets = [
- (None, {'fields': ['question_text']}),
- ('Date information', {'fields': ['pub_date']}),
- ]
+ .. code-block:: pycon
- admin.site.register(Question, QuestionAdmin)
+ >>> import django
+ >>> django.setup()
-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:
+ 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.
-.. image:: _images/admin08t.png
- :alt: Form has fieldsets now
+ 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.
-You can assign arbitrary HTML classes to each fieldset. Django provides a
-``"collapse"`` class that displays a particular fieldset initially collapsed.
-This is useful when you have a long form that contains a number of fields that
-aren't commonly used:
+ For more information on all of this, see the :doc:`django-admin
+ documentation </ref/django-admin>`.
-.. snippet::
- :filename: polls/admin.py
+Once you're in the shell, explore the :doc:`database API </topics/db/queries>`::
- from django.contrib import admin
+ >>> from polls.models import Question, Choice # Import the model classes we just wrote.
- from .models import Question
+ # 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())
- class QuestionAdmin(admin.ModelAdmin):
- fieldsets = [
- (None, {'fields': ['question_text']}),
- ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
- ]
+ # Save the object into the database. You have to call save() explicitly.
+ >>> q.save()
- admin.site.register(Question, QuestionAdmin)
+ # 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
-.. image:: _images/admin09.png
- :alt: Fieldset is initially collapsed
+ # 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>)
-Adding related objects
-======================
+ # Change values by changing the attributes, then calling save().
+ >>> q.question_text = "What's up?"
+ >>> q.save()
-OK, we have our Question admin page. But a ``Question`` has multiple ``Choices``, and
-the admin page doesn't display choices.
+ # objects.all() displays all the questions in the database.
+ >>> Question.objects.all()
+ [<Question: Question object>]
-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:
+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::
- :filename: polls/admin.py
+ :filename: polls/models.py
- from django.contrib import admin
+ from django.db import models
- from .models import Choice, Question
- # ...
- admin.site.register(Choice)
+ class Question(models.Model):
+ # ...
+ def __str__(self): # __unicode__ on Python 2
+ return self.question_text
+
+ class Choice(models.Model):
+ # ...
+ def __str__(self): # __unicode__ on Python 2
+ return self.choice_text
-Now "Choices" is an available option in the Django admin. The "Add choice" form
-looks like this:
+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.
-.. image:: _images/admin10.png
- :alt: Choice admin page
+.. admonition:: ``__str__`` or ``__unicode__``?
-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.
+ On Python 3, it's easy, just use
+ :meth:`~django.db.models.Model.__str__`.
-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.
+ 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.
-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.
+ If all of this is gibberish to you, just use Python 3.
-Remove the ``register()`` call for the ``Choice`` model. Then, edit the ``Question``
-registration code to read:
+Note these are normal Python methods. Let's add a custom method, just for
+demonstration:
.. snippet::
- :filename: polls/admin.py
+ :filename: polls/models.py
- from django.contrib import admin
+ import datetime
- from .models import Choice, Question
+ from django.db import models
+ from django.utils import timezone
- class ChoiceInline(admin.StackedInline):
- model = Choice
- extra = 3
+ class Question(models.Model):
+ # ...
+ def was_published_recently(self):
+ return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
+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>`.
- class QuestionAdmin(admin.ModelAdmin):
- fieldsets = [
- (None, {'fields': ['question_text']}),
- ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
- ]
- inlines = [ChoiceInline]
+Save these changes and start a new Python interactive shell by running
+``python manage.py shell`` again::
- admin.site.register(Question, QuestionAdmin)
+ >>> from polls.models import Question, Choice
-This tells Django: "``Choice`` objects are edited on the ``Question`` admin page. By
-default, provide enough fields for 3 choices."
+ # Make sure our __str__() addition worked.
+ >>> Question.objects.all()
+ [<Question: What's up?>]
-Load the "Add question" page to see how that looks:
+ # 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?>]
-.. image:: _images/admin11t.png
- :alt: Add question page now has choices on it
+ # 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?>
-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.
+ # 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.
-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:
+ # 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?>
-.. image:: _images/admin15t.png
- :alt: Additional slot added dynamically
+ # Make sure our custom method worked.
+ >>> q = Question.objects.get(pk=1)
+ >>> q.was_published_recently()
+ True
-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:
+ # 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)
-.. snippet::
- :filename: polls/admin.py
+ # Display any choices from the related object set -- none so far.
+ >>> q.choice_set.all()
+ []
- class ChoiceInline(admin.TabularInline):
- #...
+ # 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)
-With that ``TabularInline`` (instead of ``StackedInline``), the
-related objects are displayed in a more compact, table-based format:
+ # Choice objects have API access to their related Question objects.
+ >>> c.question
+ <Question: What's up?>
-.. image:: _images/admin12t.png
- :alt: Add question page now has more compact choices
+ # 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
-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.
+ # 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>]
-Customize the admin change list
-===============================
+ # Let's delete one of the choices. Use delete() for that.
+ >>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
+ >>> c.delete()
-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.
+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>`.
-Here's what it looks like at this point:
+Introducing the Django Admin
+============================
-.. image:: _images/admin04t.png
- :alt: Polls change list page
+.. admonition:: Philosophy
-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:
+ Generating admin sites for your staff or clients to add, change, and delete
+ content is tedious work that doesn't require much creativity. For that
+ reason, Django entirely automates creation of admin interfaces for models.
-.. snippet::
- :filename: polls/admin.py
+ Django was written in a newsroom environment, with a very clear separation
+ between "content publishers" and the "public" site. Site managers use the
+ system to add news stories, events, sports scores, etc., and that content is
+ displayed on the public site. Django solves the problem of creating a
+ unified interface for site administrators to edit content.
- class QuestionAdmin(admin.ModelAdmin):
- # ...
- list_display = ('question_text', 'pub_date')
+ The admin isn't intended to be used by site visitors. It's for site
+ managers.
-Just for good measure, let's also include the ``was_published_recently`` custom
-method from Tutorial 1:
+Creating an admin user
+----------------------
-.. snippet::
- :filename: polls/admin.py
+First we'll need to create a user who can login to the admin site. Run the
+following command:
- class QuestionAdmin(admin.ModelAdmin):
- # ...
- list_display = ('question_text', 'pub_date', 'was_published_recently')
+.. code-block:: console
-Now the question change list page looks like this:
+ $ python manage.py createsuperuser
-.. image:: _images/admin13t.png
- :alt: Polls change list page, updated
+Enter your desired username and press enter.
-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.
+.. code-block:: text
-You can improve that by giving that method (in :file:`polls/models.py`) a few
-attributes, as follows:
+ Username: admin
-.. snippet::
- :filename: polls/models.py
+You will then be prompted for your desired email address:
- 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?'
+.. code-block:: text
-For more information on these method properties, see
-:attr:`~django.contrib.admin.ModelAdmin.list_display`.
+ Email address: admin@example.com
-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``::
+The final step is to enter your password. You will be asked to enter your
+password twice, the second time as a confirmation of the first.
- list_filter = ['pub_date']
+.. code-block:: text
+
+ Password: **********
+ Password (again): *********
+ Superuser created successfully.
+
+Start the development server
+----------------------------
+
+The Django admin site is activated by default. Let's start the development
+server and explore it.
+
+If the server is not running start it like so:
+
+.. code-block:: console
+
+ $ python manage.py runserver
-That adds a "Filter" sidebar that lets people filter the change list by the
-``pub_date`` field:
+Now, open a Web browser and go to "/admin/" on your local domain -- e.g.,
+http://127.0.0.1:8000/admin/. You should see the admin's login screen:
-.. image:: _images/admin14t.png
- :alt: Polls change list page, updated
+.. image:: _images/admin01.png
+ :alt: Django admin login screen
-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."
+Since :doc:`translation </topics/i18n/translation>` is turned on by default,
+the login screen may be displayed in your own language, depending on your
+browser's settings and if Django has a translation for this language.
-This is shaping up well. Let's add some search capability::
+.. admonition:: Doesn't match what you see?
- search_fields = ['question_text']
+ If at this point, instead of the above login page, you get an error
+ page reporting something like::
-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.
+ ImportError at /admin/
+ cannot import name patterns
+ ...
-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.
+ then 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.
-Customize the admin look and feel
-=================================
+Enter the admin site
+--------------------
-Clearly, having "Django administration" at the top of each admin page is
-ridiculous. It's just placeholder text.
+Now, try logging in with the superuser account you created in the previous step.
+You should see the Django admin index page:
-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.
+.. image:: _images/admin02.png
+ :alt: Django admin index page
-.. _ref-customizing-your-projects-templates:
+You should see a few types of editable content: groups and users. They are
+provided by :mod:`django.contrib.auth`, the authentication framework shipped
+by Django.
-Customizing your *project's* templates
---------------------------------------
+Make the poll app modifiable in the admin
+-----------------------------------------
-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.
+But where's our poll app? It's not displayed on the admin index page.
-Open your settings file (:file:`mysite/settings.py`, remember) and add a
-:setting:`DIRS <TEMPLATES-DIRS>` option in the :setting:`TEMPLATES` setting:
+Just one thing to do: we need to tell the admin that ``Question``
+objects have an admin interface. To do this, open the :file:`polls/admin.py`
+file, and edit it to look like this:
.. snippet::
- :filename: mysite/settings.py
+ :filename: polls/admin.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',
- ],
- },
- },
- ]
+ from django.contrib import admin
-:setting:`DIRS <TEMPLATES-DIRS>` is a list of filesystem directories to check
-when loading Django templates; it's a search path.
+ from .models import Question
-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.
+ admin.site.register(Question)
-.. admonition:: Where are the Django source files?
+Explore the free admin functionality
+------------------------------------
- If you have difficulty finding where the Django source files are located
- on your system, run the following command:
+Now that we've registered ``Question``, Django knows that it should be displayed on
+the admin index page:
- .. code-block:: console
+.. image:: _images/admin03t.png
+ :alt: Django admin index page, now with polls displayed
- $ python -c "import django; print(django.__path__)"
+Click "Questions". Now you're at the "change list" page for questions. This page
+displays all the questions in the database and lets you choose one to change it.
+There's the "What's up?" question we created earlier:
-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:
+.. image:: _images/admin04t.png
+ :alt: Polls change list page
-.. code-block:: html+django
+Click the "What's up?" question to edit it:
- {% block branding %}
- <h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1>
- {% endblock %}
+.. image:: _images/admin05t.png
+ :alt: Editing form for question object
-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.
+Things to note here:
-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.
-Don't worry if you can't make any sense of the template right now --
-we'll delve into Django's templating language in Tutorial 3.
+* The form is automatically generated from the ``Question`` model.
-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.
+* The different model field types (:class:`~django.db.models.DateTimeField`,
+ :class:`~django.db.models.CharField`) correspond to the appropriate HTML
+ input widget. Each type of field knows how to display itself in the Django
+ admin.
-Customizing your *application's* templates
-------------------------------------------
+* Each :class:`~django.db.models.DateTimeField` gets free JavaScript
+ shortcuts. Dates get a "Today" shortcut and calendar popup, and times get
+ a "Now" shortcut and a convenient popup that lists commonly entered times.
-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).
+The bottom part of the page gives you a couple of options:
-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.
+* Save -- Saves changes and returns to the change-list page for this type of
+ object.
-See the :ref:`template loading documentation <template-loading>` for more
-information about how Django finds its templates.
+* Save and continue editing -- Saves changes and reloads the admin page for
+ this object.
-Customize the admin index page
-==============================
+* Save and add another -- Saves changes and loads a new, blank form for this
+ type of object.
-On a similar note, you might want to customize the look and feel of the Django
-admin index page.
+* Delete -- Displays a delete confirmation 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.
+If the value of "Date published" doesn't match the time when you created the
+question in :doc:`Tutorial 1</intro/tutorial01>`, it probably
+means you forgot to set the correct value for the :setting:`TIME_ZONE` setting.
+Change it, reload the page and check that the correct value appears.
+
+Change the "Date published" by clicking the "Today" and "Now" shortcuts. Then
+click "Save and continue editing." Then click "History" in the upper right.
+You'll see a page listing all changes made to this object via the Django admin,
+with the timestamp and username of the person who made the change:
-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. Again,
-don't worry if you can't understand the template language -- we'll cover that
-in more detail in Tutorial 3.
+.. image:: _images/admin06t.png
+ :alt: History page for question object
-When you're comfortable with the admin site, read :doc:`part 3 of this tutorial
-</intro/tutorial03>` to start working on public poll views.
+When you're comfortable with the models API and have familiarized yourself with
+the admin site, read :doc:`part 3 of this tutorial</intro/tutorial03>` to learn
+about how to add more views to our polls app.