diff options
Diffstat (limited to 'docs/intro/tutorial02.txt')
| -rw-r--r-- | docs/intro/tutorial02.txt | 995 |
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. |
