- PVSM.RU - https://www.pvsm.ru -

Эффективный Django. Часть 2

Эффективный Django. Часть 2 - 1

Продолжение перевода статей о Django с сайта effectivedjango.com [1]. Наткнулся я на этот сайт во время изучения данного фреймворка. Информация размещенная на этом ресурсе показалась мне полезной, но так как нигде не нашел перевода на русский, решил сделать сие доброе дело сам. Этот цикл статей, как мне думается, будет полезен веб-разработчикам, которые делают только первые шаги в изучении Django.


Оглавление

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

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

Представления Django получают HTTP запрос [8] и возвращает пользователю HTTP ответ [9]:

Эффективный Django. Часть 2 - 2

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

from django.http import HttpResponse

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

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

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

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

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

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

Минимальное представление, реализованное как CBV, является наследником класса View doc [12] и реализует поддерживаемые 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 [13], от которого мы наследовали представление, сам составлен из нескольких примесей, которые реализуют некоторое поведение. Это дает нам большую мощь при малом количестве кода. В нашем случае мы указали модель (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 [14] по конфигурации 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/ [15] в вашем браузере, вы все же получите ошибку TemplateDoesNotExists

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

Эффективный Django. Часть 2 - 3


Большинство общих представлений 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/ [15], вы должны будете увидеть как минимум один контакт, созданный нами ранее через интерактивную оболочку.

Эффективный Django. Часть 2 - 4 [16]

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'а списка контактов.

Примечание: Контекстные переменные в представлениях-классах
Набор переменных доступных в шаблоне, когда он выводится, называется процессоров контекста [17].
Когда вы используете встроенные общие представления, не совсем очевидно какие переменные доступны в контексте. Со временем вы откроете для себя, что их имена (предоставляемые общими представлениями в контекст шаблона) достаточно последовательны — 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 [18] для нашей модели. Так как мы не указали ни одного своего, Django создал один для нас. Как заботливо;
  • если бы мы просто написали {{ form }} мы бы получили табличные ряды; но мы добавляем .as_ul, что заставляет выводить поля ввода как элементы ненумерованного списка. Попробуйте вместо этого использовать .as_p и посмотреть что из этого получится;
  • когда мы выводим форму (пр.: автоматически сгенерированную) выводятся только наши поля, но не обрамляющий форму тег <form> или кнопка отправки <input type="submit" />, так что мы добавим их в шаблон сами;
  • шаблонный тег {% csrf_token %} вставляет скрытое поле, по которому Django определяет, что запрос пришел с вашего проекта и что это не межсайтовая подделка запроса (CSRF [19]). Попробуйте не включать его в шаблон: вы все еще будете иметь доступ к странице, но как только вы попробуете отправить форму — вы получите ошибку;
  • мы используем шаблонный тег 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 [20] для того что бы создать новый контакт:

Эффективный Django. Часть 2 - 5 [21]

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

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

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

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

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

У Django имеется два инструмента, которые помогают писать модульные тесты: Client doc [22] и RequestFactory doc [23]. Оба этих инструмента имеют схожий 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 [24]. Он является тем, что следует из его названия: тестом, который запускается на живом сервере. По умолчанию, Django запустит для вас сервер разработки сразу как только вы запустите эти тесты, но они также могут быть запущены и на другом сервере.

Selenium [25] — это инструмент, который помогает в написание тестов, управляющих браузером, и это то, что мы будем использовать для наших интеграционных тестов. Используя 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 [8] и превращают его в HttpResponse doc [9];
  • общие классы-представления были введены в Django 1.3;
  • они позволяют вам создавать представления пригодные для повторного использования и компоновки;
  • URL'ы определяются в файле urls.py вашего проекта;
  • именование URL'ов, позволяет указывать их в представлении;
  • RequestFactory doc [23] создает запрос (Request) для тестирования представлений с его помощью;
  • LiveServerTestCase doc [24] предоставляет основу для написания интеграционных тестов;

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

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

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

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

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

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 включает тег шаблона [27], который облегчает создание ссылок на статические файлы из вашего шаблона. Для того что бы загрузить библиотеку тегов из 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 [28] сообщает 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 о развертывании статических файлов [29] даст вам всю необходимую информацию о вариантах размещения ваших статических ресурсов в CDN или на сервере для статических файлов.

Для разработки, однако, удобно делать все это в одном процессе, и для этого существует doc [30]:

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.


Буду рад конструктивной критике в комментариях.
Об ошибках и неточностях перевода прошу сообщать в личном сообщении.

Использованное в начале поста изображение создано как вариация изображения [31] пользователя MaGIc2laNTern [32]

Автор: bjakushka

Источник [33]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/75026

Ссылки в тексте:

[1] effectivedjango.com: http://effectivedjango.com/

[2] Введение: http://habrahabr.ru/post/240463/#introduction

[3] Глава 1. Приступая к работе: http://habrahabr.ru/post/240463/#chapter_01

[4] Глава 2. Используем модель: http://habrahabr.ru/post/240463/#chapter_02

[5] Глава 3. Пишем представление: #chapter_03

[6] Глава 4. Используем статические ресурсы: #chapter_04

[7] ⇧: #contents

[8] HTTP запрос: https://docs.djangoproject.com/en/1.7/ref/request-response/#httprequest-objects

[9] HTTP ответ: https://docs.djangoproject.com/en/1.7/ref/request-response/#httpresponse-objects

[10] общие представления: https://docs.djangoproject.com/en/1.7/topics/class-based-views/generic-display/

[11] представления-классы: https://docs.djangoproject.com/en/1.7/topics/class-based-views/

[12] doc: https://docs.djangoproject.com/en/1.7/ref/class-based-views/base/#view

[13] doc: https://docs.djangoproject.com/en/1.7/ref/class-based-views/generic-display/#listview

[14] документацию Django: https://docs.djangoproject.com/en/1.7/ref/urls/#handler403

[15] http://localhost:8080/: http://localhost:8080/

[16] Image: http://habrastorage.org/files/a20/34f/294/a2034f294bf34cb9a0b07640c75c6f86.png

[17] процессоров контекста: https://docs.djangoproject.com/en/1.7/ref/templates/api/#subclassing-context-requestcontext

[18] Django Form: https://docs.djangoproject.com/en/1.7/topics/forms/

[19] CSRF: https://en.wikipedia.org/wiki/Cross-site_request_forgery

[20] http://localhost:8080/new: http://localhost:8080/new

[21] Image: http://habrastorage.org/files/676/f32/3d4/676f323d4e4d49a39c3c5f8eb2404cb3.png

[22] doc: https://docs.djangoproject.com/en/1.7/topics/testing/tools/#the-test-client

[23] doc: https://docs.djangoproject.com/en/1.7/topics/testing/advanced/#the-request-factory

[24] doc: https://docs.djangoproject.com/en/1.7/topics/testing/tools/#liveservertestcase

[25] Selenium: http://www.seleniumhq.org/

[26] Bootstrap: http://getbootstrap.com/

[27] тег шаблона: https://docs.djangoproject.com/en/1.7/ref/templates/builtins/

[28] STATIC_URL: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-STATIC_URL

[29] развертывании статических файлов: https://docs.djangoproject.com/en/1.7/howto/static-files/deployment/

[30] doc: https://docs.djangoproject.com/en/1.7/ref/contrib/staticfiles/#django.contrib.staticfiles.urls.staticfiles_urlpatterns

[31] изображения: http://aruseni.deviantart.com/art/Django-white-and-green-318200642

[32] MaGIc2laNTern: http://habrahabr.ru/users/magic2lantern/

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