Создаём своё первое приложение с Django, часть 1

Давайте учиться на примере.

Мы создадим проект, состоящий из простого приложения для голосования.

Проект будет состоять из двух частей:

  • Публичный сайт, который отображает пользователям голосования и позволяет им голосовать.

  • Интерфейс администратора, который позволяет вам добавлять, изменять и удалять голосования.

Мы предполагаем, что вы уже установили Django. Вы можете проверить это, запустив консоль Python и выполнив следующие команды:

$ python -c "import django; print(django.get_version())"

Если Django установлен, вы должны увидеть текущую версию. Иначе получите ошибку “No module named django”.

Учебник написан для Django 1.8 и Python 3.2 и выше. Если версия Django отличается, вы можете обратиться к документации, соответствующей версии Django, или обновить Django до последней версии. Если вы используете Python 2.7, ваш код может отличаться от приведенного в этом учебнике, и вы должны немного изменить, как это будет указано в комментариях.

Смотрите Как установить Django, чтобы узнать как удалить старую версию Django и установить новую.

Где искать помощь:

Если у вас возникают затруденения во время прохождения этого учебника, пожалуйста, оставьте сообщение на django-users или попросите помощи в чате #django on irc.freenode.net. (Прим. пер. : также можете спросить совета на нашем форуме).

Создание проекта

Если вы используете Django первый раз, вам придется позаботиться о некоторых первоначальных настройках. А именно, сгенерировать основу проекта Django – настройки проекта, базы данных, приложений и др.

Используя командную строку, перейдите в каталог, где вы хотите хранить код, и выполните следующую команду:

$ django-admin startproject mysite

Это создаст каталог mysite в текущем каталоге. Если нет, смотрите Трудности с запуском django-admin.

Примечание

Вы не должны использовать в качестве названия проекта названия компонентов Python или Django. Это означает, что проект не может называться django (что конфликтует с Django) или test (конфликтует со стандартным пакетом Python).

Где разместить этот код?

Если вы раньше использовали PHP, то, наверное, привыкли размещать код проекта в корневом каталоге сайта на Web-сервере (например, /var/www). C Django вы не должны этого делать. Это плохая идея добавлять код проекта в корень Web-сервера, так как есть риск, что он будет доступен для просмотра. Не делайте этого в целях безопасности.

Разместите код в каталоге вне корневой директории сайта, например /home/mycode.

Давайте посмотрим, что было создано при помощи команды startproject:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Рассмотрим эти файлы:

  • Внешний каталог mysite/ – это просто контейнер для вашего проекта. Его название никак не используется Django, и вы можете переименовать его во что угодно.

  • manage.py: Скрипт, который позволяет вам взаимодействовать с проектом Django. Подробности о manage.py читайте в разделе django-admin и manage.py.

  • Внутренний каталог mysite/ - это пакет Python вашего проекта. Его название – это название пакета Python, которое вы будете использовать для импорта чего-либо из проекта (например, mysite.urls).

  • mysite/__init__.py: Пустой файл, который указывает Python, что текущий каталог является пакетом Python. (Читайте о пакетах в официальной документации Python, если вы новичок в Python.)

  • mysite/settings.py: Настройки/конфигурация проекта. Раздел Настройки Django расскажет вам все о настройках проекта.

  • mysite/urls.py: Конфигурация URL-ов для вашего проекта Django. Это “содержание” всех Django-сайтов. Вы можете прочитать о конфигурации URL-ов в разделе Менеджер URL-ов.

  • mysite/wsgi.py: Точка входа вашего проекта для WSGI-совместимых веб-серверов. Подробности читайте в разделе Развёртывание с WSGI.

Настройка базы данных

Теперь отредактируйте mysite/settings.py. Это просто Python модуль с переменными, которые являются значениями настроек Django.

По умолчанию используется SQLite. Это хороший вариант, если вы новичок в базах данных, или хотите попробовать Django. SQLite включен в Python, так что вам не нужно устанавливать что либо еще. Однако, при создании своего первого настоящего проекта, лучше использовать более функциональную базу данных, например PostgreSQL, чтобы избежать проблем со сменой базы данных в процессе разработки.

Если вы хотите использовать другую базу данных, установите необходимые библиотеки и измените настройки подключения к базе данных в DATABASES 'default':

  • ENGINE – Доступные значения: 'django.db.backends.postgresql_psycopg2', 'django.db.backends.mysql', 'django.db.backends.sqlite3' или 'django.db.backends.oracle'. Также доступны другие бэкэнды.

  • NAME – Название базы данных. Если вы используете SQLite, база данных будет файлом на вашем компьютере; в таком случае NAME должна содержать полный путь, включая название этого файла. Значение по умолчанию, os.path.join(BASE_DIR, 'db.sqlite3'), сохранит файл в каталоге проекта.

Если вы используете не SQLite, необходимо указать USER, PASSWORD, HOST. Подробности смотрите в описании настройки DATABASES.

Примечание

Если вы используете PostgreSQL или MySQL, убедитесь, что вы создали базу данных. Вы можете сделать это, выполнив запрос “CREATE DATABASE database_name;” в консоли базы данных.

Если вы используете SQLite, вам ничего не нужно создавать самостоятельно - файл базы данных будет создан автоматически при необходимости.

При редактировании mysite/settings.py, добавьте значение вашей временной зоны в TIME_ZONE.

Также обратите внимание на INSTALLED_APPS в начале файла настроек. Эта переменная содержит приложения Django, которые используются в проекте. Приложения могут использоваться в различных проектах, вы можете упаковать их и распространять для использования другими пользователями.

По умолчанию, INSTALLED_APPS содержит следующие приложения, все они включены в Django:

Эти приложения включаются по умолчанию, так как они полезны для типовых проектов.

Некоторые из этих приложений могут использовать хотя бы одну таблицу в базе данных,поэтому нам нужно создать таблицы прежде, чем мы сможем использовать их. Для этого выполните следующую команду:

$ python manage.py migrate

Команда migrate анализирует значение INSTALLED_APPS и создает все необходимые таблицы в базе данных, используя настройки базы данных из файла mysite/settings.py и миграции из приложения (об этом еще расскажем). Вы увидите сообщение о каждой выполненной миграции. Если вам интересно, запустите в терминале клиент для вашей базы данных и выполните \dt (PostgreSQL), SHOW TABLES; (MySQL) или .schema (SQLite) чтобы увидеть таблицы, созданные Django.

Для минималистов

Как мы уже говорили ранее, приложения по умолчанию удобны для типовых проектов, но не для всех. Если вы не нуждаетесь в некоторых или во всех, закомментируйте или удалите соответствующие строки в INSTALLED_APPS перед запуском migrate. Команда migrate выполнит миграции только для приложений в INSTALLED_APPS.

Сервер для разработки

Давайте проверим, что все заработало. Перейдите во внешний каталог mysite, если вы этого еще не сделали, и выполните команду:

$ python manage.py runserver

Вы увидите следующий вывод:

Performing system checks...

0 errors found
June 02, 2016 - 15:50:53
Django version 1.8, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Только что вы запустили сервер для разработки Django, простой Web-сервер написанный на Python. Мы включили его в Django, чтобы вы сразу могли приступить к разработке, без дополнительной настройки боевого веб-сервера – например, Apache – пока вам это действительно не понадобится.

Следует заметить: НИКОГДА НЕ используйте этот сервер на “живом” сайте. Он создан исключительно для разработки. (Мы умеем делать Web-фреймворки, не Web-сервера.)

Теперь, когда сервер запущен, перейдите на страницу http://127.0.0.1:8000/ в браузере. Вы увидите страницу с “Welcome to Django”. Работает!

Поменять порт

По умолчанию, команда runserver запускает сервер для разработки на локальном IP используя порт 8000.

Если вы хотите изменить порт, укажите его как аргумент. Например, эта команда запускает сервер используя порт 8080:

$ python manage.py runserver 8080

Если вы хотите изменить IP, передайте его вместе со значением порта. Чтобы слушать все публичные IP (полезно, если вы хотите показать свою работу на других компьютерах), используйте:

$ python manage.py runserver 0.0.0.0:8000

Смотрите полное описание команды runserver.

Автоматическая перезагрузка runserver

Dev-сервер самостоятельно перегружается при изменении Python файлов. Однако некоторые действия не перегружают сервер, например, добавление новых файлов. В таких случаях необходимо самостоятельно перегрузить сервер.

Создание моделей

Теперь, после создания окружения (проекта), мы можем приступить к работе.

Каждое приложение Django состоит из пакета Python, который следует некоторым соглашениям. Django содержит команду, которая создает структуру для нового приложения, что позволяет вам сосредоточиться на написании кода, а не на создании каталогов.

Проекты или приложения

Какая разница между приложением и проектом? Приложение – это Web-приложение, которое предоставляет определенный функционал – например, Web-блог, хранилище каких-то записей или простое приложение для голосования. Проект – это совокупность приложений и конфигурации сайта. Проект может содержать несколько приложений. Приложение может использоваться несколькими проектами.

Ваше приложение может находиться где угодно в путях Python. В этом учебнике, мы будем создавать приложение голосования возле файла manage.py, и оно может быть импортировано как независимый модуль, а не подмодуль mysite.

Создавая приложение, убедитесь, что вы находитесь в том же каталоге, что и файл manage.py, и выполните команду:

$ python manage.py startapp polls

Эта команда создаст каталог polls:

polls/
    __init__.py
    admin.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

Эти файлы являются частью приложения голосования.

Первый шаг в создании Web-приложения с БД в Django – это создание его моделей, которые являются, по сути, схемой базы данных с дополнительными метаданными.

Философия

Модель - это основной источник данных. Он содержит набор полей и поведение данных, которые вы храните. Django следует принципу DRY. Смысл в том, чтобы определять модели в одном месте.

Частью работы с данными также являются миграции. В отличии от Ruby On Rails, например, миграции вынесены из файла моделей и являются просто историей, которую Django может использовать для изменения базы данных в соответствии с текущей структурой моделей.

В нашем простом приложении голосования, мы создадим две модели: Question и Choice. Question содержит вопрос и дату публикации. Choice содержит: текст ответа и количество голосов. Каждый объект Choice связан с объектом Question.

Эти понятия отображаются простыми классами Python. Отредактируйте файл polls/models.py, чтобы он выглядел таким образом:

polls/models.py
from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

Код очень простой. Каждая модель представлена классом, унаследованным от django.db.models.Model. Каждая модель содержит несколько атрибутов, каждый из которых отображает поле в таблице базы данных.

Каждое поле представлено экземпляром класса Field – например, CharField для текстовых полей и DateTimeField для полей даты и времени. Это указывает Django какие типы данных хранят эти поля.

Названия каждого экземпляра Field (например, question_text или pub_date ) это название поля, в “машинном”(machine-friendly) формате. Вы будете использовать эти названия в коде, а база данных будет использовать их как названия колонок.

Вы можете использовать первый необязательный аргумент конструктора класса Field, чтобы определить отображаемое, удобное для восприятия, название поля. Оно используется в некоторых компонентах Django, и полезно для документирования. Если это название не указано, Django будет использовать “машинное” название. В этом примере, мы указали отображаемое название только для поля Question.pub_date. Для всех других полей будет использоваться “машинное” название.

Некоторые классы, унаследованные от Field, имеют обязательные аргументы. Например, CharField требует, чтобы вы передали ему max_length. Это используется не только в схеме базы данных, но и при валидации, как мы скоро увидим.

Field может принимать различные необязательные аргументы; в нашем примере мы указали default значение для votes` равное 0.

Заметим, что связь между моделями определяется с помощью ForeignKey. Это указывает Django, что каждый Choice связан с одним объектом Question. Django поддерживает все основные типы связей: многие-к-одному, многие-ко-многим и один-к-одному.

Активация моделей

Эта небольшая часть кода моделей предоставляет Django большое количество информации, которая позволяет Django:

  • Создать структуру базы данных (CREATE TABLE) для приложения.

  • Создать Python API для доступа к данным объектов Question и Choice.

Но первым делом мы должны указать нашему проекту, что приложение polls установлено.

Философия

Приложения Django “подключаемые”: вы можете использовать приложение в нескольких проектах и вы можете распространять приложение, так как они не связаны с конкретным проектом Django.

Отредактируйте файл mysite/settings.py и измените настройку INSTALLED_APPS добавив строку 'polls'. В результате получим:

mysite/settings.py
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'polls',
)

Теперь Django знает, что необходимо использовать приложение polls. Давайте выполним следующую команду:

$ python manage.py makemigrations polls

Вы увидите приблизительно такое:

Migrations for 'polls':
  0001_initial.py:
    - Create model Question
    - Create model Choice
    - Add field question to choice

Выполняя makemigrations, вы говорите Django, что внесли некоторые изменения в ваши модели (в нашем случае мы создали несколько новых) и хотели бы сохранить их в миграции.

Миграции используются Django для сохранения изменений ваших моделей (и структуры базы данных) - это просто файлы на диске. Вы можете изучить миграцию для создания ваших моделей, она находится в файле polls/migrations/0001_initial.py. Не волнуйтесь, вам не нужно каждый раз их проверять. Но их формат удобен для чтения на случай, если вы захотите внести изменения.

В Django есть команда, которая выполняет миграции и автоматически обновляет базу данных - она называется migrate. Мы скоро к ней вернемся, но сначала давайте посмотрим какой SQL выполнит эта миграция. Команда sqlmigrate получает название миграции и возвращает SQL:

$ python manage.py sqlmigrate polls 0001

Вы увидите приблизительно такое (мы отформатировали результат для читабельности):

BEGIN;
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);
CREATE TABLE "polls_question" (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;

COMMIT;

Обратите внимание на следующее:

  • Полученные запросы зависят от базы данных, которую вы используете. Пример выше получен для PostgreSQL.

  • Названия таблиц созданы автоматически из названия приложения(polls) и названия модели в нижнем регистре – question и choice. (Вы можете переопределить это.)

  • Первичные ключи (ID) добавлены автоматически. (Вы можете переопределить и это.)

  • Django добавляет "_id" к названию внешнего ключа. (Да, вы можете переопределить и это.)

  • Связь явно определена через FOREIGN KEY constraint. Не волнуйтесь о DEFERRABLE, это просто указание для PostgreSQL не применять ограничения FOREIGN KEY до окончания транзакции.

  • Учитываются особенности базы данных, которую вы используете. Специфические типы данных такие как auto_increment (MySQL), serial (PostgreSQL), или integer primary key (SQLite) будут использоваться автоматически. Тоже касается и экранирование названий, что позволяет использовать в названии кавычки – например, использование одинарных или двойных кавычек.

  • Команда sqlmigrate не применяет миграцию к базе данных - она просто выводит запросы на экран, чтобы вы могли увидеть какой SQL создает Django. Это полезно, если вы хотите проверить что выполнит Django, или чтобы предоставить вашему администратору базы данных SQL скрипт.

Если необходимо, можете выполнить python manage.py check. Эта команда ищет проблемы в вашем проекте не применяя миграции и не изменяя базу данных.

Теперь, выполните команду migrate снова, чтобы создать таблицы для этих моделей в базе данных:

$ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, messages
  Apply all migrations: admin, contenttypes, polls, auth, sessions
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying <migration name>... OK

Команда migrate выполняет все миграции, которые ещё не выполнялись, (Django следит за всеми миграциями, используя таблицу в базе данных django_migrations) и применяет изменения к базе данных, синхронизируя структуру базы данных со структурой ваших моделей.

Миграции - очень мощная штука. Они позволяют изменять ваши модели в процессе развития проекта без необходимости пересоздавать таблицы в базе данных. Их задача изменять базу данных без потери данных. Мы ещё вернемся к ним, а пока запомните эти инструкции по изменению моделей:

  • Внесите изменения в модели (в models.py).

  • Выполните python manage.py makemigrations чтобы создать миграцию для ваших изменений

  • Выполните python manage.py migrate чтобы применить изменения к базе данных.

Две команды необходимы для того, чтобы хранить миграции в системе контроля версий. Они не только помогают вам, но и могут использоваться другими программистами вашего проекта.

О всех возможностях manage.py вы можете прочитать в разделе о django-admin.

Поиграемся с API

Теперь, давайте воспользуемся консолью Python и поиграем с API, которое предоставляет Django. Чтобы запустить консоль Python выполните:

$ python manage.py shell

Мы используем эту команду вместо просто “python”, потому что manage.py устанавливает переменную окружения DJANGO_SETTINGS_MODULE, которая указывает Django путь импорта для файла mysite/settings.py.

Запуск без manage.py

Если вы не желаете использовать manage.py, не проблема. Просто установите переменную окружения DJANGO_SETTINGS_MODULE в mysite.settings, запустите интерпретатор Python и инициализируйте Django:

>>> import django
>>> django.setup()

Если вы получили исключение AttributeError, возможно вы используете версию Django, которая не соответствует этому учебнику. Вам следует читать старую версию учебника или обновить Django.

Запустить python необходимо в каталоге, в котором находится файл manage.py (или убедитесь, что каталог находится в путях Python, и import mysite работает).

Полную информацию обо всем этом смотрите в разделе о django-admin.

Теперь, когда вы в консоли, исследуем API базы данных:

>>> from polls.models import Question, Choice   # Import the model classes we just wrote.

# No questions are in the system yet.
>>> Question.objects.all()
[]

# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

# Now it has an ID. Note that this might say "1L" instead of "1", depending
# on which database you're using. That's no biggie; it just means your
# database backend prefers to return integers as Python long integer
# objects.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() displays all the questions in the database.
>>> Question.objects.all()
[<Question: Question object>]

Одну минуту. <Question: Question object> – крайне непрактичное отображение объекта. Давайте исправим это, отредактировав модель Question (в файле polls/models.py) и добавив метод __str__() для моделей Question и Choice:

polls/models.py
from django.db import models

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

Важно добавить метод __str__() не только для красивого отображения в консоли, но так же и потому, что Django использует строковое представление объекта в интерфейсе администратора.

__str__ или __unicode__?

В Python 3 все проще, просто используйте __str__().

В Python 2 необходимо определить метод __unicode__(), который возвращает unicode. Модели Django содержат метод __str__(), который вызывает метод __unicode__() и конвертирует результат в UTF-8 байтовую строку. Это означает, что unicode(p) вернет строку Unicode, и str(p) вернет обычную строку, символы которой закодированы в UTF-8. Python делает наоброт: object содержит метод __unicode__, который вызывает __str__ и конвертирует результат ASCII байтовую строку. Это отличие может вводить в заблуждение.

Если все это звучит как бред для вас, используйте Python 3.

Заметим, это стандартные методы Python. Давайте добавим свой метод, просто для демонстрации:

polls/models.py
import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

Мы добавили import datetime и from django.utils import timezone для использования стандартной библиотеки Python datetime и модуля Django для работы с временными зонами django.utils.timezone соответственно. Если вы не знакомы, как Python работает с временными зонами, вы можете прочитать об этом в разделе о поддержке временных зон.

Сохраните эти изменения и запустите консоль Python снова, выполнив python manage.py shell:

>>> from polls.models import Question, Choice

# Make sure our __str__() addition worked.
>>> Question.objects.all()
[<Question: What's up?>]

# 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?>]

# 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?>

# 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.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
[]

# Create three choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# Choice objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]
>>> q.choice_set.count()
3

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
[<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]

# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

Подробности о работе со связанными объектами смотрите в соответствующем разделе. Подробности об использовании синтаксиса двойного нижнего подчеркивания читайте в разделе о фильтрах полей. Полная информация об API для работы с базой данных содержится в соответствующем разделе.

Изучив API приступим ко второй части учебника, чтобы настроить интерфейс администратора Django.