From 074785cea106179cb3305637055ab0a009ca74f2 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:42:52 -0500 Subject: initial commit --- .../learn/server-side/django/admin_site/index.html | 340 ++++++++ .../django/development_environment/index.html | 398 +++++++++ .../django/django_assessment_blog/index.html | 320 ++++++++ files/ru/learn/server-side/django/forms/index.html | 650 +++++++++++++++ .../server-side/django/generic_views/index.html | 605 ++++++++++++++ .../learn/server-side/django/home_page/index.html | 359 +++++++++ files/ru/learn/server-side/django/index.html | 66 ++ .../ru/learn/server-side/django/models/index.html | 448 +++++++++++ .../server-side/django/skeleton_website/index.html | 357 +++++++++ .../ru/learn/server-side/django/testing/index.html | 892 +++++++++++++++++++++ .../tutorial_local_library_website/index.html | 74 ++ .../django/web_application_security/index.html | 179 +++++ .../index.html" | 688 ++++++++++++++++ .../index.html" | 259 ++++++ .../index.html" | 680 ++++++++++++++++ .../index.html" | 181 +++++ 16 files changed, 6496 insertions(+) create mode 100644 files/ru/learn/server-side/django/admin_site/index.html create mode 100644 files/ru/learn/server-side/django/development_environment/index.html create mode 100644 files/ru/learn/server-side/django/django_assessment_blog/index.html create mode 100644 files/ru/learn/server-side/django/forms/index.html create mode 100644 files/ru/learn/server-side/django/generic_views/index.html create mode 100644 files/ru/learn/server-side/django/home_page/index.html create mode 100644 files/ru/learn/server-side/django/index.html create mode 100644 files/ru/learn/server-side/django/models/index.html create mode 100644 files/ru/learn/server-side/django/skeleton_website/index.html create mode 100644 files/ru/learn/server-side/django/testing/index.html create mode 100644 files/ru/learn/server-side/django/tutorial_local_library_website/index.html create mode 100644 files/ru/learn/server-side/django/web_application_security/index.html create mode 100644 "files/ru/learn/server-side/django/\320\260\321\203\321\202\320\265\320\275\321\202\320\270\321\204\320\270\320\272\320\260\321\206\320\270\321\217/index.html" create mode 100644 "files/ru/learn/server-side/django/\320\262\320\262\320\265\320\264\320\265\320\275\320\270\320\265/index.html" create mode 100644 "files/ru/learn/server-side/django/\321\200\320\260\320\267\320\262\320\276\321\200\320\260\321\207\320\270\320\262\320\260\320\275\320\270\320\265/index.html" create mode 100644 "files/ru/learn/server-side/django/\321\201\320\265\321\201\321\201\320\270\320\270/index.html" (limited to 'files/ru/learn/server-side/django') diff --git a/files/ru/learn/server-side/django/admin_site/index.html b/files/ru/learn/server-side/django/admin_site/index.html new file mode 100644 index 0000000000..e8094df592 --- /dev/null +++ b/files/ru/learn/server-side/django/admin_site/index.html @@ -0,0 +1,340 @@ +--- +title: 'Руководство Django часть 4: административная панель Django' +slug: Learn/Server-side/Django/Admin_site +translation_of: Learn/Server-side/Django/Admin_site +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}
+ +

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

+ + + + + + + + + + + + +
Предусловия:Сначала завершите: Руководство часть 3: использование моделей.
Цель: +

Уяснить преимущества и ограничения админ-панели Django, научиться использовать её для создания записей для наших моделей.

+
+ +

Обзор

+ +

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

+ +

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

+ +

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

+ +

Регистрация моделей 

+ +

Вначале откройте файл admin.py в папке приложения (/locallibrary/catalog/admin.py). Пока он выглядит так (заметьте, что он уже содержит импорт  django.contrib.admin):

+ +
from django.contrib import admin
+
+# Register your models here.
+
+ +

Зарегистрируйте модели путем вставки следующего текста в нижнюю часть этого файла. Этот код просто импортирует модели и затем вызывает  admin.site.register для регистрации каждой из них.

+ +
from .models import Author, Genre, Book, BookInstance
+
+admin.site.register(Book)
+admin.site.register(Author)
+admin.site.register(Genre)
+admin.site.register(BookInstance)
+
+ +
Примечание: Если вы приняли участие в создании модели для представления естественного языка книги (см. обучающую статью о моделях), импортируйте и зарегистрируйте её тоже!
+ +

Это самый простой способ регистрации модели или моделей. Админ-панель имеет множество настроек. Мы рассмотрим другие способы регистрации ваших моделей ниже.

+ +

Создание суперпользователя

+ +

Для того, чтобы войти в админ-панель, нам необходимо иметь учетную запись пользователя со статусом Staff (сотрудники). Для просмотра и создания записей, пользователю также понадобится разрешение для управления всеми нашими объектами. Вы можете создать учетную запись  "superuser", которая дает полный доступ к сайту и все необходимые разрешения, используя manage.py.

+ +

Для создания суперпользователя вызовите следующую команду из той же папки, где расположен manage.py. Вас попросят ввести имя пользователя, адрес электронной почты и надежный пароль. 

+ +
python3 manage.py createsuperuser
+
+ +

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

+ +
python3 manage.py runserver
+
+
+ +

Вход в админ-панель и ее использование

+ +

Для входа в админ-панель откройте ссылку /admin (например  http://127.0.0.1:8000/admin) и введите логин и пароль вашего нового суперпользователя  (вас перенаправят на login-страницу и потом обратно на /admin после ввода всех деталей).

+ +

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

+ +

Admin Site - Home page

+ +

Кликните на ссылке Add справа от Books, чтобы создать новую книгу (появится диалоговое окно как на картинке внизу). Заметьте, что заголовок каждого поля - это тип используемого виджета, и    help_text (если есть) совпадает со значением, которое вы указали в модели. 

+ +

Введите значение для полей. Вы можете создавать новых авторов или жанры, нажимая на значок "+ ", расположенный рядом с соответствующим полем (или выберите существующее значение из списков, если вы уже создали их). Когда вы закончили, нажмите на SAVE,  Save and add another, или Save and continue editing, чтобы сохранить записи.

+ +

Admin Site - Book Add

+ +
+

Примечание: А сейчас, хотелось бы, чтобы вы добавили несколько книг, авторов и жанров (например,  Фэнтези) в ваше приложение. Удостоверьтесь, что каждый автор и жанр включает пару различных книг (позже, когда мы реализуем представления "list" и "detail", это сделает их более интересными).

+
+ +

После того, когда книги добавлены, для перехода на главную страницу админ-панели кликните на ссылке Home в верхней части страницы. Потом кликните на ссылке Books для отображения текущего списка книг (или на одной из других ссылок, чтобы увидеть список соответствующей модели). После добавления нескольких книг список может выглядеть наподобие скриншота ниже.  Отображается название каждой из книг. Его возвращает метод __str__() в модели Book, созданной в предыдущей статье.

+ +

Admin Site - List of book objects

+ +

Для удаления книги из этого списка выберите чекбокс рядом с ней и действие delete...  из выпадающего списка Action, а затем нажмите кнопку Go. Также можно добавить новую книгу, нажав на кнопку ADD BOOK

+ +

Вы можете редактировать книгу, кликнув по ссылке с ее названием. Страница редактирования книги, приведенная ниже, практически идентична странице добавления новой книги. Основные отличия - это заголовок страницы (Change book) и наличие кнопок Delete, HISTORY и VIEW ON SITE.  Последняя присутствует, так как мы определили метод get_absolute_url() в нашей модели.

+ +

Admin Site - Book Edit

+ +

Теперь перейдите назад на страницу Home (используя ссылку Home в навигационной цепочке вверху страницы) и просмотрите списки Author и Genre. В них уже должно быть несколько элементов, созданных при добавлении новых книг. Если хотите, добавьте еще.

+ +

Однако у вас не будет ни одного экземпляра книги, потому что они не создаются из модели Book (хотя можно создать книгу из модели BookInstance — такова природа поля ForeignKey). Для отображения страницы Add book instance (см. рисунок ниже) вернитесь на страницу Home  и нажмите кнопку Add. Обратите внимание на длинный уникальный Id для идентификации конкретного экземпляра книги в библиотеке.

+ +

Admin Site - BookInstance Add

+ +

Создайте несколько экземпляров для каждой из ваших книг. Установите статус Available (доступен) для некоторых экземплров и On loan (выдан) для остальных. Если статус экземпляра not Available (недоступен), то также установите дату возврата (Due back).

+ +

Вот и все!  Вы изучили как запустить и использовать админ-панель. Также были созданы записи для Book, BookInstance, Genre и Author, которые можно будет использовать после создания наших собственных представлений и шаблонов.

+ +

"Продвинутая" конфигурация

+ +

Django выполняет неплохую работу по созданию базовой админ-панели используя информацию из зарегистрированых моделей:

+ + + +

Можно настроить интерфейс пользователя для упрощения его использования. Некоторые доступные настройки:

+ + + +

В этом разделе рассмотрим некоторые изменения для совершенствования интерфейса пользователя нашей местной библиотеки, а именно: добавление дополнительной информации в списки моделей Book и Author ,  а также улучшение расположения элементов соответствующих представлений редактирования. Пользовательский интерфейс моделей Language and Genre изменять не будем, так как это не даст заметного улучшения, поскольку он содержит только по одному полю!

+ +

Полное руководство по всем возможным вариантам настройки админ-панели можно найти в The Django Admin site (документация Django).

+ +

Регистрация класса ModelAdmin

+ +

Для измененения отображения модели в пользовательском интерфейсе админ-панели, необходимо определить класс ModelAdmin  (он описывает расположение элементов интерфейса, где Model - наименование модели) и зарегистрировать его для использования с этой моделью.

+ +

Давайте начнем с модели Author. Откройте файл admin.py в каталоге приложения (/locallibrary/catalog/admin.py). Закомментируйте исходную регистрацию (используя префикс #) этой модели:

+ +
# admin.site.register(Author)
+ +

Теперь добавьте новый класс AuthorAdmin и зарегистрируйте его как показано ниже:

+ +
# Define the admin class
+class AuthorAdmin(admin.ModelAdmin):
+    pass
+
+# Register the admin class with the associated model
+admin.site.register(Author, AuthorAdmin)
+
+ +

Сейчас мы добавим классы ModelAdmin для моделей Book  BookInstance. Нам снова нужно закомментировать исходную регистрацию:

+ +
#admin.site.register(Book)
+#admin.site.register(BookInstance)
+ +

В этот раз для создания и регистрации новых моделей используем декоратор  @register (он делает то же самое, что и метод admin.site.register()):

+ +
# Register the Admin classes for Book using the decorator
+
+@admin.register(Book)
+class BookAdmin(admin.ModelAdmin):
+    pass
+
+# Register the Admin classes for BookInstance using the decorator
+
+@admin.register(BookInstance)
+class BookInstanceAdmin(admin.ModelAdmin):
+    pass
+
+ +

Пока что все наши admin-классы пустые (см. "pass"), поэтому ничего не изменится ! Добавим код для задания особенностей интерфейса моделей.

+ +

Настройка отображения списков

+ +

Сейчас приложение LocalLibrary отображает всех авторов, используя имя объекта, возвращаемое методом __str__() модели. Это приемлемо, когда есть только несколько авторов, но, если их количество значительно, возможны дубликаты. Чтобы различить их или просто отобразить более интересную информацию о каждом авторе, можно использовать list_display (для добавления дополнительных полей). 

+ +

Замените класс AuthorAdmin кодом, приведенным ниже. Названия полей, которые будут отображаться в списке, перечислены в кортеже list_display в требуемом порядке  (это те же имена, что и в исходной модели).

+ +
class AuthorAdmin(admin.ModelAdmin):
+    list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death')
+
+ +

Перезапустите сайт и перейдите к списку авторов. Указанные поля должны отображаться следующим образом:

+ +

Admin Site - Improved Author List

+ +

Для нашей модели Book добавим отображение полей author и genre. Поле author  - это  внешний ключ (ForeignKey ) связи один к одному, поэтому оно будет представлено значением __str()__  для связанной записи. Замените класс BookAdmin на версию, приведенную ниже.

+ +
class BookAdmin(admin.ModelAdmin):
+    list_display = ('title', 'author', 'display_genre')
+
+ +

К сожалению, мы не можем напрямую поместить поле genre в list_display, так как оно является  ManyToManyField (Django не позволяет это из-за большой "стоимости" доступа к базе данных). Вместо этого мы определим функцию display_genre для получения строкового представления информации (вызов этой функции есть в list_display,  ее определение см. ниже).

+ +
+

Примечание: Получение здесь значения поля genre возможно не самая хорошая идея вследствие "стоимости" операции базы данных. Мы показываем это, потому что вызов функций в ваших моделях может быть очень полезен по другим причинам, например, для добавления ссылки  Delete рядом с каждым пунктом списка.

+
+ +

Добавьте следующий код в вашу модель Book (models.py). В нем создается строка из первых трех значений поля genre (если они существуют) и short_description,  которое может быть использовано в админ-панели.

+ +
    def display_genre(self):
+        """
+        Creates a string for the Genre. This is required to display genre in Admin.
+        """
+        return ', '.join([ genre.name for genre in self.genre.all()[:3] ])
+    display_genre.short_description = 'Genre'
+
+ +

После сохранения модели и обновления админ-панели, перезапустите ее и перейдите на страницу списка Books. Вы должны увидеть список книг, наподобие приведенного ниже:

+ +

Admin Site - Improved Book List

+ +

Модель Genre (и модель Language, если вы ее определили) имеет единственное поле. Поэтому нет необходимости создания для них дополнительных моделей с целью отображения дополнительных полей.

+ +
+

Примечание: целесообразно, чтобы в списке модели BookInstance отображались хотя бы статус и ожидаемая дата возврата. Мы добавили это в качестве "испытания" в конце этой статьи!

+
+ +

Добавление фильтров списка

+ +

Если в вашем списке есть множество элементов, может быть полезной возможность фильтрации отображаемых пунктов. Это выполняется путем перечисления их в атрибуте list_filter. Замените класс BookInstanceAdmin на следующий:

+ +
class BookInstanceAdmin(admin.ModelAdmin):
+    list_filter = ('status', 'due_back')
+
+ +

Представление списка теперь будет содержать панель фильтрации справа. Обратите внимание, как выбирать даты и статус для фильтрации:

+ +

Admin Site - BookInstance List Filters

+ +

Формирование макета с подробным представлением

+ +

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

+ +
+

Примечание: Модели LocalLibrary относительно просты, поэтому нам не нужно менять макет, но мы все равно внесем некоторые изменения, просто чтобы показать вам, как это сделать.

+
+ +

Управление отображаемыми и вложенными полями

+ +

Обновите ваш AuthorAdmin класс, чтобы добавить строку полей, как показано ниже (выделено полужирным шрифтом):

+ +
class AuthorAdmin(admin.ModelAdmin):
+    list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death')
+    fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')]
+
+ +

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

+ +

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

+ +

Admin Site - Improved Author Detail

+ +
+

Примечание: Так же, вы можете использовать exclude атрибут для объявления списка атрибутов, которые будут исключены из формы (все остальные атрибутыв модели, будут отображаться). 

+
+ +

Разделение на секции/Выделение подробного представления

+ +

Вы можете добавлять "разделы" (sections) для группировки связанной информации в модели в форме детализации, используя атрибут fieldsets .

+ +

В модели BookInstance мы имеем информацию соответствия конкретной книги (т.е. name, imprint, and id) и датой когда она вновь станет доступной (status, due_back). Мы можем добавить их  в разные секции, добавив текст жирным шрифтом в наш BookInstanceAdmin класс. 

+ +
@admin.register(BookInstance)
+class BookInstanceAdmin(admin.ModelAdmin):
+    list_filter = ('status', 'due_back')
+
+    fieldsets = (
+        (None, {
+            'fields': ('book','imprint', 'id')
+        }),
+        ('Availability', {
+            'fields': ('status', 'due_back')
+        }),
+    )
+ +

Каждая секция имеет свой заголовок (или None, если заголовок не нужен) и ассоциированный кортеж полей в словаре - формат сложный для описания, но относительно простой для понимания, если вы посмотрите на фрагмент кода, представленный выше.

+ +

Перезапустите сайт и перейдите к списку экземпляров; форма должна отображаться следующим образом:

+ +

Admin Site - Improved BookInstance Detail with sections

+ +

Встроенное редактирование связанных записей

+ +

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

+ +

Вы можете это сделать, объявив inlines, и указав тип TabularInline (горизонтальное расположение) или  StackedInline (вертикальное расположение, так же как и в модели по умолчанию). Вы можете добавить  BookInstance информацию в подробное описание  Book , добавив строки, представленные ниже и распологающиеся рядом с  BookAdmin

+ +
class BooksInstanceInline(admin.TabularInline):
+    model = BookInstance
+
+@admin.register(Book)
+class BookAdmin(admin.ModelAdmin):
+    list_display = ('title', 'author', 'display_genre')
+    inlines = [BooksInstanceInline]
+
+ +

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

+ +

Admin Site - Book with Inlines

+ +

В этом случае, всё, что мы сделали - объявили наш встроенный класс tablular, который просто добавляет все поля из встроенной модели. Вы можете указать все виды дополнительной информации для макета, включая отображаемые поля, их порядок, независимо от того, являются ли они только для чтения или нет, и т. д. (См. TabularInline для получения дополнительной информации). 

+ +
+

Примечание: В этой функции есть некоторые неприятные ограничения! На скриншоте выше у нас есть три существующих экземпляра книги, за которыми следуют три поля для новых экземпляров книги (которые очень похожи!). Было бы лучше НЕ иметь лишних экземпляров книг по умолчанию и просто добавить их с помощью ссылки Add another Book instance или иметь возможность просто перечислять BookInstances как нечитаемые здесь ссылки. Первый вариант можно сделать, установив extra атрибут в 0 в модели BookInstanceInline, попробуйте сами.

+
+ +

Проверьте себя

+ +

Мы многое изучили в этом разделе и теперь настало время вам самостоятельно попробовать несколько вещей:

+ +
    +
  1. Для представления списка BookInstance , добавьте код  для отображения книги, статуса, даты возврата, и id (вместо значения по умолчанию возвращаемого  __str__() ).
  2. +
  3. Добавьте встроенный список перечня Book в представление списка Author , используя тот же самый подход, который мы применили для Book/BookInstance.
  4. +
+ + + +

Заключение

+ +

Вот и всё! Теперь вы узнали, как настроить сайт администрирования как в самой простой, так и в улучшенной форме, о создании суперпользователя и о том, как перемещаться по сайту администратора, просматривать, удалять и обновлять записи. По пути вы создали множество книг, экземпляров, жанров и авторов, которые мы сможем перечислить и отобразить, как только мы создадим собственный вид и шаблоны.

+ +

Дополнительные материалы

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}

diff --git a/files/ru/learn/server-side/django/development_environment/index.html b/files/ru/learn/server-side/django/development_environment/index.html new file mode 100644 index 0000000000..6173f67714 --- /dev/null +++ b/files/ru/learn/server-side/django/development_environment/index.html @@ -0,0 +1,398 @@ +--- +title: Setting up a Django development environment +slug: Learn/Server-side/Django/development_environment +translation_of: Learn/Server-side/Django/development_environment +--- +

{{LearnSidebar}}

+ +

{{PreviousMenuNext("Learn/Server-side/Django/Introduction", "Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django")}}

+ +

Теперь, когда вы знаете, что такое Django, мы покажем вам, как настроить и протестировать среду разработки Django для Windows, Linux (Ubuntu) и Mac OS X - какую бы операционную систему вы не использовали, эта статья должна дать вам все, что необходимо для возможности начать разрабатывать приложения Django.

+ + + + + + + + + + + + +
Требования:Знание как открыть терминал / командную строку, как устанавливать программные пакеты в операционной системе вашего компьютера.
Задача:Создать среду разработки для Django (1.10) и запустить её на вашем компьютере.
+ +

Обзор среды разработки Django

+ +

Django упрощает настройку собственного компьютера, чтобы вы могли начать разработку веб-приложений. В этом разделе объясняется, что входит в состав среды разработки, и дается обзор некоторых параметров настройки и конфигурации. В оставшейся части статьи объясняется рекомендуемый метод установки среды разработки Django на Ubuntu, Mac OS X и Windows, и как вы можете ее протестировать.

+ +

Что такое среда разработки Django?

+ +

Среда разработки - это установка Django на вашем локальном компьютере, которую вы можете использовать для разработки и тестирования приложений Django до их развертывания в производственной среде.
+
+ Основными инструментами, которые предоставляет сам Django, является набор скриптов Python для создания и работы с проектами Django, а также простой веб-сервер разработки, который можно использовать для тестирования локальных (то есть на вашем компьютере, а не на внешнем веб-сервере) веб-приложений Django на веб-браузере вашего компьютера.
+
+ Существуют и другие периферийные инструменты, являющиеся частью среды разработки, которые мы не будем освещать здесь. К ним относятся такие вещи, как текстовый редактор или IDE для редактирования кода, и инструмент управления исходным кодом, например Git, для безопасного управления различными версиями вашего кода. Мы предполагаем, что у вас уже установлен текстовый редактор.

+ +

Какие есть разновидности установки Django ?

+ +

Django очень гибок с точки зрения способа и места установки и настройки. Django может быть:

+ + + +

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

+ +
+

Замечание: Другие возможные способы установки можно найти в официальной документации Django. Мы ссылаемся на соответствующие документы.

+
+ +

Какие операционные системы поддерживаются?

+ +

Веб-приложения Django можно запускать почти на любых машинах, которые поддерживают язык программирования Python 3, среди прочих: Windows, Mac OS X, Linux/Unix, Solaris. Почти любой компьютер имеет необходимую производительность для запуска Django во время разработки.

+ +

В этой статье мы предоставляем инструкции для Windows, Mac OS X, and Linux/Unix.

+ +

Какую версию Python стоит использовать?

+ +

Мы рекомендуем использовать самую последнюю доступную версию - на момент написания статьи это Python 3.6.

+ +
+

Замечание: Python 2.7 не может быть использован вместе с Django 2.0 (последние поддерживаемые серии для Python 2.7 - Django 1.11.x).

+
+ +

Откуда можно скачать Django?

+ +

Для загрузки Django можно воспользоваться 3 источниками:

+ + + +

Данный материал описывает способ установки Django из PyPi с целью получения последней стабильной версии.

+ +

Какую базу данных выбрать?

+ +

Django поддерживает 4 основных базы данных (PostgreSQL, MySQL, Oracle и SQLite), также есть публичные библиотеки, которые предоставляют разные уровни поддержки других SQL и NoSQL баз данных. Мы рекомендуем вам выбрать одинаковую БД для обеих рабочей и разрабатываемой сред (несмотря на  то, что Django нивелирует множество различий баз данных при помощи Object-Relational Mapper (ORM), все равно возможны потенциальные проблемы, которых лучше избегать.

+ +

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

+ +
+

Замечание: Django сконфигурирован для использования SQLite по умолчанию, при создании вашего проекта с использованием стандартных инструментов (django-admin). Это отличный выбор для начала работы, потому что он не требует дополнительной настройки.

+
+ +

Глобальная установка или установка в виртуальную среду Python?

+ +

Когда вы устанавливаете Python 3 на свой компьютер, вы получаете единую глобальную среду (набор установленных пакетов) для вашего кода Python, которая доступна любому коду на компьютере. Вы можете установить любые пакеты Python, необходимые вам в этой среде, но только одну конкретную версию конкретного пакета.

+ +
+

Замечание: Установленные в глобальную среду приложения Python потенциально могут конфликтовать друг с другом (т.е. если они зависят от разных версий одного и того же пакета).

+
+ +

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

+ +

По этой причине опытные разработчики Python / Django часто предпочитают вместо этого запускать свои приложения Python в независимых виртуальных средах Python. Это позволяет разработчикам иметь несколько разных сред Django на одном компьютере. Команда разработчиков Django сама рекомендует использовать виртуальные среды Python!

+ +

Этот модуль предполагает вашу установку Django в виртуальную среду, и мы покажем, как это сделать.

+ +

Установка Python 3

+ +

Для использования Django вам необходимо установить Python 3 на свою операционную систему. Вам также понадобится инструмент Python Package Index — pip3 — который используется для управления (установка, обновление и удаление) библиотек/пакетов Python, используемых Django и другими вашими приложениями Python.

+ +

Этот раздел коротко описывает то, как вы можете проверить имеющиеся версии и при необходимости установить новые для Ubuntu Linux 16.04, Mac OS X, and Windows 10.

+ +
+

Замечание: В зависимости от платформы, вы можете иметь возможность установки Python/pip из собственного менеджера пакетов операционной системы или при помощи других инструментов. Для большинства платформ вы можете скачать необходимые установочные файлы из https://www.python.org/downloads/ и установить их при помощи соответствующего специфичного для платформы метода.

+
+ +

Ubuntu 16.04

+ +

Ubuntu Linux включает в себя Python 3 по умолчанию. Вы можете удостовериться в этом, выполнив следующую команду в терминале:

+ +
python3 -V
+ Python 3.5.2
+ +

Однако, инструмент Python Package Index, при помощи которого вам нужно будет установаить пакеты для  Python 3 (включая Django), по умолчанию не установлен. Вы можете установить pip3 через терминал bash при помощи:

+ +
sudo apt-get install python3-pip
+
+ +

Mac OS X

+ +

Mac OS X "El Capitan" не включает Python 3. Вы можете удостовериться в этом, выполнив следующую команду в терминале:

+ +
python3 -V
+ -bash: python3: command not found
+ +

Вы можете легко установить Python 3 (вместе с инструментом pip3) с python.org:

+ +
    +
  1. Скачайте нужный установочный файл: +
      +
    1. Перейдите в https://www.python.org/downloads/
    2. +
    3. Нажмите на кнопку Скачать Python 3.6.4 (точная основная версия может отличаться).
    4. +
    +
  2. +
  3. Найдите файл при помощи Finder, дважды кликните по нему и следуйте подсказкам по установке.
  4. +
+ +

Удостовериться в успешной установке вы можете проверкой на наличие  Python 3, как показано ниже:

+ +
python3 -V
+ Python 3.5.20
+
+ +

Подобным образом вы можете проверить установку pip3, отобразив список доступных пакетов:

+ +
pip3 list
+ +

Windows 10

+ +

Windows не включает Python по умолчанию, но вы можете легко установить его (вместе с инструментом pip) с python.org:

+ +
    +
  1. Скачайте нужный установочный файл: +
      +
    1. Перейдите в https://www.python.org/downloads/
    2. +
    3. Нажмите на кнопку Скачать Python 3.6.4 (точная основная версия может отличаться).
    4. +
    +
  2. +
  3. Установите Python, дважды кликнув на скачанный файл и следуя инструкциям по установке.
  4. +
+ +

После этого вы сможете подтвердить успешную установку Python путем выполнения следующего текста в командной строке:

+ +
py -3 -V
+ Python 3.5.2
+
+ +

Установщик Windows включает в себя pip3 (менеджер пакетов Python) по умолчанию. Вы можете отобразить список установленных пакетов, как показано далее:

+ +
pip list
+
+ +
+

Замечание: Установщик должен сделать все, что необходимо для корректной работы указанной команды. Однако, если вы видите сообщение о том, что Python не может быть найден, вам может потребоваться добавить его в системный путь.

+
+ +

Использование Django внутри виртуальной среды Python

+ +

Для создания виртуальных сред мы будем использовать библиотеки virtualenvwrapper (Linux и macOS X) и virtualenvwrapper-win (Windows), которые в свою очередь обе используют инструмент virtualenv. Инструмент обертки предоставляет совместимый интерфейс для управления интерфейсами на всех платформах.

+ +

Установка ПО виртуальной среды

+ +

Установка виртуальной среды для Ubuntu

+ +

После установки Python и pip вы можете установить virtualenvwrapper (который включает в себя virtualenv). Вы можете либо воспользоваться официальной инструкций по установке отсюда, либо следовать следующим инструкциям:

+ +

Установите инструмент при помощи pip3:

+ +
sudo pip3 install virtualenvwrapper
+
+ +

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

+ +
export WORKON_HOME=$HOME/.virtualenvs
+export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
+export VIRTUALENVWRAPPER_VIRTUALENV_ARGS=' -p /usr/bin/python3 '
+export PROJECT_HOME=$HOME/Devel
+source /usr/local/bin/virtualenvwrapper.sh
+
+ +

Затем перезагрузите файл загрузки, выполнив в терминале следующую команду:

+ +
source ~/.bashrc
+
+ +

В этот момент вы должны увидеть запуск группы скриптов, как показано ниже:

+ +
virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/premkproject
+virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postmkproject
+...
+virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/preactivate
+virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/postactivate
+virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/get_env_details
+
+ +

Теперь вы можете создать новую виртуальную среду при помощи команды mkvirtualenv.

+ +

Установка виртуальной среды для macOS X

+ +

Установка virtualenvwrapper на macOS X почти идентична Ubuntu (и снова вы можете воспользоваться либо официальными, либо следующими инструкциями).

+ +

Установите инструмент при помощи pip3:

+ +
sudo pip3 install virtualenvwrapper
+
+ +

Затем добавьте следующие строки в конец вашего файла загрузки программной оболочки:

+ +
export WORKON_HOME=$HOME/.virtualenvs
+export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
+export PROJECT_HOME=$HOME/Devel
+source /usr/local/bin/virtualenvwrapper.sh
+
+ +
+

Замечание: Переменная VIRTUALENVWRAPPER_PYTHON указывает на обычное расположение Python3. Если virtualenv не работает во время тестирования, то вам следует проверить, находится ли интерпертатор Python в нужном расположении (и затем поменять его соответствующим образом в значении переменной).

+
+ +

Эти строки такие же, как в случае с Ubuntu, но файл загрузки в вашей домашней директории назван иначе - .bash_profile

+ +
+

Замечание: Если вы не можете найти и изменить .bash_profile при помощи Finder, то можно также открыть его при помощи редактора терминала nano.

+ +

Команды в этом случае выглядят примерно так:

+ + + +
cd ~ # Navigate to my home directory
+ls -la #List the content of the directory. You should see .bash_profile
+nano .bash_profile # Open the file in the nano text editor, within the terminal
+# Scroll to the end of the file, and copy in the lines above
+# Use Ctrl+X to exit nano, Choose Y to save the file.
+
+
+ +

После этого перезагрузите файл загрузки путем выполнения следующей команды в терминале:

+ +
source ~/.bash_profile
+
+ +

В этот момент вы должны увидеть запуск группы скриптов (те же скрипты, что и в случае установки на Ubuntu).

+ +

Теперь вы должны иметь возможность создания новой виртуальной среды при помощи команды mkvirtualenv.

+ +

Установка виртуальной среды для Windows 10

+ +

Установка virtualenvwrapper-win еще более проста, чем установка virtualenvwrapper, потому что вам не нужно настраивать расположения сохранения информации о виртаульной среде инструментом (эти значения заданы по умолчанию). Все, что вам нужно сделать, это запустить следующую команду в командной строке:

+ +
pip3 install virtualenvwrapper-win
+ +

Теперь вы можете создать новую виртуальную среду при помощи команды mkvirtualen.

+ +

Создание виртуальной среды

+ +

После установки virtualenvwrapper и virtualenvwrapper-win работа с виртуальными средами становится одинаковой для всех платформ.

+ +

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

+ +
$ mkvirtualenv my_django_environment
+Running virtualenv with interpreter /usr/bin/python3 ...
+virtualenvwrapper.user_scripts creating /home/ubuntu/.virtualenvs/t_env7/bin/get_env_details
+(my_django_environment) ubuntu@ubuntu:~$
+ +

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

+ +
+

Замечание: С этого момента в этой статье (и всем модуле) пожалуйста учитывайте, что любые команды запускаются в виртуальной среде Python, как та, что мы показали выше.

+
+ +

Использование виртуальной среды

+ +

Есть еще несколько полезных команд, которые вам следует знать (в документации по инструменту их гораздо больше, но эти вы будете использовать регулярно):

+ + + +

Установка Django

+ +

После создания виртуальной среды и вызова workon для входа в нее вы можете использовать pip3 для установки Django. 

+ +
pip3 install django
+
+ +

Вы можете проверить установку Django, выполнив следующую команду (она просто проверяет, что Python может найти модуль Django):

+ +
# Linux/Mac OS X
+python3 -m django --version
+ 1.10.10
+
+# Windows
+py -3 -m django --version
+ 1.10.10
+
+ +
+

Замечание: Для Windows вы запускаете скрипты Python 3 с префиксом команды py -3, в то время как для Linux/Mac OSX префикс - python3.

+
+ +
+

Важно: В оставшейся части материала используется вариант команды Linux для вызова Python 3 (python3) . Если вы работаете в Windows, то просто замените этот префикс на: py -3

+
+ +

Проверка вашей установки

+ +

Указанная выше проверка работает, но не представляет особого интереса.Более интересная проверка заключается в создании шаблона проекта и проверки его работы. Для ее выполнения перейдите в командной строке/терминале в место, где планируете сохранять приложения Django. Создайте папку для теста и перейдите в нее.

+ +
mkdir django_test
+cd django_test
+
+ +

Затем вы можете создать шаблон сайта "mytestsite" при помощи инструмента django-admin. После создания сайта вы можете перейти в папку, где найдете основной скрипт для управления проектами с именем manage.py.

+ +
django-admin startproject mytestsite
+cd mytestsite
+ +

Мы можем запустить веб-сервер разработки из этой папки при помощи manage.py и команды runserver, как показано ниже.

+ +
$ python3 manage.py runserver 
+Performing system checks...
+
+System check identified no issues (0 silenced).
+
+You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
+Run 'python manage.py migrate' to apply them.
+
+September 19, 2016 - 23:31:14
+Django version 1.10.1, using settings 'mysite.settings'
+Starting development server at http://127.0.0.1:8000/
+Quit the server with CONTROL-C.
+
+ +
+

Замечание: Указанная команда демонстрирует выполнение для Linux/Mac OS X. В настоящий момент вы можете проигнорировать предупреждения о  "13 непримененных миграциях"!

+
+ +

Как только сервер запущен, вы можете посмотреть сайт, перейдя по следующему адресу в вашем браузере: http://127.0.0.1:8000/. Вы должны увидеть, что сайт выглядит следующим образом:

+ +

Django Skeleton App Homepage

+ + + +

Заключение

+ +

Теперь у вас на компьютере установлена и запущена среда разработки Django.

+ +

В разделе проверки вам коротко был показан способ создания нового сайта на Django при помощи django-admin startproject и его запуск в вашем браузере при помощи веб-сервера разработки (python3 manage.py runserver). В следующей статье мы подробнее рассмотрим этот процесс создания простого, но полноценного веб-приложения.

+ +

В этом модуле

+ + diff --git a/files/ru/learn/server-side/django/django_assessment_blog/index.html b/files/ru/learn/server-side/django/django_assessment_blog/index.html new file mode 100644 index 0000000000..59a1d9e1d2 --- /dev/null +++ b/files/ru/learn/server-side/django/django_assessment_blog/index.html @@ -0,0 +1,320 @@ +--- +title: 'Задание: DIY Джанго мини блог' +slug: Learn/Server-side/Django/django_assessment_blog +tags: + - Аттестация + - Бэкэнд + - Бэкэнд программирование + - Джанго + - Изучение + - Начинающий + - блог + - скриптовый кодинг +translation_of: Learn/Server-side/Django/django_assessment_blog +--- +
{{LearnSidebar}}
+ +
{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}
+ +

В этом задании вы будете оценивать знания Django, которые вы приобрели в Django Web Framework (Python), чтобы создать очень простой блог.

+ + + + + + + + + + + + +
Предпосылки:Перед этим заданием, вы должны были проработать все статьи этого модуля.
Задача:Проверить понимание основ Django, включая конфигурации URL, модели, представления, формы и шаблоны.
+ +

Краткое описание проекта

+ +

Страницы, которые должны отображаться, их URL-адреса и другие требования, перечислены ниже:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PageURLRequirements
Home page/ and /blog/Страница индекса, описывающая сайт.
List of all blog posts/blog/blogs/ +

Список всех сообщений блога:

+ +
    +
  • Доступно для всех пользователей из боковой панели.
  • +
  • Список отсортирован по дате публикации (от самого нового до самого старого).
  • +
  • Список разбит на группы по 5 статьям.
  • +
  • Элементы списка отображают название блога, дату публикации и автора.
  • +
  • Названия сообщений блога связаны с страницами подробных сведений о блоге.
  • +
  • Blogger (имена авторов) связаны с страницами подробных сведений о блоге.
  • +
+
Blog author (blogger) detail page/blog/blogger/<author-id> +

Информация для указанного автора (по id) и список постов:

+ +
    +
  • Доступен для всех пользователей по ссылкам на автора в сообщениях в блогах и т. Д.
  • +
  • Содержит некоторые биографические данные в blogger/author.
  • +
  • Список отсортирован по дате добавления (от новых к старым).
  • +
  • Не разбит на страницы.
  • +
  • Элементы списка отображают только имя сообщения в блоге и дату публикации.
  • +
  • Названия блога связаны со страницей блога.
  • +
+
Blog post detail page/blog/<blog-id> +

Сведения о блоге.

+ +
    +
  • Доступно для всех пользователей из списков блога.
  • +
  • Страница содержит сообщение в блоге: имя, автор, дата публикации и содержание.
  • +
  • Комментарии к сообщению в блоге должны отображаться внизу.
  • +
  • Комментарии должны быть отсортированы по порядку: от старых до самых последних.
  • +
  • Содержит ссылку для добавления комментариев на конец для зарегистрированных пользователей (см. Страницу формы комментариев)
  • +
  • В блогах и комментариях должен отображаться только обычный текст. Нет необходимости поддерживать какую-либо разметку HTML (например, ссылки, изображения, полужирный / курсив и т. Д.).
  • +
+
List of all bloggers/blog/bloggers/ +

Список блоггеров в системе:

+ +
    +
  • Доступный для всех пользователей с боковой панели сайта
  • +
  • Имя блогера связано с блогом автора страницы.
  • +
+
Comment form page/blog/<blog-id>/create +

Создать комментарий для публикации в блоге:

+ +
    +
  • Доступно только зарегистрированным пользователям (только) из ссылки внизу страницы с подробными сведениями блога.
  • +
  • Отображает форму с описанием для ввода комментариев (дата публикации и блог недоступны для редактирования).
  • +
  • После того, как комментарий будет опубликован, страница будет перенаправлена ​​на связанную страницу блога.
  • +
  • Пользователи не могут редактировать или удалять свои сообщения.
  • +
  • Вышедшие пользователи будут перенаправлены на страницу входа в систему, чтобы добавить комментарии. После входа в систему они будут перенаправлены на страницу блога, которую они хотели бы прокомментировать.
  • +
  • Страницы комментариев должны содержать имя / ссылку на комментарий блога.
  • +
+
User authentication pages/accounts/<standard urls> +

Стандартные страницы аутентификации Django для входа, выхода и установки пароля:

+ +
    +
  • Вход / выход должен быть доступен через ссылки боковой панели.
  • +
+
Admin site/admin/<standard urls> +

Админ-сайт должен быть включен, чтобы разрешить создание / редактирование / удаление сообщений в блогах, авторов блога и комментариев блога (это механизм для создания блоггеров в блогах):

+ +
    +
  • В админ панеле должен отображаться список комментариев в строке (внизу каждого сообщения в блоге).
  • +
  • Имена комментариев в админке создаются усеканием описания комментария до 75 знаков
  • +
  • Другие типы записей могут использовать базовую регистрацию.
  • +
+
+ +

Кроме того, вы должны написать некоторые базовые тесты для проверки:

+ + + +
+

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

+
+ +

В следующем разделе показаны скриншоты сайта, который выполняет перечисленные выше требования.

+ +

Скриншоты

+ +

Следующий скриншот - пример того, что должна выводить готовая программа.

+ +

Список всех сообщений в блоге

+ +

Это отображает список всех сообщений в блоге (доступны из ссылки "All blogs" на боковой панели). Что нужно отметить:

+ + + +

List of all blogs

+ +

Список всех блоггеров

+ +

 

+ +

Это ссылки на всех блоггеров в "All bloggers" по ссылке, которая на боковой панели. В этом случае мы можем увидеть на боковой панели, что ни один пользователь не вошел в систему.

+ +

List of all bloggers

+ +

Подробная страница блога

+ +

Это показывает подробную страницу для конкретного блога.

+ +

Blog detail with add comment link

+ +

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

+ +

Comment link when not logged in

+ +

Добавить форму комментария

+ +

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

+ +

Add comment form

+ +

Об авторе

+ +

Здесь отображается информация о блоггере вместе со списком его блогов.

+ +

Blogger detail page

+ +

Завершающие шаги

+ +

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

+ +
    +
  1. Создайте скелет проекта и веб-приложение для сайта (как описано в  Django Tutorial Part 2: Creating a skeleton website). Вы можете использовать «diyblog» для имени проекта и «blog» для имени приложения.
  2. +
  3. Создавайте модели для записей в блогах, комментариев и любых других необходимых объектов. +
      +
    • Каждый комментарий будет иметь только один блог, но блог может иметь много комментариев.
    • +
    • Посты в блоге и комментарии должны быть отсортированы по дате поста.
    • +
    • Не каждый пользователь обязательно будет автором блога, хотя любой пользователь может быть комментатором.
    • +
    • Блог автора также должен включать информацию о себе.
    • +
    +
  4. +
  5. Запустите миграцию для новых моделей и создайте суперпользователя.
  6. +
  7. Используйте админ панель, чтобы создать какой-нибудь пример блога и комментарии в блогах.
  8. +
  9. Создайте представления, шаблоны, и URL-конфигурации для публикации блога и списка страниц блоггера.
  10. +
  11. Создайте представления, шаблоны, и URL-конфигурации для публикации блога и подробных страниц блоггера.
  12. +
  13. Создайте страницу с формой для добавления новых комментариев (не забудьте сделать это доступным только для зарегистрированных пользователей!)
  14. +
+ +

Советы и подсказки

+ +

Этот проект очень похож на  LocalLibrary учебник. Вы сможете настроить скелет, поведение входа пользователя / выхода из системы, поддержку статических файлов, представлений, URL-адресов, форм, базовых шаблонов и конфигурации админ-панели, используя почти все те же подходы.

+ +

Некоторые общие рекомендации:

+ +
    +
  1. Индексная страница (index page) может быть реализована в качестве основной функции представления и шаблона (как и для locallibrary).
  2. +
  3. Просмотр списка публикаций блога и блогеров, а также подробное представление для сообщений в блоге можно создать с помощью  generic list and detail views.
  4. +
  5. Список постов в блоге конкретного автора может быть создан с помощью общего списка Blog list view и фильтрация для объекта блога, соответствующего указанному автору. +
      +
    • Вам придется реализовать  get_queryset(self) для фильтрации (как и в нашем классе библиотеки LoanedBooksAllListView) и получить информацию об авторе из URL-адреса.
    • +
    • Вам также необходимо передать имя автора на страницу в контексте. Чтобы сделать это в представлении на основе классов, вам необходимо реализовать  get_context_data() (обсуждается ниже).
    • +
    +
  6. +
  7. Форма добавления комментариев может быть создана с использованием функционального представления (и связанной модели и формы) или с использованием общего CreateView. Если вы используете CreateView (рекомендуется): +
      +
    • Вам также нужно будет передать имя блога на страницу комментариев в контексте (реализовать  get_context_data() как обсуждается ниже).
    • +
    • Форма должна отображать только комментарий «описание» для записи пользователя (дата и связанная с ними запись в блоге не должны редактироваться). Поскольку они не будут в форме, ваш код должен будет установить автора комментария в  form_valid() функцию, поэтому он может быть сохранен в модели (as described here — Django docs). В этой же функции мы устанавливаем связанный блог. Возможная реализация показана ниже (pk это идентификатор блога, переданный из URL / URL конфигурации ). +
          def form_valid(self, form):
      +        """
      +        Add author and associated blog to form data before setting it as valid (so it is saved to model)
      +        """
      +        #Add logged-in user as author of comment
      +        form.instance.author = self.request.user
      +        #Associate comment with blog based on passed id
      +        form.instance.blog=get_object_or_404(Blog, pk = self.kwargs['pk'])
      +        # Call super-class form validation behaviour
      +        return super(BlogCommentCreate, self).form_valid(form)
      +
      +
    • +
    •  Для успешного перенаправления после проверки формы вам нужно будет указать URL-адрес;  это должен быть оригинальный блог. Для этого вам нужно будет переопределить  get_success_url() и «обратный» URL-адрес для исходного блога. Вы можете получить требуемый ID блога, используя self.kwargs атрибут, как показано в методе form_valid() выше.
    • +
    +
  8. +
+ +

Мы кратко говорили о передаче контекста шаблону в представлении на основе классов в теме  Django Tutorial Part 6: Generic list and detail views. Для этого вам нужно переопределить get_context_data() (сначала получить существующий контекст, обновить его любыми дополнительными переменными, которые вы хотите передать шаблону, а затем вернуть обновленный контекст). Например, фрагмент кода ниже показывает, как вы можете добавить объект blogger в контекст на основе его BlogAuthor id.

+ +
class SomeView(generic.ListView):
+    ...
+
+    def get_context_data(self, **kwargs):
+        # Call the base implementation first to get a context
+        context = super(SomeView, self).get_context_data(**kwargs)
+        # Get the blogger object from the "pk" URL parameter and add it to the context
+        context['blogger'] = get_object_or_404(BlogAuthor, pk = self.kwargs['pk'])
+        return context
+
+ +

Аттестация

+ +

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

+ +

После того, как вы завершили этот модуль, вы также закончили весь контент MDN для изучения базового веб-сайта на сервере Django! Надеемся, вам понравится этот модуль и вы почувствуете, что у вас есть хорошее понимание основ!

+ +

{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}

+ +

 

+ +

В этом модуле

+ + + +

 

diff --git a/files/ru/learn/server-side/django/forms/index.html b/files/ru/learn/server-side/django/forms/index.html new file mode 100644 index 0000000000..3b155495a9 --- /dev/null +++ b/files/ru/learn/server-side/django/forms/index.html @@ -0,0 +1,650 @@ +--- +title: 'Руководство часть 9: Работа с формами' +slug: Learn/Server-side/Django/Forms +tags: + - HTML + - django + - Для начинающих + - Руководство + - Серверная сторона + - Формы + - Формы Django +translation_of: Learn/Server-side/Django/Forms +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}
+ +

На этом уроке мы покажем вам процесс работы с HTML-формами в Django. В частности, продемонстрируем самый простой способ построения формы для создания, обновления и удаления экземпляров модели. При этом мы расширим сайт  местной библиотеки, чтобы библиотекари могли обновлять книги, создавать, обновлять и удалять авторов, используя наши собственные формы (а не возможности приложения администратора).

+ + + + + + + + + + + + +
Необходимые условия:Завершите все предыдущие учебные темы, в том числе Django руководство часть 8: Аутентификация пользователя и права доступа.
Цель:Научиться понимать, как создавать формы, чтобы получать информацию от пользователей и обновлять базу данных. Узнать, как обобщенные классы отображения форм могут значительно упростить процесс создания форм при работе с одной моделью.
+ +

Обзор

+ +

HTML форма - это группа из одного или нескольких полей/виджетов на веб-странице, которая используется для сбора информации от пользователей для последующей отправки на сервер. Формы являются гибким механизмом сбора пользовательских данных, поскольку имеют целый набор виджетов для ввода различных типов данных, как то: текстовые поля, флажки, переключатели, установщики дат и т. д. Формы являются относительно безопасным способом взаимодействия пользовательского клиента и сервера, поскольку они позволяют отправлять данные в POST-запросах, применяя защиту от Межсайтовой подделки запроса (Сross Site Request Forgery - CSRF)

+ +

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

+ +

Admin Site - Book Add

+ +

Работа с формами может быть достаточно сложной! Разработчикам надо описать форму на HTML, проверить ее валидность, а также, на стороне сервера, проверять введенные пользователем данные (а возможно и на стороне клиента), далее, в случае возникновения ошибок необходимо опять показать пользователю форму и, при этом, указать на то, что пошло не так, в случае же успеха проделать с данными необходимые операции и каким-то образом проинформировать об этом пользователя. Django, при работе с формами, берет большую часть, описанной выше работы, на себя. Он предоставляет фреймворк, который позволяет вам определять форму и ее поля программно, а затем использовать эти объекты и для генерации непосредственно кода HTML-формы, и для контроля за процессом валидации и других пользовательский взаимодействий с формой.

+ +

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

+ +

Формы HTML

+ +

Начнем мы с краткого обзора Форм HTML. Рассмотрим простую форму HTML, имеющую поле для ввода имени некоторой "команды" ("team"), и, связанную с данным полем, текстовой меткой:

+ +

Simple name field example in HTML form

+ +

Форма описывается на языке HTML как набор элементов, расположенных внутри парных тэгов <form>...</form>. Любая форма содержит как минимум одно поле-тэг input типа type="submit".

+ +
<form action="/team_name_url/" method="post">
+    <label for="team_name">Enter name: </label>
+    <input id="team_name" type="text" name="name_field" value="Default name for team.">
+    <input type="submit" value="OK">
+</form>
+ +

Здесь у нас только одно поле для ввода имени команды, но форма может иметь любое количество элементов ввода и, связанных с ними, текстовых меток. Атрибут элемента type определяет какого типа виджет будет показан в данной строке. Атрибуты name и  id используются для однозначной идентификации данного поля в JavaScript/CSS/HTML, в то время как value содержит значение для поля (когда оно показывается в первый раз). Текстовая метка добавляется при помощи тэга label (смотрите "Enter name", в предыдущем фрагменте) и имеет атрибут for со значением идентификатора id, того поля, с которым данная текстовая метка связана.

+ +

Элемент input  с  type="submit" будет показана как кнопка (по умолчанию), нажав на которую, пользователь отправляет введенные им данные на сервер (в данном случае только значение поля с идентификатором team_name). Атрибуты формы определяют каким методом будут отправлены данные на сервер (атрибут method) и куда  (атрибут action):

+ + + +

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

+ +

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

+ +

Процесс управления формой в Django

+ +

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

+ +

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

+ +

Updated form handling process doc.

+ +

В соответствии с данной диаграммой, главными моментами, которые берут на себя формы Django являются:

+ +
    +
  1. Показ формы по умолчанию при первом запросе со стороны пользователя. +
      +
    • Форма может содержать пустые поля (например, если вы создаете новую запись в базе данных), или они (поля) могут иметь начальные значения (например, если вы изменяете запись, или хотите заполнить ее каким-либо начальным значением).
    • +
    • Форма в данный момент является несвязанной, потому что она не ассоциируется с какими-либо введенными пользователем данными (хотя и может иметь начальные значения).
    • +
    +
  2. +
  3. Получение данных из формы (из HTML-формы) со стороны клиента и связывание их с формой (классом формы) на стороне сервера. +
      +
    • Связывание данных с формой означает, что данные, введенные пользователем, а также возможные ошибки, при переотрисовке в дальнейшем, будут относиться именно к данной форме, а не к какой-либо еще.
    • +
    +
  4. +
  5. Очистка и валидация данных. +
      +
    • Очистка данных  - это их проверка на наличие возможных значений, или вставок в поля ввода (то есть очистка - это удаление неправильных символов, которые потенциально могут использоваться для отправки вредоносного содержимого на сервер), с последующей конвертацией очищеных данных в подходящие типы данных Python.
    • +
    • Валидация проверяет, значения полей (например, правильность введенных дат, их диапазон и так далее)
    • +
    +
  6. +
  7. Если какие-либо данные являются неверными, то выполнение перерисовки формы, но на этот раз, с уже введенными пользователем данными и сообщениями об ошибках, описывающих возникшие проблемы.
  8. +
  9. Если все данные верны, то исполнение необходимых действий (например, сохранение данных, отправка писем, возврат результата поиска, загрузка файла и так далее)
  10. +
  11. Когда все действия были успешно завершены, то перенаправление пользователя на другую страницу.
  12. +
+ +

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

+ +
+

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

+
+ +

HTML-форма обновления книги. Класс Form и функция отображения

+ +

Данная глава будет посвещена процессу создания страницы, которая позволит библиотекарям обновлять информацию о книгах (в частности, вводить дату возврата книги). Для того, чтобы сделать это мы создадим форму, которая позволит пользователям вводить значение дат. Мы проинициализируем поле датой, равной 3 неделям, начиная с текущего дня, и, для того, чтобы библотекарь не имел возможность ввести "неправильную" дату, мы добавим валидацию введенных значений, которая будет проверять, чтобы введенная дата не относилась к прошлому, или к слишком далекому будущему. Когда будет получена "правильная" дата мы запишем ее значение в поле  BookInstance.due_back.

+ +

Данный пример будет использовать отображение на основе функции, а также продемонстрирует работу с классом Form. Следующие разделы покажут изменения, которые вам надо сделать, чтобы продемонстрировать работу форм в проекте LocalLibrary.

+ +

Класс Form

+ +

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

+ +

Объявление класса формы Form

+ +

Синтаксис объявления для класса формы Form очень похож на объявление класса модели Model, он даже использует те же типы полей (и некоторые похожие параметры). Это существенный момент, поскольку в обоих случаях нам надо убедиться, что каждое поле управляет правильным типом данных, соответствует нужному диапазону (или другому критерию) и имеет необходимое описание для показа/документации.

+ +

Для того, чтобы создать класс с функционалом базового класса Form мы должны импорировать библиотеку forms, наследовать наш класс от класса Form, а затем объявить поля формы. Таким образом, самый простой класс формы в нашем случае будет иметь вид, показанный ниже:

+ +
from django import forms
+
+class RenewBookForm(forms.Form):
+    renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
+
+ +

Поля формы

+ +

В  нашем случае мы имеем одно поле типа DateField, которое служит для ввода обновленной даты возврата книги, которое будет отрендерено в HTML с пустым значением и текстовой меткой "Renewal date:", а также текстовым описанием: "Enter a date between now and 4 weeks (default 3 weeks)." Так как никаких дополнительных опций мы не определяем, то поле будет "получать" даты в следующем формате input_formats: YYYY-MM-DD (2016-11-06), MM/DD/YYYY (02/26/2016), MM/DD/YY (10/25/16), а для отрисовки по умолчанию, будет использовать виджет: DateInput.

+ +

Существует множество других типов полей для класса формы, которые по своему функционалу подобны соответствующим им эквивалентам типов полей для классов моделей: BooleanField, CharField, ChoiceField, TypedChoiceField, DateField, DateTimeField, DecimalField, DurationField, EmailField, FileField, FilePathField, FloatField, ImageField, IntegerField, GenericIPAddressField, MultipleChoiceField, TypedMultipleChoiceField, NullBooleanField, RegexField, SlugField, TimeField, URLField, UUIDField, ComboField, MultiValueField, SplitDateTimeField, ModelMultipleChoiceField, ModelChoiceField​​​​.

+ +

Общие аргументы для большинства полей перечислены ниже:

+ + + +

Валидация

+ +

Django предоставляет несколько мест где вы можете осуществить валидацию ваших данных. Простейшим способом проверки значения одиночного поля является переопределение методаclean_<fieldname>() (здесь, <fieldname> это имя поля, которое вы хотите проверить). Например, мы хотим проверить, что введенное значение renewal_date находится между текущей датой и  4 неделями в будущем. Для этого мы создаем метод clean_renewal_date(), как показано ниже:

+ +
from django import forms
+
+from django.core.exceptions import ValidationError
+from django.utils.translation import ugettext_lazy as _
+import datetime #for checking renewal date range.
+
+class RenewBookForm(forms.Form):
+    renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
+
+    def clean_renewal_date(self):
+        data = self.cleaned_data['renewal_date']
+
+        #Проверка того, что дата не выходит за "нижнюю" границу (не в прошлом).
+        if data < datetime.date.today():
+            raise ValidationError(_('Invalid date - renewal in past'))
+
+        #Проверка того, то дата не выходит за "верхнюю" границу (+4 недели).
+        if data > datetime.date.today() + datetime.timedelta(weeks=4):
+            raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
+
+        # Помните, что всегда надо возвращать "очищенные" данные.
+        return data
+ +

Необходимо отметить два важных момента. Первый это то, что мы получаем наши данные при помощи словаря self.cleaned_data['renewal_date'], а затем в конце возвращаем полученное значение, для проведения необходимых проверок. Данный шаг позволяет нам, при помощи валидаторов, получить "очищенные", проверенные, а затем, приведенные к стандартным типам, данные (в нашем случае к типу Python datetime.datetime).

+ +

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

+ +
+

Примечание: Существует множество других методов и примеров валидации различных форм, которые можно найти в Формы и валидация поля (Django docs). Например, в случае, если у вас имеется много полей, которые зависят один от другого, вы можете переопределить функцию Form.clean() и, при необходимости, "выкинуть" ValidationError.

+
+ +

В целом, это все, что нам понадобится для создания формы в данном примере!

+ +

Копирование класса формы

+ +

Создайте и откройте файл locallibrary/catalog/forms.py, а затем скопируйте в него весь код, указанный в предыдущем фрагменте.

+ +

Конфигурация URL-адресов

+ +

Перед созданием отображения давайте добавим соответствующую конфигурацию URL-адреса для страницы обновления книг. Скопируйте следующий фрагмент в нижнюю часть файла locallibrary/catalog/urls.py.

+ +
urlpatterns += [
+    url(r'^book/(?P<pk>[-\w]+)/renew/$', views.renew_book_librarian, name='renew-book-librarian'),
+]
+ +

Данная конфигурация перенаправит запросы с адресов формата /catalog/book/<bookinstance id>/renew/ в функции с именем renew_book_librarian() в views.py, туда же передаст идентификатор id записи BookInstance в качестве параметра с именем pk. Шаблон соответствует только если pk это правильно отформатированный uiid.

+ +
+

Примечание: Вместо имени "pk" мы можем использовать любое другое, по нашему желанию, потому что мы имеем полный контроль над функцией отображения (которого у нас нет в случае использования встроенного обобщенного класса отображения, который ожидает параметр с определенным именем). Тем не менее имя pk является понятным сокращением от "primary key", поэтому мы его тут и используем!

+
+ +

Отображение

+ +

Как было отмечено в разделе Процесс управление формой в Django, отображение должно отрендерить форму по умолчанию, когда она вызывается в первый раз и, затем, перерендерить ее, в том случае, если возникли какие-либо ошибки при работе с ее полями. В случае же успеха, после обработки "правильных" данных отображение перенаправляет пользователя на новую (другую) страницу. Для того чтобы выполнить все эти действия, отображение должно знать вызвано ли оно в первый раз для отрисовки формы по умолчанию, а если это не так, то провести валидацию полученных данных.

+ +

Для форм, которые используют POST-запрос при отправке информации на сервер, наиболее общей схемой проверки данного факта является следующая строка кода if request.method == 'POST':. GET-запросу, а также первому запросу формы, в таком случае соответствует блок else. Если вы хотите отправлять свои данные в виде GET-запроса, то в таком случае приемом проверки того факта, что данный запрос первый (или последующий), является получение значения какого-либо поля формы (например, если значение скрытого поля формы пустое, то данный вызов является первым).

+ +

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

+ +
from django.shortcuts import get_object_or_404
+from django.http import HttpResponseRedirect
+from django.urls import reverse
+import datetime
+
+from .forms import RenewBookForm
+
+def renew_book_librarian(request, pk):
+    book_inst = get_object_or_404(BookInstance, pk=pk)
+
+    # Если данный запрос типа POST, тогда
+    if request.method == 'POST':
+
+        # Создаем экземпляр формы и заполняем данными из запроса (связывание, binding):
+        form = RenewBookForm(request.POST)
+
+        # Проверка валидности данных формы:
+        if form.is_valid():
+            # Обработка данных из form.cleaned_data
+            #(здесь мы просто присваиваем их полю due_back)
+            book_inst.due_back = form.cleaned_data['renewal_date']
+            book_inst.save()
+
+            # Переход по адресу 'all-borrowed':
+            return HttpResponseRedirect(reverse('all-borrowed') )
+
+    # Если это GET (или какой-либо еще), создать форму по умолчанию.
+    else:
+        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
+        form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
+
+    return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
+ +

В первую очередь мы импортируем наш класс формы (RenewBookForm), а также другие необходимые объекты и методы:

+ + + +

В отображении аргумент pk мы используем в функцииget_object_or_404() для получения текущего объекта типа BookInstance (если его не существует, то функция, а следом и наше отображение прервут свое выполнение, а на странице пользователя отобразится сообщение об ошибке: "объект не найден"). Если запрос вызова отображения не является POST-запросом, то мы переходим к условному блоку else, в котором мы создаем форму по умолчанию и передаем ей начальное значенияinitial для поля renewal_date (выделено жирным ниже, - 3 недели, начиная с текущей даты). 

+ +
    book_inst = get_object_or_404(BookInstance, pk=pk)
+
+    # Если это GET (или другой метод), тогда создаем форму по умолчанию
+    else:
+        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
+        form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
+
+    return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
+ +

После создания формы мы вызываем функцию render(), чтобы создать HTML страницу; передаем ей в качестве параметров шаблон и контекст, который содержит объект формы. Кроме того, контекст содержит объект типа BookInstance, который мы будем использовать в шаблоне, для получения информации об обновляемой книге.

+ +

Если все таки у нас POST-запрос, тогда мы создаем объект с  именем form и заполняем его данными, полученными из запроса. Данный процесс называется связыванием (или, биндингом, от англ. "binding") и позволяет нам провести валидацию данных. Далее осуществляется валидация формы, при этом проверяются все поля формы — для этого используются как код обобщенного класса, так и пользовательских функций, в частности нашей функции проверки введенных дат clean_renewal_date()

+ +
    book_inst = get_object_or_404(BookInstance, pk=pk)
+
+    # Если данный запрос типа POST, тогда
+    if request.method == 'POST':
+
+        # Создаем экземпляр формы и заполняем данными из запроса (связывание, binding):
+        form = RenewBookForm(request.POST)
+
+        # Проверка валидности формы:
+        if form.is_valid():
+            # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
+            book_inst.due_back = form.cleaned_data['renewal_date']
+            book_inst.save()
+
+            # redirect to a new URL:
+            return HttpResponseRedirect(reverse('all-borrowed') )
+
+    return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
+ +

Если формы не прошла валидацию, то мы снова вызываем функцию render(), но на этот раз форма будет содержать сообщения об ошибках. 

+ +

Если форма прошла валидацию, тогда мы можем начать использовать данные, получая их из атрибута формы form.cleaned_data  (то есть, data = form.cleaned_data['renewal_date']). Здесь мы просто сохраняем данные в поле due_back , соответствующего объекта BookInstance.

+ +
+

Важно: Хотя вы также можете получить доступ к данным формы непосредственно через запрос (например request.POST['renewal_date'], или request.GET['renewal_date'] (в случае GET-запроса), это НЕ рекомендуется. Очищенные данные проверены на вредоносность и преобразованы в типы, совместимые с Python.

+
+ +

Последним шагом в части обработки формы представления является перенаправление на другую страницу, обычно страницу «Успех». В нашем случае мы используем объект класса HttpResponseRedirect и функцию reverse() для перехода к отображению с именем 'all-borrowed' (это было домашним заданием в Руководство часть 8: Аутентификация и разграничение доступа). Если вы не создали данную страницу, то просто укажите переход на домашнюю страницу сайта по адресу '/').

+ +

Все это необходимо для управления формой как таковой, но нам нужно как-то ограничить доступ к отображению (открыть доступ только библиотекарям). Мы могли бы создать новое разрешение (permission) в классе BookInstance ("can_renew"), но мы пойдем простым путем и воспользуемся функцией-декоратором @permission_required  вместе с нашим существующим разрешениемcan_mark_returned.

+ +

Окончательный вид отображения показан ниже. Пожалуйста, скопируйте данный текст в нижнюю часть файла locallibrary/catalog/views.py.

+ +
from django.contrib.auth.decorators import permission_required
+
+from django.shortcuts import get_object_or_404
+from django.http import HttpResponseRedirect
+from django.urls import reverse
+import datetime
+
+from .forms import RenewBookForm
+
+@permission_required('catalog.can_mark_returned')
+def renew_book_librarian(request, pk):
+    """
+    View function for renewing a specific BookInstance by librarian
+    """
+    book_inst = get_object_or_404(BookInstance, pk=pk)
+
+    # If this is a POST request then process the Form data
+    if request.method == 'POST':
+
+        # Create a form instance and populate it with data from the request (binding):
+        form = RenewBookForm(request.POST)
+
+        # Check if the form is valid:
+        if form.is_valid():
+            # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
+            book_inst.due_back = form.cleaned_data['renewal_date']
+            book_inst.save()
+
+            # redirect to a new URL:
+            return HttpResponseRedirect(reverse('all-borrowed') )
+
+    # If this is a GET (or any other method) create the default form.
+    else:
+        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
+        form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
+
+    return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
+
+ +

Шаблон

+ +

Создайте шаблон, на который ссылается наше отображение (/catalog/templates/catalog/book_renew_librarian.html) и скопируйте в него код, указаный ниже:

+ +
{% extends "base_generic.html" %}
+{% block content %}
+
+    <h1>Renew: \{{bookinst.book.title}}</h1>
+    <p>Borrower: \{{bookinst.borrower}}</p>
+    <p{% if bookinst.is_overdue %} class="text-danger"{% endif %}>Due date: \{{bookinst.due_back}}</p>
+
+    <form action="" method="post">
+        {% csrf_token %}
+        <table>
+        \{{ form }}
+        </table>
+        <input type="submit" value="Submit" />
+    </form>
+
+{% endblock %}
+ +

Большая его часть вам знакома из предыдущих частей руководства. Мы расширяем базовый шаблон, а затем замещаем блок содержимого content. У нас имеется возможность ссылаться на переменную \{{bookinst}} (и ее поля) поскольку мы передали ее в объект контекста при вызове функции render(). Здесь мы используем данный объект для вывода заголовка книги, дат ее получения и возврата.

+ +

Код формы относительно прост. В первую очередь мы объявляем тэгform, затем определяем куда будут отправлены данные (action) и каким способом (method, в данном случае "HTTP POST") — если обратитесь к обзору раздела Формы HTML в верхней части данной страницы, то найдете там замечение, что пустое значние атрибута action, означает, что данные из формы будут переданы обратно по текущему URL-адресу данной страницы  (чего мы и хотим!). Внутри тэга формы мы объявляем кнопку submit при помощи которой мы можем отправить наши данные. Блок {% csrf_token %}, добавленный первой строкой внутри блока формы, является частью фреймворка Django и служит для борьбы с CSRF.

+ +
+

Примечание: Добавляйте {% csrf_token %} в каждый шаблон Django, в котором вы создаете форму для отправки данных методом POST. Это поможет уменьшить вероятность взлома вашего сайта злоумышленниками.

+
+ +

Все что осталось, это указать переменную \{{form}}, которую мы передали в шаблон в словаре контекста. Возможно это вас не удивит, но таким образом мы предоставим возможность форме отрендерить свои поля с их метками, виджетами и дополнительными текстами, и в результате мы получим следующее:

+ +
<tr>
+  <th><label for="id_renewal_date">Renewal date:</label></th>
+  <td>
+    <input id="id_renewal_date" name="renewal_date" type="text" value="2016-11-08" required />
+    <br />
+    <span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span>
+  </td>
+</tr>
+
+ +
+

Примечание: Возможно это не очевидно, поскольку наша форма содержит только одно поле, но по умолчанию каждое поле формы помещается в ее собственную строку таблицы (поэтому переменная \{{form}} находится внутри тэга table .​​​​​​ Тот же результат можно получить, если воспользоваться следующим вызовом \{{ form.as_table }}.

+
+ +

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

+ +
<tr>
+  <th><label for="id_renewal_date">Renewal date:</label></th>
+   <td>
+      <ul class="errorlist">
+        <li>Invalid date - renewal in past</li>
+      </ul>
+      <input id="id_renewal_date" name="renewal_date" type="text" value="2015-11-08" required />
+      <br />
+      <span class="helptext">Enter date between now and 4 weeks (default 3 weeks).</span>
+    </td>
+</tr>
+ +

Другие варианты применения переменной шаблона form

+ +

В простом случае применения \{{form}} как показано выше, каждое поле рендерится  в виде отдельной строки таблицы. Кроме того, вы можете отрендерить каждое поле как список элементов (\{{form.as_ul}} ), или как параграф (\{{form.as_p}}).

+ +

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

+ + + +

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

+ +

Тестирование страницы

+ +

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

+ +
{% if perms.catalog.can_mark_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a>  {% endif %}
+ +
+

Примечание: Помните что, для того чтобы перейти на страницу обновления книги, ваш тестовый логин должен иметь разрешение доступа типа "catalog.can_mark_returned"(возможно надо воспользоваться вашим аккаунтом для суперпользователя).

+
+ +

Вы можете попробовать вручную создать URL-адрес для тестирования, например — http://127.0.0.1:8000/catalog/book/<bookinstance_id>/renew/ (правильный идентификатор записи id для bookinstance можно получить, если перейти на страницу детальной информации книги и скопировать поле id).

+ +

Как теперь все это выглядит?

+ +

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

+ +

+ +

А такой наша форма будет в случае ввода неправильной даты:

+ +

+ +

Список всех книг с ссылками на странцу обновления данных:

+ +

+ +

Класс ModelForm

+ +

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

+ +

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

+ +

Базовая реализация ModelForm содержит тоже поле как и ваш предыдущий класс формы  RenewBookForm, что и показано ниже. Все что вам необходимо сделать, - внутри вашего нового класса добавить класс Meta и связать его с моделью model (BookInstance), а затем перечислить поля модели в поле fields которые должны быть включены в форму (вы можете включить все поля при помощи fields = '__all__', или можно вопользоваться полем exclude (вместо fields), чтобы определить поля модели, которые не нужно включать).

+ +
from django.forms import ModelForm
+from .models import BookInstance
+
+class RenewBookModelForm(ModelForm):
+    class Meta:
+        model = BookInstance
+        fields = ['due_back',]
+
+ +
+

Примечание: Это не выглядит сильно проще, чем просто использовать класс Form (и это действительно так, поскольку мы используем только одно поле). Тем не менее, если вы хотите иметь много полей, то такой способ построения формы может значительно уменьшить количество кода и ускорить разработку!

+
+ +

Оставшаяся часть информации касается объявления полей модели (то есть, текстовых меток, виджетов, текстов, сообщений об ошибках). Если они недостаточно "правильные", то тогда мы можем переопределить их в нашем классе Meta при помощи словаря, содержащего поле, которое надо изменить и его новое значение. Например, в нашей форме мы могли бы поменять текст метки для поля "Renewal date" (вместо того, чтобы оставить текст по умолчанию: Due date), а кроме того мы хотим написать другой вспомогательный текст. Класс Meta, представленный ниже, показывает вам, как переопределить данные поля. Кроме того, при необходимости, вы можете установить значения для виджетов widgets и сообщений об ошибках error_messages.

+ +
class Meta:
+    model = BookInstance
+    fields = ['due_back',]
+    labels = { 'due_back': _('Renewal date'), }
+    help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } 
+
+ +

Чтобы добавить валидацию, вы можете использовать тот же способ как и для класса Form — вы определяете функцию с именем  clean_field_name() из которой выбрасываете исключение ValidationError, если это необходимо. Единственным отличием от нашей оригинальной формы будет являться то, что поле модели имеет имя due_back, а не "renewal_date".

+ +
from django.forms import ModelForm
+from .models import BookInstance
+
+class RenewBookModelForm(ModelForm):
+    def clean_due_back(self):
+       data = self.cleaned_data['due_back']
+
+       #Проверка того, что дата не в прошлом
+       if data < datetime.date.today():
+           raise ValidationError(_('Invalid date - renewal in past'))
+
+       #Check date is in range librarian allowed to change (+4 weeks)
+       if data > datetime.date.today() + datetime.timedelta(weeks=4):
+           raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
+
+       # Не забывайте всегда возвращать очищенные данные
+       return data
+
+    class Meta:
+        model = BookInstance
+        fields = ['due_back',]
+        labels = { 'due_back': _('Renewal date'), }
+        help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), }
+
+ +

Теперь класс RenewBookModelForm является функциональным эквивалентом нашему предыдущему классу RenewBookForm. Вы можете импортировать и использовать его в тех же местах, где и RenewBookForm.

+ +

Обобщенные классы отображения для редактирования

+ +

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

+ +
+

Примечание: В дополнение к отображениям для реактирования, описываемых здесь, существует также класс FormView, который по своему предназначению находится где-то между "простой" функцией отображения и другими обобщенными отображенями, то есть в каком-то смысле, в диапазоне: "гибкость" против "усилия при программировании". Применяя FormView, вы все еще нуждаетесь в создании класса Form, но вам не нужно реализовавыть весь "стандартный" функционал работы с формой. Вместо этого, вы должны просто реализовать функцию, которая будет вызвана в тот момент, когда станет понятно, что получаемые из формы данные, "правильные" (валидны).

+
+ +

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

+ +

Отображения

+ +

Откройте файл отображений (locallibrary/catalog/views.py) и добавьте следующий код в его нижнюю часть:

+ +
from django.views.generic.edit import CreateView, UpdateView, DeleteView
+from django.urls import reverse_lazy
+from .models import Author
+
+class AuthorCreate(CreateView):
+    model = Author
+    fields = '__all__'
+    initial={'date_of_death':'12/10/2016',}
+
+class AuthorUpdate(UpdateView):
+    model = Author
+    fields = ['first_name','last_name','date_of_birth','date_of_death']
+
+class AuthorDelete(DeleteView):
+    model = Author
+    success_url = reverse_lazy('authors')
+ +

Как вы видите, для создания отображений вам надо наследоваться от следующих классовCreateView, UpdateView и DeleteView (соответственно), а затем связать их с соответствующей моделью.

+ +

Для случаев "создать" и "обновить" вам также понадобится определить поля для показа на форме (применяя тот же синтаксис, что и для ModelForm). В этом случае мы демонстриурем синтаксис и для показаза "всех" полей, и перечисление их по отдельности. Также вы можете указать начальные значения для каждого поля, применяя словарь пар имя_поля/значение (в целях демонстрации, в нашем примере мы явно указываем дату смерти — если хотите, то вы можете удалить это поле). По умолчанию отображения перенаправляют пользователя на страницу "успеха", показывая только что созданные/отредатированные данные (записи в модели). В нашем случае это, созданная в предыдущей части руководства, подробная информация об авторе. Вы можете указать альтернативное перенаправление при помощи параметра success_url (как в примере с классом AuthorDelete).

+ +

Классу AuthorDelete не нужно показывать каких либо полей, таким образом их не нужно и декларировать. Тем не менее, вам нужно указать success_url, потому что, в данном случае, для Django не очевидно что делать после успешного выполнения операции удаления записи. Мы используем функцию reverse_lazy() для перехода на страницу списка авторов после удаления одного из них — reverse_lazy() это более "ленивая" версия reverse().

+ +

Шаблоны

+ +

Отображения  "создать" и "обновить" используют  шаблоны с именем model_name_form.html, по умолчанию: (вы можете поменять суффикс на что-нибудь другое, при помощи поля template_name_suffix в вашем отображении, например, template_name_suffix = '_other_suffix')

+ +

Создайте файл шаблона locallibrary/catalog/templates/catalog/author_form.html  и скопируйте в него следующий текст.

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+
+<form action="" method="post">
+    {% csrf_token %}
+    <table>
+    \{{ form.as_table }}
+    </table>
+    <input type="submit" value="Submit" />
+
+</form>
+{% endblock %}
+ +

Это напоминает наши предыдущие формы и рендер полей при помощи таблицы. Заметьте, что мы снова используем{% csrf_token %}.

+ +

Отображения "удалить" ожидает "найти" шаблон с именем формата model_name_confirm_delete.html (и снова, вы можете изменить суффикс при помощи поля отображенияtemplate_name_suffix). Создайте файл шаблона locallibrary/catalog/templates/catalog/author_confirm_delete.html и скопируйте в него текст, указанный ниже.

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+
+<h1>Delete Author</h1>
+
+<p>Are you sure you want to delete the author: \{{ author }}?</p>
+
+<form action="" method="POST">
+  {% csrf_token %}
+  <input type="submit" value="Yes, delete." />
+</form>
+
+{% endblock %}
+
+ +

Настройки URL-адресов

+ +

Откройте файл конфигураций URL-адресов (locallibrary/catalog/urls.py) и добавьте в его нижнюю часть следующие настройки:

+ +
urlpatterns += [
+    url(r'^author/create/$', views.AuthorCreate.as_view(), name='author_create'),
+    url(r'^author/(?P<pk>\d+)/update/$', views.AuthorUpdate.as_view(), name='author_update'),
+    url(r'^author/(?P<pk>\d+)/delete/$', views.AuthorDelete.as_view(), name='author_delete'),
+]
+ +

Здесь нет ничего нового! Как вы видите отображения являются классами и следовательно должны вызываться через метод .as_view(). Паттерны URL-адресов для каждого случая должны быть вам понятны. Мы обязаны использовать pk как имя для "захваченного" значения первичного ключа, так как параметр именно с таким именем ожидается классами отображения.

+ +

Страницы создания, обновления и удаления автора теперь готовы к тестированию (мы не будем создавать на них ссылки в отдельном меню, но вы, если хотите, можете их сделать).

+ +
+

Примечание: Наблюдательные пользователи могли заметить, что мы ничего не делаем, чтобы предотвратить несанкционированный доступ к страницам! Мы оставили это в качестве упражнения для вас (подсказка: вы можете использовать PermissionRequiredMixin и, либо создать новое разрешение, или воспользоваться нашим прежним can_mark_returned).

+
+ +

Тестирование страницы

+ +

Залогиньтесь на сайте с аккаунтом, который позволит вам получить доступ к страницам редактирования данных (и записей) автора.

+ +

Затем перейдите на страницу создания новой записи автора: http://127.0.0.1:8000/catalog/author/create/, которая должна быть похожей на следующий скриншот.

+ +

Form Example: Create Author

+ +

Введите в поля значения и нажмите на кнопку Submit, чтобы сохранить новую запись об авторе. После этого, вы должны были перейти на страницу редактирования только что созданного автора, имеющий адрес, похожий на следующий http://127.0.0.1:8000/catalog/author/10.

+ +

У вас есть возможность редактирования записей при помощи добавления /update/ в конец адреса подробной информации (то есть, http://127.0.0.1:8000/catalog/author/10/update/) — мы не показываем скриншот, потому что он выглядит в точности также как  страница "создать"!

+ +

И последнее, мы можем удалить страницу, добавляя строку /delete/ в конец адреса подробной информации автора (то есть, http://127.0.0.1:8000/catalog/author/10/delete/). Django должен показать страницу, которая похожа на представленную ниже. Нажмите Yes, delete., чтобы удалить запись и перейти на страницу со списком авторов.

+ +

+ +

Проверьте себя

+ +

Создайте несколько форм создания, редактирования и удаления записей в модели Book. При желании, вы можете использовать теже структуры как и в случае с моделью Authors. Если ваш шаблон book_form.html является просто копией шаблона author_form.html, тогда новая страница "create book" будет выглядеть как на следующем скриншоте:

+ +

+ + + +

Итоги

+ +

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

+ +

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

+ +

Смотрите также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}

diff --git a/files/ru/learn/server-side/django/generic_views/index.html b/files/ru/learn/server-side/django/generic_views/index.html new file mode 100644 index 0000000000..7b1bf6f08c --- /dev/null +++ b/files/ru/learn/server-side/django/generic_views/index.html @@ -0,0 +1,605 @@ +--- +title: 'Руководство часть 6: Отображение списков и детальной информации' +slug: Learn/Server-side/Django/Generic_views +tags: + - django + - Для начинающих + - Отображения django + - Руководство + - Шаблоны django +translation_of: Learn/Server-side/Django/Generic_views +--- +
{{LearnSidebar}}
+{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}
+ +

Данная часть расширяет наш сайт LocalLibrary, добавляя в него списки и страницы, путем предоставления подробной информации о книгах и авторах. В текущей части мы подробно изучим обобщенные базовые классы отображения и покажем как они могут существенно сократить количество кода, который вы должны были бы написать в обычной ситуации. Кроме того, мы более подробно рассмотрим управление и настройки URL-адресов, показывая как выполнить простое сопоставление какой-либо строки паттерну регулярного выражения.

+ + + + + + + + + + + + +
Требования:Завершить все предыдущие части руководства, включая Руководство Django Часть 5: Создание домашней страницы.
Цель:Понимать где и как применять обобщенные базовые классы отображения, и как применять паттерны URL-адресов для передачи информации в отображения.
+ +

Обзор

+ +

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

+ +

Данный процесс похож на создание главной страницы сайта, который мы показывали в предыдущей части руководства. Нам все также надо создать URL-преобразования, отображения и шаблоны страниц. Основным отличием будет то, что для страниц подробной информации перед нами встанет дополнительная задача получения информации из паттерна URL-адреса и передачи ее отображению. Для этих страниц мы собираемся продемонстрировать совершенно другой тип отображения, основанный на применении  обобщеных классов отображения списка и детальной информации о записи. Это может существенно сократить количество кода, необходимого для отображения и сделает его (код) более простым для написания и поддержки.

+ +

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

+ +

Страница со списком книг

+ +

Страница со списком книг показывает все книги в наличии и будет доступна по адресу: catalog/books/. Эта страница для каждой записи выводит заголовок и автора, при этом каждый заголовок является гиперссылкой на соответствующую страницу подробной информации о книге. Данная страница будет иметь ту же структуру и навигацию как и все остальные страницы сайта, таким образом мы сможем расширить базовый шаблон сайта (base_generic.html), который мы создали в предыдущей части руководства.

+ +

Преобразования URL-адресов

+ +

Откройте файл /catalog/urls.py и скопируйте в него строки, выделенные жирным внизу. Практически также как и для главной страницы сайта, данная функция url() определяет регулярное выражение (r'^books/$'), связывающее URL-адрес с функцией отображения (views.BookListView.as_view() ), которая будет вызвана, если URL-адрес будет соответствовать паттерну РВ. Кроме того, определяется имя для данного сопоставления.

+ +
from django.urls import path
+from . import views
+from django.conf.urls import url
+
+urlpatterns = [
+    url(r'^$', views.index, name='index'),
+    url(r'^books/$', views.BookListView.as_view(), name='books'),
+]
+ +

Данный паттерн РВ сопоставления URL-адреса полностью соответствует строке books/ (^ является маркером начала строки, а $  - маркер конца строки). Как было отмечено в предыдущей части руководства, URL-адрес уже должен содержать /catalog, таким образом полный адрес, на самом деле, имеет вид : /catalog/books/.

+ +

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

+ +

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

+ +

Отображение (на основе базового класса)

+ +

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

+ +

Откройте catalog/views.py и скопируйте следующий код, в нижнюю часть данного файла:

+ +
from django.views import generic
+
+class BookListView(generic.ListView):
+    model = Book
+ +

Это всё! Обобщенное отображение выполнит запрос к базе данных, получит все записи заданной модели (Book), затем отрендерит (отрисует) соответствующий шаблон, расположенный в /locallibrary/catalog/templates/catalog/book_list.html (который мы создадим позже). Внутри данного шаблона вы можете получить доступ к списку книг при помощи переменной шаблона object_list ИЛИ book_list (если обобщить, то "the_model_name_list").

+ +
+

Примечание: Этот, выглядящий странно, путь к файлу шаблона не является опечаткой — обобщенное отображение ищет файл шаблона /application_name/the_model_name_list.html (catalog/book_list.html, в данном случае) внутри директории приложения /application_name/templates/ (у нас - /catalog/templates/).

+
+ +

Вы можете использовать атрибуты для того, чтобы изменить поведение по умолчанию. Например, вы могли бы указать другой файл шаблона, например, если в вашем распоряжении имеется несколько отображений, которые используют одну и ту же модель, или вам позарез захотелось бы использовать другое имя переменной шаблона, если book_list не является интуитивно понятным. Возможно, наиболее полезным вариантом является изменение/отфильтрование результата запроса к базе данных — таким образом, вместо перечисления всех книг вы могли бы показывать 5 наиболее популярных.

+ +
class BookListView(generic.ListView):
+    model = Book
+    context_object_name = 'my_book_list'   # ваше собственное имя переменной контекста в шаблоне
+    queryset = Book.objects.filter(title__icontains='war')[:5] # Получение 5 книг, содержащих слово 'war' в заголовке
+    template_name = 'books/my_arbitrary_template_name_list.html'  # Определение имени вашего шаблона и его расположения
+ +

Переопределение методов в классах отображения

+ +

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

+ +

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

+ +
class BookListView(generic.ListView):
+    model = Book
+
+    def get_queryset(self):
+        return Book.objects.filter(title__icontains='war')[:5] # Получить 5 книг, содержащих 'war' в заголовке
+
+ +

Мы также могли бы переопределить метод get_context_data() для того, чтобы в контексте (в переменной контекста) передавать шаблону дополнительные переменные (например, список книг передается по умолчанию). Фрагмент, представленный ниже, показывает как добавить переменную с именем "some_data" в контекст (затем она будет доступна как переменная шаблона).

+ +
class BookListView(generic.ListView):
+    model = Book
+
+    def get_context_data(self, **kwargs):
+        # В первую очередь получаем базовую реализацию контекста
+        context = super(BookListView, self).get_context_data(**kwargs)
+        # Добавляем новую переменную к контексту и иниуиализируем ее некоторым значением
+        context['some_data'] = 'This is just some data'
+        return context
+ +

В процессе выполнения всего этого важно придерживаться определенной последовательности действий:

+ + + +
+

Примечание: Посмотрите Встроенные обобщенные классы отображения (Django docs) для ознакомления с большим количеством примеров того, что вы могли бы сделать.

+
+ +

Создание шаблона Отображения Списка

+ +

Создайте HTML-файл /locallibrary/catalog/templates/catalog/book_list.html и скопируйте в него текст, указанный ниже. Как было отмечено ранее, это файл шаблона по умолчанию, который будет "искать" обобщенный класс отображения списка (для модели с именем Book в приложении с именем catalog).

+ +

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

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+    <h1>Book List</h1>
+
+    {% if book_list %}
+    <ul>
+
+      {% for book in book_list %}
+      <li>
+        <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}})
+      </li>
+      {% endfor %}
+
+    </ul>
+    {% else %}
+      <p>There are no books in the library.</p>
+    {% endif %} 
+{% endblock %}
+ +

По умолчанию отображение передает контекст (список книг) как object_list и book_list (синонимы; оба варианта будут работать).

+ +

Условные ветвления

+ +

Мы применяем теги шаблона if, else и endif для того, чтобы проверить определена ли переменная book_list и содержит ли она данные. Если список НЕ пуст, тогда мы выполняем итерации по списку книг. Если список пуст (else-случай) тогда мы показываем текст, поясняющий, что в наличии нет книг.

+ +
{% if book_list %}
+  <!-- здесь наш код "бежит" по списку книг -->
+{% else %}
+  <p>В библиотеке книг нет.</p>
+{% endif %}
+
+ +

В данном фрагменте проверяется только одно условие, но вы можете протестировать другие варианты при помощи тэга шаблона elif (например, {% elif var2 %} ). Для дополнительной информации по данной теме смотрите: if, ifequal/ifnotequal и ifchanged в главе Встроенные тэги и фильтры шаблона (Django Docs).

+ +

Цикл For

+ +

Шаблон использует тэги for и endfor для того, чтобы "пробежаться" по списку книг, как показано ниже. На каждой итерации (каждом цикле) в переменную шаблона book передается информация текущего эелемента списка.

+ +
{% for book in book_list %}
+  <li> <!-- здесь код, который использует информацию из каждого элемента book списка--> </li>
+{% endfor %}
+
+ +

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

+ +

Доступ к переменным

+ +

Код внутри цикла создает экземпляр для каждой книги из списка, при помощи которой показывается заголовок (как ссылка на "скоро-будет-сделано" подробное отображение) и автора книги.

+ +
<a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}})
+
+ +

Мы получаем доступ к полям соответствующей записи о книге при помощи "дот-нотации", то есть через точку (например, book.title и book.author), где текст, который идет после book, является именем поля (так, как определено в модели).

+ +

Кроме того, внутри нашего шаблона, мы можем вызывать функции модели — в данном случае, мы вызываем Book.get_absolute_url() для получения URL-адреса, который мы используем для показа детальной информации о книге. Данный вызов работает только для функции у которой нет аргументов (в шаблоне не существует возможности передать аргументы в функцию!)

+ +
+

Примечание: Мы должны быть достаточно осмотрительными для того, чтобы избегать "сторонних эффектов" когда мы вызываем функции из шаблона. В данном случае мы просто получаем URL-адрес, но функции могут делать все что угодно — мы не хотели бы "убить" наша базу данных (например) просто отрендеривая наш шаблон!

+
+ +

Обновление базового шаблона

+ +

Откройте файл базового шаблона (/locallibrary/catalog/templates/base_generic.html) и вставьте {% url 'books' %}  в URL-ссылку для пункта All books, как показано ниже. Тем самым, мы создали "переход" на страницу с книгами (теперь мы можем смело это сделать, поскольку у нас имеется соответствующее "книжное" url-преобразование).

+ +
<li><a href="{% url 'index' %}">Home</a></li>
+<li><a href="{% url 'books' %}">All books</a></li>
+<li><a href="">All authors</a></li>
+ +

Как же теперь все это выглядит?

+ +

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

+ +

Страница с подробной информацией о книге

+ +

Доступ к странице с подробной информацией о книге осуществляется при помощи URL-адреса catalog/book/<id> (где <id> является первичным ключом для данной книги). В дополнение к полям модели Book  (автор, краткое содержание, ISBN, язык и жанр), также мы перечислим детали доступных экземпляров книги (BookInstances) включая их статус, ожидаемую дату возврата, штамп (imprint) и id. Это должно позволить нашим читателям не просто узнать о книге, но также убедиться, имеется ли она в наличии и/или когда будет доступна.

+ +

URL-преобразования

+ +

Откройте /catalog/urls.py и добавьте 'book-detail' URL-преобразование, отмеченное жирным в следующем фрагменте. Эта функция url() определяет паттерн, связанный с обобщенным классом отображения детальной информации, а также имя для данной связи.

+ +
from django.urls import path
+from . import views
+from django.conf.urls import url
+
+urlpatterns = [
+    url(r'^$', views.index, name='index'),
+    url(r'^books/$', views.BookListView.as_view(), name='books'),
+    url(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),
+]
+ +

В отличие от предыдущих преобразований, в данном случае мы применяем наше регулярное выражение (РВ) для сопоставления "настоящего паттерна", а не просто строки. Данное РВ сопоставляет любой URL-адрес, который начинается с book/, за которым до конца строки (до маркера конца строки - $) следуют одна, или более цифр. В процессе выполнения данного преобразования, оно "захватывает" цифры и передает их в функцию отображения как параметр с именем pk.

+ +
+

Примечание: как было отмечено ранее, наш преоразуемый URL-адрес в реальности выглядит вот так catalog/book/<digits> (потому что мы находимся в приложении catalog, то подразумевается каталог /catalog/).

+
+ +
+

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

+
+ +

Отдельный пример с регулярными выражениями

+ +

Паттерны регулярного выражения является невероятно мощным инструментом преобразования. Пока что, мы не очень много говорили о них, поскольку мы сопоставляли URL-адреса с простыми строками (а не паттернами), и потому что они не интуитивны и  пугающи для начинающих.

+ +
+

Примечание: Без паники! Мы будем рассматривать и использовать достаточно простые паттерны и при этом хорошо задокументированные!

+
+ +

В первую очередь вы должны знать что обычно регулярные выражения объявляются при помощи строкового литерала (то есть, они заключены в кавычки: r'<ваше регулярное выражение>').

+ +

Главными элементами синтаксиса объявления паттерна, который вы должны знать, являются:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
СимволЗначение
^Соответствует началу строки
$Соответствует концу строки
\dСоответствует цифре (0, 1, 2, ... 9)
\wСоответствует любому символу из алфавита в верхнем- или нижнем- регистре, цифре, или символу подчеркивания (_)
+Соответствует одному, или более предыдущему символу. Например, для соответствия одной, или более цифре вы должны использовать \d+. Для одного и более символа "a", вы можете использовать a+
*Соответствует отсутствию вообще, или присутствию одного, или более предыдущему символу. Например, для соответствия "ничему", или слову (то есть, любому символу) вы можете использовать  \w*
( )Захват части паттерна внутри скобок. Любое захваченное значение будет передано отображению как безымянный параметр (если захватывается множество паттернов, то соответствующие параметры будут поставляться в порядке их объявления).
(?P<name>...)Захват части паттерна (обозначеного через ...) как именованной переменной (в данном случае <name>). Захваченные значения передаются в отображение с определенным именем. Таким образом, ваше отображение должно объявить аргумент с тем же самым именем!
[  ]Соответствует одному символу из множества. Например, [abc] будет соответствовать либо 'a', или 'b', или 'c'. [-\w] будет соответствовать либо символу '-' , или любому другому словарному символу.
+ +

Большинство других символов могут быть заданы буквально!

+ +

Давайте рассмотрим несколько реальных примеров паттернов:

+ + + + + + + + + + + + + + + + + + + + + + +
ПаттернОписание
r'^book/(?P<pk>\d+)$' +

Это РВ применяется в нашем url-преобразовании. Оно соответствует строке, которая начинается с book/ (^book/), затем имеет одну, или более цифр (\d+), а затем завершается (цифрой и только цифрой).

+ +

Оно также захватывает все цифры (?P<pk>\d+) и передает их в отображение, в параметре с именем 'pk'. Захваченные значения всегда передаются как строка!

+ +

Например, данному паттерну должна соответствовать следующая строка book/1234 , которая отправляет переменную pk='1234' в отображение.

+
r'^book/(\d+)$'Этот паттерн соответствует тем же самым URL-адресам как и в предыдущем случае. Захваченная информация будет отправлена в отображение как безымянный параметр.
r'^book/(?P<stub>[-\w]+)$' +

Данный паттерн соответствует строке, которая начинается с book/ (^book/), затем идут один, или более символов либо '-', или  словарные символы ([-\w]+), а затем завершается. Он также захватывает данное множество символов и передает их в отображение в параметре с именем 'stub'.

+ +

Это довольно типичный паттерн для "стаба". Стабы являются дружественными URL-адресами - первичными ключами для данных. Вы могли бы применить стаб,  если вы захотели бы, чтобы URL-адрес вашей книги был более информативным. Например, /catalog/book/the-secret-garden, выглядит немного лучше чем /catalog/book/33.

+
+ +

Вы можете захватить (указать) несколько паттернов в одном преобразовании и, тем самым, закодировать много различной информации в URL-адресе.

+ +
+

Примечание: В качестве дополнительного задания, рассмотрите возможность того, как вы могли бы закодировать url на список всех книг, вышедших в определенный год, месяц, день и какое РВ (паттерн) должно соответствовать этому.

+
+ +

Передача дополнительных настроек в ваши преобразования URL-адресов

+ +

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

+ +
url(r'^/url/$', views.my_reused_view, {'my_template_name': 'some_path'}, name='aurl'),
+url(r'^/anotherurl/$', views.my_reused_view, {'my_template_name': 'another_path'}, name='anotherurl'),
+
+
+ +
+

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

+
+ +

Отображение (на основе класса)

+ +

Откройте catalog/views.py, и скопируйте следующий код в нижнюю часть файла:

+ +
class BookDetailView(generic.DetailView):
+    model = Book
+ +

Это всё! Все что вам надо теперь сделать это создать шаблон с именем /locallibrary/catalog/templates/catalog/book_detail.html, а отображение передаст ему информацию из базы данных для определенной записи Book,выделенной при помощи URL-преобразования. Внутри шаблона вы можете получить доступ к списку книг при помощи переменной с именем object или book (обобщённо "the_model_name").

+ +

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

+ +

Что произойдет,  если записи не существует?

+ +

Если запрашиваемой записи не существует, тогда обобщенный класс отображения подробной информации автоматически "выкинет" исключение Http404 — в продакшине это приведет к автоматическому отображению страницы с текстом "resource not found" ("ресурс не найден"), которую, конечно же, вы можете настроить по своему усмотрению.

+ +

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

+ +
def book_detail_view(request,pk):
+    try:
+        book_id=Book.objects.get(pk=pk)
+    except Book.DoesNotExist:
+        raise Http404("Book does not exist")
+
+    #book_id=get_object_or_404(Book, pk=pk)
+
+    return render(
+        request,
+        'catalog/book_detail.html',
+        context={'book':book_id,}
+    )
+
+ +

В первую очередь отображение пытается получить определенную запись о книге из модели. Если ей это не удается, то "выбрасывается" исключение Http404, которое сигнализирует, что данная книга не найдена "not found". Последним шагом является, как обычно, вызов функции render() с именем соответствующего шаблона и данных о книге, передаваемых в параметре с именем context (в виде словаря).

+ +
+

Примечание: Функция get_object_or_404() (показана закомментированной) является удобным "ярлыком" для генерации исключения Http404 если запись не найдена.

+
+ +

Создание шаблона детальной информации

+ +

Создайте HTML файл /locallibrary/catalog/templates/catalog/book_detail.html и скопируйте в него содержимое, представленное ниже. Как было указано ранее, это шаблон "по умолчанию" (имя шаблона), который "ожидается"обобщенным классом отображения детальной информации (для модели с именем Book в приложении с именем catalog).

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+  <h1>Title: \{{ book.title }}</h1>
+
+  <p><strong>Author:</strong> <a href="">\{{ book.author }}</a></p> <!-- author detail link not yet defined -->
+  <p><strong>Summary:</strong> \{{ book.summary }}</p>
+  <p><strong>ISBN:</strong> \{{ book.isbn }}</p>
+  <p><strong>Language:</strong> \{{ book.language }}</p>
+  <p><strong>Genre:</strong> {% for genre in book.genre.all %} \{{ genre }}{% if not forloop.last %}, {% endif %}{% endfor %}</p>
+
+  <div style="margin-left:20px;margin-top:20px">
+    <h4>Copies</h4>
+
+    {% for copy in book.bookinstance_set.all %}
+    <hr>
+    <p class="{% if copy.status == 'a' %}text-success{% elif copy.status == 'd' %}text-danger{% else %}text-warning{% endif %}">\{{ copy.get_status_display }}</p>
+    {% if copy.status != 'a' %}<p><strong>Due to be returned:</strong> \{{copy.due_back}}</p>{% endif %}
+    <p><strong>Imprint:</strong> \{{copy.imprint}}</p>
+    <p class="text-muted"><strong>Id:</strong> \{{copy.id}}</p>
+    {% endfor %}
+  </div>
+{% endblock %}
+ + + +
+

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

+ +
<a href="{% url 'author-detail' book.author.pk %}">\{{ book.author }}</a>
+
+
+ +

Хотя и несколько больше, но почти все в данном шаблоне нам уже встречалось ранее:

+ + + +

Одной интересной вещью, которую мы не видели ранее, является функция book.bookinstance_set.all(). Данный метод является "автомагически"-сконструированным Django для того, чтобы вернуть множество записей BookInstance, связанных с данной книгой Book.

+ +
{% for copy in book.bookinstance_set.all %}
+<!-- итерации по каждой копии/экземпляру книги -->
+{% endfor %}
+ +

Этот метод создан, потому что вы, на стороне "многим" данной связи, объявили поле ForeignKey (один-ко многим). Поскольку вы ничего не объявили на другой стороне ("один") данной модели (то есть, модель Book "ничего не знает" про модель BookInstance), то она не имеет никакой возможности (по умолчанию) для получения множества соответствующих записей. Для того, чтобы обойти эту проблему, Django конструирует соответствующую функцию "обратного просмотра" ("reverse lookup"), которой вы можете воспользоваться. Имя данной функции создается в нижнем регистре и состоит из имени модели, в которой был объявлен ForeignKey (то есть, bookinstance), за которым следует _set (то есть функция, созданная для Book будет иметь вид bookinstance_set()).

+ +
+

Примечание: Здесь мы используем  all() для получения всех записей (по умолчанию). Вы, наверное, могли бы использовать метод filter() для получения подмножетсва записей в коде, но, к сожалению, вы НЕ можете применить данный вызов в шаблоне, потому что вы не можете передать в нем (в шаблоне) аргументы в функцию.

+ +

Обратите внимание, что если вы не определяете порядок выдачи данных (в вашем отображении, или в модели), то сервер разработки "выкинет" сообщения об ошибках, похожие на следующие:

+ +
[29/May/2017 18:37:53] "GET /catalog/books/?page=1 HTTP/1.1" 200 1637
+/foo/local_library/venv/lib/python3.5/site-packages/django/views/generic/list.py:99: UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list: <QuerySet [<Author: Ortiz, David>, <Author: H. McRaven, William>, <Author: Leigh, Melinda>]>
+  allow_empty_first_page=allow_empty_first_page, **kwargs)
+
+ +

Это случилось потому что, paginator object (далее объект постраничного вывода) ожидает видеть некую упорядоченность ORDER BY при запросе к базе данных. Без этого, он не сможет гарантировать правильный вывод полученных данных!

+ +

Данное руководство пока не дошло до описания Pagination (пока, но скоро будет), и поскольку вы не можете использовать функцию sort_by() и передавать параметр (по той же причине, что и filter()) вы должны выбрать один из трех вариантов дальнейших действий:

+ +
    +
  1. Добавить атрибут ordering внутри Meta-класса объявленного в вашей модели.
  2. +
  3. Добавить атрибут queryset в вашей реализации класса отображения, определяющего order_by().
  4. +
  5. Добавить метод get_queryset в вашу реализацию класса отображения и также определить метод order_by().
  6. +
+ +

Если вы выбрали пункт номер один с Meta-классом для модели Author  (вероятно, не такой гибкий как вариант с настройкой класса отображения, но тем не менее, достаточно простой), вы должны прийти к чему-то похожему на следующее:

+ +
class Author(models.Model):
+    first_name = models.CharField(max_length=100)
+    last_name = models.CharField(max_length=100)
+    date_of_birth = models.DateField(null=True, blank=True)
+    date_of_death = models.DateField('Died', null=True, blank=True)
+
+    def get_absolute_url(self):
+        return reverse('author-detail', args=[str(self.id)])
+
+    def __str__(self):
+        return '%s, %s' % (self.last_name, self.first_name)
+
+    class Meta:
+        ordering = ['last_name']
+ +

Конечно же, поле не обязательно должно иметь имя last_name: оно может быть любым.

+ +

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

+
+ +

Как это теперь выглядит?

+ +

На данный момент мы должны были создать все необходимое для показа страниц со списком книг и детальной информацией. Запустите сервер (python3 manage.py runserver) и откройте ваш браузер http://127.0.0.1:8000/.

+ +
+

Предупреждение: Не кликайте на каком-либо авторе, - ссылки пока не заданы — это будет вашим дополнительным заданием!

+
+ +

Кликните ссылку All books для перехода на страницу со списком книг. 

+ +

Book List Page

+ +

Затем кликните на ссылку одной из ваших книг. Если все настроено как надо, то вы должны увидеть то, что указано на картинке.

+ +

Book Detail Page

+ +

Постраничный вывод (Pagination)

+ +

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

+ +

Django имеет отличный встроенный механизм для постраничного вывода. Даже более того, он встроен в обобщенный класс отображения списков, следовательно вам не нужно проделывать большой объем работы, чтобы воспользоваться возможностями постраничного вывода!

+ +

Отображения

+ +

Откройте catalog/views.py и добавьте поле paginate_by как показано жирным в следующем фрагменте.

+ +
class BookListView(generic.ListView):
+    model = Book
+    paginate_by = 10
+ +

Как только у вас появится более 10 записей в базе данных отображение начнет формировать постраничный вывод данных, которые он передает шаблону. К различным страницам данного вывода можно получить доступ при помощи параметров GET-запроса — к странице 2 вы можете получить доступ, используя URL-адрес: /catalog/books/?page=2.

+ +

Шаблоны

+ +

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

+ +

Откройте /locallibrary/catalog/templates/base_generic.html и, ниже блока content, вставьте блок (во фрагменте не выделен жирным), отвечающий за постраничный вывод. Данный код, в первую очередь, проверяет "включен" ли механизм постраничного вывода для данной страницы и если это так, то он добавляет ссылки next и previous,соответственно (а также, номер текущей страницы). 

+ +
{% block content %}{% endblock %}
+
+{% block pagination %}
+  {% if is_paginated %}
+      <div class="pagination">
+          <span class="page-links">
+              {% if page_obj.has_previous %}
+                  <a href="\{{ request.path }}?page=\{{ page_obj.previous_page_number }}">previous</a>
+              {% endif %}
+              <span class="page-current">
+                  Page \{{ page_obj.number }} of \{{ page_obj.paginator.num_pages }}.
+              </span>
+              {% if page_obj.has_next %}
+                  <a href="\{{ request.path }}?page=\{{ page_obj.next_page_number }}">next</a>
+              {% endif %}
+          </span>
+      </div>
+  {% endif %}
+{% endblock %} 
+ +

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

+ +

Мы используем \{{ request.path }} для получения URL-адреса текущей страницы, для того, чтобы создать ссылки на соответствующие страницы, обратите внимание, что данный вызов не зависит от объекта page_objи, таким образом, может использоваться отдельно.

+ +

На этом все!

+ +

Как это выглядит?

+ +

Картинка ниже показывает как выглядит постраничный вывод — если вы не добавили более 10 записей в вашу базу данных, тогда вы можете проверить как это работает, просто уменьшив значение в paginate_by, в файле catalog/views.py. Для получения результата, соответствующего картинке ниже, мы изменилиpaginate_by = 2.

+ +

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

+ +

Book List Page - paginated

+ +

Проверьте себя

+ +

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

+ + + +

Соответствующий код для URL-преобразований и оторажений должен быть идентичным коду для списка книг и детальной информаци о книге Book, который мы создали ранее. Шаблоны будут отличаться, но будут иметь похожее поведение.

+ +
+

Примечание:

+ + +
+ +

Когда вы закончите, ваши страницы должны будут выглядеть как на картинке.

+ +

Author List Page

+ + + +

Author Detail Page

+ + + +

Итоги

+ +

Поздравляем! Наш базовый функционал библиотеки готов! 

+ +

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

+ +

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

+ +

Дополнительная информация

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}

diff --git a/files/ru/learn/server-side/django/home_page/index.html b/files/ru/learn/server-side/django/home_page/index.html new file mode 100644 index 0000000000..b573f3fdb6 --- /dev/null +++ b/files/ru/learn/server-side/django/home_page/index.html @@ -0,0 +1,359 @@ +--- +title: 'Руководство часть 5: Создание домашней страницы' +slug: Learn/Server-side/Django/Home_page +tags: + - django + - Для начинающих + - Изучение + - Кодирование + - Отображения + - Руководство + - Серверная сторона + - Статья + - Шаблоны +translation_of: Learn/Server-side/Django/Home_page +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}
+ +

Теперь мы готовы создать код нашей первой страницы — домашняя страница сайта LocalLibrary будет показывать количество записей в каждой модели, кроме того, она будет выводить боковую навигационную панель с ссылками на другие страницы сайта. В результате мы приобретем практический навык написания простых URL-преобразований и отображений, получения записей из базы данных и применения шаблонов.

+ + + + + + + + + + + + +
Требования:Прочитать Введение в Django. Завершить изучение предыдущех частей руководства (включая Руководство часть 4: Django административный раздел сайта).
Цель:Понимать как создавать простые url-преобразования (которые не содержат никаких данных) и отображения, как получать данные из моделей и создавать шаблоны.
+ +

Обзор

+ +

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

+ +

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

+ + + +

+ +

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

+ +

Определяем URL-адреса страниц

+ +

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

+ +

Перечислим URL-адреса, которые понадобятся для наших страниц:

+ + + +

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

+ +

Последние два URL-адреса применяются для показа детальной информации об определенной книге, или авторе — в себе они содержат соответствующее значение идентификатора (показан как <id>, выше). URL-преобразование получает данную информацию и передает ее в отображение, которое применяет ее для запроса к базе данных. Для кодирования и применения данной информации в вашем URL-адресе нам понадобится только одно url-преобразование, соответствующее отображение и шаблон страницы для показа любой книги (или автора). 

+ +
+

Примечание: Django позволяет вам конструировать ваши URL-адреса любым, удобным для вас, способом — вы можете закодировать информацию в теле URL-адреса, как показано выше, или использовать URL-адрес типа GET (например, /book/?id=6). Независимо от ваших предпочтений, URL-адреса должны быть понятными, логичными и читабельными (посмотрите совет W3C здесь).
+
+ Документация Django рекомендует кодировать информацию в теле URL-адреса, на практике это приводит к лучшей стркутуре сайта.

+
+ +

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

+ +

Создание главной страницы сайта

+ +

Первой страницей, которую мы создадим, будет главная страница сайта (catalog/). Она будет небольшой статической HTML-страницей, которая будет показывать вычисленные "количества" различных записей из базы данных. Для того, чтобы проделать данную работу мы вначале создадим URL-преобразование, затем отображение и шаблон. 

+ +
+

Примечание: Лучше уделить больше внимания на данный раздел, поскольку информация, представленная здесь, применяется для создания всех страниц сайта.

+
+ +

URL-преобразование

+ +

Когда мы создавали скелет сайта мы обновили locallibrary/urls.py так что всякий раз, когда начинается URL-адрес наш catalog/ получен и URLConf catalog.urls подлючен для обработки оставшейся части строки.

+ +
urlpatterns += [
+    path('catalog/', include('catalog.urls')),
+]
+ +

Примечание: всякий раз, когда Django сталкивается c  django.urls.include()  он отбрасывает часть совпавшего URL , и отправляет оставшуюся строку в включенный URLconf для дальнейшей обработки.

+ +

Внутри нашего каталога приложения откройте urls.py и поместите в него текст, отмеченный жирным, ниже. 

+ +
urlpatterns = [
+    path('', views.index, name='index'),
+]
+ +

Эта функция path() определяет URL-паттерн (в данном случае это пустая строка:'' - мы поговорим чуть более подробно о них далее в данном руководстве) и функцию отображения, которая будет вызвана, если введенный адрес будет соответствать данному паттерну (views.index — это функция с именем index() в views.py).

+ +

Данная функция path(), кроме того, определяет параметр name, который уникально определяет это частное URL-преобразование. Вы можете использовать данное имя для "обратного" ("reverse") преобразования — то есть, для динамического создания URL-адреса, указывающего на ресурс, на которое указывает данное преобразование. Например, теперь, когда у нас имеется данное символическое имя, мы можем ссылаться на нашу домашнюю страницу при помощи создания следующей ссылки внутри какого-либо шаблона:

+ +
<a href="{% url 'index' %}">Home</a>.
+ +
+

Примечание: Мы могли бы, конечно, жестко указать прямую ссылку (то есть, <a href="/catalog/">Home</a>), но тогда, если мы изменим адрес нашей домашней страницы (например на /catalog/index), то данные ссылки перестанут корректно работать. Применение "обратного" url-преобразования более гибкий и эффективный подход!

+
+ +

Отображения (на основе функций)

+ +

Отображение является функцией, которая обрабатывает HTTP-запрос, получает данные из базы данных (при необходимости), которые применяются для генерации страницы HTML. Затем функция отображения возвращает сгенерированную страницу пользователю в виде HTTP-ответа. В нашем случае, индексная функция демонстрирует этот процесс — она получает информацию о количестве записей Book, BookInstance, доступности BookInstance, а также записи Author из базы данных, затем передает эти записи в шаблон страницы, генерирует страницу и передает ее пользователю (клиенту пользователя, например браузеру).

+ +

Откройте catalog/views.py и отметьте для себя, что данный файл уже импортирует функцию render() - функцию, которая генерирует HTML-файлы при помощи шаблонов страниц и соответствующих данных. 

+ +
from django.shortcuts import render
+
+# Создайте ваше отображение здесь
+
+ +

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

+ +
from .models import Book, Author, BookInstance, Genre
+
+def index(request):
+    """
+    Функция отображения для домашней страницы сайта.
+    """
+    # Генерация "количеств" некоторых главных объектов
+    num_books=Book.objects.all().count()
+    num_instances=BookInstance.objects.all().count()
+    # Доступные книги (статус = 'a')
+    num_instances_available=BookInstance.objects.filter(status__exact='a').count()
+    num_authors=Author.objects.count()  # Метод 'all()' применен по умолчанию.
+
+    # Отрисовка HTML-шаблона index.html с данными внутри
+    # переменной контекста context
+    return render(
+        request,
+        'index.html',
+        context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors},
+    )
+ +

Первая часть функции отображения получает количество записей при помощи вызова функции objects.all() у атрибута objects, доступного для всех классов моделей. Похожим образом мы получаем список объектов BookInstance, которые имеют статус 'a' (Доступно). Вы можете найти дополнительную инофрмацию о работе с моделями в предыдущей части руководства (Руководство часть 3: Применение моделей > Поиск записей).

+ +

В конце функции index вызывается функция  render(), которая, в качестве ответа, создает и возвращает страницу HTML  (эта функция "оборачивает" вызовы нескольких функций, тем самым существенно упрощая процесс разработки). В качестве параметров ей передаются объект request  (типа HttpRequest), шаблон HTML-страницы с метками (placeholders), которые будут замещены данными,  а также переменной context (словарь Python, который содержит данные, которые и будут замещать метки в шаблоне). 

+ +

В следующем разделе мы более подробно поговорим о шаблонах и переменной контекста. Давайте создадим наш шаблон, чтобы показать уже что-нибудь пользователю!

+ +

Шаблон

+ +

Шаблон это текстовый файл, который определяет структуру и расположение данных в файле, кроме того, в нем размещают специальные метки (placeholders), которые используются для показа реального содержимого, то есть данных. По умолчанию Django ищет файлы шаблонов в директории с именем 'templates' внутри вашего приложения. Например, внутри индексной функции отображения, которую мы только что создали, вызов render() будет пытаться найти файл /locallibrary/catalog/templates/index.html и в случае неудачи сгенерирует ошибку о том, что файл не найден. Вы можете увидеть данную ошибку, если вы сохраните предыдущие изменения, затем перейдете в браузер и наберете в адресной строке 127.0.0.1:8000. В результате, в окно браузера будет выведено сообщение об ошибке "TemplateDoesNotExist at /catalog/" и некоторая другая информация.

+ +
+

Примечание: На самом деле, в зависимости от настроек проекта, Django просматривает несколько мест в поисках шаблона (поиск в директории приложения осуществляется по умолчанию!). Вы можете найти больше информации о шаблонах и форматах, которые они поддерживают, перейдя по ссылке Шаблоны (Django docs).

+
+ +

Расширение шаблонов

+ +

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

+ +

Например, базовый шаблон base_generic.html может выглядеть как показано ниже. Как вы видите, этот файл содержит некоторую "общую" структуру HTML, разделы для заголовка, панель навигации и содержимое, отмеченное тэгами шаблона block и endblock (показано жирным). Данные блоки могут быть пустыми, или иметь содержимое, которое будет использоваться "по умолчанию" всеми страницами-наследниками.

+ +
+

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

+
+ +
<!DOCTYPE html>
+<html lang="en">
+<head>
+  {% block title %}<title>Local Library</title>{% endblock %}
+</head>
+
+<body>
+  {% block sidebar %}<!-- insert default navigation text for every page -->{% endblock %}
+  {% block content %}<!-- default content text (typically empty) -->{% endblock %}
+</body>
+</html>
+
+ +

Когда мы определяем шаблон для конкретного отображения, то в первую очередь мы объявляем базовый шаблон (при помощи тэга extends — смотрите код в следующем фрагменте). Если имеются блоки в базовом шаблоне, которые мы хотим заместить, тогда в нашем текущем шаблоне мы объявляем block/endblock и указываем соответствующее имя блока. 

+ +

Например фрагмент кода, показанный ниже, демонстрирует применение тэга extends и переопределяет блок с именем content. Окончальный код HTML будет содержать все структуры базового файла шаблона (включая содержимое по умолчанию, которое мы указали в блоке title) и код блока content, который мы разместили в текущем файле шаблона.

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+<h1>Local Library Home</h1>
+<p>Welcome to <em>LocalLibrary</em>, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.</p>
+{% endblock %}
+ +

Базовый шаблон сайта LocalLibrary

+ +

Базовый шаблон, который мы планируем использовать для сайта LocalLibrary, представлен ниже. Как вы видите, данный фрагмент содержит HTML код и объявляет следующие блоки title, sidebar и content. Мы добавили заголовок по умолчанию (который, возможно, мы захотим изменить), а также боковую панель навигации, содержащей ссылки на списки всех книг и авторов (панель навигации, мы, вероятно, не будем менять/замещать, но, тем не менее, добавив этот блок, мы оставим для себя такую возможность).

+ +
+

Примечание: Во фрагменте мы используем два дополнительных шаблонных тега: url и load static. Они будут описаны в следующих разделах.

+
+ +

Создайте новый файл — /locallibrary/catalog/templates/base_generic.html — и добавьте в него следующее содержимое:

+ +
<!DOCTYPE html>
+<html lang="en">
+<head>
+
+  {% block title %}<title>Local Library</title>{% endblock %}
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
+  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
+  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
+
+  <!-- Добавление дополнительного статического CSS файла -->
+  {% load static %}
+  <link rel="stylesheet" href="{% static 'css/styles.css' %}">
+</head>
+
+<body>
+
+  <div class="container-fluid">
+
+    <div class="row">
+      <div class="col-sm-2">
+      {% block sidebar %}
+      <ul class="sidebar-nav">
+          <li><a href="{% url 'index' %}">Home</a></li>
+          <li><a href="">All books</a></li>
+          <li><a href="">All authors</a></li>
+      </ul>
+     {% endblock %}
+      </div>
+      <div class="col-sm-10 ">
+      {% block content %}{% endblock %}
+      </div>
+    </div>
+
+  </div>
+</body>
+</html>
+ +

Шаблон использует (и включает в себя) JavaScript и CSS от Bootstrap для лучшего размещения элементов и формирования внешнего вида HTML страницы. Применение Bootstrap, или любого другого фреймворка для клиентской части сайта, является довольно продуктивным способом повышения привлекательности страницы, в том числе, это учитывает возможность запроса и показа сайта с устройств, с различными разрешениями экрана, а кроме того, это позволяет нам повысить уровень взаимодействия с пользователем — мы направим большую часть своих усилий на серверную часть нашего сайта!

+ +

Базовый шаблон ссылается на локальный файл css  (styles.css), который предоставляет дополнительные стили. Создайте /locallibrary/catalog/static/css/styles.css и добавьте в него следующее содержимое:

+ +
.sidebar-nav {
+    margin-top: 20px;
+    padding: 0;
+    list-style: none;
+}
+ +

Индексный шаблон (шаблон главной страницы сайта)

+ +

Создайте файл HTML /locallibrary/catalog/templates/index.html и скопируйте в него код, указанный ниже. Как вы наверное заметили, в первой строке мы расширяем наш базовый шаблон, а затем замещаем содержимое блока content, базового шаблона, новым содержимым текущего шаблона.

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+<h1>Local Library Home</h1>
+
+  <p>Welcome to <em>LocalLibrary</em>, a very basic Django website developed as a tutorial example on the Mozilla Developer Network.</p>
+
+<h2>Dynamic content</h2>
+
+  <p>The library has the following record counts:</p>
+  <ul>
+    <li><strong>Books:</strong> \{{ num_books }}</li>
+    <li><strong>Copies:</strong> \{{ num_instances }}</li>
+    <li><strong>Copies available:</strong> \{{ num_instances_available }}</li>
+    <li><strong>Authors:</strong> \{{ num_authors }}</li>
+  </ul>
+
+{% endblock %}
+ +

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

+ +
+

Примечание: Переменные шаблона заключаются в двойные фигурные скобки (\{{ num_books }}) , а тэги шаблона (функции шаблона), помещаются в одинарные фигурные скобки со знаками процента ({% extends "base_generic.html" %}).

+
+ +

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

+ +
return render(
+    request,
+    'index.html',
+     context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors},
+)
+ +

Ссылка на статические файлы их шаблонов

+ +

Любой ваш проект с большой вероятностью будет использовать статические ресурсы, включая JavaScript, CSS и изображения. В связи с тем, что расположение этих файлов может быть неизвестно (или может измениться), Django позволяет вам в шаблоне указать относительное расположение данных файлов при помощи глобального значения STATIC_URL (по умолчанию, значение параметра STATIC_URL установлено в '/static/',  но вы можете выбрать любое другое значение, указав, например, сетевой ресурс, или что-то еще).

+ +

Внутри шаблона вы  вызываете функцию (тэг) load, которая загружает статическую библиотеку "static" (как показано ниже). После того как статическая библиотека загружена, вы можете использовать тэг шаблона static, который указывает относительный путь URL к интересующему вас файлу.

+ +
 <!-- Добавляем дополнительный статический CSS-файл -->
+{% load static %}
+<link rel="stylesheet" href="{% static 'css/styles.css' %}">
+ +

Тем же способом вы можете загрузить нужное изображение. Например:

+ +
{% load static %}
+<img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="My image" style="width:555px;height:540px;"/>
+
+ +
+

Примечание: Фрагменты выше указывают пути расположения файлов, но Django не использует их по умолчанию. В процессе разработки сервер использует значения, указанные в глобальном файле URL-преобразований (/locallibrary/locallibrary/urls.py), который мы создали в части создание скелета сайта. В дальнейшем, в продакшине, вам нужно будет уточнить параметры расположения статических файлов. Мы вернемся к этому позже.

+
+ +

Для получения более подробной информации о работе со статическими файлами  обратитесь к документации по ссылке Управление статическими файлами (Django docs).

+ +

Построение URL-адресов

+ +

Базовый шаблон, указанный выше, вводит тэг url.

+ +
<li><a href="{% url 'index' %}">Home</a></li>
+
+ +

Данный тэг с именем url(), ищет в файле urls.py связанное значение переменной, указанной в качестве ее параметра 'index', а затем возвращает URL, который вы можете использовать для ссылки на соответствующие ресурсы.

+ +

Как теперь все это выглядит?

+ +

На данный момент мы должны были сделать все что необходимо, для того, чтобы показать главную страницу нашего сайта. Запустите сервер (python3 manage.py runserver) и введите в ваш браузер адрес http://127.0.0.1:8000/. Если все настроено как надо, то ваш сайт должен выглядеть как показано на следующей картинке.

+ +

Index page for LocalLibrary website

+ +
+

Примечание: На данном этапе вы не сможете воспользоваться ссылками на страницы All books и All authors, потому что url-адреса, отображения и шаблоны для данных страниц не созданы (мы просто объявили метки для соответствующих ссылок в базовом шаблоне base_generic.html).

+
+ +

Проверьте себя

+ +

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

+ +
    +
  1. В главном файле шаблона (base_generic.html) есть блок title. Переопределите этот блок в индексном шаблоне (index.html) и задейте новый заголовок для этой страницы.
  2. +
  3. Модифицируйте функцию отображения таким образом, чтобы получать из базы данных количество жанров и количество книг, которые содержат в своих заголовках какое-либо слово (без учета регистра), а затем передайте эти значения в шаблон.
  4. +
+ + + +

Итог

+ +

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

+ +

В  следующей части, при помощи наших новых знаний, мы  создадим еще четыре страницы.

+ +

Смотрите также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}

diff --git a/files/ru/learn/server-side/django/index.html b/files/ru/learn/server-side/django/index.html new file mode 100644 index 0000000000..7e167477b1 --- /dev/null +++ b/files/ru/learn/server-side/django/index.html @@ -0,0 +1,66 @@ +--- +title: Веб-фреймворк Django (Python) +slug: Learn/Server-side/Django +tags: + - back-end программирование + - Введение + - Джанго + - Изучать + - Начинающим +translation_of: Learn/Server-side/Django +--- +
{{LearnSidebar}}
+ +

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

+ +

Требования

+ +

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

+ +

Рекомендуется базовое понимание концепций программирования и языка Python, но это не обязательно для освоения основных понятий.

+ +
+

Примечание: Python является одним из самых доступных в чтении и понимании для новичков языком программирования. Тем не менее, если вы захотите глубже понять этот модуль, в Интернете сейчас доступны многочисленные бесплатные книги и учебные пособия (новички в программирование возможно захотят посетить Python for Non Programmers на вики-страницах python.org).

+
+ +

Руководство

+ +
+
Введение в Django
+
В этой первой статье по Django мы ответим на вопрос "Что такое Django?" и сделаем обзор того, что делает этот веб-фреймворк особенным. Мы кратко рассмотрим основные особенности, включая некоторый продвинутый функционал, на котором у нас не будет возможности подробно остановиться в этом модуле. Мы также покажем вам некоторые из основных строительных блоков приложения Django, чтобы дать вам представление о том, что он может сделать, прежде чем вы перейдете к установке и начнете экспериментировать.
+
Установка среды разработки Django
+
Теперь, когда вы знаете, что такое Django, мы покажем вам, как установить и протестировать среду разработки Django для Windows, Linux (Ubuntu) и Mac OS X — какую бы операционную систему вы не использовали, эта статья должна дать вам понимание того, что вам потребуется, чтобы начать разработку Django-приложений .
+
Учебник Django: Веб-сайт местной библиотеки
+
Первая статья в нашей серии практических уроков объясняет, что вы узнаете, и представит обзор веб-сайта «местной библиотеки», над которым мы будем работать и развиваться в последующих статьях.
+
Учебник Django часть 2: Создание скелета веб-сайта
+
В этой статье показано, как вы можете создать проект  веб-сайта «каркас» в качестве основы, после чего вы сможете заполнить параметры сайта, urls, модели, представления и шаблоны.
+
Учебник Django часть 3: Использование моделей
+
В этой статье показано, как определить модели для сайта местной библиотеки — модели представляют структуры данных, в которых мы хотим хранить данные нашего приложения, а также позволяют Django хранить данные в базе данных для нас (и модифицировать позже). Она раскрывает, что такое модель, как она объявляется и некоторые из основных типов полей. В ней также кратко показаны некоторые из основных способов доступа к данным модели.
+
Учебник Django часть 4: Django admin веб-сайта
+
Теперь, когда мы создали модели для сайта местной библиотеки, мы будем использовать Django Admin, чтобы добавить данные о книгах в библиотеке. Сначала мы покажем вам, как регистрировать и администрировать модели сайта а затем мы покажем вам, как входить в систему и создавать некоторые данные. В конце мы покажем некоторые способы дальнейшего улучшения представлений сайта.
+
Учебник Django часть 5: Создание главной страницы 
+
Теперь мы готовы добавить код для отображения нашей первой полной страницы — главной страницы сайта местной библиотеки, которая показывает, сколько записей у нас есть для каждого типа модели, и предоставляет ссылки на боковых панелях на другие наши страницы. По пути мы получим практический опыт написания основных карт и представлений URL, получения записей из базы данных и использования шаблонов.
+
Учебник Django часть 6: Общий список и подробные представления
+
Это руководство расширяет наш сайт местной библиотеки, добавляя список и подробные страницы для книг и авторов. Здесь мы узнаем об общих представлениях на основе классов и покажем, как они могут уменьшить количество кода, который вы должны писать для случаев общего использования. Мы также перейдем к обработке URL-адресов более подробно, покажем, как выполнить базовое сопоставление шаблонов.
+
Учебник Django часть 7:  Структура сессий
+
Это руководство расширяет наш сайт местной библиотеки, добавляя счётчик посещений домашней страницы. Это относительно простой пример, но он показывает, как вы можете использовать структуру сессии, чтобы обеспечить постоянное поведение анонимных пользователей на ваших собственных сайтах.
+
Учебник Django часть 8: Авторизация и права пользователей
+
В этом уроке мы покажем вам, как разрешить пользователям входить на ваш сайт со своими учетными записями и как управлять тем, что они могут делать и видеть на основе того, зарегистрированы ли они или нет, и их допусках. В рамках этой демонстрации мы расширим сайт местной библиотеки, добавив страницы входа и выхода, а также страницы пользователей и персональные страницы для просмотра книг, которые были взяты на руки.
+
Учебник Django часть 9: Работа с формами
+
В этом уроке мы покажем вам, как работать с HTML Forms в Django, и  в частности, самый простой способ писать формы для создания, обновления и удаления экземпляров модели. В рамках этой демонстрации мы расширим сайт местной библиотеки, чтобы библиотекари могли вносить новые книги, создавать, обновлять и удалять авторов, используя наши собственные формы (а не использовать приложение администратора).
+
Учебник Django часть10: Тестирование веб-приложения Django
+
По мере роста веб-сайтов становится сложнее проверять вручную — требуется больше проверок, поскольку взаимодействие между компонентами усложняется, небольшое изменение в одной области может потребовать дополнительные тесты для проверки его влияния на другие области. Один из способов смягчить эти проблемы - написать автоматизированные тесты, которые можно легко и надежно запускать каждый раз, когда вы вносите изменения. В этом руководстве показано, как автоматизировать модульное тестирование вашего сайта с помощью тестовой среды Django.
+
Учебник Django часть 11: Деплой Django на продакшн
+
Теперь вы создали (и протестировали) удивительный сайт местной библиотеки, вам захочется установить его на общедоступный веб-сервер, чтобы к нему мог получить доступ персонал библиотеки и пользователи Интернета. В этой статье представлен обзор того, как вы можете найти хост для развертывания вашего веб-сайта и что вам нужно сделать, чтобы подготовить ваш сайт к выпуску.
+
Безопасность веб-приложений Django
+
Защита пользовательских данных является неотъемлемой частью любой разработки сайта. Ранее мы объяснили некоторые из наиболее распространенных угроз безопасности в статье Web security — Эта статья дает практическую демонстрацию того, как встроенные средства защиты Django справляются с такими угрозами.
+
+ +

Задания

+ +

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

+ +
+
DIY Django мини-блог
+
В этом задании вы будете использовать некоторые знания, которые вы узнали из этого модуля, чтобы создать свой собственный блог.
+
diff --git a/files/ru/learn/server-side/django/models/index.html b/files/ru/learn/server-side/django/models/index.html new file mode 100644 index 0000000000..9ed7993e0b --- /dev/null +++ b/files/ru/learn/server-side/django/models/index.html @@ -0,0 +1,448 @@ +--- +title: 'Django учебник Часть 3: Использование моделей' +slug: Learn/Server-side/Django/Models +tags: + - Джанго + - данные + - модель + - туториал +translation_of: Learn/Server-side/Django/Models +--- +
             {{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}
+ +

В этой статье показано, как определить модели для LocalLibrary сайта. Она объясняет, что такое модель, как она объявляется, и некоторые из основных типов полей. В ней также кратко показаны некоторые из основных способов доступа к данным модели.

+ + + + + + + + + + + + +
ПредпосылкиDjango Tutorial Part 2: Creating a skeleton website.
Задача:Научиться проектировать и создавать свои собственные модели, выбирая подходящие поля.
+ +

Обзор

+ +

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

+ +

В этом учебнике показано, как определить и получить доступ к моделям на примере LocalLibrary website.

+ +

Проектирование моделей LocalLibrary

+ +

Перед тем, как вы начнёте программировать модели, стоит потратить несколько минут, чтобы подумать о том, какие данные нам нужно хранить, и о взаимоотношениях между разными объектами.

+ +

Мы знаем, что нам нужно хранить информацию о книгах (название, резюме, автор, язык, на котором написана книга, категория, ISBN) и что у нас может быть несколько доступных экземпляров (с уникальным глобальным идентификатором, статусом доступности и т. Д.). Нам может потребоваться хранить больше информации об авторе, чем просто их имя, и могут быть несколько авторов с одинаковыми или похожими именами. Мы хотим иметь возможность сортировать информацию на основе названия книги, автора, письменного языка и категории.

+ +

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

+ +

Вы также можете использовать модели для представления параметров списка выбора (например, как выпадающий список вариантов), вместо жёсткого кодирования выбора на самом веб-сайте - это рекомендуется, когда все варианты неизвестны заранее или могут измениться. Очевидные кандидаты на модели в этом случае включают жанр книги (например, «Научная фантастика», «Французская поэзия» и т. д.) И язык (английский, французский, японский).

+ +

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

+ +

Диаграмма ассоциации UML, приведённая ниже показывает модели, которые мы определили в этом случае (в виде блоков). Как и выше, мы создали модели для книги (общие сведения о книге), экземпляр книги (статус конкретных физических копий книги, доступных в системе) и автора.Мы также решили создать модель для жанра, чтобы можно было создавать / выбирать значения через интерфейс администратора. Мы решили не иметь модель для BookInstance: status - мы жестко закодировали значения (LOAN_STATUS), потому что мы не ожидаем их изменения. В каждом из полей вы можете увидеть имя модели, имена и типы полей, а также методы и их типы возврата.

+ +

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

+ +

LocalLibrary Model UML - v3

+ +
+

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

+
+ +

Модель для начинающих

+ +

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

+ +

Определение модели

+ +

Модели обычно определяются в приложении models.py. Они реализуются как подклассы django.db.models.Model, и могут включать поля, методы и метаданные. В приведенном ниже фрагменте кода показана «типичная» модель, названная MyModelName:

+ +
from django.db import models
+
+class MyModelName(models.Model):
+    """
+    A typical class defining a model, derived from the Model class.
+    """
+
+    # Fields
+    my_field_name = models.CharField(max_length=20, help_text="Enter field documentation")
+    ...
+
+    # Metadata
+    class Meta:
+        ordering = ["-my_field_name"]
+
+    # Methods
+    def get_absolute_url(self):
+         """
+         Returns the url to access a particular instance of MyModelName.
+         """
+         return reverse('model-detail-view', args=[str(self.id)])
+
+    def __str__(self):
+        """
+        String for representing the MyModelName object (in Admin site etc.)
+        """
+        return self.field_name
+ +

В следующих разделах мы подробно рассмотрим каждый элемент внутри модели:

+ +

Поля

+ +

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

+ +
my_field_name = models.CharField(max_length=20, help_text="Enter field documentation")
+ +

Наш вышеприведенный пример имеет одно поле, называемое my_field_name, типа models.CharField — что означает, что это поле будет содержать строки буквенно-цифровых символов. Типы полей назначаются с использованием определенных классов, которые определяют тип записи, которая используется для хранения данных в базе данных, а также критерии проверки, которые должны использоваться, когда значения получены из формы HTML (то есть, что составляет действительное значение). Типы полей также могут принимать аргументы, которые дополнительно определяют, как поле хранится или может использоваться. В этом случае мы даем нашему полю два аргумента:

+ + + +

Имя поля используется для обращения к нему в запросах и шаблонах. В полях также есть метка, которая задается как аргумент (verbose_name), либо выводится путем заглавной буквы первой буквы имени переменной поля и замены любых символов подчеркивания пробелом (например, my_field_name будет иметь метку по умолчанию My field name).

+ +

Порядок, в котором объявляются поля, будет влиять на их порядок по умолчанию, если модель отображается в форме (например, на сайте администратора), хотя это может быть переопределено.

+ +
Общие аргументы поля
+ +

Следующие общие аргументы могут использоваться при объявлении многих / разных типов полей:

+ + + +

Есть много других вариантов - вы можете просмотреть full list of field options here.

+ +
Общие типы полей
+ +

Следующие общие аргументы могут использоваться при объявлении многих / разных типов полей:

+ + + +

Существует много других типов полей, включая поля для разных типов чисел (большие целые числа, малые целые числа, дробные), логические значения, URL-адреса, slugs, уникальные идентификаторы и другие «связанные с временем» сведения (продолжительность, время и т. д.). Вы можете просмотреть full list here.

+ +

Метаданные

+ +

Вы можете объявить метаданные на уровне модели для своей модели, объявив класс Meta, как показано на рисунке.

+ +
class Meta:
+    ordering = ["-my_field_name"]
+    ...
+ +

Одной из наиболее полезных функций этих метаданных является управление сотрировкой записей, возвращаемых при запросе типа модели. Вы можете сделать это, указав соответствия названия полей для сортировки, как показано выше. Порядок будет зависеть от типа поля (поля символов отсортированы в алфавитном порядке, а поля даты отсортированы в хронологическом порядке). Как показано выше, вы можете префикс имени поля минус-символом (-), чтобы изменить порядок сортировки.

+ +

Например, если мы решили сортировать книги по умолчанию:

+ +
ordering = ["title", "-pubdate"]
+ +

Книги будут отсортированы по алфавиту по названию, от A-Z, а затем по дате публикации внутри каждого названия, от самого нового до самого старого.

+ +

Другим распространенным атрибутом является verbose_name, подробное имя для класса в единственной и множественной форме:

+ +
verbose_name = "BetterName"
+ +

Другие полезные атрибуты позволяют создавать и применять новые «разрешения доступа» для модели (разрешения по умолчанию применяются автоматически), разрешить упорядочение на основе другого поля или объявить, что класс является «абстрактным» (базовый класс, для которого вы не можете создавать записи, и вместо этого будет создан для создания других моделей). Многие другие параметры метаданных управляют тем, какая база данных должна использоваться для модели и как хранятся данные (это действительно полезно, если вам нужно сопоставить модель с существующей базой данных). Полный список опций метаданных доступен здесь: Model metadata options (Django документация).

+ +

Методы

+ +

Модель также может иметь методы. Минимально в каждой модели вы должны определить стандартный метод класса для Python __str __ (), чтобы вернуть удобочитаемую строку для каждого объекта. Эта строка используется для представления отдельных записей на сайте администрирования (и в любом другом месте, где вам нужно обратиться к экземпляру модели). Часто это возвращает поле названия или имени из модели.

+ +
def __str__(self):
+    return self.field_name
+ +

Другим распространенным методом включения в модели Django является get_absolute_url (), который возвращает URL-адрес для отображения отдельных записей модели на веб-сайте (если вы определяете этот метод, тогда Django автоматически добавит кнопку «Просмотр на сайте» на экранах редактирования записей модели на сайте администратора). Типичный шаблон для get_absolute_url () показан ниже.

+ +
def get_absolute_url(self):
+    """
+    Returns the url to access a particular instance of the model.
+    """
+    return reverse('model-detail-view', args=[str(self.id)])
+
+ +
+

Примечание. Предполагется, что вы будете использовать URL-адреса, например / myapplication / mymodelname / 2, для отображения отдельных записей для вашей модели (где «2» - это идентификатор для определенной записи), вам нужно будет создать URL-карту, чтобы передать ответ и идентификатор «Образцовое представление модели» (которое будет выполнять работу, необходимую для отображения записи). Вышеуказанная функция reverse () может «перевернуть» ваш URL-адрес (в приведенном выше примере с именем «model-detail-view»), чтобы создать URL-адрес правильного формата.

+ +

Конечно, для выполнения этой работы вам все равно придется писать сопоставление URL-адрес, просмотр и шаблон!

+
+ +

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

+ +

Управление моделью

+ +

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

+ +

Создание и изменение записей

+ +

Чтобы создать запись, вы можете определить экземпляр модели, а затем вызвать метод save ().

+ +
# Create a new record using the model's constructor.
+a_record = MyModelName(my_field_name="Instance #1")
+
+# Save the object into the database.
+a_record.save()
+
+ +
+

Примечание. Если вы не указали какое-либо поле в качестве primary_key, новая запись будет выдаваться автоматически, с идентификатором имени поля. Вы можете запросить это поле после сохранения указанной выше записи, и оно будет иметь значение 1.

+
+ +

Вы можете получить доступ к полям в этой новой записи с использованием синтаксиса точек и изменить значения. Вы должны вызвать save (), чтобы сохранить измененные значения в базе данных.

+ +
# Access model field values using Python attributes.
+print(a_record.id) #should return 1 for the first record.
+print(a_record.my_field_name) # should print 'Instance #1'
+
+# Change record by modifying the fields, then calling save().
+a_record.my_field_name="New Instance Name"
+a_record.save()
+ +

Поиск записей

+ +

Вы можете искать записи, соответствующие определенным критериям, используя атрибут объектов модели (предоставляемый базовым классом).

+ +
+

Примечание. Объяснение того, как искать записи, используя «абстрактную» модель и имена полей, может быть немного запутанным. В приведенном ниже обсуждении мы будем ссылаться на модель книги с полями названия и жанра, где жанр также является моделью с единственным именем в поле.

+
+ +

Мы можем получить все записи для модели как объект QuerySet,  используя objects.all(). QuerySet - это итерируемый объект, означающий, что он содержит несколько объектов, которые мы можем перебирать / прокручивать.

+ +
all_books = Book.objects.all()
+
+ +

Метод filter() Django позволяет отфильтровать возвращаемый QuerySet для соответствия указанному текстовому или числовому полю по конкретным критериям. Например, чтобы отфильтровать книги, содержащие  слово «wild» («дикие») в заголовке, а затем подсчитать их, мы могли бы сделать следующее.

+ +
wild_books = Book.objects.filter(title__contains='wild')
+number_wild_books = Book.objects.filter(title__contains='wild').count()
+
+ +

Соответствующие поля и тип соответствия определяются в имени параметра фильтра, используя формат: field_name__match_type (обратите внимание на двойное подчеркивание между заголовком выше). Выше мы фильтруем заголовок с учетом регистра. Есть много других типов совпадений, которые вы можете сделать: icontains (без учета регистра), iexact (точное совпадение без учета регистра), exact (точное совпадение с учетом регистра ) и in, gt (больше), startswith и т. д смотреть полный список (Django Docs, [EN]).

+ +

В некоторых случаях вам нужно будет фильтровать поле, которое определяет отношение «один ко многим» к другой модели (например, ForeignKey). В этом случае вы можете «индексировать» поля в связанной модели с дополнительными двойными подчеркиваниями. Так, например, чтобы фильтровать книги с определенным жанровым рисунком, вам нужно будет указывать имя в поле жанра, как показано ниже:

+ +
books_containing_genre = Book.objects.filter(genre__name__icontains='fiction')  # Will match on: Fiction, Science fiction, non-fiction etc.
+
+ +
+

Примечание: Вы можете использовать символы подчеркивания (__) для навигации по многим уровням отношений (ForeignKey / ManyToManyField) по своему усмотрению. Например, книга, имеющая разные типы, определяемая с использованием дополнительной связи «обложка», может иметь имя параметра: type__cover__name__exact = 'hard'.

+
+ +

Существует гораздо больше возможностей для запросов, включая обратные поиски от связанных моделей, цепочки фильтров, возврат меньшего набора значений и т. д. Для получения дополнительной информации см. Making queries (Django Docs, [EN]).

+ +

Определение моделей LocalLibrary

+ +

В этом разделе мы начнем определять модели для библиотеки. Откройте models.py (в / locallibrary / catalog /). Шаблон в верхней части страницы импортирует модуль моделей, который содержит базовый класс модели models.Model, от которого наследуются наши модели.

+ +
from django.db import models
+
+# Create your models here.
+ +

Модель жанра

+ +

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

+ +
class Genre(models.Model):
+    """
+    Model representing a book genre (e.g. Science Fiction, Non Fiction).
+    """
+    name = models.CharField(max_length=200, help_text="Enter a book genre (e.g. Science Fiction, French Poetry etc.)")
+
+    def __str__(self):
+        """
+        String for representing the Model object (in Admin site etc.)
+        """
+        return self.name
+ +

Модель имеет один CharField field (имя), которое используется для описания жанра (оно ограничено 200 символами и имеет некоторый help_text. В конце модели мы объявляем метод __str__(), который просто возвращает имя жанра, определенного конкретной записью. Verbose name не был определен, поэтому поле будет называться Name в формах.

+ +

Модель книги

+ +

Скопируйте модель книги ниже и снова вставьте ее в нижнюю часть файла. Модель книги представляет всю информацию о доступной книге в общем смысле, но не конкретный физический «экземпляр» или «копию» для временного использования. Модель использует CharField для представления названия книги и isbn (обратите внимание, как isbn указывает свой ярлык как «ISBN», используя первый неименованный параметр, поскольку в противном случае ярлык по умолчанию был бы «Isbn»). Модель использует TextField для summary, потому что этот текст, возможно, должен быть очень длинным.

+ +
from django.urls import reverse #Used to generate URLs by reversing the URL patterns
+
+class Book(models.Model):
+    """
+    Model representing a book (but not a specific copy of a book).
+    """
+    title = models.CharField(max_length=200)
+    author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
+    # Foreign Key used because book can only have one author, but authors can have multiple books
+    # Author as a string rather than object because it hasn't been declared yet in the file.
+    summary = models.TextField(max_length=1000, help_text="Enter a brief description of the book")
+    isbn = models.CharField('ISBN',max_length=13, help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn">ISBN number</a>')
+    genre = models.ManyToManyField(Genre, help_text="Select a genre for this book")
+    # ManyToManyField used because genre can contain many books. Books can cover many genres.
+    # Genre class has already been defined so we can specify the object above.
+
+    def __str__(self):
+        """
+        String for representing the Model object.
+        """
+        return self.title
+
+
+    def get_absolute_url(self):
+        """
+        Returns the url to access a particular book instance.
+        """
+        return reverse('book-detail', args=[str(self.id)])
+
+ +

Жанр представляет из себя ManyToManyField, так что книга может иметь несколько жанров, а жанр может иметь много книг. Автор объявляется через ForeignKey, поэтому в каждой книге будет только один автор, но у автора может быть много книг (на практике книга может иметь несколько авторов, но не в такой реализации!)

+ +

В обоих типах полей соответствующий класс модели объявляется как первый неименованный параметр, используя либо класс модели, либо строку, содержащую имя соответствующей модели. Вы должны использовать имя модели как строку, если связанный класс еще не был определен в этом файле до того, как он будет указан! Другими параметрами, представляющими интерес для поля автора, являются null=True, которое позволяет базе данных хранить значение Null , если автор не выбран, и on_delete = models. SET_NULL установит значение автора в Null, если связанная с автором запись будет удалена.

+ +

Модель также определяет __str __ (), используя поле заголовка книги для представления книги. Окончательный метод get_absolute_url () возвращает URL-адрес, который можно использовать для доступа к подробной записи для этой модели (для этого нам нужно будет определить сопоставление URL-адресов, в котором содержится подробная информация о книге, и определить связанное представление и шаблон ).

+ +

Модель BookInstance

+ +

Затем скопируйте модель BookInstance (показано ниже) под другие модели. BookInstance представляет собой определенную копию книги, которую кто-то может брать взаймы, и включает информацию о том, доступна ли копия или в какой день она ожидается, «отпечаток» или сведения о версии, а также уникальный идентификатор книги в библиотеке. Теперь некоторые из полей и методов будут знакомы. Модель использует

+ + + +
import uuid # Required for unique book instances
+
+class BookInstance(models.Model):
+    """
+    Model representing a specific copy of a book (i.e. that can be borrowed from the library).
+    """
+    id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text="Unique ID for this particular book across whole library")
+    book = models.ForeignKey('Book', on_delete=models.SET_NULL, null=True)
+    imprint = models.CharField(max_length=200)
+    due_back = models.DateField(null=True, blank=True)
+
+    LOAN_STATUS = (
+        ('m', 'Maintenance'),
+        ('o', 'On loan'),
+        ('a', 'Available'),
+        ('r', 'Reserved'),
+    )
+
+    status = models.CharField(max_length=1, choices=LOAN_STATUS, blank=True, default='m', help_text='Book availability')
+
+    class Meta:
+        ordering = ["due_back"]
+
+
+    def __str__(self):
+        """
+        String for representing the Model object
+        """
+        return '%s (%s)' % (self.id,self.book.title)
+ +

Мы дополнительно объявляем несколько новых типов полей:

+ + + +

Модель __str __ () представляет объект BookInstance, используя комбинацию его уникального идентификатора и связанного с ним заголовка книги.

+ +
+

Примечание. Немного Python'а:

+ + +
+ +

Модель автора

+ +

Скопируйте модель автора (показано ниже) под существующим кодом в models.py.

+ +

Теперь все поля/методы должны быть знакомы. Модель определяет автора как имя, фамилию, дату рождения и (необязательную) дату смерти. Он указывает, что по умолчанию __str __ () возвращает имя в фамилии, порядковый номер первого имени. Метод get_absolute_url () отменяет сопоставление URL-адреса автора с целью получения URL-адреса для отображения отдельного автора.

+ +
class Author(models.Model):
+    """
+    Model representing an author.
+    """
+    first_name = models.CharField(max_length=100)
+    last_name = models.CharField(max_length=100)
+    date_of_birth = models.DateField(null=True, blank=True)
+    date_of_death = models.DateField('Died', null=True, blank=True)
+
+    def get_absolute_url(self):
+        """
+        Returns the url to access a particular author instance.
+        """
+        return reverse('author-detail', args=[str(self.id)])
+
+
+    def __str__(self):
+        """
+        String for representing the Model object.
+        """
+        return '%s, %s' % (self.last_name, self.first_name)
+
+ +

Повторно выполнить миграцию базы данных

+ +

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

+ +
python3 manage.py makemigrations
+python3 manage.py migrate
+ +

Языковая модель - вызов

+ +

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

+ +

Некоторые вещи, которые следует учитывать:

+ + + +

После того, как вы решили, добавьте поле. Вы можете увидеть наше решение на Github here.

+ + + + + +

Резюме

+ +

В этой статье мы узнали, как определять модели, а затем использовать эту информацию в разработке и внедрении соответствующих моделей для сайта LocalLibrary.

+ +

На этом этапе мы отвлечемся от создания сайта и проверим Django Administration site. Этот сайт позволит нам добавить некоторые данные в библиотеку, которые мы можем отобразить с помощью наших (еще не созданных) представлений и шаблонов.

+ +

Смотрите также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}

diff --git a/files/ru/learn/server-side/django/skeleton_website/index.html b/files/ru/learn/server-side/django/skeleton_website/index.html new file mode 100644 index 0000000000..48dda2c3b8 --- /dev/null +++ b/files/ru/learn/server-side/django/skeleton_website/index.html @@ -0,0 +1,357 @@ +--- +title: 'Руководство по Django часть 2: создание скелета' +slug: Learn/Server-side/Django/skeleton_website +tags: + - django + - Введение + - Для новичков + - Программирование + - Руководство + - Статья +translation_of: Learn/Server-side/Django/skeleton_website +--- +
{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}
+ +

Это вторая статья из нашего руководства по Django, которая показывает, как можно создать "скелет" сайта, как фундамент, на котором можно строить всё остальное: настройки, ссылки, модели, контроллеры и представления.

+ + + + + + + + + + + + +
Необходимо: +

Настройка окружения. Прочитать первую статью руководства по Django.

+
Цель:Научиться использовать инструменты Django для создания новых веб-сайтов.
+ +

Обзор

+ +

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

+ +

Алгоритм следующий:

+ +
    +
  1. Использовать django-admin для создания папки проекта, шаблонов остальных файлов, и скрипта для управления проектом (manage.py).
  2. +
  3. Использовать manage.py для создания одного или нескольких приложений. +
    +

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

    +
    +
  4. +
  5. Зарегистрировать в настройках эти приложения, чтобы использовать их в проекте.
  6. +
  7. Настроить маршруты url адресов для каждого из приложений.
  8. +
+ +

Для Сайта местной библиотеки папка сайта и проекта будет называться locallibrary, и у нас будет одно приложение с названием catalog. Верхняя структура проекта будет следующей:

+ +
locallibrary/         # Папка сайтаmanage.py         # Скрипт для управления проектов (создан manage.py)
+    locallibrary/     # Папка сайта/проекта (создана manage.py)
+    catalog/          # Папка приложения (также создана manage.py)
+
+ +

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

+ +

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

+ +

Для начала откройте командную строку/терминал, перейдите в ту папку, куда вы хотите поместить проект Django(лучше в папке профиля пользователя C:\Users\user_name, при запуске командной строки используется именно эта директория), и создайте папку для вашего нового сайта (в данном случае: locallibrary). Затем войдите в эту папку, используя команду cd:

+ +
mkdir locallibrary
+cd locallibrary
+ +

Создайте новую папку, используя команду django-admin startproject как в примере ниже, и затем зайдите в созданную папку.

+ +
  django-admin startproject locallibrary .
+cd locallibrary
+ +

Команда django-admin создаст файловую структуру, как в примере ниже:

+ +
locallibrary/manage.pylocallibrary/
+        settings.py
+        urls.py
+        wsgi.py
+ +

Подпапка проекта locallibrary это ключевая директория нашего проекта: 

+ + + +

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

+ +

Создание приложения Каталог

+ +

Выполнив предыдущие шаги, запустите следующую команду для создания приложения catalog, который будет размещён внутри папки locallibrary (команду необходимо выполнять из папки, в которой находится manage.py):

+ +
python3 manage.py startapp catalog
+ +
+

Заметка: приведённая выше команда справедлива для GNU Linux/Mac OS. На Windows команда должна иметь вид: py -3 manage.py startapp catalog

+ +

Если вы работаете под Windows, заменяйте команду python3 на py -3 в этой и следующих статьях.

+
+ +

Эта команда создаст новую папку и наполнит её файлами различных частей приложения (выделенные полужирным ниже). Большинство файлов названы, исходя из их назначения (например контроллеры(views) должны находится во views.py, модели в models.py, тесты в tests.py, настройки административной части в admin.py, регистрация приложения в apps.py) и уже содержат некоторый шаблонный код для работы с вышеназванными объектами.

+ +

Обновлённая директория должна выглядеть следующим образом:

+ +
locallibrary/
+    manage.py
+    locallibrary/
+    catalog/
+        admin.py
+        apps.py
+        models.py
+        tests.py
+        views.py
+        __init__.py
+        migrations/
+
+ +

Кроме перечисленных выше файлов были созданы:

+ + + +
+

Заметка: Заметили, что некоторых файлов не хватает? В то время, как там нашли себе место файлы для контроллеров(views) и моделей(models), файлов для настройки url соотносителя, шаблонов, и статичных файлов создано не было. Далее мы покажем, как их создать (они не обязательны для каждого сайта, но нужны в данном примере).

+
+ +

Регистрация папки с приложением

+ +

После создания приложения, нам нужно зарегистрировать его в проекте, чтобы различные утилиты затрагивали его своим действием (например при добавлении моделей в базу данных). Приложения регистрируются добавлением их названий в список INSTALLED_APPS в настройках проекта(который, как мы помним, называется settings.py). 

+ +

Откройте файл locallibrary/locallibrary/settings.py и найдите в нём список INSTALLED_APPS . Затем добавьте новую строку в конец списка, как показано полужирным ниже.

+ +
INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'catalog.apps.CatalogConfig', 
+]
+ +

Новая строка указывает на файл конфигурации приложения (CatalogConfig), который был создан в /locallibrary/catalog/apps.py , когда вы создали приложение.

+ +
+

Заметка: Легко заметить, что в INSTALLED_APPS уже подключено большое количество приложений (и объектов MIDDLEWARE, ниже в файле конфигурации). Они добавляют поддержку админ-панели Django и, как следствие, огромное количество функционала (включая сессии, аутентификацию и прочее).

+
+ +

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

+ +

На этом шаге обычно указывают базу данных для будущего проекта — имеет смысл использовать для разработки и размещённого в Сети одну и ту же базу данных, по возможности, чтобы исключить различия в поведении.  Про различные варианты вы можете прочитать в документации Django в разделе Базы данных

+ +

Мы будем использовать базу данных SQLite для этого проекта, потому что не предпологаем большое количество одновременных запросов на неё, а ещё потому, что для её настройки совсем не надо ничего делать! Вы можете видеть, что база данных уже настроена в settings.py (подробная информация указана ниже):

+ +
DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+ +

Так как мы используем SQLite, то нам не нужно ничего делать.

+ +

Давайте продолжим!

+ +

Другие настройки проекта

+ +

Файл settings.py так же применяется и для некоторых других настроек, но на данном шаге имеет смысл поменять разве что TIME_ZONE — это значение должно быть представлено строкой, указанной в списке часовых поясов tz (колонка TZ в таблице, в строке временной зоны, которая вам нужна). Измените TIME_ZONE на одну из строк из таблицы, которая отвечает вашему часовому поясу. Например:

+ +
TIME_ZONE = 'Europe/Moscow'
+ +

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

+ + + +

Подключение URL-адреса

+ +

При создании сайта, был создан файл сопоставления URL (urls.py) в корне проекта. Хотя можно использовать его для обработки всех URL адресов, более целесообразно подключать отдельные файлы сопоставлений для каждого приложения.

+ +

Откройте locallibrary/locallibrary/urls.py и обратите внимание на закомментированный текст, который объясняет суть происходящего. 

+ +
"""
+locallibrary URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/1.10/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.conf.urls import url, include
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
+"""
+from django.urls import path
+from django.contrib import admin
+
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+]
+
+ +

URL соотношения хранятся в переменной urlpatterns, которая является списком функций path(). Каждая path() функция или ассоциирует шаблон URL с контроллером(views) или же его с другим таким списком (во втором случае, первый URL становится "базовым" для других, которые определяются в дочернем списке). Список urlpatterns инициализирует список функции, которая, например, соотносит admin/ с модулем admin.site.urls , который содержит собственный файл-соотноситель.

+ +

Добавьте строчки, приведённые ниже в низ файла urls.py , чтобы добавить новый элемент в список urlpatterns. Этот элемент содержит url() который направляет запросы с URL catalog/ к модулю  catalog.urls (файл с относительным путём /catalog/urls.py).

+ +
# Используйте include() чтобы добавлять URL из каталога приложения
+from django.urls import include
+from django.urls import path
+urlpatterns += [
+     path('catalog/', include('catalog.urls')),
+]
+
+ +

Теперь давайте перенаправим корневой URL нашего сайта (например 127.0.0.1:8000) на URL 127.0.0.1:8000/catalog/; это единственное приложение, которое мы собираемся использовать, поэтому это вполне разумно. Чтобы это использовать, нам понадобится специальная функция (RedirectView), которая принимает первым параметром новый относительный URL на который следует перенаправлять (/catalog/) когда указанный в функции url() адрес  соотносится с адресом запроса (корневой URL, в данном случае).

+ +

Добавьте следующие строчки, тоже в конец файла:

+ +
# Добавьте URL соотношения, чтобы перенаправить запросы с корневового URL, на URL приложения
+from django.views.generic import RedirectView
+urlpatterns += [
+    path('', RedirectView.as_view(url='/catalog/', permanent=True)),
+]
+ +

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

+ +

Добавьте последнюю часть в конец файла:

+ +
# Используйте static() чтобы добавить соотношения для статических файлов
+# Только на период разработки
+from django.conf import settings
+from django.conf.urls.static import static
+
+urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+
+ +
+

Заметка: Существуют различные способы дополнения списка urlpatterns (в примере мы просто добавляли объект, испольщуя оператор += чтобы чётко разделить изначальный и дописанный код). Вместо этого, мы могли бы добавить соотношения внутрь определения переменной:

+ +
urlpatterns = [   path('admin/', admin.site.urls),
+path('catalog/', include('catalog.urls')),path('',
+RedirectView.as_view(url='/catalog/', permanent=True)), ] +
+static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+ +

Кроме того, мы добавили import вниз файла (from django.urls import include) ,чтобы видеть, что мы добавили, но обычно все import'ы добавляются в верхнюю часть файла.

+
+ +

Напоследок, создайте файл urls.py внутри папки catalog, и добавьте следующий код, чтобы определить (пустой) urlpatterns. Сюда мы будем добавлять наши URL соотношения, по мере разработки сайта. 

+ +
from django.urls import path
+from . import views
+
+
+urlpatterns = [
+
+]
+
+ +

Тестирование работы скелета

+ +

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

+ +

До этого, нам предстоит впервые запустить  миграцию базы данных. Это обновит нашу базу данных и добавит туда необходимые модели (и уберёт некоторые предупреждения, которые были бы показаны при попытке запуска).

+ +

Запуск миграций базы данных

+ +

Django использует Объектный Соотноситель Связей (ORM) чтобы соотносить определения моделей в Django приложении со структурами данных, которые используются базой данных. Когда мы меняем наши модели, Django отслеживает изменения и может создать файлы миграций (в папке /locallibrary/catalog/migrations/) чтобы применить соответствующие структуры данных к базе, чтобы та соответствовала модели.

+ +

При создании сайта, Django автоматически добавил несколько моделей, чтобы мы могли их использовать в админ-панели (о которой мы поговорим позже). Выполните следующие команды, чтобы создать нужные таблицы в базе данных, соответствующие этим моделям (убедитесь, что вы находитесь в папке с manage.py):

+ +
python3 manage.py makemigrations
+python3 manage.py migrate
+
+ +
+

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

+
+ +

Команда makemigrations создаёт (но не применяет) миграции для всех приложений, которые установлены в ваш проект (вы так же можете указать в конце имя конкретного приложения, чтобы создать миграции только для него). Это даёт вам возможность проверить код перед тем, как их применить — когда вы станете хорошо разбираться в Django, то сможете даже менять их!

+ +

Команда migrate применяет созданные миграции к базе (Django отслеживает, какие миграции были созданы для данной базы).

+ +
+

Заметка: Посмотрите раздел Миграции в документации Django чтобы получить информацию о менее распространённых командах для управления миграциями.

+
+ +

Запуск сайта

+ +

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

+ +
+

Заметка: Отладочный веб-сервер не настолько функционален и производителен, для постоянного размещения , но это самый простой способ запустить свой сайт на Django и проверить его на наличие ошибок. По умолчанию, он разместит сайт на вашем компьютере (http://127.0.0.1:8000/), но вы так же можете указать различные компьютеры в вашей сети для этой цели. Для получения большего количества информации загляните в раздел django-admin и manage.py: отладочный сервер документации Django.

+
+ +

Запустите веб-сервер, используя команду runserver (в той же папке, что и manage.py):

+ +
python3 manage.py runserver
+
+ Performing system checks...
+
+ System check identified no issues (0 silenced).
+ September 22, 2016 - 16:11:26
+ Django version 1.10, using settings 'locallibrary.settings'
+ Starting development server at http://127.0.0.1:8000/
+ Quit the server with CTRL-BREAK.
+
+ +

Когда сервер запустится, вы сможете посетить сайт по адресу http://127.0.0.1:8000/ в вашем веб-браузере. Вы должны увидеть страницу с ошибкой, навроде этой:

+ +

Django debug page for a 404 not found error

+ +

Не волнуйтесь! Эта страница должна появиться и сообщить нам, что мы ещё не настроили ни одной страницы в модуле catalogs.urls (на который мы были перенаправлены запросили корневой URL сайта). 

+ +
+

Заметка: Показанная выше страница открывает нам одно из замечательных свойств Django — автоматические отчёты об ошибках. На экране с ошибкой отображается множество полезной информации, когда страница не найдена, или ошибка была вызвана кодом. В данном случае, мы видим, что запрошенный URL  не соответствует ни одному шаблону (из указанных). Подобные отчёты будут выключены при DEBUG=False (когда мы разместим приложение в Сеть), в этом случае будет показана менее информативная, но более дружелюбная к пользователю страница(которую вам надо будет создать - прим. переводчика).

+
+ +

На данном этапе, мы поняли, что Django работает должным образом! 

+ +
+

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

+
+ +

Домашнее задание

+ +

Папка catalog/ содержит файлы контроллеров(views), моделей(models), и других частей приложения. Просмотрите эти файлы. 

+ +

Как было написано выше, URL соотноситель для админ-панели был подключен в файле urls.py. Войдите в административную часть и посмотрите, что произойдёт (вы можете найти URL из соотношения выше).

+ + + +

Подводя итоги

+ +

Теперь вы создали полноценный скелет веб-приложения, который теперь вы можете расширить url соотносителями, контроллерами(views) и моделями(models).

+ +

Теперь скелет Сайта местной библиотеки сделан и запущен, теперь самое время начать писать код, который научит сайт делать то, что он должен делать. 

+ +

Также посмотрите эти статьи

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}

diff --git a/files/ru/learn/server-side/django/testing/index.html b/files/ru/learn/server-side/django/testing/index.html new file mode 100644 index 0000000000..699f9f0d23 --- /dev/null +++ b/files/ru/learn/server-side/django/testing/index.html @@ -0,0 +1,892 @@ +--- +title: 'Руководство часть 10: Тестирование приложений Django' +slug: Learn/Server-side/Django/Testing +tags: + - TDD + - django + - Для начинающих + - Разработка через тесты + - Руководство + - Сервер + - Тестирование в django + - сторона сервера + - тестирование + - юнит-тесты +translation_of: Learn/Server-side/Django/Testing +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}
+ +

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

+ + + + + + + + + + + + +
Требования:Изучить все предыдущие темы руководства, включая Руководство Django Часть 9: Работа с формами.
Цель:Понимать как создавать юнит тесты для сайта на основе Django.
+ +

Обзор

+ +

LocalLibrary в настоящий момент содержит страницы для показа списков всех книг, авторов, подробной информации о книгах Book и авторах Author, а также страницу для обновления информации об экземпляре книги BookInstance и, кроме того, страницы для создания, обновления и удаления записей модели Author (и модели Book, в том случае, если вы выполнили домашнее задание в руководстве работа с формами). Даже в случае небольшого сайта, ручной переход на каждую страницу и беглая проверка того, что все работает как следует, может занять несколько минут. В процессе внесения изменений и роста сайта требуемое время для проведения проверок будет только возрастать. Если бы мы продолжили в том же духе, то в какой-то момент на проведение тестов мы тратили бы больше времени, чем на написание кода и внесение изменений.

+ +

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

+ +

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

+ +

Данное руководство показывает процесс создания автоматических тестов в Django при помощи добавления их к разработке сайта LocalLibrary.

+ +

Типы тестирования

+ +

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

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

Примечание:  К другим типам тестов относятся методы чёрного ящика, белого ящика, ручные, автоматические, канареечные (canary), дымные (smoke), соответствия (conformance), принятия (acceptance), функциональные (functional),  системные (system), эффективности (performance), загрузочные (load) и стресс-тесты (stress tests).

+
+ +

Что Django предоставляет для тестирования?

+ +

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

+ +

Django предоставляет фреймворк для создания тестов, построенного на основе иерархии классов, которые, в свою очередь, зависят от стандартной библиотеки Python  unittest. Несмотря на название, данный фреймворк подходит и для юнит-, и для интеграционного тестирования. Фреймворк Django добавляет методы API и инструменты, которые помогают тестировать как веб так и, специфическое для Django, поведение. Это позволяет вам имитировать URL-запросы, добавление тестовых данных, а также проводить проверку выходных данных ваших приложений. Кроме того, Django предоставляет API (LiveServerTestCase) и инструменты для применения различных фреймфорков тестирования, например вы можете подключить популярный фреймворк Selenium для имитации поведения пользователя в реальном браузере.

+ +

Для написания теста вы должны наследоваться от любого из классов тестирования Django (или юниттеста)  (SimpleTestCaseTransactionTestCaseTestCaseLiveServerTestCase), а затем реализовать отдельные методы проверки кода (тесты это функции-"утверждения", которые проверяют, что результатом выражения являются значения True или False, или что два значения равны и так далее). Когда вы запускаете тест, фреймворк выполняет соответствующие тестовые методы в вашем классе-наследнике. Методы тестирования запускаются независимо друг от друга, начиная с метода настроек и/или завершаясь методом разрушения (tear-down), определенном в классе, как показано ниже.

+ +
class YourTestClass(TestCase):
+
+    def setUp(self):
+        # Установки запускаются перед каждым тестом
+        pass
+
+    def tearDown(self):
+        # Очистка после каждого метода
+        pass
+
+    def test_something_that_will_pass(self):
+        self.assertFalse(False)
+
+    def test_something_that_will_fail(self):
+        self.assertTrue(False)
+
+ +

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

+ +
+

Примечание: Класс django.test.TestCase очень удобен, но он может приводить к замедленной работе в некоторых случаях (не для каждого теста необходимо настраивать базу данных, или имитировать взаимодействие с отображеним). Когда вы познакомитесь с работой данного класса, то сможете заменить некоторые из ваших тестов на более простые классы тестирования.

+
+ +

Что вы должны тестировать?

+ +

Вы должны тестировать все аспекты, касающиеся вашего кода, но не библиотеки, или функциональность, предоставляемые Python, или Django.

+ +

Например, рассмотрим модель Author, определенную ниже. Вам не нужно проверять тот факт, что first_name и last_name были сохранены в базу данных как CharField, потому что за это отвечает непосредственно Django (хотя конечно, на практике  в течение разработки вы косвенно будете проверять данную функциональность). Тоже касается и, например, проверки того, что поле date_of_birth является датой, поскольку это тоже часть реализации Django.

+ +

Вы должны проверить текст для меток (First name, Last_name, Date of birth, Died), и размер поля, выделенного для текста (100 символов), потому что они являются частью вашей разработки и чем-то, что может сломаться/измениться в будущем.

+ +
class Author(models.Model):
+    first_name = models.CharField(max_length=100)
+    last_name = models.CharField(max_length=100)
+    date_of_birth = models.DateField(null=True, blank=True)
+    date_of_death = models.DateField('Died', null=True, blank=True)
+
+    def get_absolute_url(self):
+        return reverse('author-detail', args=[str(self.id)])
+
+    def __str__(self):
+        return '%s, %s' % (self.last_name, self.first_name)
+ +

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

+ +
+

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

+
+ +

Ну что же, усвоив данную информацию, давайте перейдем к процессу определения и запуска тестов.

+ +

Обзор стуктуры тестов

+ +

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

+ +

Django использует юнит-тестовый модуль - встроенный "обнаружитель" тестов, который находит тесты в текущей рабочей директории, в любом файле с шаблонным именем test*.py. Предоставляя соответствующие имена файлов, вы можете работать с любой структурой которая вас устраивает. Мы рекомендуем создать пакет для вашего тестирующего кода и, следовательно, отделить файлы моделей, отображений, форм и любые другие, от кода который будет использоваться для тестов. Например:

+ +
catalog/
+  /tests/
+    __init__.py
+    test_models.py
+    test_forms.py
+    test_views.py
+
+ +

В проекте LocalLibrary создайте файловую структуру, указанную выше. Файл __init__.py должен быть пустым (так мы говорим Питону, что данная директория является пакетом). Вы можете создать три тестовых файла при помощи копирования и переименования файла-образца /catalog/tests.py.

+ +
+

Примечание: Скелет тестового файла /catalog/tests.py был создан автоматически когда мы выполняли построение скелета сайта Django. Является абсолютно "легальным" действием - поместить все ваши тесты в данный файл, тем не менее, если вы проводите тесты "правильно", то вы очень быстро придете к очень большому и неуправляемому файлу тестирования.

+ +

Можете удалить данный файл, поскольку больше он нам не понадобится.

+
+ +

Откройте /catalog/tests/test_models.py. Файл должен импортировать django.test.TestCase, как показано ниже:

+ +
from django.test import TestCase
+
+# Поместите ваш код тестов здесь
+
+ +

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

+ +

Добавьте тестовый класс, показанный ниже, в нижнюю часть файла. Данный класс демонстрирует как создать класс тестирования при помощи наследования от TestCase.

+ +
class YourTestClass(TestCase):
+
+    @classmethod
+    def setUpTestData(cls):
+        print("setUpTestData: Run once to set up non-modified data for all class methods.")
+        pass
+
+    def setUp(self):
+        print("setUp: Run once for every test method to setup clean data.")
+        pass
+
+    def test_false_is_false(self):
+        print("Method: test_false_is_false.")
+        self.assertFalse(False)
+
+    def test_false_is_true(self):
+        print("Method: test_false_is_true.")
+        self.assertTrue(False)
+
+    def test_one_plus_one_equals_two(self):
+        print("Method: test_one_plus_one_equals_two.")
+        self.assertEqual(1 + 1, 2)
+ +

Этот класс определяет два метода которые вы можете использовать для дотестовой настройки (например, создание какой-либо модели, или других объектов, которые вам понадобятся):

+ + + +
+

Примечание. Классы тестирования также содержат метод tearDown(), который мы пока не используем. Этот метод не особенно полезен для тестирования баз данных, поскольку базовый класс TestCase автоматически разрывает соединения с ними.

+
+ +

Далее идут несколько методов, которые используют функции Assert, проверяющие условия "истинно" (true), "ложно" (false) или равенство (AssertTrue, AssertFalse, AssertEqual). Если условия не выполняются как ожидалось, то это приводит к провалу теста и выводу соответствующего сообщения об ошибке на консоль.

+ +

Функции проверки утверждений AssertTrue, AssertFalse, AssertEqual реализованы в unittest.  В данном фреймворке существуют и другие подобные функции, а кроме того, специфические для Django функции проверки, например, перехода из/к отображению (assertRedirects), проверки использования какого-то конкретного шаблона (assertTemplateUsed) и так далее.

+ +
+

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

+
+ +

Как запускать тесты

+ +

Простейшим способом запуска всех тестов является применение следующей команды:

+ +
python3 manage.py test
+ +

Таким образом мы найдем в текущей директории все файлы с именем test*.py и запустим все тесты (у нас имеются несколько файлов для тестирования, но на данный момент, только /catalog/tests/test_models.py содержит какие-либо тесты). По умолчанию, тесты сообщат что-нибудь, только в случае провала.

+ +

Запустите тесты из корневой папки сайта LocalLibrary. Вы должны увидеть вывод, который похож на следующий.

+ +
>python manage.py test
+
+Creating test database for alias 'default'...
+setUpTestData: Run once to set up non-modified data for all class methods.
+setUp: Run once for every test method to setup clean data.
+Method: test_false_is_false.
+.setUp: Run once for every test method to setup clean data.
+Method: test_false_is_true.
+.setUp: Run once for every test method to setup clean data.
+Method: test_one_plus_one_equals_two.
+.
+======================================================================
+FAIL: test_false_is_true (catalog.tests.tests_models.YourTestClass)
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "D:\Github\django_tmp\library_w_t_2\locallibrary\catalog\tests\tests_models.py", line 22, in test_false_is_true
+    self.assertTrue(False)
+AssertionError: False is not true
+
+----------------------------------------------------------------------
+Ran 3 tests in 0.075s
+
+FAILED (failures=1)
+Destroying test database for alias 'default'...
+ +

Как видите, один тест провалился и мы можем точно увидеть в какой именно функции это произошло и почему (так и было задумано, поскольку False не равен True!).

+ +
+

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

+
+ +

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

+ +

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

+ +

Еще больше тестовой информации

+ +

Если вы желаете получать больше информации о тестах вы должны изменить значение параметра verbosity. Например, для вывода списка успешных и неуспешных тестов (и всю информацию о том, как прошла настройка базы данных) вы можете установить значение verbosity равным "2":

+ +
python3 manage.py test --verbosity 2
+ +

Доступными значениями для verbosity являются  0, 1 (значение по умолчанию), 2 и 3.

+ +

Запуск определенных тестов

+ +

Если вы хотите запустить подмножество тестов, тогда вам надо указать полный путь к вашему пакету, модулю/подмодулю, классу наследникуTestCase, или методу:

+ +
python3 manage.py test catalog.tests   # Run the specified module
+python3 manage.py test catalog.tests.test_models  # Run the specified module
+python3 manage.py test catalog.tests.test_models.YourTestClass # Run the specified class
+python3 manage.py test catalog.tests.test_models.YourTestClass.test_one_plus_one_equals_two  # Run the specified method
+
+ +

Тестирование LocalLibrary

+ +

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

+ +
+

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

+
+ +

Модели

+ +

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

+ +

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

+ +
class Author(models.Model):
+    first_name = models.CharField(max_length=100)
+    last_name = models.CharField(max_length=100)
+    date_of_birth = models.DateField(null=True, blank=True)
+    date_of_death = models.DateField('Died', null=True, blank=True)
+
+    def get_absolute_url(self):
+        return reverse('author-detail', args=[str(self.id)])
+
+    def __str__(self):
+        return '%s, %s' % (self.last_name, self.first_name)
+ +

Откройте файл /catalog/tests/test_models.py и замените все его содержимое кодом, приведенном во фрагменте для тестирования модели Author (фрагмент представлен ниже).

+ +

В первой строке мы импортируем класс TestCase, а затем наследуемся от него, создавая класс с описательным именем (AuthorModelTest), оно поможет нам идентифицировать места провалов в тестах во время вывода информации на консоль. Затем мы создаем метод setUpTestData(), в котором создаем объект автора, который мы будем использовать в тестах, но нигде не будем изменять.

+ +
from django.test import TestCase
+
+# Create your tests here.
+
+from catalog.models import Author
+
+class AuthorModelTest(TestCase):
+
+    @classmethod
+    def setUpTestData(cls):
+        #Set up non-modified objects used by all test methods
+        Author.objects.create(first_name='Big', last_name='Bob')
+
+    def test_first_name_label(self):
+        author=Author.objects.get(id=1)
+        field_label = author._meta.get_field('first_name').verbose_name
+        self.assertEquals(field_label,'first name')
+
+    def test_date_of_death_label(self):
+        author=Author.objects.get(id=1)
+        field_label = author._meta.get_field('date_of_death').verbose_name
+        self.assertEquals(field_label,'died')
+
+    def test_first_name_max_length(self):
+        author=Author.objects.get(id=1)
+        max_length = author._meta.get_field('first_name').max_length
+        self.assertEquals(max_length,100)
+
+    def test_object_name_is_last_name_comma_first_name(self):
+        author=Author.objects.get(id=1)
+        expected_object_name = '%s, %s' % (author.last_name, author.first_name)
+        self.assertEquals(expected_object_name,str(author))
+
+    def test_get_absolute_url(self):
+        author=Author.objects.get(id=1)
+        #This will also fail if the urlconf is not defined.
+        self.assertEquals(author.get_absolute_url(),'/catalog/author/1')
+ +

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

+ +
# Получение объекта для тестирования
+author=Author.objects.get(id=1)
+
+# Получение метаданных поля для получения необходимых значений
+field_label = author._meta.get_field('first_name').verbose_name
+
+# Сравнить значение с ожидаемым результатом
+self.assertEquals(field_label,'first name')  
+ +

Интересно отметить следующее:

+ + + +
+

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

+
+ +

Кроме того, нам надо провести тесты наших собственных методов. Они просто проверяют, что имена объектов имеют следующие значения "Last Name, First Name" и что URL-адрес, по которому мы получаем экземпляр Author, такой как ожидается.

+ +
def test_object_name_is_last_name_comma_first_name(self):
+    author=Author.objects.get(id=1)
+    expected_object_name = '%s, %s' % (author.last_name, author.first_name)
+    self.assertEquals(expected_object_name,str(author))
+
+def test_get_absolute_url(self):
+    author=Author.objects.get(id=1)
+    #This will also fail if the urlconf is not defined.
+    self.assertEquals(author.get_absolute_url(),'/catalog/author/1')
+ +

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

+ +
======================================================================
+FAIL: test_date_of_death_label (catalog.tests.test_models.AuthorModelTest)
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "D:\...\locallibrary\catalog\tests\test_models.py", line 32, in test_date_of_death_label
+    self.assertEquals(field_label,'died')
+AssertionError: 'Died' != 'died'
+- Died
+? ^
++ died
+? ^
+ +

Это несущественный баг, но он демонстрирует нам то, что написание тестов может более тщательно проверить все неточности, которые вы можете сделать.

+ +
+

Примечание: Измените значение метки для поля date_of_death (/catalog/models.py) на "died" и перезапустите тесты.

+
+ +

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

+ +

Формы

+ +

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

+ +

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

+ +

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

+ +
class RenewBookForm(forms.Form):
+    """
+    Форма обновления книг для библиотекарей
+    """
+    renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).")
+
+    def clean_renewal_date(self):
+        data = self.cleaned_data['renewal_date']
+
+        #Проверка, что дата не в прошлом.
+        if data < datetime.date.today():
+            raise ValidationError(_('Invalid date - renewal in past'))
+        #Если дата в "далеком" будущем (+4 недели)
+        if data > datetime.date.today() + datetime.timedelta(weeks=4):
+            raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
+
+        # Всегда надо возвращать очищенные данные.
+        return data
+ +

Откройте файл /catalog/tests/test_forms.py и замените весь существующий в нем код, следующим кодом теста для формы RenewBookForm. Мы начали его с импорта нашей формы и некоторых библиотек Python и Django, которые погут нам провести тесты. Затем, тем же способом как мы делали для моделей, объявляем тестовый класс нашей формы, то есть применяя описательное имя класс наследника TestCase.

+ +
from django.test import TestCase
+
+# Создайте ваши тесты здесь
+
+import datetime
+from django.utils import timezone
+from catalog.forms import RenewBookForm
+
+class RenewBookFormTest(TestCase):
+
+    def test_renew_form_date_field_label(self):
+        form = RenewBookForm()
+        self.assertTrue(form.fields['renewal_date'].label == None or form.fields['renewal_date'].label == 'renewal date')
+
+    def test_renew_form_date_field_help_text(self):
+        form = RenewBookForm()
+        self.assertEqual(form.fields['renewal_date'].help_text,'Enter a date between now and 4 weeks (default 3).')
+
+    def test_renew_form_date_in_past(self):
+        date = datetime.date.today() - datetime.timedelta(days=1)
+        form_data = {'renewal_date': date}
+        form = RenewBookForm(data=form_data)
+        self.assertFalse(form.is_valid())
+
+    def test_renew_form_date_too_far_in_future(self):
+        date = datetime.date.today() + datetime.timedelta(weeks=4) + datetime.timedelta(days=1)
+        form_data = {'renewal_date': date}
+        form = RenewBookForm(data=form_data)
+        self.assertFalse(form.is_valid())
+
+    def test_renew_form_date_today(self):
+        date = datetime.date.today()
+        form_data = {'renewal_date': date}
+        form = RenewBookForm(data=form_data)
+        self.assertTrue(form.is_valid())
+
+    def test_renew_form_date_max(self):
+        date = timezone.now() + datetime.timedelta(weeks=4)
+        form_data = {'renewal_date': date}
+        form = RenewBookForm(data=form_data)
+        self.assertTrue(form.is_valid())
+
+ +

Первые две функции проверяют текст который должны содержать поля label и help_text. Доступ к полю мы получаем при помощи словаря (то есть, form.fields['renewal_date']). Отметим, что мы должны проверять содержит ли метка значение None, иначе в поле текста метки вы увидите "None".

+ +

Оставшиеся функции проверяют валидность дат, то есть их нахождение внутри определенного интервала, а также невалидность для значений, которые находятся вне заданного интервала. Для получения исходного значения мы использовали функцию получения текущей даты (datetime.date.today()), а также функцию datetime.timedelta() (которая принимает определенное число дней, или недель). Затем мы просто создали форму, передавая ей наши данные и проверяя ее на валидность.

+ +
+

Примечание: В данном примере мы не использовали ни базу данных, ни тестовый клиент. Рассмотрите модификацию этих тестов при помощи класса SimpleTestCase.

+ +

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

+
+ +

На этом с формами можно закончить; у нас имеются и другие тесты, но они были созданы обобщенными классами отображения для редактирования! Запустите тесты и убедитесь, что наш код все еще им соответствует!

+ +

Отображения

+ +

Для проверки поведения отображения мы используем тестовый клиет Django Client. Данный класс действует как упрощенный веб-браузер который мы применяем для имитации  GET и POST запросов и проверки ответов. Про ответы мы можем узнать почти все, начиная с низкоуровневого HTTP (итоговые заголовки и коды статусов) и вплоть до применяемых шаблонов, которые используются для HTML-рендера, а также контекста, который передается в соответствующий  шаблон. Кроме того, мы можем отследить последовательность перенаправлений (если имеются), проверить URL-адреса и коды статусов на каждом шаге. Все это позволит нам проверить, что каждое отображение выполняет то, что ожидается.

+ +

Давайте начнем с одного из простейших отображений которое возвращает список всех авторов. Вы можете его увидеть по URL-адресу /catalog/authors/ (данный URL-адрес можно найти в разделе приложения catalog,  в файле настроек urls.py по имени  'authors').

+ +
class AuthorListView(generic.ListView):
+    model = Author
+    paginate_by = 10
+
+ +

Поскольку это обобщенное отображение списка, то почти все за нас делает Django. Если вы доверяете Django, то единственной вещью, которую вам нужно протестировать, является переход к данному отображению по указанному URL-адресу. Таким образом, если вы применяете методику TDD (test-driven development, разработка через тесты), то начните проект с написания тестов, которые будут проверять, что данное отображение выводит всех авторов и, к тому же, например, блоками по 10.

+ +

Откройте файл /catalog/tests/test_views.py замените все его содержимое на следующий код теста для класса AuthorListView. Как и ранее, мы импортируем нашу модель и некоторые полезные классы. В методе setUpTestData() мы задаем число объектов класса Author которые мы тестируем при постраничном выводе.

+ +
from django.test import TestCase
+
+# Create your tests here.
+
+from catalog.models import Author
+from django.urls import reverse
+
+class AuthorListViewTest(TestCase):
+
+    @classmethod
+    def setUpTestData(cls):
+        #Create 13 authors for pagination tests
+        number_of_authors = 13
+        for author_num in range(number_of_authors):
+            Author.objects.create(first_name='Christian %s' % author_num, last_name = 'Surname %s' % author_num,)
+
+    def test_view_url_exists_at_desired_location(self):
+        resp = self.client.get('/catalog/authors/')
+        self.assertEqual(resp.status_code, 200)
+
+    def test_view_url_accessible_by_name(self):
+        resp = self.client.get(reverse('authors'))
+        self.assertEqual(resp.status_code, 200)
+
+    def test_view_uses_correct_template(self):
+        resp = self.client.get(reverse('authors'))
+        self.assertEqual(resp.status_code, 200)
+
+        self.assertTemplateUsed(resp, 'catalog/author_list.html')
+
+    def test_pagination_is_ten(self):
+        resp = self.client.get(reverse('authors'))
+        self.assertEqual(resp.status_code, 200)
+        self.assertTrue('is_paginated' in resp.context)
+        self.assertTrue(resp.context['is_paginated'] == True)
+        self.assertTrue( len(resp.context['author_list']) == 10)
+
+    def test_lists_all_authors(self):
+        #Get second page and confirm it has (exactly) remaining 3 items
+        resp = self.client.get(reverse('authors')+'?page=2')
+        self.assertEqual(resp.status_code, 200)
+        self.assertTrue('is_paginated' in resp.context)
+        self.assertTrue(resp.context['is_paginated'] == True)
+        self.assertTrue( len(resp.context['author_list']) == 3)
+ +

Все тесты используют клиент (принадлежащего классу TestCase, от которого мы наследовались) для имитации GET-запроса и получения ответа (resp). Первая версия проверяет заданный URL-адрес (заметьте, - просто определенный путь без указания домена), в то время как второй генерирует URL-адрес при помощи его имени, указанного в настройках.

+ +
resp = self.client.get('/catalog/authors/')
+resp = self.client.get(reverse('authors'))
+
+ +

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

+ +

Наиболее интересной переменной является resp.context, которая является объектом контекста, который передается шаблону из отображения. Он (объект контекста) очень полезен для тестов, поскольку позволяет нам убедиться, что наш шаблон получает все данные которые ему необходимы. Другими словами мы можем проверить, что мы используем правильный шаблон с данными, которые проделывают долгий путь проверок чтобы соответствовать данному шаблону.

+ +

Отображения и регистрация пользователей

+ +

В некоторых случаях вам нужно провести тесты отображений к которым имеют доступ только зарегистрированные пользователи. Например, LoanedBooksByUserListView очень похоже на наше предыдущее отображение, но доступно только для залогинившихся пользователей и показывает только те записи (BookInstance), которые соответствуют текущему пользователю, имеют статус 'on loan' (книга взята домой), а также забронированны.

+ +
from django.contrib.auth.mixins import LoginRequiredMixin
+
+class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
+    """
+    Обобщенный класс отображения списка взятых книг текущим пользователем
+    """
+    model = BookInstance
+    template_name ='catalog/bookinstance_list_borrowed_user.html'
+    paginate_by = 10
+
+    def get_queryset(self):
+        return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')
+ +

Добавьте тестовый код следующего фрагмента в /catalog/tests/test_views.py. В нем, для создания нескольких аккаунтов и  объектов BookInstance которые будут использоваться в дальнейших тестах, мы используем метод SetUp() (вместе с соответствующими книгами и другими записями). Половина книг бронируется  тестовыми пользователями, но в начале для них всех мы устанавливаем статус "доступно". Использование метода SetUp() предпочтительнее чем setUpTestData(), поскольку в дальнейшем мы будем модифицировать некоторые объекты.

+ +
+

Примечание: Метод setUp() создает книгу с заданным языком Language, но ваш код может не включать в себя модель Language, поскольку это было домашним заданием. В таком случае просто закомментируйте соответствующие строки. Поступите также и в следующем разделе, посвященном RenewBookInstancesViewTest.

+
+ +
import datetime
+from django.utils import timezone
+
+from catalog.models import BookInstance, Book, Genre, Language
+from django.contrib.auth.models import User # Необходимо для представления User как borrower
+
+class LoanedBookInstancesByUserListViewTest(TestCase):
+
+    def setUp(self):
+        # Создание двух пользователей
+        test_user1 = User.objects.create_user(username='testuser1', password='12345')
+        test_user1.save()
+        test_user2 = User.objects.create_user(username='testuser2', password='12345')
+        test_user2.save()
+
+        # Создание книги
+        test_author = Author.objects.create(first_name='John', last_name='Smith')
+        test_genre = Genre.objects.create(name='Fantasy')
+        test_language = Language.objects.create(name='English')
+        test_book = Book.objects.create(title='Book Title', summary = 'My book summary', isbn='ABCDEFG', author=test_author, language=test_language)
+        # Create genre as a post-step
+        genre_objects_for_book = Genre.objects.all()
+        test_book.genre.set(genre_objects_for_book) # Присвоение типов many-to-many напрямую недопустимо
+        test_book.save()
+
+        # Создание 30 объектов BookInstance
+        number_of_book_copies = 30
+        for book_copy in range(number_of_book_copies):
+            return_date= timezone.now() + datetime.timedelta(days=book_copy%5)
+            if book_copy % 2:
+                the_borrower=test_user1
+            else:
+                the_borrower=test_user2
+            status='m'
+            BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=the_borrower, status=status)
+
+    def test_redirect_if_not_logged_in(self):
+        resp = self.client.get(reverse('my-borrowed'))
+        self.assertRedirects(resp, '/accounts/login/?next=/catalog/mybooks/')
+
+    def test_logged_in_uses_correct_template(self):
+        login = self.client.login(username='testuser1', password='12345')
+        resp = self.client.get(reverse('my-borrowed'))
+
+        # Проверка что пользователь залогинился
+        self.assertEqual(str(resp.context['user']), 'testuser1')
+        # Проверка ответа на запрос
+        self.assertEqual(resp.status_code, 200)
+
+        # Проверка того, что мы используем правильный шаблон
+        self.assertTemplateUsed(resp, 'catalog/bookinstance_list_borrowed_user.html')
+
+ +

Если пользователь не залогирован то, чтобы убедиться в том что отображение перейдет на страницу входа (логирования), мы используем метод assertRedirects, что продемонстрировано в методе test_redirect_if_not_logged_in(). Затем мы осуществляем вход для пользователя и проверям что полученный статус status_code равен 200 (успешно). 

+ +

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

+ +
    def test_only_borrowed_books_in_list(self):
+        login = self.client.login(username='testuser1', password='12345')
+        resp = self.client.get(reverse('my-borrowed'))
+
+        #Проверка, что пользователь залогинился
+        self.assertEqual(str(resp.context['user']), 'testuser1')
+        #Check that we got a response "success"
+        self.assertEqual(resp.status_code, 200)
+
+        #Проверка, что изначально у нас нет книг в списке
+        self.assertTrue('bookinstance_list' in resp.context)
+        self.assertEqual( len(resp.context['bookinstance_list']),0)
+
+        #Теперь все книги "взяты на прокат"
+        get_ten_books = BookInstance.objects.all()[:10]
+
+        for copy in get_ten_books:
+            copy.status='o'
+            copy.save()
+
+        #Проверка, что все забронированные книги в списке
+        resp = self.client.get(reverse('my-borrowed'))
+        #Проверка, что пользователь залогинился
+        self.assertEqual(str(resp.context['user']), 'testuser1')
+        #Проверка успешности ответа
+        self.assertEqual(resp.status_code, 200)
+
+        self.assertTrue('bookinstance_list' in resp.context)
+
+        #Подтверждение, что все книги принадлежат testuser1 и взяты "на прокат"
+        for bookitem in resp.context['bookinstance_list']:
+            self.assertEqual(resp.context['user'], bookitem.borrower)
+            self.assertEqual('o', bookitem.status)
+
+    def test_pages_ordered_by_due_date(self):
+
+        #Изменение статуса на "в прокате"
+        for copy in BookInstance.objects.all():
+            copy.status='o'
+            copy.save()
+
+        login = self.client.login(username='testuser1', password='12345')
+        resp = self.client.get(reverse('my-borrowed'))
+
+        #Пользователь залогинился
+        self.assertEqual(str(resp.context['user']), 'testuser1')
+        #Check that we got a response "success"
+        self.assertEqual(resp.status_code, 200)
+
+        #Подтверждение, что из всего списка показывается только 10 экземпляров
+        self.assertEqual( len(resp.context['bookinstance_list']),10)
+
+        last_date=0
+        for copy in resp.context['bookinstance_list']:
+            if last_date==0:
+                last_date=copy.due_back
+            else:
+                self.assertTrue(last_date <= copy.due_back)
+ +

Если хотите, то вы, безусловно, можете добавить тесты проверяющие постраничный вывод!

+ +

Тестирование форм и отображений

+ +

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

+ +

В качестве демонстрации давайте напишем некоторые тесты для отображения, которые отвечают за обновление книг(renew_book_librarian()):

+ +
from .forms import RenewBookForm
+
+@permission_required('catalog.can_mark_returned')
+def renew_book_librarian(request, pk):
+    """
+    Функция отображения обновления экземпляра BookInstance библиотекарем
+    """
+    book_inst=get_object_or_404(BookInstance, pk = pk)
+
+    # Если это POST-запрос, тогда обработать данные формы
+    if request.method == 'POST':
+
+        # Создать объект формы и заполнить ее данными из запроса (связывание/биндинг):
+        form = RenewBookForm(request.POST)
+
+        # Проверка валидности формы:
+        if form.is_valid():
+            # process the data in form.cleaned_data as required (here we just write it to the model due_back field)
+            book_inst.due_back = form.cleaned_data['renewal_date']
+            book_inst.save()
+
+            # переход по URL-адресу:
+            return HttpResponseRedirect(reverse('all-borrowed') )
+
+    # Если это GET-запрос (или что-то еще), то создаем форму по умолчанию
+    else:
+        proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
+        form = RenewBookForm(initial={'renewal_date': proposed_renewal_date,})
+
+    return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})
+ +

Нам надо проверить что к данному отображению имеют доступ только те пользователи, которые имеют разрешение типа can_mark_returned, а кроме того, что пользователи перенаправляются на страницу ошибки HTTP 404  если они пытаются обновить экземпляр книги  BookInstance, который не существует. Мы должны проверить что начальное значение формы соответствует дате через 3 недели в будущем, а также то, что если форма прошла валидацию, то мы переходим на страницу отображения книг "all-borrowed" (забронированных). Для тестов, отвечающих за проверку "провалов", мы также должны удостовериться что они отправляют соответствующие сообщения об ошибках.

+ +

В нижнюю часть файла /catalog/tests/test_views.py добавьте класс тестрования (показан во фрагменте, ниже). Он создает двух пользователей и два экземпляра книги, но только один пользователь получает необходимый доступ к соответствующему отображению. Код, который "присваивает" соответствующий доступ, выделен в коде жирным:

+ +
from django.contrib.auth.models import Permission # Required to grant the permission needed to set a book as returned.
+
+class RenewBookInstancesViewTest(TestCase):
+
+    def setUp(self):
+        #Создание пользователя
+        test_user1 = User.objects.create_user(username='testuser1', password='12345')
+        test_user1.save()
+
+        test_user2 = User.objects.create_user(username='testuser2', password='12345')
+        test_user2.save()
+        permission = Permission.objects.get(name='Set book as returned')
+        test_user2.user_permissions.add(permission)
+        test_user2.save()
+
+        #Создание книги
+        test_author = Author.objects.create(first_name='John', last_name='Smith')
+        test_genre = Genre.objects.create(name='Fantasy')
+        test_language = Language.objects.create(name='English')
+        test_book = Book.objects.create(title='Book Title', summary = 'My book summary', isbn='ABCDEFG', author=test_author, language=test_language,)
+        #Создание жанра Create genre as a post-step
+        genre_objects_for_book = Genre.objects.all()
+        test_book.genre=genre_objects_for_book
+        test_book.save()
+
+        #Создание объекта BookInstance для для пользователя test_user1
+        return_date= datetime.date.today() + datetime.timedelta(days=5)
+        self.test_bookinstance1=BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user1, status='o')
+
+        #Создание объекта BookInstance для для пользователя test_user2
+        return_date= datetime.date.today() + datetime.timedelta(days=5)
+        self.test_bookinstance2=BookInstance.objects.create(book=test_book,imprint='Unlikely Imprint, 2016', due_back=return_date, borrower=test_user2, status='o')
+ +

В нижнюю часть класса тестирования добавьте следующие методы (из следующего фрагмента). Они проверяют, что только пользователь с соответствущим доступом (testuser2) имеет доступ к отображению. Мы проверяем все случаи: когда пользователь не залогинился, когда залогинился, но не имеет соответствующего доступа, когда имеет доступ, но не является заемщиком книги (тест должен быть успешным), а также, что произойдет если попытаться получить доступ к книге BookInstance которой не существует. Кроме того, мы проверям то, что используется правильный (необходимый) шаблон.

+ +
    def test_redirect_if_not_logged_in(self):
+        resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
+        #Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
+        self.assertEqual( resp.status_code,302)
+        self.assertTrue( resp.url.startswith('/accounts/login/') )
+
+    def test_redirect_if_logged_in_but_not_correct_permission(self):
+        login = self.client.login(username='testuser1', password='12345')
+        resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
+
+        #Manually check redirect (Can't use assertRedirect, because the redirect URL is unpredictable)
+        self.assertEqual( resp.status_code,302)
+        self.assertTrue( resp.url.startswith('/accounts/login/') )
+
+    def test_logged_in_with_permission_borrowed_book(self):
+        login = self.client.login(username='testuser2', password='12345')
+        resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance2.pk,}) )
+
+        #Check that it lets us login - this is our book and we have the right permissions.
+        self.assertEqual( resp.status_code,200)
+
+    def test_logged_in_with_permission_another_users_borrowed_book(self):
+        login = self.client.login(username='testuser2', password='12345')
+        resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
+
+        #Check that it lets us login. We're a librarian, so we can view any users book
+        self.assertEqual( resp.status_code,200)
+
+    def test_HTTP404_for_invalid_book_if_logged_in(self):
+        import uuid
+        test_uid = uuid.uuid4() #unlikely UID to match our bookinstance!
+        login = self.client.login(username='testuser2', password='12345')
+        resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':test_uid,}) )
+        self.assertEqual( resp.status_code,404)
+
+    def test_uses_correct_template(self):
+        login = self.client.login(username='testuser2', password='12345')
+        resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
+        self.assertEqual( resp.status_code,200)
+
+        #Check we used correct template
+        self.assertTemplateUsed(resp, 'catalog/book_renew_librarian.html')
+
+ +

Добавьте еще один тестовый метод, показанный ниже. Он проверяет что начальная дата равна трем неделям в будущем. Заметьте, что мы имеем возможность получить доступ к начальному значению из поля формы (выделено жирным).

+ +
    def test_form_renewal_date_initially_has_date_three_weeks_in_future(self):
+        login = self.client.login(username='testuser2', password='12345')
+        resp = self.client.get(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}) )
+        self.assertEqual( resp.status_code,200)
+
+        date_3_weeks_in_future = datetime.date.today() + datetime.timedelta(weeks=3)
+        self.assertEqual(resp.context['form'].initial['renewal_date'], date_3_weeks_in_future )
+
+ +

Следующий тест (тоже добавьте его в свой класс) проверяет что отображение, в случае успеха, перенаправляет пользователя к списку всех забронированных книг. Здесь мы показываем как при помощи клиента вы можете создать и передать данные в POST-запросе. Данный запрос передается вторым аргументом в пост-функцию и представляет из себя словарь пар ключ/значение.

+ +
    def test_redirects_to_all_borrowed_book_list_on_success(self):
+        login = self.client.login(username='testuser2', password='12345')
+        valid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=2)
+        resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future} )
+        self.assertRedirects(resp, reverse('all-borrowed') )
+
+ +
+

Вместо перехода к отображению all-borrowed, добавленого в качестве домашнего задания, вы можете перенаправить пользователя на домашнюю страницу '/'. В таком случае, исправьте две последние строки тестового кода на код, показанный ниже. Присваивание follow=True, в запросе, гарантирует что запрос вернет окончательный URL-адрес пункта назначения (следовательно проверяется /catalog/, а не /).

+ +
 resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future},follow=True )
+ self.assertRedirects(resp, '/catalog/')
+
+ +

Скопируйте две последние функции в класс, представленные ниже. Они тоже проверяют  POST-запросы, но для случая неверных дат. Мы используем функцию assertFormError(), чтобы проверить сообщения об ошибках.

+ +
    def test_form_invalid_renewal_date_past(self):
+        login = self.client.login(username='testuser2', password='12345')
+        date_in_past = datetime.date.today() - datetime.timedelta(weeks=1)
+        resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':date_in_past} )
+        self.assertEqual( resp.status_code,200)
+        self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal in past')
+
+    def test_form_invalid_renewal_date_future(self):
+        login = self.client.login(username='testuser2', password='12345')
+        invalid_date_in_future = datetime.date.today() + datetime.timedelta(weeks=5)
+        resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':invalid_date_in_future} )
+        self.assertEqual( resp.status_code,200)
+        self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead')
+
+ +

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

+ +

Шаблоны

+ +

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

+ +

Другие рекомендованные инструменты для тестирования

+ +

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

+ +

Из всего множества сторонних инструментов тестирования, мы кратко опишем возможности двух:

+ + + +

Домашняя работы

+ +

Существуют другие модели и отображения, которые мы могли бы протестировать. В качестве простого упражнения, попробуйте создать тестовый вариант для отображения AuthorCreate.

+ +
class AuthorCreate(PermissionRequiredMixin, CreateView):
+    model = Author
+    fields = '__all__'
+    initial={'date_of_death':'12/10/2016',}
+    permission_required = 'catalog.can_mark_returned'
+ +

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

+ +

Итоги

+ +

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

+ +

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

+ +

Следующая и последняя часть руководства покажет вам как запустить ваш чудесный (и полностью протестированный!) веб-сайт Django.

+ +

Смотрите также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}

diff --git a/files/ru/learn/server-side/django/tutorial_local_library_website/index.html b/files/ru/learn/server-side/django/tutorial_local_library_website/index.html new file mode 100644 index 0000000000..36ad7aa9cb --- /dev/null +++ b/files/ru/learn/server-side/django/tutorial_local_library_website/index.html @@ -0,0 +1,74 @@ +--- +title: 'Руководство по Django: сайт местной библиотеки' +slug: Learn/Server-side/Django/Tutorial_local_library_website +tags: + - django + - Для начинающих + - Программирование + - Руководство + - Серверная часть +translation_of: Learn/Server-side/Django/Tutorial_local_library_website +--- +
{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}
+ +

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

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

Обзор руководства

+ +

Добро пожаловать на руководство MDN "Сайт местной библиотеки" по фреймворку Django, который может использоваться для управления архивом библиотеки.

+ +

В цикле статей мы научимся:

+ + + +

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

+ +

Сайт местной библиотеки

+ +

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

+ +

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

+ + + +

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

+ +

Я застрял, где мне взять код?

+ +

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

+ +

Если вы застряли, то можете найти полноценную версию сайта на Github.

+ +

Подводя итоги

+ +

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

+ +

{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}

diff --git a/files/ru/learn/server-side/django/web_application_security/index.html b/files/ru/learn/server-side/django/web_application_security/index.html new file mode 100644 index 0000000000..84448f9eb3 --- /dev/null +++ b/files/ru/learn/server-side/django/web_application_security/index.html @@ -0,0 +1,179 @@ +--- +title: Безопасность веб-приложения Django +slug: Learn/Server-side/Django/web_application_security +translation_of: Learn/Server-side/Django/web_application_security +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}
+ +

Защита пользовательских данных - важная часть проектирования любого веб-сайта.Ранее мы рассматривали некоторые наиболее распространенные угрозы безопасности в теме Веб безопасность. В данной статье будет представлена практическая демонстрация того, как встроенные механизмы защиты Django's обрабатывают подобные угрозы.

+ + + + + + + + + + + + +
Требования:Прочитайте тему Веб безопасность. Завершите изучение предыдущих частей руководства до Руководство часть 9: Работа с формами включительно.
Цель:Понять, что нужно делать (или наоборот не делать), для обеспечения безопасности вашего веб-приложения.
+ +

Обзор

+ +

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

+ +
+

Важно: Наиболее важный урок, который вы должны усвоить, состоит в том - что никогда не стоит доверять переданным пользователем данным. Они включают в себя GET параметры в URL, тело POST запроса, HTTP заголовки, cookies, загруженные пользователем данные и т.д. Всегда проверяйте и обрабатывайте все входные данные. Всегда готовьтесь к худшему.

+
+ +

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

+ +

Распространенные угрозы/методы защиты

+ +

Мы не будем дублировать документацию Django и в данной статье продемонстрируем некоторые основные методы обеспечения безопасности в контексте разрабатываемого в данном руководстве приложения LocalLibrary.

+ +

Межсайтовый скриптинг (XSS)

+ +

XSS это термин, применяющийся для описания класса атак, позволяющего атакующему, через веб-сайт внедрить скрипты, которые будут выполнены на устройстве зашедшего на страницу пользователя. Часто это происходит через сохранение вредоносного кода в базе данных, откуда данный код будет возвращен и выполнен для запросившего некие данные пользователя (типичный пример - сохранение тега <script> с вредоносным кодом в комментарии, который может увидеть другой пользователь). Другой вектор атаки - в том чтобы сгенерировать определенную ссылку, при клике на которую пользователь запустит выполнение некоего замаскированного кода JavaScript в своем браузере.

+ +

Система шаблонов Django защищает от большинства XSS атак,  экранируя определенные символы, считающиеся "опасными" в HTML. Мы можем продемонстрировать это, попытавшись внедрить произвольный JavaScript код в наше приложение LocalLibrary через форму добавления автора, созданную в Руководство часть 9: Работа с формами.

+ +
    +
  1. Запустите веб-сайт, используя сервер разработки (python3 manage.py runserver).
  2. +
  3. Откройте сайт в вашем браузере и войдите под аккаунтом супер-пользователя.
  4. +
  5. Перейдите на страницу добавления автора (она должна быть доступна по URL: http://127.0.0.1:8000/catalog/author/create/).
  6. +
  7. Введите данные об имени и фамилии, датах рождения и смерти автора. Затем добавьте следующую строку в поле фамилии:
    + <script>alert('Test alert');</script>.
    + Author Form XSS test +
    +

    Примечание: Это безобидный скрипт, который, если будет выполнен, отобразит окно с сообщением "Test alert" в вашем браузере. Если данное окно отображается при открытии страницы с созданной подобным образом записью - значит сайт уязвим перед атаками XSS.

    +
    +
  8. +
  9. Нажмите Submit для сохранения записи.
  10. +
  11. После сохранения автора - он должен быть отображен, как показано ниже. Так как сработала защита от XSS - команда alert() не будет запущена. Вместо этого скрипт будет отображаться как обычный текст.Author detail view XSS test
  12. +
+ +

Если вы посмотрите исходный HTML код, вы увидите, что "опасные" символы - например такие как скобки тегов - были заменены на их безопасные эквивалентные html сущности (к примеру > на &gt;)

+ +
<h1>Author: Boon&lt;script&gt;alert(&#39;Test alert&#39;);&lt;/script&gt;, David (Boonie) </h1>
+
+ +

Использование шаблонов Django защищает вас от большинтсва XSS атак. Однако существует возможность отключения данной защиты, при котором экранирование не будет автоматически применятся ко всем полям, которые не должны будут заполнятся пользователем(к примеру, поле help_text обычно заполняется не пользователем, поэтому Django не будет экранировать его значение).

+ +

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

+ +

Межсайтовая подделка запроса (CSRF)

+ +

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

+ +
+

Примечание: Очевидно, что наш хакер делает это не ради денег! Более амбициозные хакеры могут использовать описываемый подход для выполнения более опасных задач (например, переводы денег пользователей на их личные счета и т.д).

+
+ +

Для того, чтобы сделать это, хакер может создать HTML файл, подобный продемонстрированному ниже, который будет содержать форму создания автора (похожую на ту, что мы разрабатывали в предыдущих частях руководства), которая будет отправлена как только данный файл будет загружен в браузер. Хакер отправит данный файл всем Библиотекарям и будет ждать пока кто-либо из них откроет файл (он содержит только безобидную информацию, честно!). Если файл будет открыт любым залогиненным пользователм, с правами Библиотекаря - тогда форма будет  отправлена от его имени и создаст нового пользователя.

+ +
<html>
+<body onload='document.EvilForm.submit()'>
+
+<form action="http://127.0.0.1:8000/catalog/author/create/" method="post" name='EvilForm'>
+  <table>
+    <tr><th><label for="id_first_name">First name:</label></th><td><input id="id_first_name" maxlength="100" name="first_name" type="text" value="Mad" required /></td></tr>
+    <tr><th><label for="id_last_name">Last name:</label></th><td><input id="id_last_name" maxlength="100" name="last_name" type="text" value="Man" required /></td></tr>
+    <tr><th><label for="id_date_of_birth">Date of birth:</label></th><td><input id="id_date_of_birth" name="date_of_birth" type="text" /></td></tr>
+    <tr><th><label for="id_date_of_death">Died:</label></th><td><input id="id_date_of_death" name="date_of_death" type="text" value="12/10/2016" /></td></tr>
+  </table>
+  <input type="submit" value="Submit" />
+</form>
+
+</body>
+</html>
+
+ +

Запустите веб-сервер разработки и войдите в аккаунт супер-пользователя. Скопируйте приведенный выше текст в файл и затем откройте его в браузере. Вы должны получить CSRF ошибку, потому что у Django есть защита от атак данного вида!

+ +

Механизм защиты заключается в том, что вы добавляете тег шаблона {% csrf_token %} в вашу форму. Этот токен будет отображен в вашем HTML как показано ниже, со значением, уникальным для каждого запрашивающего форму пользователя.

+ +
<input type='hidden' name='csrfmiddlewaretoken' value='0QRWHnYVg776y2l66mcvZqp8alrv4lb8S8lZ4ZJUWGZFA5VHrVfL2mpH29YZ39PW' />
+
+ +

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

+ +

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

+ +

Защита Django от CSRF атак по умолчанию включена. Вам всегда следует использовать тег{% csrf_token %} в ваших формах и использовать POST для запросов, которые могут изменить или добавить данные в вашу базу данных.

+ +

Другие атаки

+ +

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

+ +
+
Защита от SQL инъекции
+
Уязвимость SQL инъекции позволяет атакующему выполнить произвольный SQL код в базе данных и получить доступ к данным (прочитать, отредактировать и изменить) независимо от текущих прав доступа пользователя. В большинстве случаев вы будете получать доступ к данным базы данных, используя сущности queryset/model Django. Используя их для генерации SQL запросов, вы получите корректно сформированный и экранированный запрос для выбранной базы данных. Если вам необходимо писать "сырые" запросы, вам так же нужно будет продумать защиту от инъекций.
+
Защита от Кликджекинга
+
В данном виде атак атакующий перехватывает ввод на видимом слое страницы и перенаправляет их на скрытый слой под ним. Этот метод может быть использован к примеру для отображения официального сайта банка, с перехватом данных для входа в невидимом  <iframe>, который контролирует атакующий. Django содержит защиту от кликджекинга в виде промежуточного програмного обеспечения (middleware) X-Frame-Options, который поддерживается браузерами и может запретить отображение страницы внутри <iframe>.
+
SSL/HTTPS
+
SSL/HTTPS может быть использован на веб-сервере для шифрования всего трафика между сервером и пользователем, включая данные входа, которые иначе будут отправлятся как обычный текст (который сможет прочитать любой перехвативший запрос человек). Использование HTTPS высоко рекомендовано. Если используется HTTPS, Django позволяет использовать следующие методы защиты:
+
+ + + +
+
Валидация заголовка Host
+
Используйте ALLOWED_HOSTS чтобы принимать только запросы от доверенных хостов.
+
+ +

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

+ + + +

Подводим итоги

+ +

Django имеет методы обеспечения защиты от распространенных видов атак, включая XSS и CSRF атаки. В данной статье мы продемонстрировали, как различные виды атак обрабатываются Django на примере нашего приложения LocalLibrary. Мы так же кратко рассмотрели другие виды уязвимостей и методы защиты от них.

+ +

Это было очень краткое погружение в вопрос веб-безопасности. Мы крайне рекомендуем вам прочитать Безопасность в Django для более глубокого понимания.

+ +

Следующим и последним шагом в данном руководстве будет выполнение самостоятельной работы.

+ +

Смотрите также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}

+ +

 

+ +

In this module

+ + + +

 

diff --git "a/files/ru/learn/server-side/django/\320\260\321\203\321\202\320\265\320\275\321\202\320\270\321\204\320\270\320\272\320\260\321\206\320\270\321\217/index.html" "b/files/ru/learn/server-side/django/\320\260\321\203\321\202\320\265\320\275\321\202\320\270\321\204\320\270\320\272\320\260\321\206\320\270\321\217/index.html" new file mode 100644 index 0000000000..807db42a90 --- /dev/null +++ "b/files/ru/learn/server-side/django/\320\260\321\203\321\202\320\265\320\275\321\202\320\270\321\204\320\270\320\272\320\260\321\206\320\270\321\217/index.html" @@ -0,0 +1,688 @@ +--- +title: 'Руководство Django Часть 8: Аутентификация и авторизация пользователя' +slug: Learn/Server-side/Django/Аутентификация +tags: + - Python + - Аутентификация + - Аутентификация django + - Джанго + - Начинающий + - Обучение + - Разграничение доступа + - Руководство + - Сервер + - Статья + - Формы + - на стороне сервера + - сессии +translation_of: Learn/Server-side/Django/Authentication +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}
+ +

В данном руководстве мы продемонстрируем вам систему входа пользователя на ваш сайт используя его собственный аккаунт. Кроме того, мы покажем как реализовать  контроль того, что может видеть и делать пользователь, в зависимости от того, залогинен он, или нет, а также имеет ли он соответствующий уровень прав доступа (permissions). Для того чтобы продемонстрировать все это, мы расширим LocalLibrary, добавив страницы для входа/выхода, а также страницы просмотра/редактирования книг, специфические для пользователя и персонала.

+ + + + + + + + + + + + +
Требования:Завершить изучение предыдущих тем руководства, включая Руководство Django Часть 7: Работа с сессиями.
Цель:Понимать как настроить и использовать механизм аутентификации пользователя и разграничений прав доступа.
+ +

Обзор

+ +

Django предоставляет систему аутентификации и авторизации ("permission") пользователя, реализованную на основе фреймворка работы с сессиями, который мы рассматривали в предыдущей части. Система аутентификации и авторизации позволяет вам проверять учетные данные пользователей и определять какие действия какой пользователь может выполнять. Данный фреймворк включает в себя встроенные модели для Пользователей и Групп (основной способ применения прав доступа для более чем одного пользователя), непосредственно саму систему прав доступа (permissions)/флаги, которые определяют может ли пользователь выполнить задачу, с какой формой и отображением для авторизованых пользователей, а так же получить доступ к контенту с ограниченым доступом.

+ +
+

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

+
+ +

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

+ +

Система аутентификации является очень гибкой и позволяет вам формировать свои собственные URL-адреса, формы, отображения, а также шаблоны страниц, если вы пожелаете, с нуля, через простой вызов функций соответствующего API для авторизации пользователя. Тем не менее, в данной статье мы будем использовать "встроенные" в Django методы отображений и форм аутентификации, а также методы построения страниц входа и выхода. Нам все еще необходимо создавать шаблоны страниц, но это будет достаточно несложно.

+ +

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

+ +

Подключение аутентификации

+ +

Аутентификация была подключена автоматически когда мы создали скелет сайта (в части 2), таким образом на данный момент вам ничего не надо делать.

+ +
+

Примечание: Необходимые настройки были выполнены для нас, когда мы создали приложение при помощи команды django-admin startproject. Таблицы базы данных для пользователей и модели авторизации были созданы, когда в первый раз выполнили команду python manage.py migrate.

+
+ +

Соответствующие настройки сделаны в параметрах INSTALLED_APPS и MIDDLEWARE файла проекта (locallibrary/locallibrary/settings.py), как показано ниже:

+ +
INSTALLED_APPS = [
+    ...
+    'django.contrib.auth',  # Фреймворк аутентификации и моделей по умолчанию.
+    'django.contrib.contenttypes',  # Django контент-типовая система (даёт разрешения, связанные с моделями).
+    ....
+
+MIDDLEWARE = [
+    ...
+    'django.contrib.sessions.middleware.SessionMiddleware',  # Управление сессиями между запросами
+    ...
+    'django.contrib.auth.middleware.AuthenticationMiddleware',  # Связывает пользователей, использующих сессии, запросами.
+    ....
+
+ +

Создание пользователей и групп

+ +

Вы уже создали своего первого пользователя когда мы рассматривали Административная панель сайта Django в части 4 (это был суперпользователь, созданный при помощи команды python manage.py createsuperuser). Наш суперпользователь уже авторизован и имеет все необходимые уровни доступа к данным и функциям, таким образом нам необходимо создать тестового пользователя для отработки соответствующей работы сайта. В качестве наиболее быстрого способа, мы будем использовать административную панель сайта для создания соответствующих групп и акканутов locallibrary.

+ +
+

Примечание: Вы можете создавать пользователей программно, как показано ниже. Например, вам мог бы подойти данный способ в том случае, если вы разрабатываете интерфейс, который позволяет пользователям создавать их собственные аккаунты (вы не должны предоставлять доступ пользователям к административной панели вашего сайта).

+ +
from django.contrib.auth.models import User
+
+# Создайте пользователя и сохраните его в базе данных
+user = User.objects.create_user('myusername', 'myemail@crazymail.com', 'mypassword')
+
+# Обновите поля и сохраните их снова
+user.first_name = 'John'
+user.last_name = 'Citizen'
+user.save()
+
+
+ +

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

+ +

Запустите сервер разработки и перейдите к административной панели вашего сайта (http://127.0.0.1:8000/admin/). Залогиньтесь на сайте при помощи параметров (имя пользователя и пароля) аккаунта суперпользователя. Самая "верхняя" страница панели Администратора показывает все наши модели. Для того, чтобы увидеть записи в разделе Authentication and Authorisation вы можете нажать на ссылку Users, или Groups.

+ +

Admin site - add groups or users

+ +

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

+ +
    +
  1. Нажмите на кнопку Add (Добавить) (рядом с Group) и создайте новую группу; для данной группы введите Name (Имя) "Library Members".Admin site - add group
  2. +
  3. Для данной группы не нужны какие-либо разрешения, поэтому мы просто нажимаем кнопку SAVE (Сохранить) (вы перейдете к списку групп).
  4. +
+ +

Теперь давайте создадим пользователя:

+ +
    +
  1. Перейдите обратно на домашнюю страницу административной панели
  2. +
  3. Для перехода к диалогу добавления пользователя нажмите на кнопку Add, соответствующую строке Users (Пользователи).Admin site - add user pt1
  4. +
  5. Введите соответствующие Username (имя пользователя) и Password/Password confirmation (пароль/подтверждение пароля) для вашего тестового пользователя
  6. +
  7. Нажмите SAVE для завершения процесса создания пользователя.
    +
    + Административная часть сайта создаст нового пользователя и немедленно перенаправит вас на страницу Change user (Изменение параметров пользователя) где вы можете, соответственно, изменить ваш username, а кроме того добавить информацию для дополнительных полей модели User. Эти поля включают в себя имя пользователя, фамилию, адрес электронной почты, статус пользователя, а также соответствующие параметры доступа (может быть установлен только флаг  Active). Ниже вы можете определить группу для пользователя и необходимые параметры доступа, а кроме того, вы можете увидеть важные даты, относящиеся к пользователю (дату подключения к сайту и дату последнего входа).Admin site - add user pt2
  8. +
  9. В разделе Groups, из списка Доступные группы выберите группу Library Member, а затем переместите ее в блок "Выбранные группы" (нажмите стрелку-"направо", находящуюся между блоками).Admin site - add user to group
  10. +
  11. Больше нам не нужно здесь нечего делать, просто нажмите "Save"(Сохранить), и вы вернетесь к списку созданых пользователей.
  12. +
+ +

Вот и все! Теперь у вас есть учетная запись «обычного члена библиотеки», которую вы сможете использовать для тестирования (как только добавим страницы, чтобы пользователи могли войти в систему).

+ +
+

Note: Попробуйте создать другого пользователя, например "Библиотекаря". Так же создайте группу "Библиотекарей" и добавьте туда своего только что созданного библиотекаря

+
+ +

Настройка представлений проверки

+ +

Django предоставляет почти все, что нужно для создания страниц аутентификации входа, выхода из системы и управления паролями из коробки. Это включает в себя url-адреса, представления (views) и формы,но не включает шаблоны — мы должны создать свой собственный шаблон!

+ +

В этом разделе мы покажем, как интегрировать систему по умолчанию в Сайт LocalLibrary и создать шаблоны.  Мы поместим их в основные URL проекта.

+ +
+

Заметка: Вы не должны использовать этот код, но вполне вероятно, что вы хотите, потому что это делает вещи намного проще. Вам почти наверняка потребуется изменить код обработки формы, если вы измените свою модель пользователя (сложная тема!) но даже в этом случае вы все равно сможете использовать функции просмотра запасов.

+
+ +
+

Заметка: В этом случае мы могли бы разумно поместить страницы аутентификации, включая URL-адреса и шаблоны, в наше приложение каталога. Однако, если бы у нас было несколько приложений, было бы лучше отделить это общее поведение входа в систему и иметь его доступным на всем сайте, так что это то, что мы показали здесь!

+
+ +

Проектирование URLs

+ +

Добавьте следующее в нижней части проекта urls.py файл (locallibrary/locallibrary/urls.py) файл:

+ +
#Add Django site authentication urls (for login, logout, password management)
+urlpatterns += [
+    path('accounts/', include('django.contrib.auth.urls')),
+]
+
+ +

Перейдите по http://127.0.0.1:8000/accounts/ URL (обратите внимание на косую черту!), Django покажет ошибку, что он не смог найти этот URL, и перечислить все URL, которые он пытался открыть. Из этого Вы можете увидеть URL-адреса, которые будут работать, например:

+ +
+

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

+
+ +
+
accounts/ login/ [name='login']
+accounts/ logout/ [name='logout']
+accounts/ password_change/ [name='password_change']
+accounts/ password_change/done/ [name='password_change_done']
+accounts/ password_reset/ [name='password_reset']
+accounts/ password_reset/done/ [name='password_reset_done']
+accounts/ reset/<uidb64>/<token>/ [name='password_reset_confirm']
+accounts/ reset/done/ [name='password_reset_complete']
+
+ +

Теперь попробуйте перейти к URL-адресу входа (http://127.0.0.1:8000/accounts/login/). Это приведет к сбою снова, но с ошибкой, сообщающей вам, что нам не хватает требуемого шаблона (registration / login.html) в пути поиска шаблона. Вы увидите следующие строки, перечисленные в желтом разделе вверху:

+ +
Exception Type:    TemplateDoesNotExist
+Exception Value:    registration/login.html
+ +

Следующий шаг - создать каталог регистрации в пути поиска, а затем добавить файл login.html.

+ +

Каталог шаблонов

+ +

URL-адреса (и неявные представления), которые мы только что добавили, ожидают найти связанные с ними шаблоны в каталоге / регистрации / где-то в пути поиска шаблонов.
+
+ Для этого сайта мы разместим наши HTML-страницы в каталоге templates / registration /. Этот каталог должен находиться в корневом каталоге проекта, то есть в том же каталоге, что и в каталоге и папках locallibrary). Создайте эти папки сейчас.

+ +
+

Примечание: Ваша структура папок теперь должна выглядеть как показано внизу:
+ locallibrary (django project folder)
+    |_catalog
+    |_locallibrary
+    |_templates (new)
+                 |_registration

+
+ +

Чтобы сделать эти директории видимыми для загрузчика шаблонов   (т. е. помещать этот каталог в путь поиска шаблона) откройте настройки проекта (/locallibrary/locallibrary/settings.py), и обновите в секции TEMPLATES строку 'DIRS' как показано.

+ +
TEMPLATES = [
+    {
+        ...
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
+        'APP_DIRS': True,
+        ...
+
+ +

Шаблон аутентификации

+ +
+

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

+
+ +

Создайте новый HTML файл, названный /locallibrary/templates/registration/login.html. дайте ему следующее содержание:

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+
+{% if form.errors %}
+  <p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+{% if next %}
+  {% if user.is_authenticated %}
+    <p>Your account doesn't have access to this page. To proceed,
+    please login with an account that has access.</p>
+  {% else %}
+    <p>Please login to see this page.</p>
+  {% endif %}
+{% endif %}
+
+<form method="post" action="{% url 'login' %}">
+{% csrf_token %}
+<table>
+
+<tr>
+  <td>\{{ form.username.label_tag }}</td>
+  <td>\{{ form.username }}</td>
+</tr>
+
+<tr>
+  <td>\{{ form.password.label_tag }}</td>
+  <td>\{{ form.password }}</td>
+</tr>
+</table>
+
+<input type="submit" value="login" />
+<input type="hidden" name="next" value="\{{ next }}" />
+</form>
+
+{# Assumes you setup the password_reset view in your URLconf #}
+<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
+
+{% endblock %}
+ +

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

+ +

Перейдите на страницу входа (http://127.0.0.1:8000/accounts/login/) когда вы сохраните свой шаблон, и вы должны увидеть что-то наподобие этого:

+ +

Library login page v1

+ +

Если ваша попытка войти в систему будет успешной,  вы будете перенаправлены на другую страницу (по умолчанию это будет http://127.0.0.1:8000/accounts/profile/). Проблема здесь в том, что по умолчанию Django ожидает, что после входа в систему вы захотите перейти на страницу профиля, что может быть или не быть. Поскольку вы еще не определили эту страницу, вы получите еще одну ошибку!
+
+ Откройте настройки проекта (/locallibrary/locallibrary/settings.py) и добавьте текст ниже. Теперь, когда вы входите в систему, вы по умолчанию должны перенаправляться на домашнюю страницу сайта.

+ +
# Redirect to home URL after login (Default redirects to /accounts/profile/)
+LOGIN_REDIRECT_URL = '/'
+
+ +

Шаблон выхода

+ +

Если вы перейдете по URL-адресу выхода (http://127.0.0.1:8000/accounts/logout/), то увидите странное поведение - ваш пользователь наверняка выйдет из системы, но вы попадете на страницу выхода администратора. Это не то, что вам нужно, хотя бы потому, что ссылка для входа на этой странице приведет вас к экрану входа в систему администратора. (и это доступно только для пользователей, у которых есть разрешение is_staff).
+
+ Создайте и откройте /locallibrary/templates/registration/logged_out.html. Скопируйте текст ниже:

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+<p>Logged out!</p>
+
+<a href="{% url 'login'%}">Click here to login again.</a>
+{% endblock %}
+ +

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

+ +

Library logout page v1

+ +

Шаблон сброса пароля

+ +

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

+ +

Форма сброса пароля

+ +

Это форма, используемая для получения адреса электронной почты пользователя (для отправки пароля для сброса пароля). Создайте /locallibrary/templates/registration/password_reset_form.html и дайте ему следующее содержание:

+ +
{% extends "base_generic.html" %}
+{% block content %}
+
+<form action="" method="post">{% csrf_token %}
+    {% if form.email.errors %} \{{ form.email.errors }} {% endif %}
+        <p>\{{ form.email }}</p>
+    <input type="submit" class="btn btn-default btn-lg" value="Reset password" />
+</form>
+
+{% endblock %}
+
+ +

Сброс пароля

+ +

Эта форма отображается после того, как ваш адрес электронной почты будет собран. Создайте /locallibrary/templates/registration/password_reset_done.html, и дайте ему следующее содержание:

+ +
{% extends "base_generic.html" %}
+{% block content %}
+<p>We've emailed you instructions for setting your password. If they haven't arrived in a few minutes, check your spam folder.</p>
+{% endblock %}
+
+ +

Сброс пароля по email

+ +

Этот шаблон предоставляет текст электронной почты HTML, содержащий ссылку на сброс, которую мы отправим пользователям. Создайте /locallibrary/templates/registration/password_reset_email.html и дайте ему следующее содержание:

+ +
Someone asked for password reset for email \{{ email }}. Follow the link below:
+\{{ protocol}}://\{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
+
+ +

Подтверждение на сброс пароля

+ +

На этой странице вы вводите новый пароль после нажатия ссылки в электронном письме с возвратом пароля. Создайте /locallibrary/templates/registration/password_reset_confirm.html и дайте ему следующее содержание:

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+
+    {% if validlink %}
+        <p>Please enter (and confirm) your new password.</p>
+        <form action="" method="post">
+            {% csrf_token %}
+            <table>
+                <tr>
+                    <td>\{{ form.new_password1.errors }}
+                        <label for="id_new_password1">New password:</label></td>
+                    <td>\{{ form.new_password1 }}</td>
+                </tr>
+                <tr>
+                    <td>\{{ form.new_password2.errors }}
+                        <label for="id_new_password2">Confirm password:</label></td>
+                    <td>\{{ form.new_password2 }}</td>
+                </tr>
+                <tr>
+                    <td></td>
+                    <td><input type="submit" value="Change my password" /></td>
+                </tr>
+            </table>
+        </form>
+    {% else %}
+        <h1>Password reset failed</h1>
+        <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset.</p>
+    {% endif %}
+
+{% endblock %}
+
+ +

Сброс пароля завершен

+ +

Это последний шаблон сброса пароля, который отображается, чтобы уведомить вас о завершении сброса пароля. Создайте /locallibrary/templates/registration/password_reset_complete.html и дайте ему следующее содержание:

+ +
{% extends "base_generic.html" %}
+{% block content %}
+
+<h1>The password has been changed!</h1>
+<p><a href="{% url 'login' %}">log in again?</a></p>
+
+{% endblock %}
+ +

Тестирование новых страниц аутентификации

+ +

Теперь, когда вы добавили конфигурацию URL и создали все эти шаблоны, теперь страницы аутентификации должны работать! Вы можете протестировать новые страницы аутентификации, попытавшись войти в систему, а затем выйдите из учетной записи суперпользователя, используя эти URL-адреса:

+ + + +

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

+ +
+

Заметка: Система сброса пароля требует, чтобы ваш сайт поддерживал электронную почту, что выходит за рамки этой статьи, поэтому эта часть еще не будет работать. Чтобы разрешить тестирование, поместите следующую строку в конец файла settings.py. Это регистрирует любые письма, отправленные на консоль (чтобы вы могли скопировать ссылку на сброс пароля с консоли).

+ +
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+
+ +

Для получения дополнительной информации см. Отправка email (Django docs).

+
+ +

Тестирование проверки подлинности пользователей

+ +

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

+ +

Тестирование в шаблонах

+ +

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

+ +

Обычно вы сначала проверяете переменную шаблона \{{user.is_authenticated}}, чтобы определить, имеет ли пользователь право видеть конкретный контент. Чтобы продемонстрировать это, мы обновим нашу боковую панель, чтобы отобразить ссылку «Вход», если пользователь вышел из системы, и ссылку «Выход», если он вошёл в систему.

+ +

Откройте базовый шаблон (/locallibrary/catalog/templates/base_generic.html) и скопируйте следующий текст в sidebar блок непосредственно перед тегом шаблона endblock.

+ +
  <ul class="sidebar-nav">
+
+    ...
+
+   {% if user.is_authenticated %}
+     <li>User: \{{ user.get_username }}</li>
+     <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li>
+   {% else %}
+     <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li>
+   {% endif %} 
+  </ul>
+ +

Как вы можете видеть, мы используем теги шаблона if-else-endif для условного отображения текста на основе того, является ли \{{user.is_authenticated}} истинным. Если пользователь аутентифицирован, мы знаем, что у нас есть действительный пользователь, поэтому мы вызываем \{{user.get_username}}, чтобы отобразить их имя.

+ +

Мы создаем URL-адрес для входа и выхода из системы, используя тег шаблона URL-адреса и имена соответствующих конфигураций URLs. Также обратите внимание на то, как мы добавили ?next=\{{request.path}} в конец URLs. Это означает, что следующий URL-адрес содержит адрес (URL) текущей страницы, в конце связанного URL-адреса. После того, как пользователь успешно выполнил вход в систему, представления будут использовать значение "next" чтобы перенаправить пользователя обратно на страницу, где они сначала нажали ссылку входа / выхода из системы.

+ +
+

Примечание: Попробуйте! Если вы находитесь на главной странице и вы нажимаете «Вход / Выход» на боковой панели, то после завершения операции вы должны вернуться на ту же страницу.

+
+ +

Тестирование в представлениях

+ +

Если вы используете функциональные представления, самым простым способом ограничить доступ к вашим функциям является применение login_required декоратор к вашей функции просмотра, как показано ниже. Если пользователь вошел в систему, ваш код просмотра будет выполняться как обычно. Если пользователь не вошел в систему, это перенаправит URL-адрес входа, определенный в настройках проекта. (settings.LOGIN_URL), передав текущий абсолютный путь в качестве next параметра URL. Если пользователю удастся войти в систему, они будут возвращены на эту страницу, но на этот раз аутентифицированы.

+ +
from django.contrib.auth.decorators import login_required
+
+@login_required
+def my_view(request):
+    ...
+ +
+

Заметка: Вы можете сделать то же самое вручную, путём тестирования request.user.is_authenticated, но декоратор намного удобнее!

+
+ +

Аналогичным образом, самый простой способ ограничить доступ к зарегистрированным пользователям в ваших представлениях на основе классов - это производные от LoginRequiredMixin. Вы должны объявить этот mixin сначала в списке суперкласса, перед классом основного представления.

+ +
from django.contrib.auth.mixins import LoginRequiredMixin
+
+class MyView(LoginRequiredMixin, View):
+    ...
+ +

Это имеет такое же поведение при переадресации, что и  login_required декоратор. Вы также можете указать альтернативное местоположение для перенаправления пользователя, если он не аутентифицирован (login_url), и имя параметра URL вместо "next" , чтобы вставить текущий абсолютный путь (redirect_field_name).

+ +
class MyView(LoginRequiredMixin, View):
+    login_url = '/login/'
+    redirect_field_name = 'redirect_to'
+
+ +

Для получения дополнительной информации ознакомьтесь с  Django docs here.

+ +

Пример - перечисление книг текущего пользователя

+ +

Теперь, когда мы знаем, как ограничить страницу определенному пользователю, создайте представление о книгах, которые заимствовал текущий пользователь.

+ +

К сожалению, у нас пока нет возможности пользователям использовать книги! Поэтому, прежде чем мы сможем создать список книг, мы сначала расширим BookInstance модель для поддержки концепции заимствования и использования приложения Django Admin для заимствования ряда книг нашему тестовому пользователю.

+ +

Модели

+ +

Прежде всего, мы должны предоставить пользователям возможность кредита на BookInstance (у нас уже есть status и due_back дата, но у нас пока нет связи между этой моделью и пользователем. Мы создадим его с помощью поля ForeignKey (один ко многим). Нам также нужен простой механизм для проверки того, просрочена ли заемная книга.

+ +

Откройте catalog/models.py, и импортируйте модель User из django.contrib.auth.models (добавьте это чуть ниже предыдущей строки импорта в верхней части файла, так User доступен для последующего кода, что позволяет использовать его):

+ +
from django.contrib.auth.models import User
+
+ +

Затем добавьте поле borrower в модель BookInstance:

+ +
borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
+
+ +

Пока мы здесь, давайте добавим свойство, которое мы можем вызвать из наших шаблонов, чтобы указать, просрочен ли конкретный экземпляр книги. Хотя мы могли бы рассчитать это в самом шаблоне, использование свойства, как показано ниже, будет намного более эффективным. Добавьте это где-нибудь в верхней части файла:

+ +
from datetime import date
+ +

Теперь добавьте следующее определение свойства внутри класса BookInstance:

+ +
@property
+def is_overdue(self):
+    if self.due_back and date.today() > self.due_back:
+        return True
+    return False
+ +
+

Примечание. Сначала мы проверим, является ли due_back пустым, прежде чем проводить сравнение. Пустое поле due_back заставило Django выкидывать ошибку, а не показывать страницу: пустые значения не сопоставимы. Это не то, что мы хотели бы, чтобы наши пользователи испытывали!

+
+ +

Теперь, когда мы обновили наши модели, нам нужно будет внести новые изменения в проект, а затем применить эти миграции:

+ +
python3 manage.py makemigrations
+python3 manage.py migrate
+
+ +

Admin

+ +

Теперь откройте каталог catalog/admin.py, и добавьте поле borrower в класс BookInstanceAdmin , как в list_display , так и в полях fieldsets , как показано ниже. Это сделает поле видимым в разделе Admin, так что мы можем при необходимости назначить User в BookInstance.

+ +
@admin.register(BookInstance)
+class BookInstanceAdmin(admin.ModelAdmin):
+    list_display = ('book', 'status', 'borrower', 'due_back', 'id')
+    list_filter = ('status', 'due_back')
+
+    fieldsets = (
+        (None, {
+            'fields': ('book','imprint', 'id')
+        }),
+        ('Availability', {
+            'fields': ('status', 'due_back','borrower')
+        }),
+    )
+ +

Займите несколько книг

+ +

Теперь, когда возможно кредитовать книги конкретному пользователю, зайдите и заработайте на нескольких записей в BookInstance. Установите borrowed поле вашему тестовому пользователю, сделайте status «В займе» и установите сроки оплаты как в будущем, так и в прошлом.

+ +
+

Заметка: Мы не будем описывать процесс, так как вы уже знаете, как использовать Admin сайт!

+
+ +

Займ в представлении

+ +

Теперь мы добавим представление для получения списка всех книг, которые были предоставлены текущему пользователю. Мы будем использовать один и тот же общий класс, с которым мы знакомы, но на этот раз мы также будем импортировать и выводить из  LoginRequiredMixin, так что только вошедший пользователь сможет вызвать это представление. Мы также решили объявить  template_name, вместо того, чтобы использовать значение по умолчанию, потому что у нас может быть несколько разных списков записей BookInstance, с разными представлениями и шаблонами.

+ +

Добавьте следующее в catalog/views.py:

+ +
from django.contrib.auth.mixins import LoginRequiredMixin
+
+class LoanedBooksByUserListView(LoginRequiredMixin,generic.ListView):
+    """
+    Generic class-based view listing books on loan to current user.
+    """
+    model = BookInstance
+    template_name ='catalog/bookinstance_list_borrowed_user.html'
+    paginate_by = 10
+
+    def get_queryset(self):
+        return BookInstance.objects.filter(borrower=self.request.user).filter(status__exact='o').order_by('due_back')
+ +

Чтобы ограничить наш запрос только объектами BookInstance для текущего пользователя, мы повторно реализуем get_queryset(), как показано выше. Обратите внимание, что "o" это сохраненный код для "on loan" и мы сортируем по дате due_back, чтобы сначала отображались самые старые элементы.

+ +

URL-адрес для заёмных книг

+ +

Теперь откройте /catalog/urls.py и добавьте url() , указывая на приведённое выше представление (вы можете просто скопировать текст ниже в конец файла).

+ +
urlpatterns += [
+    url(r'^mybooks/$', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'),
+]
+ +

Шаблон для заёмных книг

+ +

Теперь все, что нам нужно сделать для этой страницы, - это добавить шаблон. Сначала создайте файл шаблона /catalog/templates/catalog/bookinstance_list_borrowed_user.html и дайте ему следующее содержание:

+ +
{% extends "base_generic.html" %}
+
+{% block content %}
+    <h1>Borrowed books</h1>
+
+    {% if bookinstance_list %}
+    <ul>
+
+      {% for bookinst in bookinstance_list %}
+      <li class="{% if bookinst.is_overdue %}text-danger{% endif %}">
+        <a href="{% url 'book-detail' bookinst.book.pk %}">\{{bookinst.book.title}}</a> (\{{ bookinst.due_back }})
+      </li>
+      {% endfor %}
+    </ul>
+
+    {% else %}
+      <p>There are no books borrowed.</p>
+    {% endif %}
+{% endblock %}
+ +

Этот шаблон очень похож на тот, который мы создали ранее для объектов Book и Author. Единственное, что «новое» здесь, это то, что мы проверяем метод, который мы добавили в модель (bookinst.is_overdue) с целью использовать его для изменения цвета просроченных предметов.

+ +

Когда сервер разработки запущен, вы должны теперь иметь возможность просматривать список для зарегистрированного пользователя в своем браузере по адресу  http://127.0.0.1:8000/catalog/mybooks/. Попробуйте это, когда ваш пользователь войдет в систему и выйдет из системы (во втором случае вы должны быть перенаправлены на страницу входа в систему).

+ +

Добавить список на боковую панель

+ +

Последний шаг - добавить ссылку на эту новую страницу в sidebar. Мы поместим это в тот же раздел, где мы покажем другую информацию для зарегистрированного пользователя.

+ +

Откройте базовый шаблон (/locallibrary/catalog/templates/base_generic.html) и добавьте выделенную строку из sidebar, как показано на рисунке.

+ +
 <ul class="sidebar-nav">
+   {% if user.is_authenticated %}
+   <li>User: \{{ user.get_username }}</li>
+   <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li>
+   <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li>
+   {% else %}
+   <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li>
+   {% endif %}
+ </ul>
+
+ +

На что это похоже?

+ +

Когда любой пользователь войдет в систему, он будет видеть ссылку «Мной позаимствовано (My Borrowed)» в боковой колонке, и список книг, показанных ниже (первая книга не имеет установленной даты, что является ошибкой, которую мы надеемся исправить в более позднем уроке!).

+ +

Library - borrowed books by user

+ +

Права доступа

+ +

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

+ +

Модели

+ +

Определение разрешений выполняется в разделе моделей "class Meta" , используется permissions поле. Вы можете указать столько разрешений, сколько необходимо в кортеже, причем каждое разрешение определяется во вложенном кортеже, содержащем имя разрешения и отображаемое значение разрешения. Например, мы можем определить разрешение, позволяющее пользователю отметить, что книга была возвращена, как показано здесь:

+ +
class BookInstance(models.Model):
+    ...
+    class Meta:
+        ...
+        permissions = (("can_mark_returned", "Set book as returned"),)   
+ +

Затем мы могли бы назначить разрешение группе «Библиотекарь» (Librarian) на сайте администратора.

+ +

Откройте catalog/models.py, и добавьте разрешение, как показано выше. Вам нужно будет повторно выполнить миграцию (вызвав python3 manage.py makemigrations и python3 manage.py migrate) для надлежащего обновления базы данных.

+ +

Шаблоны

+ +

Разрешения текущего пользователя хранятся в переменной шаблона, называемой  \{{ perms }}. Вы можете проверить, имеет ли текущий пользователь определенное разрешение, используя конкретное имя переменной в соответствующем приложении «Django» - например, \{{ perms.catalog.can_mark_returned }} будет True если у пользователя есть это разрешение, а False - в противном случае. Обычно мы проверяем разрешение с использованием шаблона {% if %}, как показано в:

+ +
{% if perms.catalog.can_mark_returned %}
+    <!-- We can mark a BookInstance as returned. -->
+    <!-- Perhaps add code to link to a "book return" view here. -->
+{% endif %}
+
+ +

Представления

+ +

Разрешения можно проверить в представлении функции, используя  permission_required декоратор или в представлении на основе классов, используя PermissionRequiredMixin. шаблон и поведение такие же, как для аутентификации входа в систему, хотя, конечно, вы можете разумно добавить несколько разрешений.

+ +

Функция в представлении с декоратором:

+ +
from django.contrib.auth.decorators import permission_required
+
+@permission_required('catalog.can_mark_returned')
+@permission_required('catalog.can_edit')
+def my_view(request):
+    ...
+ +

Требуется разрешение mixin для представлений на основе классов.

+ +
from django.contrib.auth.mixins import PermissionRequiredMixin
+
+class MyView(PermissionRequiredMixin, View):
+    permission_required = 'catalog.can_mark_returned'
+    # Or multiple permissions
+    permission_required = ('catalog.can_mark_returned', 'catalog.can_edit')
+    # Note that 'catalog.can_edit' is just an example
+    # the catalog application doesn't have such permission!
+ +

Пример

+ +

Мы не будем обновлять LocalLibrary здесь; возможно, в следующем уроке!

+ +

Испытайте себя

+ +

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

+ +

 Вы должны следовать той же схеме, что и для другого представления. Главное отличие состоит в том, что вам нужно ограничить представление только библиотекарями. Вы можете сделать это на основе того, является ли пользователь сотрудником (декоратор функции:  staff_member_required, переменная шаблона: user.is_staff) но мы рекомендуем вам вместо этого использовать  can_mark_returned разрешения и PermissionRequiredMixin, как описано в предыдущем разделе.

+ +
+

Важно: Не забудьте использовать вашего суперпользователя для тестирования на основе разрешений (проверки разрешений всегда возвращают true для суперпользователей, даже если разрешение еще не определено!). Вместо этого создайте пользователя-библиотекаря и добавьте необходимые возможности.

+
+ +

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

+ +

All borrowed books, restricted to librarian

+ + + +

Подводим итоги

+ +

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

+ +

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

+ +

Смотрите также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}

diff --git "a/files/ru/learn/server-side/django/\320\262\320\262\320\265\320\264\320\265\320\275\320\270\320\265/index.html" "b/files/ru/learn/server-side/django/\320\262\320\262\320\265\320\264\320\265\320\275\320\270\320\265/index.html" new file mode 100644 index 0000000000..1eaffac0cf --- /dev/null +++ "b/files/ru/learn/server-side/django/\320\262\320\262\320\265\320\264\320\265\320\275\320\270\320\265/index.html" @@ -0,0 +1,259 @@ +--- +title: Django введение +slug: Learn/Server-side/Django/Введение +tags: + - Python + - Вступление + - Джанго + - Начинающим + - Серверное программирование +translation_of: Learn/Server-side/Django/Introduction +--- +
{{LearnSidebar}}
+ +
{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}
+ +

В первой статье о Django мы отвечаем на вопрос «Что есть Django?» и даём обзор того, что делает его особенным. Мы опишем основные функции, в том числе некоторые из расширенных функций, которые у нас не будет времени подробно рассмотреть в этом модуле. Мы также покажем вам некоторые основные строительные блоки приложения Django (хотя на данный момент у вас еще не будет среды разработки для тестирования).

+ + + + + + + + + + + + +
Требования:Базовая компьютерная грамотность. Общее понимание server-side website programming, и в частности, механики client-server interactions in websites.
Задача:Узнать, что такое Django, какие функции он предоставляет, и основные строительные блоки приложения Django.
+ +

Что есть Django?

+ +

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

+ +

Django помогает писать программное обеспечение, которое будет:

+ +
+
Полным
+
Django следует философии «Батарейки в комплекте» и предоставляет почти все, что разработчики могут захотеть сделать «из коробки». Поскольку все, что вам нужно, является частью единого «продукта», все это безупречно работает вместе, соответствует последовательным принципам проектирования, и имеет обширную и актуальную документацию.
+
Разносторонним
+
Django может быть (и был) использован для создания практически любого типа веб-сайтов - от систем управления контентом и wiki до социальных сетей и новостных сайтов. Он может работать с любой клиентской средой и может доставлять контент практически в любом формате (включая HTML, RSS-каналы, JSON, XML и т.д.). Сайт, который вы сейчас читаете, создан с помощью Django!
+
Хотя оDjango предоставляет решения практически для любой функциональности, которая вам может понадобиться (например, для нескольких популярных баз данных, шаблонизаторов и т. д.), внутренне он также может быть расширен сторонними компонентами, если это необходимо.
+
Безопасным
+
Django помогает разработчикам избежать многих распространенных ошибок безопасности, предоставляя фреймворк, разработанный чтобы «делать правильные вещи» для автоматической защиты сайта. Например, Django предоставляет безопасный способ управления учетными записями пользователей и паролями, избегая распространенных ошибок, таких как размещение информации о сеансе в файлы cookie, где она уязвима (вместо этого файлы cookie содержат только ключ, а фактические данные хранятся в базе данных) или непосредственное хранение паролей. вместо хэша пароля.
+
+ Хэш пароля - это значение фиксированной длины, созданное путем обработки пароля через криптографическую хэш-функциюDjango может проверить правильность введенного пароля, пропустив его через хэш-функцию и сравнив вывод с сохраненным значением хэша. Благодаря «одностороннему» характеру функции, даже если сохраненное хэш-значение скомпрометировано, злоумышленнику будет сложно определить исходный пароль.
+
+ Django, по умолчанию, обеспечивает защиту от многих уязвимостей, включая SQL-инъекцию, межсайтовый скриптинг, подделку межсайтовых запросов и кликджекинг (см.  Website security для получения дополнительной информации об этих атаках).
+
Масштабируемым
+
Django использует компонентную “shared-nothing” архитектуру (каждая её часть  независима от других и, следовательно, может быть заменена, либо изменена). Четкое разделение между частями означает, что Django может масштабироваться при увеличении трафика путем добавления оборудования на любом уровне: серверы кэширования, серверы баз данных или серверы приложений. Одни из самых загруженных сайтов успешно масштабировали Django (например, Instagram и Disqus).
+
Удобным в сопровождении
+
Код Django написан с использованием принципов и шаблонов проектирования, которые поощряют создание поддерживаемого и повторно используемого кода. В частности, в нем используется принцип «Don't Repeat Yourself» (DRY, не повторяйся), поэтому нет ненужного дублирования, что сокращает объем кода. Django также способствует группированию связанных функциональных возможностей в повторно используемые «приложения» и, на более низком уровне, группирует связанный код в модули (в соответствии с шаблоном Model View Controller (MVC)).
+
Переносным
+
Django написан на Python, который работает на многих платформах. Это означает, что вы не привязаны к какой-либо конкретной серверной платформе и можете запускать приложения на многих версиях Linux, Windows и Mac OS X. Кроме того, Django хорошо поддерживается многими веб-хостингами, которые часто предоставляют определенную инфраструктуру и документацию для размещения сайтов Django.
+
+ +

Как он появился?

+ +

Django был разработан в период с 2003 по 2005 год командой, которая занималась созданием и обслуживанием газетных веб-сайтов. После создания нескольких сайтов, команда начала повторно использовать множество общего кода и шаблонов проектирования. Этот общий код эволюционировал в веб-фреймворк "Django", как open-source проект в Июле 2005 года.

+ +

Django продолжает расти и улучшаться с момента его первого релиза (1.0) в сентябре 2008 года до недавно выпущенной версии 2.0 (2017). В каждой версии добавлены новые функциональные возможности и исправлены ошибки, начиная от поддержки новых типов баз данных, шаблонизаторов и кэширования, до добавления «общих» функций и классов (уменьшающих объем кода, который разработчики должны писать для ряда задач).

+ +
+

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

+
+ +

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

+ +

Насколько популярен Django?

+ +

Нет никаких доступных и окончательных оценок популярности серверных фреймворков (хотя сайты, подобные Hot Framework, пытаются оценить популярность, используя такие механизмы, как подсчет количества проектов на GitHub и вопросов на StackOverflow для каждой платформы). Лучший вопрос - "Достаточно ли Django популярен, чтобы избежать проблем непопулярных платформ?". Продолжает ли он развиваться? Можете ли вы получить помощь, если вам это нужно? Найдёте ли вы работу, если вы изучите Django?

+ +

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

+ +

Django используют такие крупные сайты, как Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, и Open Stack (источник: домашняя страница Django).

+ +

Является ли Django гибким?

+ +

Веб-фрейморки часто можно поделить на "гибкие" и "негибкие".

+ +

Негибкие - это те, у которых есть "правильный путь" для решения какой-либо конкретной задачи. Они часто поддерживают быстрое развёртывание в определенной области (решение проблем определенного типа), потому что правильный способ сделать что-либо обычно хорошо понимается и хорошо документируется. Однако они могут быть менее гибкими при решении проблем за пределами их основной сферы и, как правило, предлагают меньше вариантов того, какие компоненты и подходы они могут использовать.

+ +

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

+ +

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

+ +

Как выглядит код Django?

+ +

В традиционном информационом веб-сайте, веб-приложение ожидает запросов HTTP от веб-браузера (или другого клиента). Когда запрос получен, приложение разрабатывает то, что необходимо на основе URL-адреса и, возможно, информации в POST или в GET запросах. В зависимости от того, что требуется, он может читать или записывать информацию из базы данных или выполнять другие задачи, необходимые для удовлетворения запроса. Приложение затем вернет ответ веб-браузеру, часто динамически создавая страницу HTML для отображения браузера, вставляя полученные данные в HTML шаблон.

+ +

Веб-приложения написанные на Django обычно группируют код, который обрабатывает каждый из этих шагов, в отдельные файлы:

+ +

+ + + +
+

Заметка: Django реализует уровневую архитектуру "Model View Template (MVT)". Она имеет много общего с более известной архитектурой Model View Controller

+
+ + + +

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

+ +

Отправка запроса в правильное view (urls.py)

+ +

Сопоставитель URL-адресов обычно хранится в файле urls.py. В примере ниже сопоставитель  (urlpatterns) определяет список соответствия между определенными URL-шаблонами и соотвествующими функциями отображения (view). Если полученный HTTP запрос подходит под определенный шаблон ( такой как r'^$', ниже), то будет вызвана ассоциированная функция отображения (такая как views.index) и передана в запрос.

+ +
urlpatterns = [
+    url(r'^$', views.index),
+    url(r'^([0-9]+)/$', views.best),
+]
+
+ +
+

Note: Немного Python:

+ + +
+ +

Обработка запроса (views.py)

+ +

Отображения (views) - это сердце веб-приложения, принимающего HTTP-запросы от веб-клиентов и возвращающего HTTP-ответы.  В промежутке они собирают другие ресурсы платформы для доступа к базам данных, шаблонам визуализации и т. д.  

+ +

В приведенном ниже примере показана минимальная функция представления index(), которая могла быть вызвана нашим преобразователем URL в предыдущем разделе. Как и все функции представления, она получает объект HttpRequest в качестве параметра (request) и возвращает объект HttpResponse. В этом случае мы ничего не делаем с запросом, и наш ответ просто возвращает жестко запрограммированную строку. Мы покажем вам запрос, который делает что-то более интересное в следующем разделе.

+ +
## filename: views.py (Django view functions)
+
+from django.http import HttpResponse
+
+def index(request):
+    # Получить HttpRequest - параметр запроса
+    # Выполнить операции, используя информацию из запроса.
+    # Вернуть HttpResponse
+    return HttpResponse('Hello from Django!')
+
+ +
+

Заметка: Немного Python:

+ + +
+ + + +

Отображения (View) обычно сохранены в файле views.py.

+ +

Определение данных модели (models.py)

+ +

Веб-приложения Django обрабатывают и запрашивают данные через объекты Python, называемые моделями. Модели определяют структуру хранимых данных, включая типы полей и, возможно, их максимальный размер, значения по умолчанию, параметры списка выбора, текст справки для документации, текст меток для форм и т. д. Определение модели не зависит от СУБД (MySQL или PostgreSQL) - ваши модели будут работать в любой из них. После того, как вы выбрали базу данных, которую хотите использовать, Вам не нужно напрямую обращатся к ней - вы просто пишете свою структуру модели и другой код, а Django обрабатывает всю грязную работу по обращению к базе данных за вас.

+ +

В приведенном ниже фрагменте кода показана очень простая модель Django для объекта  Team . Класс Team наследуется от класса models.Model. Он определяет имя команды и командный уровень в качестве полей символов и задает максимальное количество символов, которые должны быть сохранены для каждой записи. Team_level может быть одним из нескольких значений, поэтому мы определяем его как поле выбора и предоставляем сопоставление между отображаемыми вариантами и хранимыми данными вместе со значением по умолчанию. 

+ +
# filename: models.py
+
+from django.db import models
+
+class Team(models.Model):
+    team_name = models.CharField(max_length=40)
+
+    TEAM_LEVELS = (
+        ('U09', 'Under 09s'),
+        ('U10', 'Under 10s'),
+        ('U11', 'Under 11s'),
+        ...  #список других командных уровней
+    )
+    team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11')
+
+ +
+

Заметка: Немного Python'а:

+ + +
+ +

Запросы данных (views.py)

+ +

Модель Django предоставляет простой API запросов для поиска в базе данных. Поиск может осуществляться по нескольким полям одновременно с использованием разных критериев (таких как exact, case-insensitive, greater than и т.д.), и может поддерживать сложные выражения (например, вы можете указать поиск в командах U11, у которых есть имя команды, начинающееся с «Fr» или заканчивается на «al»).

+ +

Фрагмент кода показывает функцию view (обработчик ресурсов) для отображения всех команд U09. Жирная строка показывет как мы можем использовать модель API запросов для того, чтобы отфильтровать все записи где поле team_level в точности содержит текст 'U09' (обратите внимание, как эти критерии передаются функции filter() в качестве аргумента с именем поля и типом соответствия, разделенным двойным подчеркиванием: team_level__exact).

+ +
## filename: views.py
+
+from django.shortcuts import render
+from .models import Team
+
+def index(request):
+    list_teams = Team.objects.filter(team_level__exact="U09")
+    context = {'youngest_teams': list_teams}
+    return render(request, '/best/index.html', context)
+
+ +
+
+ +

Данная функция использует функцию render() для того, чтобы создать HttpResponse, который будет отправлен назад браузеру. Эта функция является ярлыком; она создает HTML-файл, комбинируя указанный шаблон HTML и некоторые данные для вставки в шаблон (предоставляется в переменной с именем "context"). В следующем разделе мы  покажем как данные вставляются в шаблон для создания HTML-кода.

+ +

Отображение данных (HTML templates)

+ +

Системы шаблонов позволяют указать структуру выходного документа, используя заполнители для данных, которые будут заполнены при создании страницы. Шаблоны часто используются для создания HTML, но также могут создавать другие типы документов. Django поддерживает как собственную систему шаблонов, так и другую популярную библиотеку Python под названием Jinja2 (она также может быть использована для поддержки других систем, если это необходимо).

+ +

Фрагмент кода показывает, как выглядит HTML-шаблон, вызванный функцией  render() из предыдущего раздела. Этот шаблон был написан в предположении, что во время рендеринга он будет иметь доступ к переменной списка, называемой youngest_teams (содержащейся в контекстной переменной внутри функции render() выше). Внутри скелета HTML мы имеем выражение, которое сначала проверяет, существует ли переменная youngest_teams, а затем выполняет итерацию в цикле for. На каждой итерации шаблон отображает значение team_name каждой команды в элементе {{htmlelement ("li")}}.

+ +
## filename: best/templates/best/index.html
+
+<!DOCTYPE html>
+<html lang="en">
+<body>
+
+ {% if youngest_teams %}
+    <ul>
+    {% for team in youngest_teams %}
+        <li>\{\{ team.team_name \}\}</li>
+    {% endfor %}
+    </ul>
+{% else %}
+    <p>No teams are available.</p>
+{% endif %}
+
+</body>
+</html>
+ +

Что еще можно сделать?

+ +

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

+ + + +

Резюме

+ +

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

+ +

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

+ +
{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}
diff --git "a/files/ru/learn/server-side/django/\321\200\320\260\320\267\320\262\320\276\321\200\320\260\321\207\320\270\320\262\320\260\320\275\320\270\320\265/index.html" "b/files/ru/learn/server-side/django/\321\200\320\260\320\267\320\262\320\276\321\200\320\260\321\207\320\270\320\262\320\260\320\275\320\270\320\265/index.html" new file mode 100644 index 0000000000..640527b63d --- /dev/null +++ "b/files/ru/learn/server-side/django/\321\200\320\260\320\267\320\262\320\276\321\200\320\260\321\207\320\270\320\262\320\260\320\275\320\270\320\265/index.html" @@ -0,0 +1,680 @@ +--- +title: 'Django Руководство часть 11: Разворачивание сайта на сервере' +slug: Learn/Server-side/Django/Разворачивание +tags: + - Веб-сервер + - Для начинающих + - Разворачивание на сервере + - Развёртывание Django +translation_of: Learn/Server-side/Django/Deployment +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}
+ +

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

+ + + + + + + + + + + + +
Требования:Завершить изучение всех предыдущих частей руководства, включая Django Руководство часть 10:  Тестирование веб-приложений в Django.
Цель:Изучить, где и как вы можете развернуть приложение Django для публичного доступа.
+ +

Обзор

+ +

Даже когда разработка вашего сайта завершена (или "достаточно" завершена для начала публичного тестирования), то для публичного доступа вам надо его где-то разместить.

+ +

До сего момента вы работали в каком-то рабочем окружении - чтобы получать отладочную и другую частную информацию, вы использовали веб-сервер Django в локальной сети при этом запускали сайт с (небезопасными) настройками разработки. Перед тем как разместить сайт публично, вы должны сделать следующее:

+ + + +

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

+ +

Что такое окружение развертывания?

+ +

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

+ + + +
+

Примечание: У вас может быть потребность в обратном прокси, балансировщике загрузки и так далее.

+
+ +

Сервер может быть вашим собственным с подключением к интернету по скоростному каналу, но более общим подходом является применение "облачных решений". Что действительно имеет значение, так это то, что ваш код запускается на некотором удаленном компьютере (возможно и "виртуальном"), в хостинговом дата-центре. Удаленный сервер обычно предоставляет определенный доступ к компьютерным ресурсам (процессору, оперативной памяти, памяти на жестких носителях и так далее) и соединение с интернетом за некоторую цену.

+ +

Такой тип удаленного доступа к вычислительному/сетевому железу называется Инфраструктура как Сервис (Infrastructure as a Service - IaaS). Множество IaaS поставщиков предлагают услуги по предустановке какой-либо операционной системы, на которую вы можете установить необходимые для вашего рабочего окружения компоненты. Другие поставщики предлагают вам выбрать уже готовые полноценные рабочие окружения, возможно, включающие в себя Django и настроенный веб-сервер.

+ +
+

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

+
+ +

Некоторые провайдеры поддерживают Django как часть своего предложения Платформа как Сервис (Platform as a Service - PaaS). При данном виде хостинга вам не нужно беспокоиться о большей части окружения (веб-сервере, сервере приложений, балансировщике загрузки), так как сама платформа берет это на себя (включая все моменты, касающиеся роста и развития вашего приложения). В данном случае развертывание приложения является достаточно простой задачей, - вам нужно сконцентрироваться только на вашем приложении, а не на инфраструктуре.

+ +

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

+ +
+

Примечание: Если вы выбираете хостинг с поддержкой Python/Django, то он должен иметь инструкцию по установке веб-сайта Django, учитывающую различные конфигурации веб-сервера, сервера приложений, обратного прокси и так далее (это не имеет значение, если вы выбрали PaaS). Например, существует множество инструкций "шаг-за-шагом" для различный конфигураций в Документации DigitalOcean по Django.

+
+ +

Выбор хостинг провайдера

+ +

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

+ +

Некоторые вещи на которые надо обратить внимание при выборе хостинга:

+ + + +

Хорошей новостью является то, что для того, чтобы начать существует достаточное количество компаний, которые предоставляют пробные "бесплатные" тарифы типа "evaluation" (для пробы), "developer" (разработка), или "hobbyist" (хобби). Всегда существуют ресурсы с ограниченым окружением, при использовании которых вам надо беспокоиться лишь о том, что они могут быть доступны лишь в течении определенного периода времени. Тем не менее, они являются отличным решением для тестирования сайтов с небольшим трафиком в реальном окружении, а также могут предоставлять простой доступ к платным ресурсам, в случае необходимости. Наиболее популярными провайдерами являются Heroku, Python Anywhere, Amazon Web Services, Microsoft Azure и так далее.

+ +

Многие провайдеры имеют "basic" (базовый) тариф, предоставляющий достаточный уровень вычислительной мощности с небольшим количеством ограничений. Digital Ocean и Python Anywhere являются примерами провайдеров, которые предлагают относительно недорой базовый тариф (от $5 до $10USD в месяц).

+ +
+

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

+
+ +

Подготовка веб-сайта к публикации

+ +

Скелет сайта был создан при помощи инструментов django-admin и manage.py, которые настроены таким образом, чтобы сделать разработку проще. Многие настройки файла проекта (определенных в settings.py) должны быть изменены перед публикацией сайта, либо из-за вопросов безопастности, либо производительности.

+ +
+

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

+
+ +

Критически важные настройки файла settings.py:

+ + + +

Давайте изменим приложение LocalLibrary таким образом, чтобы читать SECRET_KEY и DEBUG из переменных окружения, если те определены, иначе использовать значения по умолчанию.

+ +

Откройте /locallibrary/settings.py, закомментируйте исходное значение SECRET_KEY и добавьте новые строки, как указано ниже жирным. В течении разработки, никаких переменных окружения определено не было, таким образом будут использоваться значения по умолчанию (не имеет значения какой ключ вы используете в процессе разработки, поскольку при развертывании проекта вы будете использовать другой).

+ +
# SECURITY WARNING: keep the secret key used in production secret!
+# SECRET_KEY = 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag'
+import os
+SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag')
+
+ +

Затем закомментируйте строку с настройкой DEBUG, а затем, добавьте новую, указанную ниже.

+ +
# SECURITY WARNING: don't run with debug turned on in production!
+# DEBUG = True
+DEBUG = bool( os.environ.get('DJANGO_DEBUG', True) )
+
+ +

Значение DEBUG будет True по умолчанию и станет False, в том случае, если переменная окружения DJANGO_DEBUG будет проинициализирована пустой строкой, то есть, DJANGO_DEBUG=''.

+ +
+

Примечание: Было бы более понятным, если бы мы могли просто установить и снять с  DJANGO_DEBUG непосредственно на True и False , напрямую, а не использовать «любую строку» или «пустую строку» (соответственно). К сожалению, значения переменных среды хранятся как строки Python и единственная строка, которая оценивается как False является пустой строкой (например, bool('')==False).

+
+ +

Весь перечень настроек для разворачивания вашего сайта находится по ссылке Deployment checklist (Django docs). Кроме того, вы можете получить список настроек, выполнив в терминале команду:

+ +
python3 manage.py check --deploy
+
+ +

Пример: Установка LocalLibrary на Heroku

+ +

Данный раздел предоставляет демонстрацию того, как установить LocalLibrary на Heroku PaaS cloud.

+ +

Почему Heroku?

+ +

Heroku - один из самых продолжительных и популярных облачных сервисов PaaS. Первоначально он поддерживал только приложения Ruby, но теперь его можно использовать для размещения приложений из многих сред программирования, включая Django!

+ +

Мы выбираем для использования Heroku по нескольким причинам:

+ + + +

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

+ +

Как работает Heroku?

+ +

Heroku запускает сайты Django внутри одного, или более,  изолированых друг от друга "Dynos", которые являются виртуальными Unix-контейнерами, предоставляющими необходимое окружение для вашего приложения. Данные dynos полностью изолированы и имеют эфемерную файловую систему ("короткоживущая" файловая система, которая полностью очищается и обновляется каждый раз, когда dyno перезапускается). Единственной сущностью, которую предоставляет dynos по умолчанию, является приложение по конфигурации переменных. Heroku внутри себя применяет балансировщик загрузки для распределения веб-трафика среди всех "веб"-dynos. Поскольку dynos изолированы, Heroku может масштабировать приложение горизонтально, просто добавляя больше dynos (хотя, конечно, у вас может появиться необходимость расширить базу данных для обработки дополнительных соединений).

+ +

Файловая система эфемерна, поэтому вы не можете напрямую установить необходимые для вашего приложения сервисы (то есть, базы данных, очереди, системы кэширования, хранения, сервисы электронной почты и так далее). Взамен этого, Heroku предоставляет сервисы, доступные как независимые "дополнения" ("add-ons") либо от самой Heroku, или от сторонних разработчиков. В тот момент когда ваше приложение запускается в системе, dynos получает доступ к сервисам, используя информацию, содержащуюся в переменных настройки вашего приложения.

+ +

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

+ + + +

Разработчики Developers взаимодействуют с Heroku при помощи специального клиентского приложения/терминала, который сильно похож на bash-скрипт Unix. Оно позволяет вам загружать код, находящийся в git-репозитории, контроллировать выполняемые процессы, смотреть логи, устанавливать конфигурационные переменные и многое другое!

+ +

Для того, чтобы заставить ваше приложение работать с Heroku, нам нужно разместить наше веб-приложение в git-репозитории, добавить, перечисленные ранее, файлы, подключить дополнение (add-on) базы данных и выполнить настройки для правильной работы со статическими файлами.

+ +

Когда мы выполним все, что необходимо для нашего сайта мы можем создать аккаунт Heroku, получить доступ к клиенту Heroku и использовать его, для установки нашего веб-сайта.

+ +
+

Примечание: Инструкции, перечисленные ниже, соответствуют процессу работы с Heroku во время написания данной статьи (английской версии - прим. перев.). Если Heroku значительно изменит этот процесс, вы можете воспользоваться соответствующим описанием: Heroku начало работы с Django.

+
+ +

На этом завершается краткий обзор начала работы с Heroku (более подробное руководство Как работает Heroku).

+ +

Создание репозитория приложения на Github

+ +

Heroku тесно интегрирована с системой управления версиями исходного кода git, используя ее для загрузки / синхронизации любых изменений, которые вы вносите в живую систему. Он делает это, добавляя новый «удаленный» репозиторий heroku с именем heroku, указывающий на репозиторий для вашего источника в облаке Heroku. Во время разработки вы используете git для хранения изменений в вашем «master» репозитории. Когда вы хотите развернуть свой сайт, вы синхронизируете свои изменения в репозитории Heroku.

+ +
+

Примечание: Если вы привыкли следовать хорошей практике разработки программного обеспечения, вы, вероятно, уже используете git или какую-либо другую систему SCM. Если у вас уже есть git-репозиторий, вы можете пропустить этот шаг.

+
+ +

Существует множество способов работы с git, но одним из самых простых является создание учетной записи в Github, создание репозитория там, а затем синхронизация с ним локально:

+ +
    +
  1. Посетите https://github.com/ и создайте аккаунт.
  2. +
  3. После входа в систему нажмите ссылку + в верхней панели инструментов и выберите Новый репозиторий.
  4. +
  5. Заполните все поля на этой форме. Хотя они не являются обязательными, они настоятельно рекомендуются. +
      +
    • Введите имя нового репозитория (например django_local_library), и комментарий к репозиторию (например "Local Library website written in Django".
    • +
    • Нажмите на кнопку Add .gitignore и в появившемся списке выберите Python.
    • +
    • Выберите подходящую вам лицензию из списка Add license. Если не знаете для чего это - оставьте как было.
    • +
    • +

      Установите галочку напротив Initialize this repository with a README.

      +
    • +
    +
  6. +
  7. Нажмите кнопку Create repository, тем самым создав ваш репозиторий.
  8. +
  9. Перейдите на страницу вашего репозитория. Там нажмите на зелёную кнопку "Clone or download". Скопируйте URL  из текстового поляиз появившегося диалогового окна (Это будет похоже на: https://github.com/<your_git_user_id>/django_local_library.git). Здесь <your_git_user_id> - это будет ваш id пользователя git.
  10. +
+ +

Когда ваш репозиторий будет создан - загрузите его себе на компьтер, следуя инструкции, описанной ниже:

+ +
    +
  1. Установите git себе на компьютер (Вы можете найти версию для своей платформы здесь).
  2. +
  3. Откройте командную строку (или терминал) и выполните в нём следующую команду, используя ссылку, которую вы получили с github: +
    git clone https://github.com/<your_git_user_id>/django_local_library.git
    +
    + Это создаст подпапку (с содержанием вашего репозитория и именем вашего репозитория) внутри папки, в котрой выполнялась команда.
  4. +
  5. Перейдите в эту папку: +
    cd django_local_library.git
    +
  6. +
+ +

Последний шаг. Нужно скопировать ваше Django-приложение и добавить его файлы в новый репозиторий, используя git:

+ +
    +
  1. Скопируйте ваше приложение в папку репозитория (все файлы с таким же уровнем, как у manage.py, БЕЗ папки проекта, в которой эти файлы находятся).
  2. +
  3. Откройте файл с расширением .gitignore в текстовом редакторе, вставьте в самый его конец строки, приведённые ниже, а затем сохраните (этот файл "говорит" о файлах, которые не должны быть  загружены в git по умолчанию). +
    # Text backup files
    +*.bak
    +
    +#Database
    +*.sqlite3
    +
  4. +
  5. +

    Откройте командную строку или терминал и используйте add команду с флагом -A. Эта комманда сохранит изменения в репозиторий:

    + +
    git add -A
    +
  6. +
  7. Используйте команду status,  что бы убедиться, что все файлы, которые вы собираетесь добавить верны (вы хотите включить исходные файлы, а не бинарные файлы, временные файлы и т. д.). В консоль выведется что то вроде этого: +
    > git status
    +On branch master
    +Your branch is up-to-date with 'origin/master'.
    +Changes to be committed:
    +  (use "git reset HEAD <file>..." to unstage)
    +
    +        modified:   .gitignore
    +        new file:   catalog/__init__.py
    +        ...
    +        new file:   catalog/migrations/0001_initial.py
    +        ...
    +        new file:   templates/registration/password_reset_form.html
    +
  8. +
  9. Теперь, зафиксируйте файлы в локальном репозитории: +
    git commit -m "First version of application moved into github"
    +
  10. +
  11. Синхронизируете свой локальный репозиторий с сайтом Github: +
    git push origin master
    +
  12. +
+ +

Когда эти операции завершатся, вернитесь на страницу Github где вы создали свой репозиторий, обновите страницу, и убедитесь, что ваше приложение полностью загружено. При надобности обновить файлы на репозитории - повторите цикл ввода команд add/commit/push.

+ +
+

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

+ +

Лучший способ сделать это - использовать git для управления вашими изменениями. С git вы можете не только вернуться к определенной старой версии, но и сохранить ее в отдельной «ветке» ваших производственных изменений, and cherry-pick - выбрать любые изменения для перемещения между ветвями производства и развития. Изучение Git будет стоить усилий, но это выходит за рамки данной темы. Самый простой способ сделать это - просто скопировать файлы в другое место. Используйте тот подход, который наилучшим образом соответствует вашим знаниям git!

+
+ +

Обновить приложение для Heroku 

+ +

В этой части говорится об изменениях, которые мы должны сделать на нашем приложении LocalLibrary, что бы оно работало на  Heroku. В то время как документация "начало работы с Heroku с инструкциями Django" предполагает, что вы будете использовать Heroku client для запуска локальной среды разработки, наши изменения здесь совместимы с существующим сервером разработки Django и способами работы, которые мы уже узнали.

+ +

Procfile

+ +

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

+ +
web: gunicorn locallibrary.wsgi --log-file -
+ +

«web:» сообщает Heroku, что это веб динамический и может быть отправлен HTTP-трафик. Процесс, который начнется в этом динамически, - это gunicorn, который является популярным сервером веб-приложений, который рекомендует Heroku. Мы запускаем Gunicorn, используя конфигурационную информацию в модуле locallibrary.wsgi (созданный с помощью нашего скелета приложения: /locallibrary/wsgi.py).

+ +

Gunicorn

+ +

Gunicorn рекомендуемый http сервер с Django на Heroku (Как указанов Procfile выше). Это чистый python http сервер для WSGI приложений  которые могут запускать множество параллельных python процессов в пределах одного динамического (посмотрите Deploying Python applications with Gunicorn для получения большей информации).

+ +

Также нам не понадобится Gunicorn для обслушивания нашей LocalLibrary приложения в течение разработки, мы установим это так, чтобы он стал частью наших требований к Heroku для настройки на удаленном сервере.

+ +

Установка Gunicorn локально в командной строке используя пакетный менеджер pip (которые мы установили когда настраивали среду разработки):

+ +
pip3 install gunicorn
+
+ +

Настройка Базы Данных

+ +

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

+ +

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

+ +

Информация о подключении базы данных предоставляется на web dyno, используя конфигурационную переменную с именем DATABASE_URL. Вместо того, чтобы жестко кодировать эту информацию в Django, Heroku рекомендует разработчикам использовать dj-database-url пакет для анализа DATABASE_URL переменную окружения и автоматически преобразовать ее в желаемый формат конфигурации Django. В дополнение к установке пакета dj-database-url нам также потребуется установить psycopg2, поскольку Django нуждается в этом, чтобы взаимодействовать с базами данных Postgres.

+ +
dj-database-url (Django конфигурации базы данных из переменной окружения)
+ +

Установите dj-database-url локально, чтобы он стал частью наших требований к настройке Heroku на удаленном сервере:

+ +
$ pip3 install dj-database-url
+
+ +
settings.py
+ +

Откройте /locallibrary/settings.py и скопируйте следующую конфигурацию в нижнюю часть файла:

+ +
# Heroku: Update database configuration from $DATABASE_URL.
+import dj_database_url
+db_from_env = dj_database_url.config(conn_max_age=500)
+DATABASES['default'].update(db_from_env)
+ +
+

Заметка:

+ + +
+ +
psycopg2 (Python Postgres database support)
+ +

Django нуждается в psycopg2 для работы с базами данных Postgres, и вам нужно будет добавить это в файл требований.txt для Heroku, чтобы установить это на удаленном сервере (как описано в разделе требований ниже).

+ +

Django будет использовать нашу базу данных SQLite локально по умолчанию, поскольку переменная среды DATABASE_URL не задана в нашей локальной среде. Если вы хотите полностью перейти на Postgres и использовать нашу бесплатную базу данных Heroku для разработки и производства, то вы можете. Например, чтобы установить psycopg2 и его зависимости локально в системе на базе Linux, вы должны использовать следующие команды bash / terminal:

+ +
sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib
+pip3 install psycopg2
+
+ +

Инструкции по установке для других платформ можно найти на веб-сайте psycopg2.

+ +

Однако вам не нужно это делать - вам не нужно, чтобы PostGreSQL был активным на локальном компьютере, если вы передаете его в Heroku в качестве требования в файле требований.txt (см. Ниже).

+ +

Обслуживание статических файлов в производстве

+ +


+ Во время разработки мы использовали Django и веб-сервер разработки Django для обслуживания наших статических файлов (CSS, JavaScript и т. Д.). В производственной среде вместо этого мы обычно обслуживаем статические файлы из сети доставки контента (CDN) или веб-сервера.

+ +
+

Примечание. Обслуживание статических файлов через Django / веб-приложение неэффективно, потому что запросы должны проходить через ненужный дополнительный код (Django), а не обрабатываться непосредственно веб-сервером или полностью отдельным CDN. Хотя это не имеет значения для местного использования во время разработки, это будет иметь значительное влияние на производительность, если мы будем использовать тот же подход в производстве.

+
+ +

Чтобы упростить размещение статических файлов отдельно от веб-приложения Django, Django предоставляет средство сбора данных для сбора этих файлов для развертывания (имеется переменная параметров, определяющая, где файлы должны собираться при запуске collectstatic). Шаблоны Django относятся к месту размещения статических файлов относительно переменной параметров (STATIC_URL), так что это можно изменить, если статические файлы перемещаются на другой хост / сервер.

+ +

Соответствующими параметрами настройки являются:

+ +

     STATIC_URL: это базовое расположение URL, из которого будут загружены статические файлы, например, на CDN. Это используется для переменной статического шаблона, доступ к которой осуществляется в нашем базовом шаблоне (см. Учебник по Django Part 5: Создание нашей домашней страницы).
+       STATIC_ROOT: Это абсолютный путь к каталогу, в котором инструмент «collectstatic» Django будет собирать любые статические файлы, упомянутые в наших шаблонах. После их сбора они затем могут быть загружены в группу, где бы файлы не размещались.
+       STATICFILES_DIRS: В этом списке перечислены дополнительные каталоги, в которых инструмент коллективного поиска Django должен искать статические файлы.

+ +
settings.py
+ +

Откройте /locallibrary/settings.py и скопируйте следующую конфигурацию в нижнюю часть файла. BASE_DIR уже должен быть определен в вашем файле (STATIC_URL, возможно, уже был определен в файле, когда он был создан. В то время как это не причинит вреда, вы также можете удалить дублируемую предыдущую ссылку).

+ +
# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.10/howto/static-files/
+
+# The absolute path to the directory where collectstatic will collect static files for deployment.
+STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
+
+# The URL to use when referring to static files (where they will be served from)
+STATIC_URL = '/static/'
+
+ +

Фактически мы будем делать файл, используя библиотеку WhiteNoise, которую мы устанавливаем и настраиваем в следующем разделе.

+ +

Для получения дополнительной информации см. Django и Static Assets (документы Heroku).

+ +

WhiteNoise
+ Существует множество способов обслуживания статических файлов на производстве (мы видели соответствующие настройки Django в предыдущих разделах). Heroku рекомендует использовать проект WhiteNoise для обслуживания статических активов непосредственно из Gunicorn в производстве.

+ +
+

Заметка: Heroku автоматически вызывает collectstatic и готовит ваши статические файлы для использования WhiteNoise после того, как он загрузит ваше приложение. Посмотрите WhiteNoise документацию для объяснения того, как она работает, и почему реализация является относительно эффективным методом для обслуживания этих файлов.

+
+ +

Шаги по настройке WhiteNoise для использования в проекте:

+ +
WhiteNoise
+ +

Установите WhiteNoise локально, используя следующую команду:

+ +
$ pip3 install whitenoise
+
+ +
settings.py
+ +

Чтобы установить WhiteNoise в приложение Django, откройте /locallibrary/settings.py, найдите параметр MIDDLEWARE и добавьте WhiteNoiseMiddleware в верхней части списка, чуть ниже SecurityMiddleware:

+ +
MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'whitenoise.middleware.WhiteNoiseMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ +

При желании вы можете уменьшить размер статических файлов при их обслуживании (это более эффективно). Просто добавьте следующее в конец /locallibrary/settings.py:

+ +
# Simplified static file serving.
+# https://warehouse.python.org/project/whitenoise/
+STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
+
+ +

Requirements

+ +

Требования Python вашего веб-приложения должны храниться в файле requirements.txt в корневом каталоге вашего репозитория. После этого Heroku автоматически установит их при восстановлении вашей среды. Вы можете создать этот файл с помощью pip в командной строке (запустите в корне repo):

+ +
pip3 freeze > requirements.txt
+ +

После установки всех разных зависимостей выше, файл requirements.txt должен иметь по меньшей мере эти перечисленные элементы (хотя номера версий могут отличаться). Удалите любые другие зависимости, не перечисленные ниже, если вы явно не добавили их для этого приложения.

+ +
dj-database-url==0.4.1
+Django==1.10.2
+gunicorn==19.6.0
+psycopg2==2.6.2
+whitenoise==3.2.2
+
+ +
+

Убедитесь, что строка  psycopg2, подобная приведенной выше, присутствует! Даже если вы не установили это локально, вы должны добавить это в requirements.txt.

+
+ +

Среда выполнения

+ +

Файл runtime.txt, если определён, говорит Heroku, какой язык программирования использовать. Создайте файл в корне репо и добавьте следующий текст:

+ +
python-3.5.2
+ +
+

Заметка: Heroku поддерживает только небольшое количество Python runtimes. (на момент написания статьи, в том числе и выше). Heroku будет использовать поддерживаемую среду выполнения независимо от значения, указанного в этом файле.

+
+ +

Сохраните изменения в Github и перепроверьте

+ +

Далее мы сохраним все наши изменения в Github. В терминале (whist внутри нашего репозитория) введите следующие команды:

+ +
git add -A
+git commit -m "Added files and changes required for deployment to heroku"
+git push origin master
+ +

Прежде чем продолжить, дайте возможность проверить сайт снова локально и убедиться, что это не повлияло ни на одно из наших изменений выше. Запустите веб-сервер разработки как обычно, а затем проверьте, работает ли сайт, как вы ожидаете в своем браузере.

+ +
python3 manage.py runserver
+ +

Теперь мы должны быть готовы начать развертывание LocalLibrary на Heroku.

+ +

Получить аккаунт в heroku

+ +

Чтобы начать использовать Heroku, вам сначала нужно создать учетную запись:

+ + + +

Установка клиента

+ +

Загрузите и установите клиент Heroku, следуя инструкциям Heroku здесь.

+ +

После установки клиента вам будут дотупны команды. Например, чтобы получить справку о клиенте:

+ +
heroku help
+
+ +

Создание и загрузка веб-сайта

+ +

Чтобы создать приложение, мы запускаем команду «create» в корневом каталоге нашего репозитория. Это создает git remote («указатель на удаленный репозиторий»), названный heroku в нашей локальной среде git.

+ +
heroku create
+ +
+

Заметка: Вы можете назвать удаленный, если хотите, указав значение после «create». Если вы этого не сделаете, вы получите случайное имя. Имя используется в URL-адресе по умолчанию.

+
+ +

Затем мы можем подтолкнуть наше приложение в репозиторий heroku как показано ниже. Это позволит загрузить приложение, упаковать его в dyno, запустить collectstatic, и запустить сам сайт.

+ +
git push heroku master
+ +

Если нам повезет, приложение «заработает» на сайте, но оно не будет работать должным образом, потому что мы не настроили таблицы базы данных для использования нашим приложением. Для этого нам нужно использовать команду  heroku run и запустить "one off dyno" для выполнения операции переноса. Введите в терминал следующую команду:

+ +
heroku run python manage.py migrate
+ +

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

+ +
heroku run python manage.py createsuperuser
+ +

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

+ +
heroku open
+ +

Создайте несколько книг на сайте администратора и проверьте, работает ли сайт, как вы ожидаете.

+ +

Управление аддонами

+ +

Вы можете проверить дополнения в своем приложении, используя heroku addons команду. Это будет список всех аддонов, их ценовая категория и состояние.

+ +
>heroku addons
+
+Add-on                                     Plan       Price  State
+─────────────────────────────────────────  ─────────  ─────  ───────
+heroku-postgresql (postgresql-flat-26536)  hobby-dev  free   created
+ └─ as DATABASE
+ +

Здесь мы видим, что у нас есть только одна надстройка, база данных postgres SQL. Это бесплатно и автоматически создается при создании приложения. Вы можете открыть веб-страницу, чтобы более подробно изучить надстройку базы данных (или любое другое дополнение), используя следующую команду:

+ +
heroku addons:open heroku-postgresql
+
+ +

Другие команды позволяют создавать, уничтожать, обновлять и понижать аддоны (используя аналогичный синтаксис для открытия). Для получения дополнительной информации см.  Managing Add-ons (Heroku docs).

+ +

Настройка переменных конфигурации

+ +

Вы можете проверить конфигурационные переменные для сайта, используя команду  heroku config. Ниже вы можете видеть, что у нас есть только одна переменная DATABASE_URL , используемая для настройки нашей базы данных.

+ +
>heroku config
+
+=== locallibrary Config Vars
+DATABASE_URL: postgres://uzfnbcyxidzgrl:j2jkUFDF6OGGqxkgg7Hk3ilbZI@ec2-54-243-201-144.compute-1.amazonaws.com:5432/dbftm4qgh3kda3
+ +

Если вы вспомните из раздела, посвященного  getting the website ready to publish, мы должны установить переменные среды для DJANGO_SECRET_KEY и DJANGO_DEBUG. Давайте сделаем это сейчас.

+ +
+

Заметка: Секретный ключ должен быть действительно секретным! Один из способов генерации нового ключа - создать новый проект Django (django-admin startproject someprojectname) а затем получить ключ, который генерируется для вас в его settings.py.

+
+ +

Мы устанавливаем  DJANGO_SECRET_KEY используя команду config:set (как показано ниже). Не забудьте использовать свой секретный ключ!

+ +
>heroku config:set DJANGO_SECRET_KEY=eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh&=
+
+Setting DJANGO_SECRET_KEY and restarting locallibrary... done, v7
+DJANGO_SECRET_KEY: eu09(ilk6@4sfdofb=b_2ht@vad*$ehh9-)3u_83+y%(+phh
+
+ +

Аналогично мы устанавливаем  DJANGO_DEBUG:

+ +
>heroku config:set DJANGO_DEBUG=''
+
+Setting DJANGO_DEBUG and restarting locallibrary... done, v8
+ +

Если вы посетите веб-сайт сейчас, вы получите ошибку "Bad request" , потому что в  ALLOWED_HOSTS надо внести параметры, если у вас DEBUG=False (в качестве меры безопасности). Откройте /locallibrary/settings.py и измените ALLOWED_HOSTS для включения вашего базового URL-адреса приложения (например, 'locallibrary1234.herokuapp.com') URL, который вы обычно используете на локальном сервере разработки.

+ +
ALLOWED_HOSTS = ['<your app URL without the https:// prefix>.herokuapp.com','127.0.0.1']
+# For example:
+# ALLOWED_HOSTS = ['fathomless-scrubland-30645.herokuapp.com','127.0.0.1']
+
+ +

Затем сохраните настройки и передайте их в репозиторий Github и в Heroku:

+ +
git add -A
+git commit -m 'Update ALLOWED_HOSTS with site and development server URL'
+git push origin master
+git push heroku master
+ +
+

После завершения обновления сайта на Heroku введите URL-адрес, который не существует (например,  /catalog/doesnotexist/). Раньше это отображало бы подробную страницу отладки, но теперь вы должны просто увидеть простую страницу «Не найдено».

+
+ +

Отладка

+ +

Клиент Heroku предоставляет несколько инструментов для отладки:

+ +
heroku logs  # Show current logs
+heroku logs --tail # Show current logs and keep updating with any new results
+heroku config:set DEBUG_COLLECTSTATIC=1 # Add additional logging for collectstatic (this tool is run automatically during a build)
+heroku ps   #Display dyno status
+
+ +

Если вам нужно больше информации, предоставленной здесь, вам нужно будет начать изучать Django Logging.

+ + + +

Итоги

+ +

Это конец этого руководства по настройке и развёртывании приложений Django, а также серия руководств по работе с Django. Надеемся, вы нашли их полезными. Вы можете проверить полностью проработанную версию по исходникам на Github.
+ Следующий шаг - прочитать наши последние несколько статей, а затем завершить оценочную задачу.

+ +

Смотрите также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}

+ +

В этом модуле

+ + diff --git "a/files/ru/learn/server-side/django/\321\201\320\265\321\201\321\201\320\270\320\270/index.html" "b/files/ru/learn/server-side/django/\321\201\320\265\321\201\321\201\320\270\320\270/index.html" new file mode 100644 index 0000000000..5f7d492c72 --- /dev/null +++ "b/files/ru/learn/server-side/django/\321\201\320\265\321\201\321\201\320\270\320\270/index.html" @@ -0,0 +1,181 @@ +--- +title: 'Руководство часть 7: Сессии' +slug: Learn/Server-side/Django/Сессии +tags: + - django + - Джанго + - Для начинающих + - Изучение + - Питон + - Руководство + - Серверная сторона + - Статья + - применение сессий + - сессии +translation_of: Learn/Server-side/Django/Sessions +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django")}}
+ +

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

+ + + + + + + + + + + + +
Требования:Завершить изучение всех предыдущих разделов, включая Django Руководство Часть 6: Обобщенные отображения списков и детальной информации
Цель:Понимать как применять сессии.
+ +

Обзор

+ +

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

+ +

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

+ +

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

+ +

Что такое сессии?

+ +

Все взаимодействия между браузерами и серверами осуществляются при помощи протокола HTTP, который не сохраняет свое состояние (stateless). Данный факт означает, что сообщения между клиентом и сервером являются полностью независимыми один от другого — то есть не существует какого-либо представления "последовательности", или поведения в зависимости от предыдущих сообщений. В результате, если вы хотите создать сайт который будет отслеживать взаимодействие с клиентом (браузером), вам нужно реализовать это самостоятельно.

+ +

Сессии являются механизмом, который использует Django (да и весь остальной "Интернет") для отслеживания "состояния" между сайтом и каким-либо браузером. Сессии позволяют вам хранить произвольные данные браузера и получать их в тот момент, когда между данным браузером и сайтом устанавливается соединение. Данные получаются и сохраняются в сессии при помощи соответствующего "ключа".

+ +

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

+ +

Подключение сессий

+ +

Сессии стали доступны автоматически в тот момент, когда мы создали скелет сайта (во второй части руководства).

+ +

Необходимые конфигурации выполняются в разделах INSTALLED_APPS и MIDDLEWARE файла проекта (locallibrary/locallibrary/settings.py), как показано ниже:

+ +
INSTALLED_APPS = [
+    ...
+    'django.contrib.sessions',
+    ....
+
+MIDDLEWARE = [
+    ...
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    ....
+ +

Применение сессий

+ +

Вы можете получить доступ к переменной session, в соответствующем отображении, через параметр request (HttpRequest передается как первый аргумент в каждое отображение). Переменная сессии является связью с определенным пользователем (или, если быть более точным, связью с определенным браузером, который определяется при помощи идентификатора (id) сессии, получаемого из куки браузера).

+ +

Переменная (или поле) session является объектом-словарем, который служит для чтения и записи неограниченное число раз. С ним вы можете выполнять любые стандартные операции, включая очистку всех данных, проверку наличия ключа, циклы по данным и так далее. Большую часть времени вы будете тратить на  обычные "словарные" операции - получения и установки значений.

+ +

Ниже представлены фрагменты кода, которые показывают вам как получать, задавать и удалять некоторые данные при помощи ключа "my_car", связанного с текущей сессией (браузером). 

+ +
+

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

+
+ +
# Получение значения сессии при помощи ключа(то есть, 'my_car').
+# Если такого ключа нет, то возникнет ошибка KeyError
+my_car = request.session['my_car']
+
+# Получение значения сессии. Если значения не существует,
+# то вернется значение по умолчанию ('mini')
+my_car = request.session.get('my_car', 'mini')
+
+# Передача значения в сессию
+request.session['my_car'] = 'mini'
+
+# Удаление значения из сессии
+del request.session['my_car']
+
+ +

Данное API имеет другие методы, которые большей частью используются для управления куки, связанных с сессией.  Например, существуют методы проверки того, что куки поддерживаются клиентским браузером, другие методы служат для установки и проверки предельных дат жизни куки, а также для очистки просроченных сессий из хранилища. Подробное описание API вы можете найти в разделе Как использовать сессии (Django docs).

+ +

Хранение данных сессии

+ +

По умолчанию Django сохраняет данные сессии в базу данных и отправляет соответствующие куки клиенту только тогда, когда сессия была изменена, или удалена. Если вы обновляете какие-либо данные при помощи ключа сессии, как показано в предыдущем фрагменте, тогда вам не надо беспокоиться о процессе сохранения! Например:

+ +
# Данное присваивание распознается как обновление сессии
+# и данные будут сохранены
+request.session['my_car'] = 'mini'
+ +

Если вы обновлете информацию внутри данных сессии, тогда Django не распознает эти изменения и не выполнит сохранение данных (например, если вы изменили "wheels" внутри переменной "my_car", как показано ниже). В таких случаях вам надо явно указывать, что сессия была изменена.

+ +
# Объект сессии модифицируется неявно.
+# Изменения НЕ БУДУТ сохранены!
+request.session['my_car']['wheels'] = 'alloy'
+
+# Явное указание, что данные изменены.
+# Сессия будет сохранена, куки обновлены (если необходимо).
+request.session.modified = True
+
+ +
+

Примечание: Вы можете изменить поведение сессий таким образом, чтобы они записывали любое свое изменение в базу данных и отправляли куки, при каждом запросе, путем установки SESSION_SAVE_EVERY_REQUEST = True, в файле настроек проекта (locallibrary/locallibrary/settings.py).

+
+ +

Простой пример — получение числа визитов

+ +

В качестве примера из реального мира мы обновим нашу библиотеку так, чтобы сообщать пользователю количество совершенных им визитов главной страницы сайта LocalLibrary

+ +

Откройте /locallibrary/catalog/views.py и добавьте изменения, выделенных жирным, ниже. 

+ +
def index(request):
+    ...
+
+    num_authors=Author.objects.count()  # The 'all()' is implied by default.
+
+    # Number of visits to this view, as counted in the session variable.
+    num_visits=request.session.get('num_visits', 0)
+    request.session['num_visits'] = num_visits+1
+
+    # Render the HTML template index.html with the data in the context variable.
+    return render(
+        request,
+        'index.html',
+        context={'num_books':num_books,'num_instances':num_instances,'num_instances_available':num_instances_available,'num_authors':num_authors,
+            'num_visits':num_visits}, # num_visits appended
+    )
+ +

В первую очередь мы получаем значение 'num_visits' из сессии, возвращая 0, если оно не было установлено ранее. Каждый раз при получении запроса, мы увеличиваем данное значение на единицу и сохраняем его обратно в сессии (до следующего посещения данной страницы пользователем). Затем переменная num_visits передается в шаблон через переменную контекста context.  

+ +
+

Примечание: Можно проверить наличие поддержки куки в браузере (для примера, смотрите Как использовать сессии), или разработать наш UI таким образом, чтобы это не имело значения.

+
+ +

Для показа значения переменной, из следующего фрагмента добавьте нижнюю строчку кода в ваш шаблон главной страницы сайта (/locallibrary/catalog/templates/index.html), в его нижний раздел "Dynamic content":

+ +
<h2>Dynamic content</h2>
+
+<p>The library has the following record counts:</p>
+<ul>
+<li><strong>Books:</strong> \{{ num_books }}</li>
+<li><strong>Copies:</strong> \{{ num_instances }}</li>
+<li><strong>Copies available:</strong> \{{ num_instances_available }}</li>
+<li><strong>Authors:</strong> \{{ num_authors }}</li>
+</ul>
+
+<p>You have visited this page \{{ num_visits }}{% if num_visits == 1 %} time{% else %} times{% endif %}.</p>
+
+ +

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

+ + + +

Итоги

+ +

Вы узнали как применять сессии для улучшения взаимодейстсвие с анонимными пользователями. 

+ +

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

+ +

Смотрите также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/Authentication", "Learn/Server-side/Django")}}

-- cgit v1.2.3-54-g00ecf