Django — это популярный, мощный фреймворк на языке Python. Он имеет множество «батареек«, и позволяет сразу начать разработку. Однако вся эта мощь означает, что вы можете написать низкопробный код, который будет казаться рабочим. Так что же подразумевается под Эффективным Django? Под Эффективным Django будем понимать использование Django таким образом, чтобы написанный код был связным, тестируемым и масштабируемым. Что же каждое из этих слов значит?

«Связный» код — это код, который сосредоточен на выполнении одной вещи, только одной единственной вещи. Это значит, что когда вы пишете функцию или метод — написанный вами код должен делать что-то одно и делать это хорошо.

Это непосредственно относится к написанию тестируемого кода: код, который делает много вещей, достаточно часто является чересчур сложным для тестирования. Когда я ловлю себя на мысли: «Хорошо, этот кусок кода слишком сложен, чтобы писать для него тесты — это просто не стоит потраченных усилий» — вот сигнал к тому, чтобы вернутся назад и сосредоточиться на упрощении. Тестируемый код — такой код, который позволяет просто писать для него тесты; код, в котором легко найти проблемы.

И наконец, мы хотим писать масштабируемый код. Это означает не просто масштабировать его в терминах исполнения, но так же увеличивать в терминах команды и командного понимания. Хорошо протестированные приложения проще для понимания другими (и проще для изменения ими), что подразумевает большую возможность улучшить ваше приложение, путем добавления новых инженеров.

 

Глава 1. Приступая к работе

 

1.1. Ваша среда разработки

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

Изоляция означает, что вы не сможете случайно воспользоватся инструментами или пакетами установленными вне вашего окружения. Это особенно важно, когда подобное происходит с чем-то, похожим на пакеты Python с расширениями написанными на C: если вы используете что-то установленное на системном уровне и не знаете об этом, то при развертывании или распространении своего кода вы можете обнаружить, что он работает не так как предполагалось. Инструменты наподобие virtualenv могут помочь создать нечто похожее на изолированную среду.

Ваша среда предопределена, если вы уверены в том, на какую версию ваших зависимостей вы полагаетесь и сможете ли вы наверняка воспроизвести системное окружение.

И на конец, сходство с производственной средой или средой сервера разработки означает, что везде установлена одна и та же операционная система (возможно даже одинаковый выпуск) и вы используете одинаковые инструменты как для конфигурирования вашей среды разработки, так и для конфигурирования вашей производственной среды. Это отнюдь не необходимость, но если вы строите большое и сложное программное обеспечение — сходство будет полезно для уверенности в том, что все проблемы, которые вы можете увидеть на «боевом» сервере, воспроизводимы в той среде, где вы ведете разработку. К тому же сходство ограничивает область исследования вашего кода.

1.1.1. Изоляция

 

  • Мы хотим избежать использования неизвестных зависимостей или неизвестных версий.
  • virtualenv предоставляет простой путь для работы над проектом, без использования системных site-packages.

 

1.1.2. Предопределенность

 

  • Предопределенность означает управление зависимостями.
  • Выберете один из инструментов и используйте как при разработке, так на «боевом» сервере:
    • pip и специальные файлы зависимостей;
    • buildout;
    • install-requires в setup.py.
  • Определите точные версии зависимостей.

Вы можете точно определить версии используя либо версию пакета на PyPI, либо же определенную ревизию (SHA в git, номер ревизии в Subversion и т. д.). Это гарантирует вам возможность получить в точности те же версии пакетов, которые вы используете при тестировании.

1.1.3. Сходство

 

  • Работа в среде, похожей на ту, где вы будете разворачивать ваше приложение и пытаться обнаружить проблемы.
  • Если вы разрабатываете что-то, требующее дополнительных сервисов — сходство становится еще более важным.
  • Vagrant — это инструмент для управления виртуальными машинами, позволяющий вам легко создавать окружение отделенное от вашего повседневного окружения.

 

1.2. Настройка вашего окружения

 

1.2.1. Создание чистого рабочего пространства

 

Примечание переводчика:
Для начала создадим каталог (tutorial), в котором будем работать:

~$ mkdir tutorial
~$ cd tutorial
~/tutorial$ mkdir venv project

В каталоге venv будет находится наше виртуальное окружение, а в каталоге project — Django-проект

 

~/tutorial$ virtualenv --prompt="(venv:tutorial)" ./venv/

New python executable in ./venv/bin/python
Installing setuptools............done.
Installing pip...............done.

~/tutorial$ source ./venv/bin/activate
(venv:tutorial)~/tutorial$

 

1.2.2. Создание файла зависимостей

Создайте файл requirements.txt в директории tutorial с единственной строкой (зависимостью) в нем:

Django==1.6.7

 

Примечание переводчика:
В случае, если вы хотите использовать последнюю версию Django (1.7 — на момент написания перевода) — вместо строки Django==1.6.7 оставьте просто Django — pip установит последнюю доступную версию.

 

1.2.3. Установка зависимостей

А теперь мы можем использовать pip для установки зависимостей:

(venv:tutorial)~/tutorial$ pip install -U -r requirements.txt

Downloadping/unpacking Django==1.6.7 (from -r requirements.txt (line 1))
  Downloading Django-1.6.7.tar.gz (6.6MB): 6.6MB downloaded
  Running setup.py egg_info for package Django

    warning: no previously-included files matching__pycache__found under directory ’*’
    warning: no previously-included files matching ’*.py[co]’ found under directory ’*’
Installing collected packages: Django
  Running setup.py install for Django
    changing mode of build/scripts-2.7/django-admin.py from 644 to 755

    warning: no previously-included files matching__pycache__found under directory ’*’
    warning: no previously-included files matching ’*.py[co]’ found under directory ’*’
    changing mode of /home/nathan/p/edt/bin/django-admin.py to 755
Successfully installed Django
Cleaning up...

 

1.3. Начало проекта Django

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

Django, как и многие web-фреймворки, представляет скаффолдинг для вашей разработки. Это происходит при помощи принятия решений и предоставления отправной точки для вашего кода, что позволяет вам сосредоточится на проблеме, которую вы пытаетесь решить, а не на том, как разобрать HTTP-запрос. Django предоставляет скаффолдинг как для работы с HTTP, так и для работы с файловой системой.

HTTP-скаффолдинг управляет, например, преобразованием HTTP-запроса в объект языка Python, а также предоставляет инструменты для более простого создания серверных ответов. Скаффолдинг файловой системы иной: это набор соглашений по организации вашего кода. Эти соглашения облегчают добавление новых инженеров в проект, так как инженеры (гипотетически) уже понимают как организован код. В терминах Django, проект — это конечный продукт, и он объединяет внутри себя одно или несколько приложений. В Django 1.4 было изменено то, как проекты и приложения размещаются на диске, что облегчило разъединение и повторное использование приложений в разных проектах.

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

Django устанавливает в систему скрипт django-admin.py для обработки задач скаффолдинга. Для создания файлов проекта используется задача startproject. Мы определим имя проекта и имя директории, в которой хотим разместить проект. Так как, мы уже находимся в изолированной среде, можно просто написать:

Примечание переводчика:
Перейдем директорию ~/tutorial/project/ и в дальнейшем будем работать только из этой директории (под $ далее будем подразумевать ~/tutorial/project/$):

(venv:tutorial)~/tutorial/$ cd project

 

(venv:tutorial)$ django-admin.py startproject addressbook .

Созданный проект имеет следующую структуру

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

 

1.3.2. Скаффолдинг проекта

 

  • manage.py — является ссылкой на скрипт django-admin, но с уже предустановленными переменными окружения, указывающими на ваш проект, как для чтения настроек оттуда, так и для управления им при необходимости;
  • settings.py — здесь находятся настройки вашего проекта. Файл уже содержит несколько разумных настроек, но база данных не указана;
  • urls.py — содержит URL’ы для маппирования (отображения) представлений: мы вскоре (в дальнейших главах) поговорим об этом подробнее;
  • wsgi.py — это WSGI обёртка для вашего приложения. Этот файл используется сервером разработки Django и возможно другими контейнерами, такими как mod_wsgi, uwsgi и др. на «боевом» сервере.

 

1.3.3. Создание приложения

 

(venv:tutorial)$ python ./manage.py startapp contacts

Созданное приложение имеет следующую структуру:

./contacts
    __init__.py
    models.py
    tests.py
    views.py

 

  • Начиная с Django 1.4, приложения размещаются внутри пакета с проектом. Это замечательное улучшение, особенно когда приходит время разворачивать проект на «боевом» сервере;
  • models.py будет содержать Django ORM-модели для вашего приложения;
  • views.py будет содержать код представлений;
  • tests.py будет содержать написанные вами модульные и интеграционные тесты.
  • Django 1.7: admin.py будет содержать модель для административного интерфейса.
  • Django 1.7: migrations/ содержит файлы миграций

 

Примечание переводчика:
На текущий момент наша директория ~/tutorial/ содержит файл зависимостей (requirements.txt), директорию с виртуальным окружением (venv/), один проект (project/addressbook), одно приложение (project/contacts) и имеет следующее содержание:

~/tutorial/
    requirements.txt
    venv/
        ...
    project/
        manage.py
        addressbook/
            __init__.py
            settings.py
            urls.py
            wsgi.py
        contacts/
            __init__.py
            models.py
            tests.py
            views.py

 

Глава 2. Используем модель

 

2.1. Конфигурирование базы данных

Django поддерживает «из коробки» MySQL, PostgreSQL, SQLite3 и Oracle. SQLite3 входит в состав Python начиная с версии 2.5, так что мы будем использовать его в нашем проекте (для простоты). Если вы хотите, к примеру, использовать MySQL, то нужно добавить mysql-python в ваш requirements.txt.

Для того чтобы в качестве базы данных использовать SQLite, отредактируйте определение DATABASES в файлеaddressbook/settings.py. Файл settings.py содержит настройки Django для нашего проекта. В нем есть несколько настроек, которые вы обязаны указать — например DATABASES — а так же другие, необязательные, настройки. Django устанавливает некоторые настройки сам, когда генерирует проект. Документация содержит полный список настроек. К тому же вы можете добавить свои собственные настройки, если это необходимо.

Для использования SQLite нам нужно указать движок (ENGINE) и имя базы (NAME). SQLite интерпертирует имя базы как имя файла для базы данных:

DATABASES = {
    'defaults': {
        'ENGINE': 'django.db.backends.sqlite3,' # ’postgresql_psycopg2’, ’mysql’, ’sqlite3’ or ’oracle'.
        'NAME': os.path.join(BASE_DIR, 'address.db'),
        'USER': '',     # Not used with sqlite3.
        'PASSWORD': '', # Not used with sqlite3.
        'HOST': '',     # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',     # Set to empty string for default. Not used with sqlite3.
    }
}

Заметьте, что движок базы данных указан строкой, а не прямой ссылкой на объект Python. Это сделано по той причине, что файл настроек должен быть легко импортирован не вызывая сторонних эффектов. Вы должны избегать добавления вызовов import в этот файл.

Вам редко придется непосредственно импортировать файл настроек: Django импортирует его за вас, и делает настройки доступными как django.conf.settings. Вы, как правило, импортируете настройки из django.conf:

from django.conf import settings

 

2.2. Создание модели

Модели Django отображают (грубо говоря) таблицы базы данных, и предоставляют место для инкапсулирования бизнес-логики. Все модели являются наследниками базового класса Model и содержат поля определений. Давайте создадим простую модель Contacts для нашего приложения в файле contacts/models.py:

from django.db import models

class Contact(models.Model):

    first_name = models.CharField(
        max_length=255,
    )
    last_name = models.CharField(
        max_length=255,
    )
    
    email = models.EmailField()

    def __str__(self):

        return ' '.join([
            self.first_name,
            self.last_name,
        ])

Django предоставляет набор полей для отображения типов данных и различных правил валидации. К примеру,EmailField, который мы использовали, является отображением на колонку с типом CharField, но добавляет валидацию данных.

После того, как вы создали модель, необходимо дополнить вашу базу данных новыми таблицами. Команда Django syncdbсмотрит установленные модели и создает (если нужно) таблицы для них:

Примечание переводчика:
Django предложит создать суперпользователя для андминки, которая включена в этой версии по умолчанию. Воспользуйтесь его предложением.

Примечание переводчика:
С версии Django 1.7 во фреймворк добавлена нативная поддержка миграций и команда syncdb объявлена устаревшей. Так что будьте так любезны, воспользуйтесь командой migrate вместо syncdb.

 

(venv:tutorial)$ python ./manage.py syncdb

Creating tables ...
Creating table django_admin_log
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'bjakushka'):
Email address: 
Password:
Password (again):
Superuser created successfully.
Installing custom SQL ...
installing indexes ...
Installed 0 object(s) from 0 fixture(s)

(venv:tutorial)$

Примечание переводчика:
Если вы используете Django версии 1.7 и выше — вывод будет следующий:

(venv:tutorial)$ python ./manage.py migrate

Opperation to perform:
    Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
    Applying contenttypes.0001_initial... OK
    Applying auth.0001_initial... OK
    Applying admin.0001_initial... OK
    Applying sessions.0001_initial... OK

(venv:tutorial)$ 

Однако нашей таблицы с контактами нигде не видно. Причина этого состоит в том, что нам нужно еще указать проекту использовать приложение.

Настройка INSTALLED_APPS содержит список приложений, используемых в проекте. Этот список содержит в себе строки, которые отображают пакеты Python. Django будет импортировать каждый из указанных пакетов, а потом смотреть модульmodels. Давайте добавим наше приложение contacts в настройки проекта (addressbook/settings.py):

INSTALLED_APPS = (
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  
  'contacts',
)

После этого запустите syncdb снова:

Примечание переводчика:
Для Django версии 1.7 и выше вам нужно будет запустить сначала команду makemigrations — для создания миграций на основе изменений в моделях, а после этого выполнить команду migrate — для того чтобы применить созданные миграции.

 

(venv:tutorial)$ python ./manage.py syncdb

Creating tables ...
Creating table contacts_contact
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

(venv:tutorial)$

Примечание переводчика:
Вывод для Django 1.7 и выше:

(venv:tutorial)$ python ./manage.py makemigrations

Migrations for 'contacts':
    0001_initial.py:
        - Create model Contact

(venv:tutorial)$ python ./manage.py migrate

Opperation to perform:
    Apply all migrations: admin, contenttypes, sessions, auth, contacts
Running migrations:
    Applying contacts.0001_initial... OK

(venv:tutorial)$ 

Заметьте, что Django создает таблицу с именем contacts_contact: по умолчанию Dj ango дает таблицам имена используя комбинацию имени приложения и имени модели. Вы можете изменить это с помощью опций модели Meta.

2.3. Взаимодействие с моделью

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

(venv:tutorial)$ python ./manage.py shell
Python 2.7.3 (default, Mar 14 2014, 11:57:14)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from contacts.models import Contact
>>> Contact.objects.all()
[]
>>> Contact.objects.create(first_name='Nathan', last_name='Yergler')
<Contact: Nathan Yergler>
>>> Contact.objects.all()
[<Contact: Nathan Yergler>]
>>> nathan = Contact.objects.get(first_name='Nathan')
>>> nathan
<Contact: Nathan Yergler>
>>> print nathan
Nathan Yergler
>>> nathan.id
1

Здесь использовалось несколько новых штук. Во-первых, команда manage.py shell запускает для нас интерактивную оболочку Python’а с правильно установленными путями для Django. Если вы попробуете запустить интерпретатор Python и просто импортировать ваше приложения, будет выброшено исключение, потому что Django не знает, какие настройки использовать, и не может отобразить экземпляры модели на базу данных.

Во-вторых, здесь использовалось свойство objects нашей модели. Это менеджер модели. Так, если один экземпляр модели является аналогией для строки в базе, то менеджер модели — аналогией для таблицы. По умолчанию менеджер модели предоставляет функциональность запросов и может быть настроен. Когда мы вызываем all(), filter() или сам менеджер, возвращается объект QuerySet. QuerySet является итерируемым объектом и загружает данные из базы по необходимости.

И последнее — выше использовалось поле с именем id, которое мы не определяли в нашей модели. Django добавляет это поле как первичный ключ для модели, но только в том случае если вы сами не определили какое поле будет первичным ключом.

2.4. Написание тестов

В нашей модели определен один метод, __str__, так что настало время писать тесты. Метод __str__ будет использоваться всего лишь в нескольких местах, и, вполне возможно, полностью будет показан конечному пользователю. Для этого метода стоит написать тест, пока мы понимаем как он работает. Django создал файл tests.py когда создавал приложение, так что мы добавим первый тест в этот файл, приложения contacts.

from django.test import TestCase

from contacts.models import Contact

class ContactTests(TestCase):
    """Contact model tests."""
    
    def test_str(self):
    
        contact = Contact(first_name='John', last_name='Smith')
        self.assertEquals(
            str(contact),
            'John Smith',
        )

Вы можете запустить тесты для вашего приложения используя команду manage.py test:

(venv:tutorial)$ python ./manage.py test

Если вы запустите это, то увидите что выполнилось около 420 тестов. Это удивляет, так как мы написали только один. Произошло это потому, что по умолчанию Django запускает тесты для всех установленных приложений. Когда вы добавляли приложение contacts в наш проект, то могли увидеть, что там по умолчанию были добавлены несколько встроенных приложений Django. Дополнительные 419 тестов были взяты оттуда.

Примечание переводчика:
В нашем случае (при использовании версии Django 1.6.7) предыдущий абзац несколько устарел: запустится только один тест — тот который мы создали. Вывод команды будет такой как указано ниже.

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

(venv:tutorial)$ python manage.py test contacts

Creating test database for alias ’default’...
.
----------------------------------------------------------------------
Ran 1 tests in 0.001s

OK
Destroying test database for alias ’default’...

(venv:tutorial)$

Еще одна интересная вещь на заметку, прежде чем двигаться дальше — первая и последняя строка вывода: Creating test database и Destroying test database. Некоторым тестам необходим доступ к базе данных, и поскольку мы не хотим мешать тестовые данные с «реальными» (по разным причинам, не последней из которых является предопределенность), Django услужливо создает тестовую базу для нас, прежде чем запускать тесты. По существу, создается новая база данных, и потом запускается syncdb для нее. Если тестовый класс является потомком класса TestCase (как у нас), Django так же сбросит данные в значения по умолчанию после запуска каждого теста, так что изменения в одном из тестов не затронут другие.

2.5. Резюме

 

  • Модель определяет поля в таблице, и содержит бизнес-логику.
  • Команда syncdb создает таблицы в вашей базе данных из моделей. В Django версии 1.7 и выше вместо командыsyncdb необходимо использовать сначала команду makemigrations — для создания миграций, а после этого командуmigrate — для внесение изменений в базу.
  • Менеджер модели позволяет вам оперировать коллекциями экземпляров: запросы, создание и т. д..
  • Пишите модульные тесты для методов, которые вы добавили в модель.
  • Команда управления test запускает модульные тесты на выполнение.

 

Примечание переводчика:
Для того чтобы протестировать наше, пока еще пустое, приложение нужно выполнить следующую команду:

(venv:tutorial)$ python ./manage.py runserver 0.0.0.0:8080

Это запустит встроенный сервер, функционал которого любезно предоставляет нам Django. В параметрах послеrunserver указывается ip-адрес и порт, который будет слушаться работающим сервер. В нашем случае сервер будет принимать запросы от всех ip-адресов при обращении на 8080 порт.

Я использую для разработки домашний сервер с внутренним IP 192.168.1.51. Так что для того что-бы увидеть результат работы сервера разработки в браузере я захожу по адресу http://192.168.1.51:8080/. Вы же должны подставить адрес своего сервера.

bb9f4193a8934150ab84199c02d16812

Глава 3. Пишем представление

 

3.1. Основы представлений

Представления Django получают HTTP запрос и возвращает пользователю HTTP ответ:
e3aab763e8e64329b0ac903fd7c22fe4Любой вызываемый объект языка Python может быть представлением. Единственное жесткое и необходимое требование заключается в том, что вызываемый объект Python должен принимать объект запроса в качестве первого аргумента (обычно этот параметр так и именуют — request). Это означает, что минимальное представление будет очень простым:

from django.http import HttpResponse

def hello_world(request):
    return HttpResponse("Hello, World")

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

3.2. Общие представления и представления-классы

 

  • общие представления всегда предоставляют какой-нибудь базовый функционал: визуализировать шаблон, перенаправить, создать, отредактировать модель и т. д.
  • начиная с версии 1.3, для общих представлений в Django появились представления-классы (CBV);
  • общие представления и CBV предоставляют более высокий уровень абстракции и компонуемости;
  • кроме того, они скрывают немало сложности, которая иначе могла бы сбить с толку новичков;
  • к счастью, в новых версиях Django документация всего этого стала намного лучше.

Django 1.3 вводит представления-классы на которых мы сосредоточимся в этой главе. Представления-классы или CBV, могут устранить много шаблонного кода из ваших представлений, особенно из представлений для редактирования чего-либо, где вы хотите предпринимать различные действия для GET и POST запросов. Представления-классы дадут вам возможность собирать функционал по частям. Недостаток заключается в том, что вся эта мощь влечет за собой некоторое дополнительное усложнение.

3.3. Представления-классы (CBV)

Минимальное представление, реализованное как CBV, является наследником класса View doc и реализует поддерживаемые HTTP-методы. Ниже находится очень небольшое представление «Hello, World» написанное нами ранее, но выполненное в виде представления-класса:

from django.http import HttpResponse
from django.views.generic import View

class MyView(View):

      def get(self, request, *args, **kwargs):
      	  return HttpResponse("Hello, World")

В представлении-классе имена методов HTTP отображаются на методы класса. В нашем случае мы определили обработчик для GET-запроса используя метод класса get. Точно так же, как при реализации функцией, этот метод принимает объект запроса в качестве первого параметра и возвращает HTTP ответ.

Примечание: Допустимые сигнатуры
Вы можете заметить несколько дополнительных аргументов в сигнатуре функции, по сравнению с представлением написанным нами ранее, в частности *args и **kwargs. CBV впервые были создавались для того, что бы сделать «общие» представления Django более гибкими. Подразумевалось, что они будут использоваться в множестве различных контекстов, с потенциально различными аргументами, извлеченными из URL. Занимаясь последний год написанием представлений-классов, я продолжаю писать их используя допустимые сигнатуры, и каждый раз это оказывается полезным в неожиданных ситуациях.

 

3.4. Вывод перечня контактов

Мы начнем с представления, которое выводит список контактов из базы данных.

Базовая реализация представления очень коротка. Мы можем написать его всего-лишь в несколько строк. Для этого в файле views.py нашего приложения contacts наберем следующий код:

from django.views.generic import ListView

from contacts.models import Contact

class ListContactView(ListView):

      model = Contact

Класс ListView doc, от которого мы наследовали представление, сам составлен из нескольких примесей, которые реализуют некоторое поведение. Это дает нам большую мощь при малом количестве кода. В нашем случае мы указали модель (model = Contact), что заставит это представление вывести список всех контактов модели Contact из нашей базы данных.

3.5. Определяем URL’ы

Конфигурация URL (URLconf) указывает Django как по адресу запроса найти ваш Python-код. Django смотрит конфигурацию URL, которая определена в переменной urlpatterns файла urls.py.

Давайте добавим в файл addressbook/urls.py URL-шаблон для нашего представления, которое отображает список контактов:

from django.conf.urls import patterns, include, url

import contacts.views

urlpatterns = patterns('',
    url(r'^$', contacts.views.ListContactView.as_view(),
        name='contacts-list',),
)

 

  • использование функции url() не является строго обязательным, но я предпочитаю использовать ее: когда вы начнете добавлять больше информации в URL-шаблоны, она позволит вам использовать именованные параметры, делая код более чистым;
  • первый параметр принимаемый функцией url() — это регулярное выражение. Обратите внимание на замыкающий символ $; как думаете, почему он важен?
  • вторым параметром указывается вызываемое представление. Это может быть как непосредственно вызываемый объект (импортированный вручную), так и строка описывающая его. Если это строка, то в случае соответствия URL-шаблона строке запроса, Django сам импортирует необходимый модуль (до последней точки), и вызовет последний сегмент строки (после последней точки);
  • замете, что когда мы используем представление-класс, мы обязаны использовать реальный объект, а не строку описывающую этот объект. Мы должны так делать из за того, что мы вызываем метод класса as_view(). Этот метод возвращает обертку над нашим классом, которую может вызвать диспетчер URL Django;
  • имя, данное URL-шаблону, позволят вам делать обратный поиск;
  • имя URL полезно, когда вы ссылаетесь из одного представления на другое, или выполняете перенаправление, так как вы можете управлять структурой URL’ов из одного места.

В то время, как переменная urlpatterns должна быть определена, Django также позволяет вам определить несколько других значений (переменных) для исключительных ситуаций. Эти значения включают в себя handler403, handler404, иhandler500, которые указывают Django, какое представление использовать в случае ошибки HTTP. Для более подробной информации смотрите документацию Django по конфигурации URL.

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

 

3.6. Создание Шаблона

Сейчас, определив URL для нашего представления списка контактов, мы можем испробовать его. Django включает в себя сервер подходящий для целей разработки, который вы можете использовать для тестирования вашего проекта:

(venv:tutorial)$ python ./manage.py runserver 0.0.0.0:8080
Validating models...

0 errors found
November 04, 2014 - 15:25:05
Django version 1.6.7, using settings 'addressbook.settings'
Starting development server at http://0.0.0.0:8080/
Quit the server with CONTROL-C.

Если вы посетите http://localhost:8080/ в вашем браузере, вы все же получите ошибку TemplateDoesNotExists

Примечание переводчика:
localhost указывайте в том случае, если запускаете браузер с того же хоста, где запущен сервер. Если сервер у вас запущен на другом хосте (как у меня) — вместо localhost укажите IP адрес этого хоста (в моем случае это192.168.1.51).

939e01661f7c4246a95f71216d9776b7Большинство общих представлений Django (сюда относиться, ListView использованный нами) имеет предустановленное имя шаблона, который они ожидают найти. Мы можем увидеть в этом сообщении об ошибке, что представление ожидало найти файл шаблона с именем contact_list.html, которое было получено из имени модели. Давайте создадим такой шаблон.

По умолчанию, Django ищет шаблоны в приложениях, так же как и в директориях, указанных в settings.TEMPLATE_DIRS. Общие представления ожидают, что шаблоны найдутся в директории приложения (в данном случае в директорииcontacts), и имя файла будет содержать имя модели (в данном случае ожидаемое имя файла: contact_list.html). Это приходится кстати, когда вы разрабатываете приложения для повторного использования: пользователь вашего приложения может создать свои шаблоны, которые перекроют шаблоны по умолчанию, и они будут хранится в директории, прямо связанной с приложением.

Примечание переводчика:
Django ожидает, что искомый шаблон находится по адресу имя_приложения/templates/имя_приложения/имя_шаблона

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

from django.views.generic import ListView

from contacts.models import Contact

class ListContactView(ListView):

      model = Contact
      template_name = 'contact_list.html'

Создадим в директории contacts (это директория с приложением) поддиректорию templates, а в ней создадим файл шаблона contact_list.html:

<h1>Contacts</h1>

<ul>
    {% for contact in object_list %}
    <li class="contact">{{ contact }}</li>
    {% endfor %}
</ul>

Открыв заново (или обновив) в браузере страницу http://localhost:8080/, вы должны будете увидеть как минимум один контакт, созданный нами ранее через интерактивную оболочку.

77c40f097ead4c5fa46f8a03bd83cb33

3.7. Создаем контакты

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

Так же как и в случае с выводом списка контактов, воспользуемся одним из общих представлений Django. В файлеviews.py мы добавим несколько строчек:

from django.core.urlresolvers import reverse
from django.views.generic import ListView
from django.views.generic import CreateView

from contacts.models import Contact

class ListContactView(ListView):

    model = Contact
    template_name = 'contact_list.html'

class CreateContactView(CreateView):

    model = Contact
    template_name = 'edit_contact.html'

    def get_success_url(self):
        return reverse('contacts-list')

 

Примечание переводчика:
Если вы используете Django версии >= 1.7, то можете добавить к классу CreateContactView дополнительное поле:

    fields = ['first_name', 'last_name', 'email']

Это не обязательно, но с версии Django 1.7 неиспользование этого поля в классах с автоматическим генерированием форм объявлено устаревшим (при выполнении тестов вам об этом сообщат). Если вы его не укажете — то в форме редактирования будут использоваться все поля, но с версии Django 1.8 такое поведение будет удалено.

Большинство общих представлений, которые работают с формами имеют концепцию «удачного URL»: такого URL, на которой перенаправляется пользователь, после того как форма была удачно обработана. Все представления обрабатывающие формы твердо придерживаются практики POST-перенаправление-GET для принятия изменений, так что обновление конечной страницы не отправляет заново форму. Вы можете реализовать это концепцию как свойство класса, либо переопределить метод get_success_url(), как это сделали мы. В нашем случае мы используем функцию reverse для вычисления URL’а списка контактов.

Примечание: Контекстные переменные в представлениях-классах
Набор переменных доступных в шаблоне, когда он выводится, называется Контекстом. Контекст — это комбинация данных из представления и информации из процессоров контекста.
Когда вы используете встроенные общие представления, не совсем очевидно какие переменные доступны в контексте. Со временем вы откроете для себя, что их имена (предоставляемые общими представлениями в контекст шаблона) достаточно последовательны — form, object и object_list часто используются — хотя это не поможет вам в начале пути. К счастью, документация по этому вопросу очень похорошела с версии Django 1.5.
В представлениях-классах метод get_context_data() используются для добавления информации в контекст. Если вы перегрузите этот метод, вам нужно будет разрешить **kwargs и вызвать суперкласс.

Шаблон добавления контакта будет немного более сложный, чем шаблон списка контактов, но не слишком. Наш шаблонcontacts/templates/edit_contact.html будет выглядеть как то так:

<h1>Add Contact</h1>

<form action="{% url "contacts-new" %}" method="POST">
      {% csrf_token  %}
      <ul>
        {{ form.as_ul }}
      </ul>
      <input id="save_contact" type="submit" value="Save" />
</form>

<a href="{% url "contacts-list" %}">back to list</a>

Несколько новых штук на заметку:

  • form из контекста — это Django Form для нашей модели. Так как мы не указали ни одного своего, Django создал один для нас. Как заботливо;
  • если бы мы просто написали {{ form }} мы бы получили табличные ряды; но мы добавляем .as_ul, что заставляет выводить поля ввода как элементы ненумерованного списка. Попробуйте вместо этого использовать .as_p и посмотреть что из этого получится;
  • когда мы выводим форму (пр.: автоматически сгенерированную) выводятся только наши поля, но не обрамляющий форму тег <form> или кнопка отправки <input type="submit" />, так что мы добавим их в шаблон сами;
  • шаблонный тег {% csrf_token %} вставляет скрытое поле, по которому Django определяет, что запрос пришел с вашего проекта и что это не межсайтовая подделка запроса (CSRF). Попробуйте не включать его в шаблон: вы все еще будете иметь доступ к странице, но как только вы попробуете отправить форму — вы получите ошибку;
  • мы используем шаблонный тег url для генерирования ссылки назад к списку контактов. Замете, что contacts-list — это имя нашего представления, которое мы указали в настройках URL. Используя url вместо полного пути, нам не придется беспокоится про битые ссылки. url в шаблоне является эквивалентом reverse в коде Python.

Теперь давайте отредактируем файл с настройками URL проекта (addresbook/urls.py) и добавим туда новый URL-шаблон:

from django.conf.urls import patterns, include, url

import contacts.views

urlpatterns = pattern('',
    url(r'^$', contacts.views.ListContactView.as_view(),
        name='contacts-list',),
    url(r'^new$', contacts.views.CreateContactView.as_view(),
        name='contacts-new',),

Вы можете перейти по адресу http://localhost:8080/new для того что бы создать новый контакт:

609438ffe5dd41968c0016b85c6b6699

Примечание переводчика:
Также стоит добавить в файл contacts/tempaltes/contact_list.html ссылку на страницу добавления контактов (для успешного прохождения тестов из раздела 3.9. Интеграционные тесты):

<a href="{% url "contacts-new" %}">add contact</a>

 

3.8. Тестируем наши представления

К настоящему времени, наши представления имеют прекрасно-минималистичный вид: они используют общие представления Django и содержат очень мало нашего собственного кода/логики. С одной из точек зрения вот как это должно работать: представление получает запрос и возвращает ответ, делегируя модели задачи валидации полей ввода формы и бизнес логику. Эту точку зрения разделяю и я. Чем меньше логики содержится в представлении — тем лучше.

Однако, код который находится в представлениях должен быть протестирован либо модульными, либо интеграционными тестами. Разница велика: модульные тесты сосредоточены на тестировании небольших частей (модулей) функциональности. Когда вы пишите модульные тесты, вы предполагаете что все остальное имеет свои собственные тесты и работает должным образом. Интеграционные тесты пытаются тестировать систему от начала и до конца, так что вы можете быть уверенным что все функционирует как надо. Большинство разрабатываемых систем имеет и те, и другие тесты.

У Django имеется два инструмента, которые помогают писать модульные тесты: Client doc и RequestFactory doc. Оба этих инструмента имеют схожий API, но подходы у них разные. Client получает URL для того что бы вернуть и разрешить его через конфигурацию URL проекта. Потом он создает пробный запрос и посылает его через ваше представление, которое возвращает ответ (Response). Фишка в том, что вам требуется указать URL, привязав тест к настройкам URL вашего проекта.

RequestFactory имеет схожий API: вы указываете URL, который вы хотите вернуть, а также любые параметры или данные форм. Но RequestFactory фактически не разрешает этот URL: он всего лишь возвращает объект запроса (Request). А далее вы можете вручную отправлять его вашему представлению и тестировать возвращаемый результат.

На практике, тесты основанные на RequestFactory в чем то быстрее чем тесты написанные с использованием TestClient. Это не имеет большого значения когда у вас пять тестов, но приобретает его, когда их у вас 500 или 5000. Посмотрим на схожие тесты написанные с использованием каждого инструмента:

Примечание переводчика:
Нижеследующий код тестов добавьте в файл contacts/tests.py к тесту, добавленному нами во второй главе.
Что бы запустить тесты, воспользуйтесь командой:

(venv:tutorial)$ python ./manage.py test contacts

 

from django.test import TestCase
from django.test.client import Client
from django.test.client import RequestFactory

from contacts.models import Contact
from contacts.views import ListContactView

class ContactListViewTests(TestCase):
    """Contact list view tests."""
    
    def test_contacts_in_the_context(self):
    
        client = Client()
        
        response = client.get('/')
        self.assertEquals(list(response.context['object_list']), [])
        
        Contact.objects.create(first_name='foo', last_name='bar')
        response = client.get('/')
        self.assertEquals(response.context['object_list'].count(), 1)
        
    def test_contacts_in_the_context_request_factory(self):
    
        factory = RequestFactory()
        request = factory.get('/')
        
        response = ListContactView.as_view()(request)
        self.assertEquals(list(response.context_data['object_list']), [])
        
        Contact.objects.create(first_name='foo', last_name='bar')
        response = ListContactView.as_view()(request)
        self.assertEquals(response.context_data['object_list'].count(), 1)

 

3.9. Интеграционные тесты

В Django 1.4 был добавлен новый базовый класс TestCase: LiveServerTestCase doc. Он является тем, что следует из его названия: тестом, который запускается на живом сервере. По умолчанию, Django запустит для вас сервер разработки сразу как только вы запустите эти тесты, но они также могут быть запущены и на другом сервере.

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

(venv:tutorial)$ pip install selenium

 

Примечание переводчика:
Я предлагаю указать Selenium в файле зависимостей requirements.txt и установить зависимости так, как указано в главе 1

Мы собираемся написать пару тестов для наших представлений:

  • один — создает контакт, и проверяет он выводится правильно в виду списка;
  • другой — проверяет что наша ссылка «add contact» видима и находится на странице со списком;
  • последний фактически осуществляет добавление контакта через форму, заполняя поля и отправляя форму на сервер.

 

from django.test import LiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
...
class ContactListIntegrationTests(LiveServerTestCase):

    @classmethod
    def setUpClass(cls):
        cls.selenium = WebDriver()
        super(ContactListIntegrationTests, cls).setUpClass()
        
    @classmethod
    def tearDownClass(cls):
        cls.selenium.quit()
        super(ContactListIntegrationTests, cls).tearDownClass()
        
    def test_contact_listed(self):
    
        # create a test contact
        Contact.objects.create(first_name='foo', last_name='bar')
        
        # make sure it’s listed as <first> <last> on the list
        self.selenium.get('%s%s' % (self.live_server_url, '/'))
        self.assertEqual(
            self.selenium.find_elements_by_css_selector('.contact')[0].text,
            ’foo bar’
        )
        
    def test_add_contact_linked(self):
    
        self.selenium.get('%s%s' % (self.live_server_url, '/'))
        self.assert_(
            self.selenium.find_element_by_link_text(’add contact’)
        )
        
    def test_add_contact(self):
    
        self.selenium.get('%s%s' % (self.live_server_url, '/'))
        self.selenium.find_element_by_link_text('add contact').click()
        
        self.selenium.find_element_by_id('id_first_name').send_keys('test')
        self.selenium.find_element_by_id('id_last_name').send_keys('contact')	
        self.selenium.find_element_by_id('id_email').send_keys('test@example.com')
        
        self.selenium.find_element_by_id('save_contact').click()
        self.assertEqual(
            self.selenium.find_elements_by_css_selector('.contact')[-1].text,
            'test contact'
        )

 

Примечание переводчика:
Тесты с использованием Selenium’а сработают только в том случае, когда вы их запустите на системе в которой установлен Firefox

Замете, что Selenium позволяет найти элемент на странице, проверить его состояние, нажать на него и послать клавиатурное сочетание. Короче, он делает тоже, что и мы, когда пользуемся браузером. На самом деле, вы можете запустить тесты прямо сейчас, и увидеть как запустится браузер, когда будут выполнятся тесты.

В нашем примере, мы использовали селекторы CSS для нахождения элемента внутри DOM, но мы могли бы также использовать Xpath. Это зависит от предпочтений, но я нахожу использование селекторов CSS менее хрупкими: если я изменю разметку страницы, то скорее всего, оставлю классы в нужных элементах на месте, даже если относительные позиции этих элементов в DOM изменились.

3.10. Резюме

 

  • Представления получают HttpRequest doc и превращают его в HttpResponse doc;
  • общие классы-представления были введены в Django 1.3;
  • они позволяют вам создавать представления пригодные для повторного использования и компоновки;
  • URL’ы определяются в файле urls.py вашего проекта;
  • именование URL’ов, позволяет указывать их в представлении;
  • RequestFactory doc создает запрос (Request) для тестирования представлений с его помощью;
  • LiveServerTestCase doc предоставляет основу для написания интеграционных тестов;

 

Глава 4. Используем статические ресурсы

Теперь, когда мы имеем базовое приложение, в котором мы можем добавлять контакты и выводить их в виде списка, разумно подумать о том, как заставить все это выглядеть более привлекательно. Большинство современных веб-приложений являются комбинацией серверного кода/представлений и клиентских статических ресурсов, таких как JavaScript и CSS. Независимо от того, выберете вы JavaScript или CoffeScript, CSS или SASS, Django предоставит поддержку для интеграции статических ресурсов в ваш проект.

4.1. Добавление статических файлов

Django делает различие между «статическими» и «медиа» файлами. Первые являются статическими ресурсами, включаемыми в ваше приложение или проект. Последние же являются файлами, которые были загружены пользователем с использованием одного из движков хранения файлов. Django включает в себя пакет django.contrib.staticfiles для управления статическими файлами и, что важно, для генерирования путей к ним. Вы можете, конечно, «захардкодить» URL’ы к вашим статическим файлам, и это будет работать… до поры, до времени. Но если вы захотите переместить ваши статические файла на выделенный специально для них сервер или в CDN, использование сгенерированных URL’ов позволит вам внести такие изменения в проект без необходимости обновлять все ваши шаблоны.django.contrib.staticfiles доступен по умолчанию после того, как вы создали новый проект, так что вы можете сразу начать его использовать.

Мы добавим Bootstrap в наш проект для создания базового оформления. Вы можете загрузить файлы Bootstrap с их сайтаhttp://getbootstrap.com/.

Django поддерживает добавления статических файлов как на уровне приложения, так и на уровне проекта. То, где вы будете добавлять их зависит от то, как они (статические файлы) связаны с вашими приложениями. То есть, являются ли статические файлы общими для всех приложений, или же они специфичны для какого то одного.

Статические файлы, определенные на уровне приложения, хранятся в поддиректории static директории приложения. Django также просмотрит каждую директорию, указанную в переменной STATICFILES_DIRS файла настроек. Давайте обновим настройки проекта, и укажем директорию, в которой будут хранится статические файлы (добавьте определениеSTATICFILES_DIRS в конец файла с настройками, например после определения переменной STATIC_URL):

import os.path
...
# Additional locations of static files
STATICFILES_DIRS = (
    # Put strings here, like "/home/html/static" or "C:/www/django/static".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
    os.path.join(
        os.path.dirname(__file__),
        'static',
    ),
)

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

Давайте продолжим и создадим директорию static в нашем проекте. После этого распакуем в нее Bootstrap:

(venv:tutorial)$ mkdir addressbook/static
(venv:tutorial)$ cd addressbook/static
(venv:tutorial)$ wget https://github.com/twbs/bootstrap/releases/download/v3.3.1/bootstrap-3.3.1-dist.zip
(venv:tutorial)$ unzip bootstrap-3.3.1-dist.zip
   creating: dist/
   creating: dist/css/
  inflating: dist/css/bootstrap-theme.css  
  inflating: dist/css/bootstrap-theme.css.map  
  inflating: dist/css/bootstrap-theme.min.css  
  inflating: dist/css/bootstrap.css  
  inflating: dist/css/bootstrap.css.map  
  inflating: dist/css/bootstrap.min.css  
   creating: dist/fonts/
  inflating: dist/fonts/glyphicons-halflings-regular.eot  
  inflating: dist/fonts/glyphicons-halflings-regular.svg  
  inflating: dist/fonts/glyphicons-halflings-regular.ttf  
  inflating: dist/fonts/glyphicons-halflings-regular.woff  
   creating: dist/js/
  inflating: dist/js/bootstrap.js    
  inflating: dist/js/bootstrap.min.js  
  inflating: dist/js/npm.js
(venv:tutorial)$ mv dist bootstrap
(venv:tutorial)$ rm bootstrap-3.3.1-dist.zip

 

4.2. Ссылаемся в шаблоне на статические файлы

Пакет django.contrib.staticfiles Django включает тег шаблона, который облегчает создание ссылок на статические файлы из вашего шаблона. Для того что бы загрузить библиотеку тегов из django.contrib.staticfiles, используется тегload:

{% load staticfiles %}

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

<link href="{% static 'bootstrap/css/bootstrap.min.css' %}"
    rel="stylesheet" media="screen">

Замете, что мы определяем путь к файлу относительно директории со статическими файлами. Django потом объединит этот путь с содержимым переменной STATIC_URL из файла настроек для того, что бы создать действительный URL файла.

Настройка STATIC_URL сообщает Django, где находится корень ваших статических файлов. По умолчанию, это /static/.

4.3. Простое подключение шаблона

Мы хотим добавить Bootstrap CSS во все наши шаблоны, но хотели бы избежать самоповторения: если мы добавим CSS в каждый шаблон индивидуально, то когда захотим внести изменения (к примеру, добавить другой файл со стилями) нам придется вносить эти изменения во все файлы. Для решения этой проблемы, мы создадим базовый шаблон, который будет расширятся другими шаблонами.

В директории templates приложения contacts создадим base.html:

{% load staticfiles %}
<html>
    <head>
        <link href="{% static 'bootstrap/css/bootstrap.min.css' %}"
            rel="stylesheet" media="screen">
    </head>
    <body>
        {% block content %}
        {% endblock %}
        
        <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
    </body>
</html>

Файл base.html определяет общую структуру для наших страниц, и включает в себя тег block, который заполнят другие шаблоны.

Обновим contact_list.html так, что бы он расширял base.html и заполнял блок content:

{% extends "base.html" %}

{% block content %}
<h1>Contacts</h1>

<ul>
    {% for contact in object_list %}
    <li class="contact">{{ contact }}</li>
    {% endfor %}
</ul>
    
<a href="{% url "contacts-new" %}">add contact</a>
{% endblock %}

 

Примечание переводчика:
Таким же образом поступите с шаблоном edit_contact.html

 

4.4. Обслуживание статических файлов

Мы сказали Django где мы храним наши статические файлы, и указали какую структуру URL использовать, но мы не связали все это вместе. Django не обслуживает статические файлы по умолчанию, и на это есть хорошая причина: использование сервера приложения для обслуживания статических ресурсов является, как минимум, не эффективным. Раздел документации Django о развертывании статических файлов даст вам всю необходимую информацию о вариантах размещения ваших статических ресурсов в CDN или на сервере для статических файлов.

Для разработки, однако, удобно делать все это в одном процессе, и для этого существует помощник. Отредактируем файлaddressbook/urls.py и включим в него помощник staticfiles_urlpatterns doc:

from django.conf.urls import patterns, include, url
from django.contrib.staticfiles.urls import staticfiles_urlpatterns

import contacts.views


urlpatterns = patterns('',
    url(r'^$', contacts.views.ListContactView.as_view(),
        name='contacts-list',),
    url(r'^new$', contacts.views.CreateContactView.as_view(),
        name='contacts-new',),
)

urlpatterns += staticfiles_urlpatterns()

Теперь, если мы запустим сервер и посмотрим в браузере наш проект, то увидим Bootstrapped шаблон в действии.

4.5. Резюме

 

  • Django делает различие между статическими файлами сайта и медиа-файлами, которые были загружены пользователем;
  • пакет django.contrib.staticfiless помогает управлять статическими файлами и обслуживать их в течении разработки;
  • статические файлы могут быть включены вместе с приложением, либо вместе с проектом. Выберете место их размещения, нужны ли они всем приложениям проекта или только некоторым из них;
  • шаблоны могут расширять друг друга используя тег block

Источник: http://habrahabr.ru/post/242261/