From 074785cea106179cb3305637055ab0a009ca74f2 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:42:52 -0500 Subject: initial commit --- .../apache_configuration_htaccess/index.html | 38 + .../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 +++++ .../development_environment/index.html | 391 +++++++++ .../displaying_data/author_list_page/index.html | 85 ++ .../displaying_data/book_list_page/index.html | 68 ++ .../bookinstance_list_page/index.html | 69 ++ .../date_formatting_using_moment/index.html | 60 ++ .../flow_control_using_async/index.html | 138 ++++ .../displaying_data/genre_detail_page/index.html | 121 +++ .../displaying_data/home_page/index.html | 134 ++++ .../express_nodejs/displaying_data/index.html | 83 ++ .../locallibrary_base_template/index.html | 69 ++ .../displaying_data/template_primer/index.html | 149 ++++ .../forms/create_bookinstance_form/index.html | 151 ++++ .../forms/create_genre_form/index.html | 199 +++++ .../forms/delete_author_form/index.html | 165 ++++ .../server-side/express_nodejs/forms/index.html | 269 +++++++ .../forms/update_book_form/index.html | 189 +++++ .../ru/learn/server-side/express_nodejs/index.html | 77 ++ .../express_nodejs/introduction/index.html | 512 ++++++++++++ .../server-side/express_nodejs/mongoose/index.html | 800 ++++++++++++++++++ .../server-side/express_nodejs/routes/index.html | 655 +++++++++++++++ .../express_nodejs/skeleton_website/index.html | 507 ++++++++++++ .../index.html" | 73 ++ .../first_steps/client-server_overview/index.html | 325 ++++++++ files/ru/learn/server-side/first_steps/index.html | 49 ++ .../first_steps/introduction/index.html | 217 +++++ .../first_steps/web_frameworks/index.html | 306 +++++++ .../index.html" | 169 ++++ files/ru/learn/server-side/index.html | 54 ++ .../node_server_without_framework/index.html | 84 ++ 46 files changed, 12702 insertions(+) create mode 100644 files/ru/learn/server-side/apache_configuration_htaccess/index.html 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" create mode 100644 files/ru/learn/server-side/express_nodejs/development_environment/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/introduction/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/mongoose/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/routes/index.html create mode 100644 files/ru/learn/server-side/express_nodejs/skeleton_website/index.html create mode 100644 "files/ru/learn/server-side/express_nodejs/\321\203\321\207\320\265\320\261\320\275\320\270\320\272_\321\201\320\260\320\271\321\202_local_library/index.html" create mode 100644 files/ru/learn/server-side/first_steps/client-server_overview/index.html create mode 100644 files/ru/learn/server-side/first_steps/index.html create mode 100644 files/ru/learn/server-side/first_steps/introduction/index.html create mode 100644 files/ru/learn/server-side/first_steps/web_frameworks/index.html create mode 100644 "files/ru/learn/server-side/first_steps/\320\262\320\265\320\261_\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\276\321\201\321\202\321\214/index.html" create mode 100644 files/ru/learn/server-side/index.html create mode 100644 files/ru/learn/server-side/node_server_without_framework/index.html (limited to 'files/ru/learn/server-side') diff --git a/files/ru/learn/server-side/apache_configuration_htaccess/index.html b/files/ru/learn/server-side/apache_configuration_htaccess/index.html new file mode 100644 index 0000000000..fafabc17f8 --- /dev/null +++ b/files/ru/learn/server-side/apache_configuration_htaccess/index.html @@ -0,0 +1,38 @@ +--- +title: .htaccess ( hypertext access ) +slug: Learn/Server-side/Apache_Configuration_htaccess +translation_of: Learn/Server-side/Apache_Configuration_htaccess +--- +

Название .htaccess происходит от "hypertext access". Это файл с расширением HTACCESS, который содержит различные настройки сервера apache. Он позволяет настраивать для текущей директории защиту паролем, редиректы и многое другое.

+ +

Доступ к файлу: файл htaccess  может быть открыт для редактивания любым текстовым редактором, таким как стандартный блокнот Windows, Vin, Sublime text editor или любым другим. Подсветка синтаксиса для файлов .htaccess встречается редко.

+ +

Применение

+ +

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

+ +
Redirect 301 / http://example.com/       # Постоянное перенаправление на example.com
+Redirect 302 / http://example.com/       # Временное перенаправление на example.com
+
+ +

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

+ +
deny from 146.0.74.205                   # Блокирует все запросы с адреса 146.0.74.205
+ +

SSI или Server Side Include : С помощью файла .htaccess можно настроить автоматическое подключение файлов в документ. При каждом запросе пользователя, указанные файлы автоматически будут подключены в начало или в конец документа. При этом в самом документе их подключать не нужно.

+ +
php_value auto_prepend_file "/real/path/to/file/functions.php" # Подключит файл function.php в начало документа
+php_value auto_append_file "/real/path/to/file/footer.php" # Подключит файл footer.html в конец документа
+
+ +

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

+ +
ErrorDocument 404 /notfound.html # Перенаправит пользователя на страницу notfound.html , при возникновении ошибки с кодом 404
+ErrorDocument 500 /serverr.html  # Перенаправит пользователя на страницу serverr.html , при возникновении ошибки с кодом 500
+
+ +

Для дополнительной информации читайте статью Redirect your Traffic for Error Handling.

+ +

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

+ +

MIME типы: смотрите статью correct MIME types для большей информации.

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")}}

diff --git a/files/ru/learn/server-side/express_nodejs/development_environment/index.html b/files/ru/learn/server-side/express_nodejs/development_environment/index.html new file mode 100644 index 0000000000..30000c6470 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/development_environment/index.html @@ -0,0 +1,391 @@ +--- +title: Setting up a Node development environment +slug: Learn/Server-side/Express_Nodejs/development_environment +translation_of: Learn/Server-side/Express_Nodejs/development_environment +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}
+ +

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

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

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

+ +

Node и Express упрощают настройку вашего компьютера, чтобы вы могли начать разработку веб-приложений. В этом разделе объясняется, какие инструменты нужны, приводятся некоторые из самых простых способов установки Node (и Express) на Ubuntu, macOS, and Windows, и показывается как вы можете протестировать свою установку.

+ +

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

+ +

Среда разработки Express включает в себя установку Nodejs, менеджера пакетов NPM и (необязательно) Express Application Generator на локальном компьютере.
+
+ Узел и менеджер пакетов NPM устанавливаются вместе из подготовленных двоичных пакетов, установщиков, менеджеров пакетов операционной системы или из исходного кода (как показано в следующих разделах). Затем Express устанавливается NPM как зависимость от ваших отдельных веб-приложений Express (наряду с другими библиотеками, такими как механизмы шаблонов, драйверы баз данных, промежуточное программное обеспечение для аутентификации, промежуточное программное обеспечение для обслуживания статических файлов и т. Д.)
+
+ NPM также можно использовать для (глобальной) установки Express Application Generator, удобного инструмента для создания каркасных веб-приложений Express, которые следуют шаблону MVC. Генератор приложений является необязательным, поскольку вам не нужно использовать этот инструмент для создания приложений, использующих Express, или для приложений для создан Express, имеющих одинаковую архитектурную разметку или зависимости. Мы будем использовать его, потому что это значительно облегчает начало работы и продвигает модульную структуру приложения.

+ +
+

Примечание: в отличие от некоторых других веб-сред, среда разработки не включает отдельный веб-сервер разработки. В Node / Express веб-приложение создает и запускает собственный веб-сервер!

+
+ +

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

+ +

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

+ +

Узел может быть запущен в Windows, macOS, во многих «разновидностях» Linux, Docker и т. Д. (Полный список на странице загрузок nodejs). Практически любой персональный компьютер должен иметь необходимую производительность для запуска Node во время разработки. Express работает в среде Node и, следовательно, может работать на любой платформе, на которой работает Node.
+
+ В этой статье мы предоставляем инструкции по установке для Windows, macOS и Ubuntu Linux.

+ +

Какую версию Node / Express следует использовать?

+ +

Существует множество выпусков Node - более новые выпуски содержат исправления ошибок, поддержку более свежих версий стандартов ECMAScript (JavaScript) и улучшения API-интерфейсов Node.
+
+ Как правило, вы должны использовать самый последний выпуск LTS (с долгосрочной поддержкой), поскольку он будет более стабильным, чем «текущий» выпуск, при этом все еще имея относительно недавние функции (и все еще активно поддерживается). Вы должны использовать Текущий выпуск, если вам нужна функция, которой нет в версии LTS.
+
+ Для Express вы всегда должны использовать последнюю версию.

+ +

Как насчет баз данных и других зависимостей?

+ +

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

+ +

Установка Node

+ +

Чтобы использовать Express, сначала необходимо установить Nodejs и Node Package Manager (NPM) в вашей операционной системе. В следующих разделах описывается самый простой способ установки версии Nodejs с долгосрочной поддержкой (LTS) в Ubuntu Linux 16.04, macOS и Windows 10.

+ +
+

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

+
+ +

Windows и macOS

+ +

Установка Node и NPM в Windows и macOS проста, потому что вы можете просто использовать предоставленный инсталлятор:

+ +
    +
  1. Загрузите необходимый установщик: + +
      +
    1. Перейдите по ссылке https://nodejs.org/en/
    2. +
    3. Нажмите кнопку, чтобы загрузить сборку LTS, которая «Рекомендуется для большинства пользователей».
    4. +
    +
  2. +
  3. Установите Node, дважды щелкнув по загруженному файлу и следуя инструкциям по установке.
  4. +
+ +

Ubuntu 16.04

+ +

Самый простой способ установить последнюю версию LTS Node 6.x - это использовать package manager чтобы получить его из репозитория бинарных дистрибутивов Ubuntu. Это можно сделать очень просто, выполнив следующие две команды на вашем терминале:

+ +
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
+sudo apt-get install -y nodejs
+
+ +
+

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

+
+ +
    +
+ +

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

+ +

Самый простой способ проверить, установлен ли этот узел, - это запустить команду «версия» в своем терминале / командной строке и проверить, что возвращается строка версии:

+ +
>node -v
+v8.11.3
+ +

Менеджер пакетов Nodejs NPM также должен быть установлен и может быть протестирован таким же образом:

+ +
>npm -v
+5.8.0
+ +

В качестве немного более захватывающего теста давайте создадим очень простой сервер «чистого узла», который просто печатает «Hello World» в браузере, когда вы посещаете правильный URL в вашем браузере:

+ +
    +
  1. Скопируйте следующий текст в файл с именем hellonode.js. Здесь используются чистые функции Node (ничего из Express) и некоторый синтаксис ES6: + +
    //Загрузка моделя HTTP
    +const http = require("http");
    +const hostname = '127.0.0.1';
    +const port = 3000;
    +
    +//Создание HTTP-сервера
    +const server = http.createServer((req, res) => {
    +
    +  //Установка HTTP-заголовков ответа с HTTP-статусом и Content type
    +  res.statusCode = 200;
    +  res.setHeader('Content-Type', 'text/plain');
    +  res.end('Hello World\n');
    +});
    +
    +//Слушание запросов на порту 3000, и в качестве каллбака функция, которая пишет в лог
    +server.listen(port, hostname, () => {
    +  console.log('Server running at http://${hostname}:${port}/');
    +});
    +
    +
    + +

    Код импортирует модуль «http» и использует его для создания сервера (createServer ()), который прослушивает HTTP-запросы на порту 3000. Затем сценарий выводит на консоль сообщение о том, какой URL-адрес браузера можно использовать для тестирования сервера. Функция createServer () принимает в качестве аргумента функцию обратного вызова, которая будет вызываться при получении HTTP-запроса - она просто возвращает ответ с кодом состояния HTTP 200 («ОК») и простым текстом «Hello World».

    + +
    +

    Замечание: не беспокойтесь, если вы еще не совсем понимаете, что делает этот код! Мы объясним наш код более подробно, как только мы начнем использовать Express!

    +
    +
  2. +
  3. Запустите сервер, перейдя в тот же каталог, что и ваш файл hellonode.js в командной строке, и вызвав узел вместе с именем скрипта, например так: +
    >node hellonode.js
    +Server running at http://127.0.0.1:3000/
    +
    +
  4. +
  5. Перейдите к URL-адресу http://127.0.0.1:3000. Если все работает, браузер должен просто отобразить строку «Hello World».
  6. +
+ +

Использование NPM

+ +

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

+ +
+

Замечание:  С точки зрения Node, Express - это просто еще один пакет, который вам нужно установить с помощью NPM, а затем установить его в своем собственном коде.

+
+ +

Вы можете вручную использовать NPM для получения каждого необходимого пакета отдельно. Обычно мы вместо этого управляем зависимостями, используя простой текстовый файл с именем package.json. В этом файле перечислены все зависимости для конкретного «пакета» JavaScript, включая имя пакета, версию, описание, исходный файл для выполнения, производственные зависимости, зависимости разработки, версии Node, с которыми он может работать, и т. Д. Файл package.json должен содержать все, что нужно NPM для загрузки и запуска вашего приложения (если вы пишете библиотеку многократного использования, вы можете использовать это определение для загрузки пакета в репозиторий npm и сделать его доступным для других пользователей).

+ +

Добавление зависимостей

+ +

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

+ +
+

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

+
+ +
    +
  1. Сначала создайте каталог для вашего нового приложения и перейдите в него: + +
    mkdir myapp
    +cd myapp
    +
  2. +
  3. Используйте команду npm init для создания файла package.json для вашего приложения. Эта команда запрашивает у вас несколько вещей, включая имя и версию вашего приложения, а также имя исходного файла точки входа (по умолчанию это index.js). Сейчас просто примите значения по умолчанию: +
    npm init
    + +

    Если вы отобразите файл package.json (cat package.json), вы увидите принятые по умолчанию значения, заканчивающиеся лицензией.

    + +
    {
    +  "name": "myapp",
    +  "version": "1.0.0",
    +  "description": "",
    +  "main": "index.js",
    +  "scripts": {
    +    "test": "echo \"Error: no test specified\" && exit 1"
    +  },
    +  "author": "",
    +  "license": "ISC"
    +}
    +
    +
  4. +
  5. Теперь установите Express в каталог myapp и сохраните его в списке зависимостей вашего файла package.json
  6. +
  7. +
    npm install express --save
    + +

    Раздел зависимостей вашего package.json теперь появится в конце файла package.json и будет содержать Express.

    + +
    {
    +  "name": "myapp",
    +  "version": "1.0.0",
    +  "description": "",
    +  "main": "index.js",
    +  "scripts": {
    +    "test": "echo \"Error: no test specified\" && exit 1"
    +  },
    +  "author": "",
    +  "license": "ISC",
    +  "dependencies": {
    +    "express": "^4.16.3"
    +  }
    +}
    +
    +
  8. +
  9. Для использования библиотеки вы вызываете функцию require (), как показано ниже в вашем файле index.js. +
    const express = require('express')
    +const app = express();
    +
    +app.get('/', (req, res) => {
    +  res.send('Hello World!')
    +});
    +
    +app.listen(8000, () => {
    +  console.log('Example app listening on port 8000!')
    +});
    +
    + +

    Этот код показывает минимальное веб-приложение Express «HelloWorld». Это импортирует модуль «экспресс» и использует его для создания сервера (приложения), который прослушивает HTTP-запросы на порту 8000 и выводит на консоль сообщение, объясняющее, какой URL-адрес браузера можно использовать для тестирования сервера. Функция app.get () отвечает только на запросы HTTP GET с указанным URL-путем ('/'), в этом случае вызывая функцию для отправки нашего Hello World! сообщение.
    +
    + Создайте файл с именем index.js в корне каталога приложения «myapp» и передайте ему содержимое, показанное выше.

    +
  10. +
  11. Вы можете запустить сервер, вызвав узел с помощью скрипта в командной строке: +
    >node index.js
    +Example app listening on port 8000
    +
    +
  12. +
  13. Перейдите к URL (http://127.0.0.1:8000/) . Если все работает, браузер должен просто отобразить строку «Hello World!».
  14. +
+ +

Зависимости разработки

+ +

Если зависимость используется только во время разработки, вы должны вместо этого сохранить ее как «зависимость разработки» (чтобы пользователям вашего пакета не приходилось устанавливать ее в производстве). Например, чтобы использовать популярный инструмент JavaScript Linting eslint, вы должны вызвать NPM, как показано ниже:

+ +
npm install eslint --save-dev
+ +

Следующая запись будет добавлена в package.json вашего приложения:

+ +
  "devDependencies": {
+    "eslint": "^4.12.1"
+  }
+
+ +
+

Примечание: «Линтеры» - это инструменты, которые выполняют статический анализ программного обеспечения, чтобы распознавать и сообщать о приверженности / несоблюдении некоторого набора лучших практик кодирования.

+
+ +

Запуск задач

+ +

В дополнение к определению и извлечению зависимостей вы также можете определить именованные скрипты в ваших файлах package.json и вызвать NPM, чтобы выполнить их с помощью команды run-script. Этот подход обычно используется для автоматизации выполнения тестов и частей набора инструментов разработки или сборки (например, запуска инструментов для минимизации JavaScript, сжатия изображений, LINT / анализа вашего кода и т. Д.).

+ +
+

Замечание: Для запуска тестов и других внешних инструментов могут также использоваться такие исполнители, как Gulp и Grunt.

+
+ +

Например, чтобы определить скрипт для запуска зависимости разработки eslint, которую мы указали в предыдущем разделе, мы могли бы добавить следующий блок скрипта в наш файл package.json (при условии, что наш источник приложения находится в папке / src / js):

+ +
"scripts": {
+  ...
+  "lint": "eslint src/js"
+  ...
+}
+
+ +

Чтобы пояснить немного подробнее, eslint src / js - это команда, которую мы могли бы ввести в нашем терминале / командной строке, чтобы запустить eslint для файлов JavaScript, содержащихся в каталоге src / js внутри каталога нашего приложения. Включение вышеупомянутого в файл package.json нашего приложения обеспечивает ярлык для этой команды - lint.

+ +

Затем мы сможем запустить eslint с помощью NPM, вызвав:

+ +
npm run-script lint
+# OR (using the alias)
+npm run lint
+
+ +

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

+ +

Установка Express Application Generator

+ +

Инструмент Express Application Generator создает «скелет» приложения Express. Установите генератор, используя NPM, как показано (флаг -g устанавливает инструмент глобально, чтобы вы могли вызывать его из любого места):

+ +
npm install express-generator -g
+ +

Чтобы создать приложение Express с именем «helloworld» с настройками по умолчанию, перейдите туда, где вы хотите его создать, и запустите приложение, как показано ниже:

+ +
express helloworld
+ +
+

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

+ +
express --help
+
+
+ +

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

+ +
+

Новое приложение будет иметь файл package.json в своем корневом каталоге. Вы можете открыть это, чтобы увидеть, какие зависимости установлены, включая Express и библиотеку шаблонов Jade:

+ +
{
+  "name": "helloworld",
+  "version": "0.0.0",
+  "private": true,
+  "scripts": {
+    "start": "node ./bin/www"
+  },
+  "dependencies": {
+    "body-parser": "~1.18.2",
+    "cookie-parser": "~1.4.3",
+    "debug": "~2.6.9",
+    "express": "~4.15.5",
+    "jade": "~1.11.0",
+    "morgan": "~1.9.0",
+    "serve-favicon": "~2.4.5"
+  }
+}
+
+ +

Установите все зависимости для приложения helloworld, используя NPM, как показано ниже:

+ +
cd helloworld
+npm install
+
+ +

Затем запустите приложение (команды немного отличаются для Windows и Linux / macOS), как показано ниже:

+ +
# Run the helloworld on Windows with Command Prompt
+SET DEBUG=helloworld:* & npm start
+
+# Run the helloworld on Windows with PowerShell
+SET DEBUG=helloworld:* | npm start
+
+# Run helloworld on Linux/macOS
+DEBUG=helloworld:* npm start
+
+ +

Команда DEBUG создает полезное ведение журнала, что приводит к выводу, подобному показанному ниже.

+ +
>SET DEBUG=helloworld:* & npm start
+
+> helloworld@0.0.0 start D:\Github\expresstests\helloworld
+> node ./bin/www
+
+  helloworld:server Listening on port 3000 +0ms
+ +

Откройте браузер и перейдите по адресу http://127.0.0.1:3000/, чтобы увидеть страницу приветствия Express по умолчанию.

+ +

Express - Generated App Default Screen

+ +

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

+ + + +

Резюме

+ +

Теперь на вашем компьютере установлена и запущена среда разработки Node, которую можно использовать для создания веб-приложений Express. Вы также увидели, как NPM можно использовать для импорта Express в приложение, а также как вы можете создавать приложения с помощью инструмента Express Application Generator и затем запускать их.
+
+ В следующей статье мы начнем работу с учебным пособием по созданию полноценного веб-приложения с использованием этой среды и связанных инструментов.

+ +

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

+ + + +

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

+ +

В этом модуле

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html new file mode 100644 index 0000000000..2e1edbc625 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html @@ -0,0 +1,85 @@ +--- +title: Список авторов. Тест - список жанров +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +--- +

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

+ +

Контроллер

+ +

Функция контроллера списка авторов должна получить список всех элементов в  Author , и передать этот список в шаблон для отображения.

+ +

Откройте файл /controllers/authorController.js. Найдите экспортируемый метод author_list() в начале файла и замените его следующим ниже кодом:

+ +
// Display list of all Authors.
+exports.author_list = function(req, res, next) {
+
+  Author.find()
+    .sort([['family_name', 'ascending']])
+    .exec(function (err, list_authors) {
+      if (err) { return next(err); }
+      //Successful, so render
+      res.render('author_list', { title: 'Author List', author_list: list_authors });
+    });
+
+};
+ +

Метод использует такие функции модели как find(), sort() и exec() для того, чтобы вернуть все объекты  Author отсортированными по  family_name в алфавитном порядке. В вызове exec() callback-функция имеет первый параметр- объект ошибок  (или null) и второй параметр - список всех авторов, если ошибок не было. При ошибках вызывается следующая функция промежуточного слоя  с полученным значением объекта ошибок, а если ошибок не было, отображается шаблон author_list(.pug), передавая странице title и список авторов (author_list).

+ +

Представление

+ +

Создайте файл /views/author_list.pug и поместите в него следующий текст:

+ +
extends layout
+
+block content
+  h1= title
+
+  ul
+    each author in author_list
+      li
+        a(href=author.url) #{author.name}
+        |  (#{author.date_of_birth} - #{author.date_of_death})
+
+    else
+      li There are no authors.
+ +

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

+ +

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

+ +

Запустите приложение и откройте браузер с адресом http://localhost:3000/. Выберите ссылку All authors. Если все было сделано правильно, страница должна выглядеть примерно нак, как на следующем скриншоте.

+ +

Author List Page - Express Local Library site

+ +
+

Заметка: Представление дат продолжительности жизни автора выгядит безобразно! Это можно исправить, если использовать тот же подход , который применялся для списка BookInstance  (добавить в модель Author виртуальное свойство  продолжительности жизни).  Но в этот раз, однако, некоторые даты могут отсутствовать, и ссылки на несуществующие свойства игнорируются, если не задан строгий режим.  Метод moment() возврашает текущее время, и нежелательно, чтобы отсутствующие  даты форматировались как "сегодня". Один из способов состоит в том, чтобы форматирующая функция возвращала пустую строку, если дата не существует. Например:

+ +

return this.date_of_birth ? moment(this.date_of_birth).format('YYYY-MM-DD') : '';

+
+ +

Тест - страница списка жанров!Edit

+ +

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

+ +

Genre List - Express Local Library site

+ +

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

+ +
    +
  1. Следует отредактировать genre_list() в файле  /controllers/genreController.js
  2. +
  3. Реализация почти такая же, как и для контроллера author_list() . +
      +
    • Sort the results by name, in ascending order.
    • +
    +
  4. +
  5. Отображающий шаблон должен быть назван genre_list.pug.
  6. +
  7. Шаблону для отображения должны быть переданы переменные title (строка 'Genre List') и genre_list (the list of список жанров, который вернет callback-функция Genre.find().
  8. +
  9. Представление должно соответствовать скриншоту, приведенному ранее (оно должно иметь структуру и формат, похожие на таковые в представлении списка авторов, за исключением, конечно, продолжительности жизни, так как для жанров даты не заданы).
  10. +
+ +

Далее

+ +

Вернуться к части 5 -  Express Tutorial Part 5: Displaying library data.

+ +

Перейти к следующему подразделу в части 5: подробная информация о жанрах (Genre detail page).

diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html new file mode 100644 index 0000000000..b5a4400d90 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html @@ -0,0 +1,68 @@ +--- +title: Страница списка книг +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +--- +

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

+ +

Контроллер

+ +

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

+ +

Откройте файл /controllers/bookController.js. Найдите экспортируемый метод контроллера  book_list() и замените его следующим кодом.

+ +
// Display list of all Books.
+exports.book_list = function(req, res, next) {
+
+  Book.find({}, 'title author')
+    .populate('author')
+    .exec(function (err, list_books) {
+      if (err) { return next(err); }
+      //Successful, so render
+      res.render('book_list', { title: 'Book List', book_list: list_books });
+    });
+
+};
+ +

Метод использует функцию моделиfind() для возврата всех объектов Book, выбрав для возврата только заголовок и автора, поскольку нам не нужны другие поля (он также вернет _id и виртуальные поля). Здесь мы также вызываем populate() on Book, указывая поле  author —это заменит сохраненный идентификатор автора книги полными сведениями об авторе.

+ +

При успешном выполнении, обратный вызов передаст запрос на отрисовку шаблона book_list(.pug), передаст title иbook_list (список книг с автором) в качестве переменных.

+ +

Представление

+ +

Создайте файл /views/book_list.pug и скопируйте в него текст ниже.

+ +
extends layout
+
+block content
+  h1= title
+
+  ul
+  each book in book_list
+    li
+      a(href=book.url) #{book.title}
+      |  (#{book.author.name})
+
+  else
+    li There are no books.
+ +

View расширит базовый шаблон layout.pug и переопределит block с именем 'content'. Он отображает  title который мы передали из контроллера (с помощью метода render() ), а затем перебирает переменную book_list  используя синтаксис each-in-else . Для каждой книги создается элемент списка, отображающий название книги в виде ссылки на страницу сведений о книге, за которой следует имя автора. Если в  book_list нет книг,  то выполняется else, и  отображается текст "нет книг".'

+ +
+

Заметка: Мы используем book.url  для предоставления ссылки на подробную запись для каждой книги (мы реализовали этот маршрут, но не страницу). Это виртуальное свойство модели Book , которая использует поле  _id для создания уникального URL.

+
+ +

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

+ +

На что это похоже?

+ +

Запустите приложение (смотрите тестирование маршрутов для соответствующей команды) и откройте ваш браузер по адресу:  http://localhost:3000/. Затем выберите ссылку:  All books. Если все сделано корректно, то вы должны увидеть нечто подобное скриншоту ниже.

+ +

Book List Page - Express Local Library site

+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html new file mode 100644 index 0000000000..512e78d040 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html @@ -0,0 +1,69 @@ +--- +title: Список экземпляров книг +slug: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +--- +

Далее мы реализуем список всех имеющихся в библиотеке копий книги (BookInstance) . Эта страница должна включать название книги из Book,  с которой связаны экземпляры BookInstance (linked to its detail page), а такжде дополнительнцю информацию, имеющуюся в модели BookInstance, включая статус, издание, и уникальный идентификатор каждой копии. Уникальное значение идентификатора копии должно быть связано со страницей детальной информации BookInstance.

+ +

Контроллер

+ +

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

+ +

Откройте файл /controllers/bookinstanceController.js. Найдите экспортируемый метод bookinstance_list() контроллера и замените его следующим кодом (измененный код выделен жирным).

+ +
// Display list of all BookInstances.
+exports.bookinstance_list = function(req, res, next) {
+
+  BookInstance.find()
+    .populate('book')
+    .exec(function (err, list_bookinstances) {
+      if (err) { return next(err); }
+      // Successful, so render
+      res.render('bookinstance_list', { title: 'Book Instance List', bookinstance_list: list_bookinstances });
+    });
+
+};
+ +

Чтобы вернуть все объекты BookInstance, метод использует функцию find() модели. Далее в цепочке вызывается метод populate() с аргументом - полем book, что приводит к замене идентификатора id, хранящегося для каждого экземпляра BookInstance полным документом Book.

+ +

При удаче, callback-функция, переданная запросу, заполняет шаблон bookinstance_list(.pug), передав переменные title и bookinstance_list.

+ +

Представление

+ +

Создайте файл  /views/bookinstance_list.pug и скопируйте в него текст, приведенный ниже.

+ +
extends layout
+
+block content
+  h1= title
+
+  ul
+    each val in bookinstance_list
+      li
+        a(href=val.url) #{val.book.title} : #{val.imprint} -
+        if val.status=='Available'
+          span.text-success #{val.status}
+        else if val.status=='Maintenance'
+          span.text-danger #{val.status}
+        else
+          span.text-warning #{val.status}
+        if val.status!='Available'
+          span  (Due: #{val.due_back} )
+
+    else
+      li There are no book copies in this library.
+ +

This view is much the same as all the others. It extends the layout, replacing the content block, displays the title passed in from the controller, and iterates through all the book copies in bookinstance_list. For each copy we display its status (colour coded) and if the book is not available, its expected return date. One new feature is introduced—we can use dot notation after a tag to assign a class. So span.text-success will be compiled to <span class="text-success"> (and might also be written in Pug as span(class="text-success").

+ +

What does it look like?

+ +

Run the application, open your browser to http://localhost:3000/, then select the All book-instances link. If everything is set up correctly, your site should look something like the following screenshot.

+ +

BookInstance List Page - Express Local Library site

+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html new file mode 100644 index 0000000000..58f297ce95 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html @@ -0,0 +1,60 @@ +--- +title: Форматирование даты при помощи moment +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +--- +

По умолчанию отображение дат наших моделей некрасиво: Tue Dec 06 2016 15:49:58 GMT+1100 (AUS Eastern Daylight Time). В этом разделе мы покажем, как можно обновить страницу списка BookInstance List из предыдущего раздела, чтобы представитьполе due_date  в более удобном формате: December 6th, 2016. 

+ +

Подход, который будет использован, состоит в  создании виртуального свойства в модели BookInstance,  которое будет возращать отформатированную дату. Форматирование будет производиться с использованием moment, легковесной библиотеки JavaScript для разбора, проверки, изменения и форматирования дат.

+ +
+

Заметка: Можно применять moment для форматирования непосредственно в шаблонах Pug, а можно отформатировать строку в других местах. Использование виртуального свойства позволяет получить дату, отформатированную точно так же, как при помощи due_date

+
+ +

Установка moment

+ +

Ведите следующую команду в корне проекта:

+ +
npm install moment
+ +

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

+ +
    +
  1. Откройте файл ./models/bookinstance.js.
  2. +
  3. В начало файла введите строку для импортирования moment. +
    var moment = require('moment');
    +
  4. +
+ +

Добавьте виртуальное свойство due_back_formatted сразу после свойства url.

+ +
BookInstanceSchema
+.virtual('due_back_formatted')
+.get(function () {
+  return moment(this.due_back).format('MMMM Do, YYYY');
+});
+ +
+

Заметка: Метод format method может вывести дату почти по любому образцу. Синтаксис для представления различных составляющих даты можно найти в документации ( moment documentation).

+
+ +

Обновляем представление

+ +

Откройте файл /views/bookinstance_list.pug и замените due_back на due_back_formatted.

+ +
      if val.status!='Available'
+        //span  (Due: #{val.due_back} )
+        span  (Due: #{val.due_back_formatted} )       
+ +

Вот и все. Если вы перейдете к  All book-instances в боковом меню, вы должны увидеть все даты  в привлекательном формате!

+ +

 

+ +

Далее

+ + + +

 

diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html new file mode 100644 index 0000000000..32100db740 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html @@ -0,0 +1,138 @@ +--- +title: Асинхронное управление потоками при помощи async +slug: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +tags: + - Node + - Часть 5 +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +--- +

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

+ +
+

Note:  В JavaScript существует много других способов управления аснхронным поведением и потоком выполнения, включая такой относительно  новый элемент языка JacaScript как Promises (обещания, промисы).

+
+ +

Модуль Async имеет массу полезных методов (см. документациюt the documentation). Вот некоторые наиболее важные функции:

+ + + +

Почему это необходимо?

+ +

Большинство методов, которые используются в  Express - асинхронные - вы определяете выполняемую операцию, передавая  callback-функцию. Метод завершается немедленно, а  callback-функция вызывается тогда, когда завершилась запрошенная операция. По соглашению, принятому в Express, callback-функция передает значение ошибки error  как первый параметр (или null при успехе) и результат функции (если есть) как второй параметр.

+ +

Если контроллер должен выполнить только одну асинхронную операцию, чтобы получить информацию для представления страницы, то реализация проста - мы просто представляем шаблон в колбэке. Фрагмент кода (ниже) демонстрирует это для функции, которая подсчитывает количество элементов модкли SomeModel (применяя метод Mongoose count() ):

+ +
exports.some_model_count = function(req, res, next) {
+
+  SomeModel.count({ a_model_field: 'match_value' }, function (err, count) {
+    // ... сделать что-то, если ошибка
+
+    // При успехе представить результат, передав count в render-функцию (здесь - как переменную 'data').
+    res.render('the_template', { data: count } );
+  });
+}
+
+ +

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

+ +

Намного лучше было бы выполнять все запросы параллельно, и иметь единственную callback-функцию, которая будет вызвана после того как все запросы выполнены. Именно такое выполнение операций модуль Async делает легким и простым!

+ +

Параллельные асинхронные операции

+ +

Метод async.parallel() используется для параллельного выполнения нескольких асинхронных операций.

+ +

Первый аргумент в async.parallel() - это коллекция асинхронных функций, которые требуется выполнить (массив, объект или другой итерируемый элемент). Каждая функция получает callback-функцию callback(err, result) , которую она должна вызвать при завершении, с ошибкой err (может быть null) и, возможно, со значением результата results.

+ +

Возможный второй аргумент для  async.parallel() - это callback -функция, которая должна быть вызвана после завершения всех функций, указанных в первом аргументе. Эта функция вызывается с аргументом ошибки и результатом - коллекцией результатов отдельных асинхронных операций. Тип коллекции - такой же, как и тип первого аргумента async.parallel (т.е. если передается массив асинхронных функций, итоговая callback-функция будет вызвана с массивом результатов). Если любая из параллельных функций сообщила об ошибке, сразу вызывается итоговая callback-функция, которая возвращает ошибку.

+ +

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

+ +
async.parallel({
+  one: function(callback) { ... },
+  two: function(callback) { ... },
+  ...
+  something_else: function(callback) { ... }
+  },
+  // optional callback
+  function(err, results) {
+    // 'results' равны: {one: 1, two: 2, ..., something_else: some_value}
+  }
+);
+ +

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

+ +

Последовательные асинхронные операции

+ +

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

+ +
async.series({
+  one: function(callback) { ... },
+  two: function(callback) { ... },
+  ...
+  something_else: function(callback) { ... }
+  },
+  // optional callback after the last asynchronous function completes.
+  function(err, results) {
+    // 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} 
+  }
+);
+ +
+

Заметка: Спецификация языка ECMAScript (JavaScript) устанавливает, что порядок  в перечислении объектов не определен, поэтому возможно, что функции не будут вызываться в том порядке, в котором вы их задали на всех платформах. Если порядок вызова действительно важен, вместо объекта следует передавать массив, как показано ниже.

+
+ +
async.series([
+  function(callback) {
+    // do some stuff ...
+    callback(null, 'one');
+  },
+  function(callback) {
+    // do some more stuff ... 
+    callback(null, 'two');
+  }
+ ],
+  // optional callback
+  function(err, results) {
+  // results is now equal to ['one', 'two'] 
+  }
+);
+ +

Последовательные зависимые асинхронные операции

+ +

Выполнение нескольких асинхронных операций последовательно, когда каждая операция зависит от результатов предыдущих операций, осуществляется методом async.waterfall().

+ +

Функции-callback, которая вызываются асинхронными функциями , содержит null как первый аргумент, и результаты в следующих аргументах. Каждая функция в последовательности (кроме первой) как аргументы использует результаты предыдущих функция, а callback-функция является последним аргументом. Когда  операции завершаются, вызывается финальная callback-функция, аргументы которой - объект err и результат последней операции. Как это работает, станет более ясным после рассмотрения примера - фрагмента кода, приведенного ниже ( пример взят из документации async):

+ +
async.waterfall([
+  function(callback) {//первая функция в цепочке
+    callback(null, 'one', 'two');//результаты 'one' и 'two'
+  },
+  function(arg1, arg2, callback) {//вторая функция в цепочке
+    // arg1 равен 'one' , arg2 равен  'two' 
+    callback(null, 'three'); //результат 'three'
+  },
+  function(arg1, callback) {
+    // arg1 равен 'three'
+    callback(null, 'done'); //результат 'done'
+  }
+], function (err, result) {
+  // result равен 'done'
+}
+);
+ +

Установка async

+ +

Установим модуль async при помощи менеджера пакетов NPM, чтобы использовать его в своем коде. Это делается обычным способом - откроем окно команд в корне проекта LocalLibrary и введем команду:

+ +
npm install async
+ +

Дальнейшие шаги

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html new file mode 100644 index 0000000000..be5bd57962 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html @@ -0,0 +1,121 @@ +--- +title: Страница с подробностями жанров +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +--- +

Страница "подробности" (detail) для жанров должна показывать информацию для отдельного жанра по его автоматически генерируему идентификатору _id. Должно быть показано название жанра и список книг этого жанра, со ссылками на страницу с детальной информацией для каждой книги.

+ +

Controller

+ +

Откройте файл /controllers/genreController.js и импортируйте модули async и Book  в первых строках файла.

+ +
var Book = require('../models/book');
+var async = require('async');
+
+ +

Найдите экспортируемый метод контроллера genre_detail() и замените его следующим кодом:

+ +
// Display detail page for a specific Genre.
+exports.genre_detail = function(req, res, next) {
+
+    async.parallel({
+        genre: function(callback) {
+            Genre.findById(req.params.id)
+              .exec(callback);
+        },
+
+        genre_books: function(callback) {
+          Book.find({ 'genre': req.params.id })
+          .exec(callback);
+        },
+
+    }, function(err, results) {
+        if (err) { return next(err); }
+        if (results.genre==null) { // No results.
+            var err = new Error('Genre not found');
+            err.status = 404;
+            return next(err);
+        }
+        // Successful, so render
+        res.render('genre_detail', { title: 'Genre Detail', genre: results.genre, genre_books: results.genre_books } );
+    });
+
+};
+
+ +

Метод использует async.parallel() для параллельного запроса названия жанра и связанных с ним книг, причем callback-функция возвращает страницу, когда (если) оба запроса завершились успешно.

+ +

The ID of the required genre record is encoded at the end of the URL and extracted automatically based on the route definition (/genre/:id). The ID is accessed within the controller via the request parameters: req.params.id. It is used in Genre.findById() to get the current genre. It is also used to get all Book objects that have the genre ID in their genre field: Book.find({ 'genre': req.params.id }).

+ +
+

Note: If the genre does not exist in the database (i.e. it may have been deleted) then findById()  will return successfully with no results. In this case we want to display a "not found" page, so we create an Error object and pass it to the next middleware function in the chain. 

+ +
if (results.genre==null) { // No results.
+    var err = new Error('Genre not found');
+    err.status = 404;
+    return next(err);
+}
+
+ +

The message will then propagate through to our error handling code (this was set up when we generated the app skeleton - for more information see Handling Errors).

+
+ +

The rendered view is genre_detail and it is passed variables for the title, genre and the list of books in this genre (genre_books).

+ +

View

+ +

Create /views/genre_detail.pug and fill it with the text below:

+ +
extends layout
+
+block content
+
+  h1 Genre: #{genre.name}
+
+  div(style='margin-left:20px;margin-top:20px')
+
+    h4 Books
+
+    dl
+      each book in genre_books
+        dt
+          a(href=book.url) #{book.title}
+        dd #{book.summary}
+
+      else
+        p This genre has no books
+
+ +

The view is very similar to all our other templates. The main difference is that we don't use the title passed in for the first heading (though it is used in the underlying layout.pug template to set the page title).

+ +

What does it look like?

+ +

Run the application and open your browser to http://localhost:3000/. Select the All genres link, then select one of the genres (e.g. "Fantasy"). If everything is set up correctly, your page should look something like the following screenshot.

+ +

Genre Detail Page - Express Local Library site

+ +
+

You might get an error similar to this:

+ +
Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre"
+
+ +

This is a mongoose error coming from the req.params.id. To solve this problem, first you need to require mongoose on the genreController.js page like this:

+ +
 var mongoose = require('mongoose');
+
+ +

Then use mongoose.Types.ObjectId() to convert the id to a that can be used. For example:

+ +
exports.genre_detail = function(req, res, next) {
+    var id = mongoose.Types.ObjectId(req.params.id);
+    ...
+
+
+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html new file mode 100644 index 0000000000..248187f1a5 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/home_page/index.html @@ -0,0 +1,134 @@ +--- +title: Home page +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +--- +

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

+ +

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

+ +

Маршрут

+ +

Маршруты индексной страницы созданы ранее в предыдущем разделе (previous tutorial). Напомним, все функции маршрутов определены в файле /routes/catalog.js:

+ +
// GET catalog home page.
+router.get('/', book_controller.index);  //This actually maps to /catalog/ because we import the route with a /catalog prefix
+ +

Параметр callback-функции определен в /controllers/bookController.js:

+ +
exports.index = function(req, res, next) {
+    res.send('NOT IMPLEMENTED: Site Home Page');
+}
+ +

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

+ +

Контроллер

+ +

Функция контроллера индекса должна получать информацию о том, сколько книг (Book), экземпляров книг (BookInstance), сколько из них доступно, сколько авторов (Author), жанров (Genre) имеется в БД, должна поместить эту информацию в шаблон, чтобы создать  HTML-страницу, после чего вернуть ее в  HTTP-ответе.

+ +
+

Заметка: Количество экземпляров в каждой модели вычисляется при помощи метода countDocuments() . Он вызывается для модели с возможным набором условий, необходимых для проверки соответствия первому аргументу и callback-функции второго аргумента (обсуждалось ранее в "Использование базы данных с Mongoose" Using a Database (with Mongoose)), причем можно вернуть также запрос Query, а затем выполнить его позже при помощи callback. Эта  callback-функция будет выполняться, когда БД вернет количество записей.  Значение ошибки (or null) будет первым параметром, а количество записей (или null, если была ошибка) -  вторым параметром.

+ +
SomeModel.countDocuments({ a_model_field: 'match_value' }, function (err, count) {
+ // ... do something if there is an err
+ // ... do something with the count if there was no error
+ });
+
+ +

Откройте файл /controllers/bookController.js. Почти в самом начале вы должны увидеть экспортируемую функцию index() .

+ +
var Book = require('../models/book')
+
+exports.index = function(req, res, next) {
+ res.send('NOT IMPLEMENTED: Site Home Page');
+}
+ +

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

+ +
var Book = require('../models/book');
+var Author = require('../models/author');
+var Genre = require('../models/genre');
+var BookInstance = require('../models/bookinstance');
+
+var async = require('async');
+
+exports.index = function(req, res) {
+
+    async.parallel({
+        book_count: function(callback) {
+            Book.countDocuments({}, callback); // Pass an empty object as match condition to find all documents of this collection
+// countDocuments не работает, работает только просто count
+        },
+        book_instance_count: function(callback) {
+            BookInstance.countDocuments({}, callback);
+        },
+        book_instance_available_count: function(callback) {
+            BookInstance.countDocuments({status:'Available'}, callback);
+        },
+        author_count: function(callback) {
+            Author.countDocuments({}, callback);
+        },
+        genre_count: function(callback) {
+            Genre.countDocuments({}, callback);
+        }
+    }, function(err, results) {
+        res.render('index', { title: 'Local Library Home', error: err, data: results });
+    });
+};
+ +

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

+ +

При успешном завершении callback-функции она вызывает res.render(), у которой в качестве параметров - представление (шаблон)  'index' и объект, содержащий данные, которые следует поместить в шаблон (среди них - количества элементов в моделях). Данные представлены как пары ключ-значение, и могут быть получены в шаблоне по ключу.

+ +
+

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

+
+ +

Представление

+ +

Откройте файл  /views/index.pug и замените его содержимое текстом, приведенным ниже

+ +
extends layout
+
+block content
+  h1= title
+  p Welcome to #[em LocalLibrary], a very basic Express website developed as a tutorial example on the Mozilla Developer Network.
+
+  h1 Dynamic content
+
+  if error
+    p Error getting dynamic content.
+  else
+    p The library has the following record counts:
+
+    ul
+      li #[strong Books:] !{data.book_count}
+      li #[strong Copies:] !{data.book_instance_count}
+      li #[strong Copies available:] !{data.book_instance_available_count}
+      li #[strong Authors:] !{data.author_count}
+      li #[strong Genres:] !{data.genre_count}
+ +

Представление несложное. Мы расширили базовый шаблон  layout.pug, переопределив блок (block) с именем 'content'. Первый заголовок h1 будет экранированным текстом - значением переменной title ,variable that  которая передается в функцию render() —заметьте, что применение 'h1='  говорит, что следующий текст рассматривается как выражение JavaScript. Затем расположен параграф, знакомящий с  LocalLibrary.

+ +

Под заголовком Dynamic content  мы проверяем, определена ли переданная из функции render() переменная error. Если да, отмечаем ошибку. Если нет, выводим ( как список) количества копий каждой модели, которые хранятся в переменной data.

+ +
+

Заметка:  Мы не экранируем количества элементов (т.е. используется синтаксис !{} ) потому что эти значения вычисляются. Если бы информация предоставлялась конечным пользователем, следовало бы экранировать переменную перед выводом.

+
+ +

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

+ +

Сейчас у нас есть все для того, чтобы показать страницу index. Запустите приложение и откройте браузер с адресом http://localhost:3000/. Если все задано правильно, ваш сайт должен иметь примерно такой вид, как на приведенном снимке экрана.

+ +

Home page - Express Local Library site

+ +
+

Заметка:  Элементы бокового меню использовать еще нельзя, так как адреса, представления и шаблоны для этих страниц еще не определены. Если вы попытаетесь их использовать, будет выведено сообщение об ошибке, например,  вида "NOT IMPLEMENTED: Book list" (НЕ РЕАЛИЗОВАНО: список книг), в зависимости от выбранного элемента меню.  Эти строковые литералы (которые будут замещены действительными данными) были заданы в различных файлах контроллеров в каталоге "controllers".

+
+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/index.html new file mode 100644 index 0000000000..bb2e804d2e --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/index.html @@ -0,0 +1,83 @@ +--- +title: 'Учебник Express часть 5: Отображение данных библиотеки' +slug: Learn/Server-side/Express_Nodejs/Displaying_data +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}
+ +

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

+ + + + + + + + + + + + +
Предварительные знания:Завершите изучение предыдущих тем учебника (включая Учебник Express часть 4: Маршруты и контроллеры).
Цель:Понять, как использовать асинхронный модуль и язык шаблона Pug, и как получить данные из URL в наших функциях контроллера.
+ +

Обзор

+ +

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

+ +

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

+ +

Мы начнем с обзорных / основных тем, объясняющих, как управлять асинхронными операциями в функциях контроллера и как писать шаблоны с помощью Pug. Затем мы предоставим реализации для каждой из наших основных страниц" только для чтения " с кратким объяснением любых специальных или новых функций, которые они используют.

+ +

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

+ +

Отображение данных библиотеки — подразделы

+ +

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

+ +
    +
  1. Aсинхронное управление потоками с помощью async
  2. +
  3. Пример шаблона
  4. +
  5. Базовые шаблоны LocalLibrary
  6. +
  7. Домашняя страница
  8. +
  9. Страница списка книг
  10. +
  11. Страница списка экземпляров книг
  12. +
  13. Форматирование даты с момента использования
  14. +
  15. Страница списка авторов и страница списка жанров
  16. +
  17. Страница сведений о жанре
  18. +
  19. Страница сведений о книге
  20. +
  21. Страница информации об авторе
  22. +
  23. Страница сведений об экземпляре книги и вызове
  24. +
+ +

Итог

+ +

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

+ +

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

+ +

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

+ + + +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}

+ +

In this module

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html new file mode 100644 index 0000000000..13c61ea6cb --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html @@ -0,0 +1,69 @@ +--- +title: Базовый шаблон LocalLibrary +slug: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +--- +

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

+ +

Откройте файл /views/layout.pug и замените его содержимое следующим.

+ +
doctype html
+html(lang='en')
+  head
+    title= title
+    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(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js')
+    link(rel='stylesheet', href='/stylesheets/style.css')
+  body
+    div(class='container-fluid')
+      div(class='row')
+        div(class='col-sm-2')
+          block sidebar
+            ul(class='sidebar-nav')
+              li
+                a(href='/catalog') Home
+              li
+                a(href='/catalog/books') All books
+              li
+                a(href='/catalog/authors') All authors
+              li
+                a(href='/catalog/genres') All genres
+              li
+                a(href='/catalog/bookinstances') All book-instances
+              li
+                hr
+              li
+                a(href='/catalog/author/create') Create new author
+              li
+                a(href='/catalog/genre/create') Create new genre
+              li
+                a(href='/catalog/book/create') Create new book
+              li
+                a(href='/catalog/bookinstance/create') Create new book instance (copy)
+
+        div(class='col-sm-10')
+          block content
+ +

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

+ +

Макет представляется достаточно очевидным, если Вы уже прочли статью Основы шаблонов (Template primer) выше. Обратите внимание на использование block content в качестве места для  размещения контента отдельных страниц.

+ +

Базовый шаблон также ссылается на локальный файл стилей (style.css), что обеспечивает дополнительное управление внешним видом. Откройте /public/stylesheets/style.css и замените его содержимое таким текстом:

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

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

+ +

Следующие шаги

+ + diff --git a/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html b/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html new file mode 100644 index 0000000000..3f537db354 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/displaying_data/template_primer/index.html @@ -0,0 +1,149 @@ +--- +title: Основы шаблонов +slug: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +--- +

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

+ +

Выбор шаблонов Express

+ +

В Express можно использовать много движков отображающих шаблонов ( template rendering engines). В этом руководстве для шаблонов будет использован Pug (ранее известный как Jade) . Это наиболее популярный в  Node язык шаблонов, который о себе заявляет так: чистый, чувствительный к пробелам синтаксис для написания HTML, на который сильно повлиял Haml.

+ +

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

+ +
+

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

+
+ +

Конфигурация шаблона

+ +

Когда создавался каркас (the skeleton website) веб-сайта LocalLibrary, он был настроен  на использование Pug . Можно было заметить, что модуль pug включен в зависимости в файле package.json, и установлен (app.set(...)) как движок представлений в файле app.js. Эта установка показывает,, что движок представлений -  pug, и что  Express должен искать шаблоны в подкаталоге /views.

+ +
// View engine setup.
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'pug');
+ +

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

+ +
/express-locallibrary-tutorial  //the project root
+  /views
+    error.pug
+    index.pug
+    layout.pug
+
+ +

Синтаксис шаблонов

+ +

Пример файла шаблона (ниже) демонстрирует многие наиболее полезные черты  Pug.

+ +

Сначала отметим, что файл отражает структуру типового HTML-файла, причем первое слов в (почти) каждой строке является элементом HTML, а отступы используются, чтобы показать вложенные элементы. Так, например, элемент body находится внутри элемента html, а элементы p  (параграфы) - внутри элемента body, и так далее. Невложенные элементы (т.е. индивидуальные параграфы) располагаются в отдельных строках.

+ +
doctype html
+html(lang="en")
+  head
+    title= title
+    script(type='text/javascript').
+  body
+    h1= title
+
+    p This is a line with #[em some emphasis] and #[strong strong text] markup.
+    p This line has un-escaped data: !{'<em> is emphasised</em>'} and escaped data: #{'<em> is not emphasised</em>'}.
+      | This line follows on.
+    p= 'Evaluated and <em>escaped expression</em>:' + title
+
+    <!-- You can add HTML comments directly -->
+    // You can add single line JavaScript comments and they are generated to HTML comments
+    //- Introducing a single line JavaScript comment with "//-" ensures the comment isn't rendered to HTML
+
+    p A line with a link
+      a(href='/catalog/authors') Some link text
+      |  and some extra text.
+
+    #container.col
+      if title
+        p A variable named "title" exists.
+      else
+        p A variable named "title" does not exist.
+      p.
+        Pug is a terse and simple template language with a
+        strong focus on performance and powerful features.
+
+    h2 Generate a list
+
+    ul
+      each val in [1, 2, 3, 4, 5]
+        li= val
+ +

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

+ + + +

Значения всех атрибутов экранируются (т.е. такие символы как ">" заменяются эквивалентными кодами HTML как "&gt;") , чтобы предотвратить JavaScript инъекции и межсайтовые атаки.

+ +

Если после тэга стоит знак = , следующий текст рассматривается как выражение JavaScript. Например, шиже в первой строке, содержимое тэга h1 будет переменной  title (которая определена в файле или передана в шаблон из Express). Во второй строке содержимое параграфа - это текстовая строка, соединенная с переменной  title . В каждом из случаев поведение по умолчанию - экранировать строки.

+ +
h1= title
+p= 'Evaluated and <em>escaped expression</em>:' + title
+ +

Если после тэга знак = отсутствует, тогда содержимое рассматривается как обычный текст. Внутри текста можно вставить экранированные или неэкранированные данные, применяя синтаксис  #{} и !{}, как показано ниже. В простой текст можно также вставлять "сырой" HTML.

+ +
p This is a line with #[em some emphasis] and #[strong strong text] markup.
+p This line has an un-escaped string: !{'<em> is emphasised</em>'}, an escaped string: #{'<em> is not emphasised</em>'}, and escaped variables: #{title}.
+ +
+

Совет: Почти всегда желательно экранировать данные, полученные от пользователей (при помощи синтаксиса #{} ). Данные, которым можно верить (т.е. подсчитанное количество  записей,  могут быть выведены без экранирования значений.

+
+ +

Можно использовать символ конвейера ('|') в начале строки, чтобы отметить простой текст ("plain text"). Например, дополнительный текст, приведенный ниже, будет показан в той же строке, что и предыдущий, но не будет относиться к ссылке.

+ +
a(href='http://someurl/') Link text
+| Plain text
+ +

Pug позволяет выполнять условные операции if, else , else if и unless— пример приведен ниже:

+ +
if title
+  p Переменная с именем "title" существует
+else
+  p Переменной с именем "title" не существует
+ +

Можно также выполнять циклы (итерации), применяя ситаксис each-in или while . Фрагмент кода (ниже)  содержит цикл по элементам массива, чтобы показать список элементов (отметим применение 'li=' для оценки "val" как переменной). Значение итератора val может быть также передано в шаблон как переменная!

+ +
ul
+  each val in [1, 2, 3, 4, 5]
+    li= val
+ +

Синтаксис разрешает также комментарии (которые попадут в результат или нет, по вашему желанию), смеси для создания повторно используемых блоков кода, операторы выбора case, и много другого. Более подробная информация - в документации The Pug docs.

+ +

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

+ +

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

+ +

Например, базовый шаблон layout.pug, созданный в каркасе проекта, имеет такой вид:

+ +
doctype html
+html
+  head
+    title= title
+    link(rel='stylesheet', href='/stylesheets/style.css')
+  body
+    block content
+ +

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

+ +

Умолчание для  index.pug (созданный для каркаса проекта) показывает, как можно заменить базовый шаблон. Тэг extends идентифицирует базовый шаблон, который следует использовать, а затем мы используем  block section_name, чтобы отметить новый контент раздела, который мы заменяем.

+ +
extends layout
+
+block content
+  h1= title
+  p Welcome to #{title}
+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html new file mode 100644 index 0000000000..13f409ea3c --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/create_bookinstance_form/index.html @@ -0,0 +1,151 @@ +--- +title: "Форма для создания\_BookInstance" +slug: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form +--- +

EdiВ этой статье показано, как определить страницу / форму для создания объектов BookInstance. Это очень похоже на форму, которую мы использовали для создания объектов Book.

+ +

Импорт методов проверки и очистки

+ +

Откройте /controllers/bookinstanceController.js и добавьте следующие строки вверху файла:

+ +
const { body,validationResult } = require('express-validator/check');
+const { sanitizeBody } = require('express-validator/filter');
+ +

Controller—get route

+ +

At the top of the file, require the Book module (needed because each BookInstance is associated with a particular Book).

+ +
var Book = require('../models/book');
+ +

Find the exported bookinstance_create_get() controller method and replace it with the following code.

+ +
// Display BookInstance create form on GET.
+exports.bookinstance_create_get = function(req, res, next) {
+
+    Book.find({},'title')
+    .exec(function (err, books) {
+      if (err) { return next(err); }
+      // Successful, so render.
+      res.render('bookinstance_form', {title: 'Create BookInstance', book_list: books});
+    });
+
+};
+ +

The controller gets a list of all books (book_list) and passes it to the view bookinstance_form.pug (along with the title)

+ +

Controller—post route

+ +

Find the exported bookinstance_create_post() controller method and replace it with the following code.

+ +
// Handle BookInstance create on POST.
+exports.bookinstance_create_post = [
+
+    // Validate fields.
+    body('book', 'Book must be specified').isLength({ min: 1 }).trim(),
+    body('imprint', 'Imprint must be specified').isLength({ min: 1 }).trim(),
+    body('due_back', 'Invalid date').optional({ checkFalsy: true }).isISO8601(),
+
+    // Sanitize fields.
+    sanitizeBody('book').escape(),
+    sanitizeBody('imprint').escape(),
+    sanitizeBody('status').trim().escape(),
+    sanitizeBody('due_back').toDate(),
+
+    // Process request after validation and sanitization.
+    (req, res, next) => {
+
+        // Extract the validation errors from a request.
+        const errors = validationResult(req);
+
+        // Create a BookInstance object with escaped and trimmed data.
+        var bookinstance = new BookInstance(
+          { book: req.body.book,
+            imprint: req.body.imprint,
+            status: req.body.status,
+            due_back: req.body.due_back
+           });
+
+        if (!errors.isEmpty()) {
+            // There are errors. Render form again with sanitized values and error messages.
+            Book.find({},'title')
+                .exec(function (err, books) {
+                    if (err) { return next(err); }
+                    // Successful, so render.
+                    res.render('bookinstance_form', { title: 'Create BookInstance', book_list: books, selected_book: bookinstance.book._id , errors: errors.array(), bookinstance: bookinstance });
+            });
+            return;
+        }
+        else {
+            // Data from form is valid.
+            bookinstance.save(function (err) {
+                if (err) { return next(err); }
+                   // Successful - redirect to new record.
+                   res.redirect(bookinstance.url);
+                });
+        }
+    }
+];
+ +

The structure and behaviour of this code is the same as for creating our other objects. First we validate and sanitize the data. If the data is invalid, we then re-display the form along with the data that was originally entered by the user and a list of error messages. If the data is valid, we save the new BookInstance record and redirect the user to the detail page.

+ +

View

+ +

Create /views/bookinstance_form.pug and copy in the text below.

+ +
extends layout
+
+block content
+  h1=title
+
+  form(method='POST' action='')
+    div.form-group
+      label(for='book') Book:
+      select#book.form-control(type='select' placeholder='Select book' name='book' required='true')
+        - book_list.sort(function(a, b) {let textA = a.title.toUpperCase(); let textB = b.title.toUpperCase(); return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;});
+        for book in book_list
+          if bookinstance
+            option(value=book._id selected=(bookinstance.book.toString()==book._id.toString() ? 'selected' : false)) #{book.title}
+          else
+            option(value=book._id) #{book.title}
+
+    div.form-group
+      label(for='imprint') Imprint:
+      input#imprint.form-control(type='text' placeholder='Publisher and date information' name='imprint' required='true' value=(undefined===bookinstance ? '' : bookinstance.imprint))
+    div.form-group
+      label(for='due_back') Date when book available:
+      input#due_back.form-control(type='date' name='due_back' value=(undefined===bookinstance ? '' : bookinstance.due_back))
+
+    div.form-group
+      label(for='status') Status:
+      select#status.form-control(type='select' placeholder='Select status' name='status' required='true')
+        option(value='Maintenance') Maintenance
+        option(value='Available') Available
+        option(value='Loaned') Loaned
+        option(value='Reserved') Reserved
+
+    button.btn.btn-primary(type='submit') Submit
+
+  if errors
+    ul
+      for error in errors
+        li!= error.msg
+ +

The view structure and behaviour is almost the same as for the book_form.pug template, so we won't go over it again.

+ +
+

Note: The above template hard-codes the Status values (Maintenance, Available, etc.) and does not "remember" the user's entered values. Should you so wish, consider reimplementing the list, passing in option data from the controller and setting the selected value when the form is re-displayed.

+
+ +

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

+ +

Запустите приложение и откройте в браузере  http://localhost:3000/. Затем выберите ссылку Create new book instance (copy). Если все настроено правильно, ваш сайт должен выглядеть примерно так, как показано на скриншоте. После того, как вы отправите валидный BookInstance, он должен быть сохранен, и вы попадете на страницу сведений.

+ +

+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html new file mode 100644 index 0000000000..c9407372c4 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/create_genre_form/index.html @@ -0,0 +1,199 @@ +--- +title: Create genre form +slug: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Create_genre_form +--- +

В этом подразделе рассказано, как сделать страницу для создания жанра (Genre). Хорошая точка для старта, у жанра всего одно поле - name. Как и для любой другой страницы, здесь потребуется внести изменения в соответствующие маршрут, контроллер и шаблон (route, controller, view).

+ +

Import validation and sanitisation methods

+ +

To use the express-validator in our controllers we have to require the functions we want to use from the 'express-validator/check' and 'express-validator/filter' modules.

+ +

Open /controllers/genreController.js, and add the following line at the top of the file:

+ +
const validator = require('express-validator');
+
+ +

Controller—get route

+ +

Find the exported genre_create_get() controller method and replace it with the following code. This simply renders the genre_form.pug view, passing a title variable.

+ +
// Display Genre create form on GET.
+exports.genre_create_get = function(req, res, next) {
+  res.render('genre_form', { title: 'Create Genre' });
+};
+ +

Controller—post route

+ +

Find the exported genre_create_post() controller method and replace it with the following code.

+ +
// Handle Genre create on POST.
+exports.genre_create_post =  [
+
+  // Validate that the name field is not empty.
+  validator.body('name', 'Genre name required').trim().isLength({ min: 1 }),
+
+  // Sanitize (escape) the name field.
+  validator.sanitizeBody('name').escape(),
+
+  // Process request after validation and sanitization.
+  (req, res, next) => {
+
+    // Extract the validation errors from a request.
+    const errors = validator.validationResult(req);
+
+    // Create a genre object with escaped and trimmed data.
+    var genre = new Genre(
+      { name: req.body.name }
+    );
+
+
+    if (!errors.isEmpty()) {
+      // There are errors. Render the form again with sanitized values/error messages.
+      res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});
+      return;
+    }
+    else {
+      // Data from form is valid.
+      // Check if Genre with same name already exists.
+      Genre.findOne({ 'name': req.body.name })
+        .exec( function(err, found_genre) {
+           if (err) { return next(err); }
+
+           if (found_genre) {
+             // Genre exists, redirect to its detail page.
+             res.redirect(found_genre.url);
+           }
+           else {
+
+             genre.save(function (err) {
+               if (err) { return next(err); }
+               // Genre saved. Redirect to genre detail page.
+               res.redirect(genre.url);
+             });
+
+           }
+
+         });
+    }
+  }
+];
+ +

The first thing to note is that instead of being a single middleware function (with arguments (req, res, next)) the controller specifies an array of middleware functions. The array is passed to the router function and each method is called in order.

+ +
+

Note: This approach is needed, because the sanitisers/validators are middleware functions.

+
+ +

The first method in the array defines a validator (validator.body()) from the validator  module to check that the name field is not empty (calling trim() to remove any trailing/leading whitespace before performing the validation). The  second method in the array (validator.sanitizeBody()) creates a sanitizer to escape() any dangerous  HTML characters in the name field.

+ +
// Validate that the name field is not empty.
+validator.body('name', 'Genre name required').isLength({ min: 1 }).trim(),
+
+// Sanitize (escape) the name field.
+validator.sanitizeBody('name').escape()
+ +

After specifying the validators and sanitizers we create a middleware function to extract any validation errors. We use isEmpty() to check whether there are any errors in the validation result. If there are then we render the form again, passing in our sanitised genre object and the array of error messages (errors.array()).

+ +
// Process request after validation and sanitization.
+(req, res, next) => {
+
+  // Extract the validation errors from a request.
+  const errors = validator.validationResult(req);
+
+  // Create a genre object with escaped and trimmed data.
+  var genre = new Genre(
+    { name: req.body.name }
+  );
+
+  if (!errors.isEmpty()) {
+    // There are errors. Render the form again with sanitized values/error messages.
+    res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});
+  return;
+  }
+  else {
+    // Data from form is valid.
+    ... <save the result/> ...
+  }
+};
+ +

If the genre name data is valid then we check if a Genre with the same name already exists (as we don't want to create duplicates). If it does, we redirect to the existing genre's detail page. If not, we save the new Genre and redirect to its detail page.

+ +
// Check if Genre with same name already exists.
+Genre.findOne({ 'name': req.body.name })
+  .exec( function(err, found_genre) {
+  if (err) { return next(err); }
+    if (found_genre) {
+      // Genre exists, redirect to its detail page.
+      res.redirect(found_genre.url);
+      }
+    else {
+      genre.save(function (err) {
+        if (err) { return next(err); }
+          // Genre saved. Redirect to genre detail page.
+          res.redirect(genre.url);
+        });
+    }
+});
+ +

This same pattern is used in all our post controllers: we run validators, then sanitisers,  then check for errors and either re-render the form with error information or save the data. 

+ +

View

+ +

The same view is rendered in both the GET and POST controllers/routes when we create a new Genre (and later on it is also used when we updateGenre). In the GET case the form is empty, and we just pass a title variable. In the POST case the user has previously entered invalid data—in the genre variable we pass back a sanitized version of the entered data and in the errors variable we pass back an array of error messages.

+ +
res.render('genre_form', { title: 'Create Genre'});
+res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});
+ +

Create /views/genre_form.pug and copy in the text below.

+ +
extends layout
+
+block content
+  h1 #{title}
+
+  form(method='POST' action='')
+    div.form-group
+      label(for='name') Genre:
+      input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name))
+    button.btn.btn-primary(type='submit') Submit
+
+  if errors
+   ul
+    for error in errors
+     li!= error.msg
+ +

Much of this template will be familiar from our previous tutorials. First, we extend the layout.pug base template and override the block named 'content'. We then have a heading with the title we passed in from the controller (via the render() method).

+ +

Next, we have the pug code for our HTML form that uses the POST method to send the data to the server, and because the action is an empty string, will send the data to the same URL as the page.

+ +

The form defines a single required field of type "text" called "name". The default value of the field depends on whether the genre variable is defined. If called from the GET route it will be empty as this is a new form. If called from a POST route it will contain the (invalid) value originally entered by the user.

+ +

The last part of the page is the error code. This simply prints a list of errors, if the error variable has been defined (in other words, this section will not appear when the template is rendered on the GET route).

+ +
+

Note: This is just one way to render the errors. You can also get the names of the affected fields from the error variable, and use these to control where the error messages are rendered, whether to apply custom CSS, etc.

+
+ +

What does it look like?

+ +

Run the application, open your browser to http://localhost:3000/, then select the Create new genre link. If everything is set up correctly, your site should look something like the following screenshot. After you enter a value, it should be saved and you'll be taken to the genre detail page.

+ +

Genre Create Page - Express Local Library site

+ +

The only error we validate against server-side is that the genre field must not be empty. The screenshot below shows what the error list would look like if you didn't supply a genre (highlighted in red).

+ +

+ +
+

Note: Our validation uses trim() to ensure that whitespace is not accepted as a genre name. We can also validate that the field is not empty on the client side by adding the value required='true' to the field definition in the form:

+ +
input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), required='true' )
+
+ +

Next steps

+ +
    +
  1. Return to Express Tutorial Part 6: Working with forms.
  2. +
  3. Proceed to the next sub article of part 6: Create Author form.
  4. +
diff --git a/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html new file mode 100644 index 0000000000..0e0fa6cdf3 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/delete_author_form/index.html @@ -0,0 +1,165 @@ +--- +title: Delete Author form +slug: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Delete_author_form +--- +

В этой статье показано, как определить страницу для удаления объектов Author.

+ +

Как описано в разделе  form design,  наша стратегия будет заключаться в том, чтобы разрешить удаление только объектов, на которые не ссылаются другие объекты(в этом случае это означает, что мы не позволим Author быть удаленным, если на него ссылается  Book). С точки зрения реализации это означает, что форма должна подтвердить, что нет никаких связанных книг, прежде чем автор будет удален. Если есть связанные книги, то они должны отображаться и быть удалены до того, как будеет удален объект Author.

+ +

Controller—get route

+ +

Откройте /controllers/authorController.js. Найдите экспротируемый метод контроллера  author_delete_get() и замените его на слдеующий код.

+ +
// Отображать форму для удаления автора GET
+exports.author_delete_get = function(req, res, next) {
+
+    async.parallel({
+        author: function(callback) {
+            Author.findById(req.params.id).exec(callback)
+        },
+        authors_books: function(callback) {
+          Book.find({ 'author': req.params.id }).exec(callback)
+        },
+    }, function(err, results) {
+        if (err) { return next(err); }
+        if (results.author==null) { // No results.
+            res.redirect('/catalog/authors');
+        }
+        // Удачно, значит рендерим.
+        res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } );
+    });
+
+};
+ +

TКонтроллер получает id экземпляра Author для удаления из параметра URL  (req.params.id). Он использует метод  async.parallel() , чтобы получить запись автра и паралельнно вс связанные книги. WКогда оба пораметра авершины, он рендерит страницу  author_delete.pug, передает значения для title, author, и author_books.

+ +
+

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

+ +
}, function(err, results) {
+    if (err) { return next(err); }
+    if (results.author==null) { // No results.
+        res.redirect('/catalog/authors')
+    }
+
+ +

Controller—post route

+ +

Найдите экспортируемый метод контроллера author_delete_post() и замените его на следующий код.

+ +
// Обработчик удаления автора POST.
+exports.author_delete_post = function(req, res, next) {
+
+    async.parallel({
+        author: function(callback) {
+          Author.findById(req.body.authorid).exec(callback)
+        },
+        authors_books: function(callback) {
+          Book.find({ 'author': req.body.authorid }).exec(callback)
+        },
+    }, function(err, results) {
+        if (err) { return next(err); }
+        // Success
+        if (results.authors_books.length > 0) {
+            // Автор книги. Визуализация выполняется так же, как и для GET route.
+            res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } );
+            return;
+        }
+        else {
+            //У автора нет никаких книг. Удалить объект и перенаправить в список авторов.
+            Author.findByIdAndRemove(req.body.authorid, function deleteAuthor(err) {
+                if (err) { return next(err); }
+                // Успех-перейти к списку авторов
+                res.redirect('/catalog/authors')
+            })
+        }
+    });
+};
+ +

Сначала мы проверяем, что был предоставлен id (он отправляется через параметры тела формы, а не через версию в URL). Затем мы получаем автора и связанные с ним книги так же, как и для маршрута GET. Если книг нет, то удаляем объект автора и перенаправляем в список всех авторов. Если есть еще книги, то мы просто перерисовываем форму, передавая автора и список книг, которые нужно удалить.

+ +
+

Заметка: Мы можем проверить, возвращает ли вызов findbyid () какой-либо результат, и если нет, немедленно отобразить список всех авторов.Для краткости мы оставили код как есть выше (он все равно вернет список авторов, если id не будет найден, но это произойдет после findByIdAndRemove()).

+
+ +

View

+ +

Создайте /views/author_delete.pug и скопируйет текст ниже.

+ +
extends layout
+
+block content
+  h1 #{title}: #{author.name}
+  p= author.lifespan
+
+  if author_books.length
+
+    p #[strong Delete the following books before attempting to delete this author.]
+
+    div(style='margin-left:20px;margin-top:20px')
+
+      h4 Books
+
+      dl
+      each book in author_books
+        dt
+          a(href=book.url) #{book.title}
+        dd #{book.summary}
+
+  else
+    p Do you really want to delete this Author?
+
+    form(method='POST' action='')
+      div.form-group
+        input#authorid.form-control(type='hidden',name='authorid', required='true', value=author._id )
+
+      button.btn.btn-primary(type='submit') Delete
+ +

Представление расширяет шаблон макета, переопределяя блок с именем content. Вверху отображаются сведения об авторе. Затем он включает условный оператор, основанный на количестве author_books (пункты if и else ).

+ + + +

Добавление элемента управления delete

+ +

Затем мы добавим элемент управления Delete в представление сведений об авторе (страница сведений-хорошее место для удаления записи).

+ +
+

Note: В полном объеме контроль будет доступен только авторизованным пользователям. Однако на данный момент у нас нет системы авторизации!

+
+ +

Откройте author_detail.pug и добавьте следующие строки внизу.

+ +
hr
+p
+  a(href=author.url+'/delete') Delete author
+ +

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

+ +

+ +

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

+ +

Запустите приложение и откройте в вашем браузере  http://localhost:3000/. Затем раздел All authors , а затем укажите конктретного пользователя. Наконец, выберите ссылку Delete author.

+ +

Если у автора нет книг, вам будет представлена такая страница. После нажатия клавиши delete сервер удалит автора и перенаправит в список авторов

+ +

+ +

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

+ +

+ +
+

Note: Другие страницы для удаления объектов могут быть реализованы примерно таким же образом. Мы оставили это как задачи.

+
+ +

Next steps

+ + diff --git a/files/ru/learn/server-side/express_nodejs/forms/index.html b/files/ru/learn/server-side/express_nodejs/forms/index.html new file mode 100644 index 0000000000..f877a6015c --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/index.html @@ -0,0 +1,269 @@ +--- +title: 'Учебник Express часть 6: Работа с формами' +slug: Learn/Server-side/Express_Nodejs/forms +tags: + - Начинающим + - Сервер + - Формы +translation_of: Learn/Server-side/Express_Nodejs/forms +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}
+ +

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

+ + + + + + + + + + + + +
Предварительные знания:Завершите изучение предыдущих тем учебника, включая Учебник Express Часть 5: Отображение данных библиотеки
Цель:Понять, как писать формы для получения данных от пользователей и обновлять базу данных с этими данными.
+ +

Обзор

+ +

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

+ +

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

+ +

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

+ +
+

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

+
+ +

HTML Forms

+ +

Первый краткий обзор HTML Forms. Рассмотрим простую HTML-форму с одним текстовым полем для ввода имени некоторой "команды" и связанной с ней меткой:

+ +

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, в котором указывается значение идентификатора поля (input id).

+ +

Input submit будет отображаться в виде кнопки (по умолчанию) - он может быть нажат пользователем, чтобы загрузить данные, содержащиеся в других входных элементов на сервер (в данном случае, только team_name). Атрибуты формы определяют метод HTTP, используемый для отправки данных, и назначение данных на сервере (action):

+ + + +

Процесс обработки формы

+ +

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

+ +

Блок-схема процесса обработки запросов формы показана ниже, начиная с запроса страницы, содержащей форму (показана зеленым цветом):

+ +

+ +

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

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

Часто код обработки формы реализуется с помощью GET route для начального отображения формы и POST route к тому же пути для обработки проверки и обработки данных формы. Это подход, который будет использоваться в этом уроке!

+ +

Сам Express не предоставляет какой-либо конкретной поддержки для операций обработки форм, но он может использовать промежуточное программное обеспечение для обработки POST и GET параметров из формы, а также для проверки/очистки их значений.

+ +

Валидация и обработка

+ +

Перед сохранением данных формы их необходимо проверить и очистить:

+ + + +

В этом уроке мы будем использовать популярный модуль express-validator для проверки и очистки данных формы.

+ +

Установка

+ +

Установите модуль, выполнив следующую команду в корне проекта

+ +
npm install express-validator
+
+ +

Использование express-validator

+ +
+

Note: express-validator руководство на Github предоставляет хороший обзор API. Мы рекомендуем вам прочитать это, чтобы получить представление о всех его возможностях (включая создание пользовательских валидаторов). Ниже мы рассмотрим только подмножество, которое полезно для LocalLibrary.

+
+ +

Для того, чтобы использовать валидатор в наших контроллерах, мы должны требовать функции, которые мы хотим использовать из модулей 'express-validator/check' и 'express-validator/filter', как показано ниже:

+ +
const { body,validationResult } = require('express-validator/check');
+const { sanitizeBody } = require('express-validator/filter');
+
+ +

Есть много доступных функций, позволяющих проверять и очищать данные из параметров запроса, тела, заголовков, файлов cookie и т. д., или все сразу. Для этого урока мы будем использовать bodysanitizeBody, and validationResult (как "требуется" выше).

+ +

Функции определяются следующим образом:

+ + + +

Цепочки проверки и очистки являются промежуточными запросами, которые должны быть переданы обработчику Express -маршрута (мы делаем это косвенно, через контроллер). При запуске промежуточного по каждый валидатор / средства очистки выполняется в указанном порядке..

+ +

Мы рассмотрим некоторые реальные примеры, когда мы реализуем LocalLibrary формы ниже.

+ +

Дизайн формы

+ +

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

+ + + +

Для этого проекта мы упростили реализацию, объявив, что форма может быть только:

+ + + +
+

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

+
+ +

Маршруты

+ +

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

+ +

Мы уже создали маршруты для всех страниц создания нашей модели в  /routes/catalog.js (in a previous tutorial).  Например, жанровые маршруты показаны ниже:

+ +
// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
+router.get('/genre/create', genre_controller.genre_create_get);
+
+// POST request for creating Genre.
+router.post('/genre/create', genre_controller.genre_create_post);
+
+ +

Express формы — подразделы

+ +

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

+ +
    +
  1. Форма для создания Genre — Определение нашей страницы для создания объектов Genre.
  2. +
  3. Форма для cоздания Author — Определение страницы для создания объектов Author.
  4. +
  5. Форма для создания Book — Определение страницы/формы для создания объектов Book.
  6. +
  7. Форма для создания BookInstance — Определение страницы/формы для создания объектов BookInstance.
  8. +
  9. Форма для удаления Author — Определение страницы для удаления объектов Author.
  10. +
  11. Форма для обновления Book — Определение страницы для обновления объектов Book.
  12. +
+ +

Challenge yourself

+ +

Implement the delete pages for the Book, BookInstance, and Genre models, linking them from the associated detail pages in the same way as our Author delete page. The pages should follow the same design approach:

+ + + +

A few tips:

+ + + +

Implement the update pages for the BookInstance, Author, and Genre models, linking them from the associated detail pages in the same way as our Book update page.

+ +

A few tips:

+ + + +

Summary

+ +

Express, node, and third party packages on NPM provide everything you need to add forms to your website. In this article you've learned how to create forms using Pug, validate and sanitize input using express-validator, and add, delete, and modify records in the database.

+ +

You should now understand how to add basic forms and form-handling code to your own node websites!

+ +

See also

+ + + +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}

+ +

In this module

+ + diff --git a/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html b/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html new file mode 100644 index 0000000000..16172605d1 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/forms/update_book_form/index.html @@ -0,0 +1,189 @@ +--- +title: Update Book form +slug: Learn/Server-side/Express_Nodejs/forms/Update_Book_form +translation_of: Learn/Server-side/Express_Nodejs/forms/Update_Book_form +--- +

EditНаконец, в разделе показано, как определить страницу для обновления объектов Book. Обработка форм при обновлении книги аналогична обработке форм при создании книги, за исключением того, что необходимо заполнить форму в маршруте GET значениями из базы данных.

+ +

Controller—get route

+ +

Откройте /controllers/bookController.js. Найдите экспортируемый метод контроллера book_update_get() и замените его на следующий код.

+ +
// Display book update form on GET.
+exports.book_update_get = function(req, res, next) {
+
+    // Get book, authors and genres for form.
+    async.parallel({
+        book: function(callback) {
+            Book.findById(req.params.id).populate('author').populate('genre').exec(callback);
+        },
+        authors: function(callback) {
+            Author.find(callback);
+        },
+        genres: function(callback) {
+            Genre.find(callback);
+        },
+        }, function(err, results) {
+            if (err) { return next(err); }
+            if (results.book==null) { // No results.
+                var err = new Error('Book not found');
+                err.status = 404;
+                return next(err);
+            }
+            // Success.
+            // Mark our selected genres as checked.
+            for (var all_g_iter = 0; all_g_iter < results.genres.length; all_g_iter++) {
+                for (var book_g_iter = 0; book_g_iter < results.book.genre.length; book_g_iter++) {
+                    if (results.genres[all_g_iter]._id.toString()==results.book.genre[book_g_iter]._id.toString()) {
+                        results.genres[all_g_iter].checked='true';
+                    }
+                }
+            }
+            res.render('book_form', { title: 'Update Book', authors:results.authors, genres:results.genres, book: results.book });
+        });
+
+};
+ +

Контроллер получит id Book книги для обновления из параметра URL (req.params.id). Он использует метод async.parallel()чтобы получить указанную запись Book (pаполнение полей жанра и автора) и список всех объектов Author и Genre. Когда все операции завершены, он помечает выбранные жанры как отмеченные, а затем отображает их в book_form.pug, передает переменные itle, book, всех authors, и всеgenres.

+ +

Controller—post route

+ +

Найдите экспортируемый метод контроллера book_update_post() и замените его следующим кодом.

+ +
// Handle book update on POST.
+exports.book_update_post = [
+
+    // Convert the genre to an array
+    (req, res, next) => {
+        if(!(req.body.genre instanceof Array)){
+            if(typeof req.body.genre==='undefined')
+            req.body.genre=[];
+            else
+            req.body.genre=new Array(req.body.genre);
+        }
+        next();
+    },
+
+    // Validate fields.
+    body('title', 'Title must not be empty.').isLength({ min: 1 }).trim(),
+    body('author', 'Author must not be empty.').isLength({ min: 1 }).trim(),
+    body('summary', 'Summary must not be empty.').isLength({ min: 1 }).trim(),
+    body('isbn', 'ISBN must not be empty').isLength({ min: 1 }).trim(),
+
+    // Sanitize fields.
+    sanitizeBody('title').trim().escape(),
+    sanitizeBody('author').trim().escape(),
+    sanitizeBody('summary').trim().escape(),
+    sanitizeBody('isbn').trim().escape(),
+    sanitizeBody('genre.*').trim().escape(),
+
+    // Process request after validation and sanitization.
+    (req, res, next) => {
+
+        // Extract the validation errors from a request.
+        const errors = validationResult(req);
+
+        // Create a Book object with escaped/trimmed data and old id.
+        var book = new Book(
+          { title: req.body.title,
+            author: req.body.author,
+            summary: req.body.summary,
+            isbn: req.body.isbn,
+            genre: (typeof req.body.genre==='undefined') ? [] : req.body.genre,
+            _id:req.params.id //This is required, or a new ID will be assigned!
+           });
+
+        if (!errors.isEmpty()) {
+            // There are errors. Render form again with sanitized values/error messages.
+
+            // Get all authors and genres for form.
+            async.parallel({
+                authors: function(callback) {
+                    Author.find(callback);
+                },
+                genres: function(callback) {
+                    Genre.find(callback);
+                },
+            }, function(err, results) {
+                if (err) { return next(err); }
+
+                // Mark our selected genres as checked.
+                for (let i = 0; i < results.genres.length; i++) {
+                    if (book.genre.indexOf(results.genres[i]._id) > -1) {
+                        results.genres[i].checked='true';
+                    }
+                }
+                res.render('book_form', { title: 'Update Book',authors:results.authors, genres:results.genres, book: book, errors: errors.array() });
+            });
+            return;
+        }
+        else {
+            // Data from form is valid. Update the record.
+            Book.findByIdAndUpdate(req.params.id, book, {}, function (err,thebook) {
+                if (err) { return next(err); }
+                   // Successful - redirect to book detail page.
+                   res.redirect(thebook.url);
+                });
+        }
+    }
+];
+ +

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

+ +

View

+ +

Откройте /views/book_form.pug и обновите раздел, в котором элемент управления "форма автора" имеет условный код, показанный ниже.

+ +
    div.form-group
+      label(for='author') Author:
+      select#author.form-control(type='select' placeholder='Select author' name='author' required='true' )
+        for author in authors
+          if book
+            //- Handle GET form, where book.author is an object, and POST form, where it is a string.
+            option(
+              value=author._id
+              selected=(
+                author._id.toString()==book.author._id
+                || author._id.toString()==book.author
+              ) ? 'selected' : false
+            ) #{author.name}
+          else
+            option(value=author._id) #{author.name}
+ +
+

Note: Это изменение кода необходимо для того, чтобы форму book_form можно было использовать как для создания, так и для обновления объектов book (без этого при создании формы на маршруте GET возникает ошибка).

+
+ +

Добавить кнопку обновления

+ +

Откройте book_detail.pug и убедитесь, что есть ссылки для удаления и обновления книг в нижней части страницы, как показано ниже.

+ +
  hr
+  p
+    a(href=book.url+'/delete') Delete Book
+  p
+    a(href=book.url+'/update') Update Book
+ +

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

+ +

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

+ +

Запустите приложение, откройте ваш браузер на  http://localhost:3000/, выберите ссылку  All books, затем выберите конкретную книгу. Наконец, выберите ссылку Update Book.

+ +

Форма должна выглядеть так же, как страница Create book, только с заголовком  'Update book' и предварительно заполнены значениями записей.

+ +

+ +
+

Note: Другие страницы для обновления объектов могут быть реализованы примерно таким же образом. Мы оставили это как задание.

+
+ +

 

+ +

Next steps

+ + + +

 

diff --git a/files/ru/learn/server-side/express_nodejs/index.html b/files/ru/learn/server-side/express_nodejs/index.html new file mode 100644 index 0000000000..9c1f44a50f --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/index.html @@ -0,0 +1,77 @@ +--- +title: Веб-фреймворк Express (Node.js/JavaScript) +slug: Learn/Server-side/Express_Nodejs +tags: + - Express + - Express.js + - Введение + - Для начинающих + - Программирование на стороне сервера + - Учебник +translation_of: Learn/Server-side/Express_Nodejs +--- +
{{LearnSidebar}}
+ +

Express представляет собой популярный веб-фреймворк, написанный на JavaScript и работающий внутри среды исполнения node.js. Этот модуль освещает некоторые ключевые преимущества этого фреймворка, установку среды разработки и выполнение основных задач веб-разработки и развертывания.

+ +

Предварительные требования

+ +

Перед началом этого модуля вам необходимо представлять, что из себя представляет серверное программирование и веб-фреймворки, желательно из прочтения статей другого модуля Server-side website programming first steps. Знакомство с основными концепциями программирования и языком программирования JavaScript будет очень полезным, но оно не является обязательным для понимания базовых понятий этого модуля.

+ +
+

Заметка: Этот веб-сайт содержит множество источников для изучения JavaScript в контексте  разработки на стороне клиента: JavaScriptJavaScript Guide, JavaScript BasicsJavaScript (изучение). Ключевые особенности и коцепции языка JavaScript остаются сходными и для серверной разработки на Node.js и используемый материал достаточно релевантен. Node.js предоставляет additional APIs для обеспечения функционала, который полезен для "безбраузерной" разработки, т.е. для создания HTTP-сервера и доступа к файловой системе, но не поддерживает JavaScript APIs для работы с браузером и DOM.

+ +

Это руководство обеспечит вас некоторой информацией о работе с Node.js и Express, но также существуют и другие многочисленные отличные ресурсы в Интернете и книгах — некоторые из них доступны из тем How do I get started with Node.js (StackOverflow) и What are the best resources for learning Node.js? (Quora).

+
+ +

Руководства

+ +
+
Введение в Express/Node
+
В первой статье об Express мы ответим на вопросы "Что такое Node?" и "Что такое Express?" и дадим вам представление о том, что делает веб-фреймворк Express особенным. Мы расскажем об основных функциях и покажем вам некоторые из основных строительных блоков приложений Express (хотя на данный момент у вас еще нет среды разработки, в которой можно ее протестировать).
+
Настройка среды разработки Node (Express)
+
+
Теперь, когда вы знаете, что такое Express, мы покажем вам, как настроить и протестировать среду разработки Node/Express в Windows, Linux (Ubuntu) и Mac OS X. Независимо от того, какую популярную операционную систему вы используете, эта статья даст вам то, что вам нужно, чтобы начать разработку приложений Express.
+
+
Учебник Express: сайт LocalLibrary
+
+
Первая статья в нашей серии практических уроков объясняет, что вы будете изучать, и предоставит обзор веб-сайта «локальной библиотеки», над которым мы будем работать и развивать в последующих статьях.
+
+
Учебник Express часть 2: Создание скелета веб-сайта
+
+
В этой статье показано, как вы можете создать «скелет» веб-сайта, который затем можно будет заполнить с помощью маршрутов сайта, шаблонов/представлений и баз данных.
+
+
Учебник Express часть 3: Использование базы данных (с помощью Mongoose)
+
В этой статье кратко представлены базы данных для Node/Express. Затем показывается, как мы можем использовать Mongoose для обеспечения доступа к баз данных для сайта LocalLibrary. В уроке объясняется, как объявляются объектная схема и модели, основные типы полей и базовая валидация. Также кратко показаны некоторые из основных способов доступа к данным модели.
+
Учебник Express часть 4: Маршруты и контроллеры
+
В этом уроке мы создадим маршруты (код обработки URL) с "фиктивным" обработчиком функций для всех конечных точек ресурсов, которые нам в конечном итоге понадобятся для сайта LocalLibrary. По завершении мы будем иметь модульную структуру нашего кода обработки маршрута, который мы можем расширить с помощью функций реального обработчика в следующих статьях. Мы также будем очень хорошо понимать, как создавать модульные маршруты, используя Express.
+
Учебник Express часть 5: Отображение данных библиотеки
+
Теперь мы готовы добавить страницы, на которых будут отображаться книги веб-сайта LocalLibrary и другие данные. Страницы будут включать главную страницу, которая показывает сколько записей определенного типа мы имеем и отдельную страницу для детального просмотра записи. По пути мы получим практический опыт в получении записей из баз данных и использовании шаблонов.
+
Учебник Express часть 6: Работы с формами
+
В этой части мы покажем вам, как работать с HTML формами в Express, используя Pug, и в частности, как создавать, обновлять и удалять документы из базы данных.
+
Учебник Express часть 7: Выкладка в production
+
Теперь когда вы создали восхитительный сайт LocalLibrary, вы захотите установить его на общедоступном сервере, чтобы он мог дать доступ персоналу библиотеки и пользователям в Интернет. В этой статье представлен обзор того, как вы можете найти хост для развертывания вашего сайта и что вам нужно сделать, чтобы подготовить ваш сайт к публикации.
+
+ +

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

+ +
+
Установка LocalLibrary на PWS/Cloud Foundry
+
В этой статье представлена практическая демонстрация того, как установить LocalLibrary на облаке Pivotal Web Services PaaS — это полнофункциональная альтернатива с открытым исходным кодом для Heroku, облачного сервиса PaaS  используемого в части 7 этого учебника, представленного выше. PWS/Cloud Foundry опредленно стоит попробовать, если вы ищете альтернативу Heroku (или другому PaaS облачному сервису), или просто хотите попробовать что-то другое.
+
+ +

Изучите другие учебники

+ +
+

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

+ + + +

И, конечно, было бы неплохо создать какой-либо проверочный тест знаний!

+
diff --git a/files/ru/learn/server-side/express_nodejs/introduction/index.html b/files/ru/learn/server-side/express_nodejs/introduction/index.html new file mode 100644 index 0000000000..bbe40c95f7 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/introduction/index.html @@ -0,0 +1,512 @@ +--- +title: Express/Node introduction +slug: Learn/Server-side/Express_Nodejs/Introduction +translation_of: Learn/Server-side/Express_Nodejs/Introduction +--- +
{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}
+ +

В этой первой статье по Express мы ответим на вопросы "Что такое Node?" и "Что такое Express?", и сделаем обзор того, что делает веб-фреймворк Express таким особенным. Мы расскажем об основных функциях и покажем вам некоторые из основных строительных блоков приложения Express (хотя на данный момент у вас еще нет среды разработки, в которой можно ее протестировать).

+ + + + + + + + + + + + +
Предварительные знания:Базовая компьютерная грамотность. Общее понимание серверного программирования веб-сайтов и, в частности, механики клиент-серверного взаимодействия на веб-сайтах.
Цель:Ознакомить Вас с фреймворком Express и как он вписывается в среду  Node, какие функции он предоставляет, и основные строительные блоки приложения Express.
+ +

Что такое Express и Node?

+ +

Node (или более формально Node.js) - кросплатформенная среда исполнения с открытым исходным кодом, которая позволяет разработчикам создавать всевозможные серверные инструменты и приложения  используя язык JavaScript. Среда исполнения предназначена для использования вне контекста браузера (т.е. выполняется непосредственно на компьютере или на серверной ОС). Таким образом, среда исключает API-интерфейсы JavaScript для браузера и добавляет поддержку более традиционных OS API-интерфейсов, включая библиотеки HTTP и файловых систем.

+ +

С точки зрения веб-серверной разработки Node имеет ряд преимуществ:

+ + + +

Вы можете изпользовать Node.js для создания простого веб сервера используя пакет Node HTTP. 

+ +

Hello Node.js

+ +

Следующий пример создаёт веб сервер который прослушивает любой HTTP запрос на URL http://127.0.0.1:8000/ — когда запрос будет получен, скрипт ответит строкой "Hello World". Если Вы уже установили node, можете, следуя шагам инструкции попробовать пример:

+ +
    +
  1. Откройте терминал (в Windows окно командной строки)
  2. +
  3. Создайте папку, куда вы хотите сохранить программу, к примеру test-node и перейдите в нее с помощью следующей команды:
  4. +
+ +
cd test-node
+ +
    +
  1. Используя любимый текстовый редактор, создайте файл hello.js и вставьте в него код:
  2. +
+ +
// Загружаем HTTP модуль
+const http = require("http");
+
+const hostname = "127.0.0.1";
+const port = 8000;
+
+// Создаем HTTP-сервер
+const server = http.createServer((req, res) => {
+
+   // Устанавливаем HTTP-заголовок ответа с HTTP статусом и Content type
+   res.writeHead(200, {'Content-Type': 'text/plain'});
+
+   // Отсылаем тело ответа "Hello World"
+   res.end('Hello World\n');
+});
+
+// Выводим лог как только сервер будет запущен
+server.listen(port, hostname, () => {
+   console.log(`Server running at http://${hostname}:${port}/`);
+})
+
+ +
    +
  1. Сохраните файл в папку, созданную выше.
  2. +
  3. Вернитесь в терминал и выполните следующую команду:
  4. +
+ +
node hello.js
+ +

В итоге, перейдите по ссылке http://localhost:8000 в вашем браузере; вы должны увидеть текст "Hello World" в верху слева на чистой странице.

+ +

Веб фреймворк

+ +

Другие общие для веб-программирования задачи не поддерживаются на прямую Node. Если вы хотите добавить специфичную поддержку различных HTTP методов (например GETPOSTDELETE, и т.д.) по разному для разных URL путей ("routes"), отдачу статических файлов, или использовать шаблоны для создания динамических ответов, вам нужно написать код самим, или можете отказаться от изобретения колеса и использовать фреймворк!

+ +

Введение в Express

+ +

Express - самый популярный веб-фреймворк для Node. Он является базовой библиотекой для ряда других популярных веб-фреймворков Node. Он предоставляет следующие механизмы:

+ + + +

В то время как сам express довольно минималистичный, разработчики создали совместимые пакеты промежуточного программного обеспечения для решения практически любой проблемы с веб-разработкой. Существуют библиотеки для работы с куки-файлами, сеансами, входами пользователей, параметрами URL, данными POST, заголовками безопасности и многими другими. Вы можете найти список пакетов промежуточного программного обеспечения, поддерживаемых командой Express в Express Middleware (наряду со списком некоторых популярных пакетов сторонних производителей) .

+ +
+

Примечание: Гибкость это палка о двух концах. Существуют пакеты промежуточного программного обеспечения (middleware) для решения практически любых проблем или для удовлетворения любых ваших требований, но правильный выбор подходящих пакетов иногда может быть проблемой. Также нет «правильного пути» для структурирования приложения, и многие примеры, которые вы можете найти в Интернете, не являются оптимальными или лишь показывают небольшую часть того, что вам нужно сделать для разработки веб-приложения.

+
+ +

Откуда это все взялось?

+ +

Node первоначально был выпущен только под Linux в 2009. Менеджер пакетов NPM был выпущен в 2010, а поддержка Windows была добавлена в 2012. Текущая LTS-версия Node v12.16.1 , в то время как последний выпуск Node версии 13.11.0. Это короткий экскурс в историю; обратитесь к Википедии, если вы хотите узнать больше).

+ +

Express первоначально был выпущен в ноябре 2010 и текущая версия API 4.17.1 Вы можете отследить изменения и текущий релиз, и GitHub для более детальной информации о релизах.

+ +

Насколько популярен Node/Express?

+ +

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

+ +

Не существует какого-либо доступного и точного измерения популярности серверных фреймворков (хотя сайты, такие как Hot Frameworks, пытаются оценить популярность, используя такие механизмы, как подсчет количества проектов на GitHub и вопросов на StackOverflow для каждой платформы). Лучший вопрос заключается в том, достаточно ли популярны Node и Express, чтобы избежать проблем с непопулярными платформами. Они продолжают развиваться? Можете ли вы получить помощь, если вам это нужно? Есть ли у вас возможность получить оплачиваемую работу, если вы изучаете Express?

+ +

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

+ +

Является ли Express ограничивающим?

+ +

Web-фрэймворки часто принято делить на "ограничивающие" и "неограничивающие".

+ +

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

+ +

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

+ +

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

+ +

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

+ +

В традиционных динамических веб-сайтах, веб-приложение ожидает HTTP-запроса от веб-браузера (или другого клиента). Когда запрос получен, приложение определяет, какое действие необходимо выполнить на основе URL шаблна и, возможно, связанной информации, содержащейся в данных POST или GET. В зависимости от того, что требуется, Express может затем читать или записывать данные из/в базы данных или выполнять другие задачи, в соответствии с полученным запросом. Затем приложение возвращает ответ в веб-браузер, зачастую динамически создавая HTML страницу для отображения браузером, вставляя извлеченные данные в заполнители HTML шаблона.

+ +

Express предоставляет методы позволяющие указать, какая функция вызывается для конкретного HTTP запроса (GET, POST, SET, etc.), и URL шаблон ("Route"), а также методы позволяющие указать, какой механизм шаблона ("view") используется, где находятся шаблоныы файлов и какой шаблон использовать для вывода ответа. Вы можете использовать Express middleware для добавления поддержки файлов cookies, сеансов, и пользователей, получения POST/GET параметров, и т.д. Вы можете использовать любой механизм базы данных, поддерживаемый Node (Express не определяет поведение, связанное с базой данных).

+ +

В следующих разделах объясняются некоторые общие моменты, которые вы увидите при работе с кодом Express and Node.

+ +

Helloworld Express

+ +

Сначала давайте рассмотрим стандартный пример Express Hello World (мы обсудим каждую часть этого ниже и в следующих разделах).

+ +
+

Совет: Если у вас уже установлены Node и Express (или если вы устанавливаете их, как показано в следующей статье), вы можете сохранить этот код в файле с именем app.js и запустить его в командной строке, вызвав узел app.js. отражения).

+
+ +
var express = require('express');
+var app = express();
+
+app.get('/', function(req, res) {
+  res.send('Hello World!');
+});
+
+app.listen(3000, function() {
+  console.log('Example app listening on port 3000!');
+});
+
+ +

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

+ +

Средняя часть кода (три строки, начинающиеся с app.get) показывает определение маршрута. Метод app.get () указывает функцию обратного вызова, которая будет вызываться всякий раз, когда есть HTTP-запрос GET с путем ('/') относительно корня сайта. Функция обратного вызова принимает запрос и объект ответа в качестве аргументов и просто вызывает send () для ответа, чтобы вернуть строку «Hello World!»

+ +

Последний блок запускает сервер через порт «3000» и печатает комментарий журнала в консоль. Когда сервер работает, вы можете перейти к localhost: 3000 в вашем браузере, чтобы увидеть возвращенный пример ответа.

+ +

Импорт и создание модулей

+ +

Модуль - это библиотека / файл JavaScript, который вы можете импортировать в другой код с помощью функции require () Node. Express сам по себе является модулем, как и промежуточное программное обеспечение и библиотеки баз данных, которые мы используем в наших приложениях Express.
+
+ Приведенный ниже код показывает, как мы импортируем модуль по имени, используя в качестве примера платформу Express. Сначала мы вызываем функцию require (), определяя имя модуля в виде строки («express») и вызывая возвращенный объект для создания приложения Express. Затем мы можем получить доступ к свойствам и функциям объекта приложения.

+ +
var express = require('express');
+var app = express();
+
+ +

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

+ +
+

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

+
+ +

Чтобы сделать объекты доступными вне модуля, вам просто нужно назначить их объекту экспорта. Например, модуль square.js ниже представляет собой файл, который экспортирует методы area () и perimeter ():

+ +
exports.area = function(width) { return width * width; };
+exports.perimeter = function(width) { return 4 * width; };
+
+ +

Мы можем импортировать этот модуль, используя require (), а затем вызвать экспортированные методы, как показано:

+ +
var square = require('./square'); // Here we require() the name of the file without the (optional) .js file extension
+console.log('The area of a square with a width of 4 is ' + square.area(4));
+ +
+

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

+
+ +

Если вы хотите экспортировать полный объект в одном назначении, а не создавать его по одному свойству за раз, назначьте его для module.exports, как показано ниже (вы также можете сделать это, чтобы сделать корень объекта экспорта конструктором или другой функцией) :

+ +
module.exports = {
+  area: function(width) {
+    return width * width;
+  },
+
+  perimeter: function(width) {
+    return 4 * width;
+  }
+};
+
+ +

Для получения дополнительной информации о модулях см. Modules (Node API docs).

+ +

Использование асинхронных API

+ +

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

+ +
console.log('First');
+console.log('Second');
+
+ +

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

+ +
setTimeout(function() {
+   console.log('First');
+   }, 3000);
+console.log('Second');
+
+ +

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

+ +
+

Совет: Использование обратных вызовов может быть довольно «грязным», если у вас есть последовательность зависимых асинхронных операций, которые должны выполняться по порядку, потому что это приводит к нескольким уровням вложенных обратных вызовов. Эта проблема широко известна как «ад обратного вызова». Эту проблему можно решить с помощью хороших методов кодирования (см. Http://callbackhell.com/), использования такого модуля, как async, или даже перехода к функциям ES6, таким как Promises.

+
+ +
+

Примечание. Общим соглашением для Node и Express является использование обратных вызовов с ошибками. В этом соглашении первое значение в ваших функциях обратного вызова является значением ошибки, в то время как последующие аргументы содержат данные об успехе. В этом блоге есть хорошее объяснение того, почему этот подход полезен: путь Node.js - понимание обратных вызовов с ошибками (fredkschott.com).

+
+ +

Создание обработчиков маршрута

+ +

В нашем примере Hello World Express (см. Выше) мы определили функцию обработчика маршрута (обратного вызова) для HTTP-запросов GET к корню сайта ('/').

+ +
app.get('/', function(req, res) {
+  res.send('Hello World!');
+});
+
+ +

Функция обратного вызова принимает запрос и объект ответа в качестве аргументов. В этом случае метод просто вызывает send () в ответе, чтобы вернуть строку «Hello World!» Существует ряд других методов ответа для завершения цикла запрос / ответ, например, вы можете вызвать res.json () для отправки ответа JSON или res.sendFile () для отправки файла.

+ +
+

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

+
+ +

Объект приложения Express также предоставляет методы для определения обработчиков маршрутов для всех других HTTP-глаголов, которые в основном используются одинаково: post (), put (), delete (), options (), trace (), copy ( ), lock (), mkcol (), move (), purge (), propfind (), proppatch (), unlock (), report (), mkactivity (), checkout (), merge ( ), m-search (), notify (), subscribe (), unsubscribe (), patch (), search () и connect ().

+ +

Существует специальный метод маршрутизации app.all (), который будет вызываться в ответ на любой метод HTTP. Это используется для загрузки функций промежуточного программного обеспечения по определенному пути для всех методов запроса. В следующем примере (из документации Express) показан обработчик, который будет выполняться для запросов к / secret независимо от используемого глагола HTTP (при условии, что он поддерживается модулем http).

+ +
app.all('/secret', function(req, res, next) {
+  console.log('Accessing the secret section ...');
+  next(); // pass control to the next handler
+});
+ +

Маршруты позволяют сопоставлять определенные шаблоны символов в URL-адресе, извлекать некоторые значения из URL-адреса и передавать их в качестве параметров обработчику маршрута (в качестве атрибутов объекта запроса, передаваемого в качестве параметра).
+
+ Часто полезно группировать обработчики маршрутов для определенной части сайта и получать к ним доступ с помощью общего префикса маршрута (например, сайт с вики может иметь все связанные с вики маршруты в одном файле и иметь к ним доступ с префиксом маршрута из / вики /). В Express это достигается с помощью объекта express.Router. Например, мы можем создать наш вики-маршрут в модуле с именем wiki.js, а затем экспортировать объект Router, как показано ниже:

+ +
// wiki.js - Wiki route module
+
+var express = require('express');
+var router = express.Router();
+
+// Home page route
+router.get('/', function(req, res) {
+  res.send('Wiki home page');
+});
+
+// About page route
+router.get('/about', function(req, res) {
+  res.send('About this wiki');
+});
+
+module.exports = router;
+
+ +
+

Примечание. Добавление маршрутов к объекту Router аналогично добавлению маршрутов к объекту приложения (как показано ранее).

+
+ +

Чтобы использовать маршрутизатор в нашем главном файле приложения, нам потребуется () модуль route (wiki.js), а затем вызовите use () в приложении Express, чтобы добавить маршрутизатор в путь обработки промежуточного программного обеспечения. Эти два маршрута будут доступны из / wiki / и / wiki / about /.

+ +
var wiki = require('./wiki.js');
+// ...
+app.use('/wiki', wiki);
+ +

Мы покажем вам намного больше о работе с маршрутами, и в частности об использовании маршрутизатора, позже в связанном разделе Routes and controllers .

+ +

Использование промежуточного программного обеспечения

+ +

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

+ +
+

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

+
+ +

Большинство приложений используют стороннее промежуточное программное обеспечение для упрощения общих задач веб-разработки, таких как работа с файлами cookie, сессиями, аутентификацией пользователя, доступом к данным запросов POST и JSON, ведение журнала и т. д. Список пакетов промежуточного программного обеспечения, поддерживаемых командой Express, можно найти. (который также включает в себя другие популярные сторонние пакеты). Другие экспресс-пакеты доступны в диспетчере пакетов NPM.
+
+ Для использования стороннего промежуточного программного обеспечения сначала необходимо установить его в свое приложение с помощью NPM. Например, чтобы установить промежуточное программное обеспечение средства регистрации HTTP-запросов morgan, вы должны сделать следующее:

+ +
$ npm install morgan
+
+ +

Затем вы можете вызвать use () для объекта приложения Express, чтобы добавить промежуточное программное обеспечение в стек:

+ +
var express = require('express');
+var logger = require('morgan');
+var app = express();
+app.use(logger('dev'));
+...
+ +
+

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

+
+ +

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

+ +

Вы можете добавить функцию промежуточного программного обеспечения в цепочку обработки с помощью app.use () или app.add (), в зависимости от того, хотите ли вы применить промежуточное программное обеспечение ко всем ответам или к ответам с определенным глаголом HTTP (GET, POST и т. д.). ). Маршруты задаются одинаково в обоих случаях, хотя маршрут необязателен при вызове app.use ().
+
+ В приведенном ниже примере показано, как можно добавить функцию промежуточного программного обеспечения, используя оба метода, а также с / без маршрута.

+ +
var express = require('express');
+var app = express();
+
+// An example middleware function
+var a_middleware_function = function(req, res, next) {
+  // ... perform some operations
+  next(); // Call next() so Express will call the next middleware function in the chain.
+}
+
+// Function added with use() for all routes and verbs
+app.use(a_middleware_function);
+
+// Function added with use() for a specific route
+app.use('/someroute', a_middleware_function);
+
+// A middleware function added for a specific HTTP verb and route
+app.get('/', a_middleware_function);
+
+app.listen(3000);
+ +
+

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

+
+ +

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

+ +

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

+ +

Вы можете использовать промежуточное программное обеспечение express.static для обслуживания статических файлов, включая ваши изображения, CSS и JavaScript (static () - единственная функция промежуточного программного обеспечения, которая фактически является частью Express). Например, вы должны использовать строку ниже для обслуживания изображений, файлов CSS и файлов JavaScript из каталога с именем public на том же уровне, где вы вызываете узел:

+ +
app.use(express.static('public'));
+
+ +

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

+ +
http://localhost:3000/images/dog.jpg
+http://localhost:3000/css/style.css
+http://localhost:3000/js/app.js
+http://localhost:3000/about.html
+
+ +

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

+ +
app.use(express.static('public'));
+app.use(express.static('media'));
+
+ +

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

+ +
app.use('/media', express.static('public'));
+
+ +

Теперь вы можете загружать файлы, находящиеся в публичном каталоге, из префикса / media path.

+ +
http://localhost:3000/media/images/dog.jpg
+http://localhost:3000/media/video/cat.mp4
+http://localhost:3000/media/cry.mp3
+
+ +

Для получения дополнительной информации см. Serving static files in Express.

+ +

Обработка ошибок

+ +

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

+ +
app.use(function(err, req, res, next) {
+  console.error(err.stack);
+  res.status(500).send('Something broke!');
+});
+
+ +

Они могут возвращать любой требуемый контент, но должны вызываться после всех других app.use () и маршрутизировать вызовы, чтобы они были последним промежуточным ПО в процессе обработки запросов!
+
+ Express поставляется со встроенным обработчиком ошибок, который заботится обо всех оставшихся ошибках, которые могут возникнуть в приложении. Эта промежуточная функция обработки ошибок по умолчанию добавляется в конец стека функций промежуточного программного обеспечения. Если вы передаете ошибку в next () и не обрабатываете ее в обработчике ошибок, она будет обработана встроенным обработчиком ошибок; ошибка будет записана клиенту с трассировкой стека.

+ +
+

Примечание. Трассировка стека не включена в производственную среду. Чтобы запустить его в производственном режиме, необходимо установить переменную среды NODE_ENV в «производство».

+
+ +
+

Примечание. HTTP404 и другие коды состояния «ошибка» не считаются ошибками. Если вы хотите справиться с этим, вы можете добавить функцию промежуточного программного обеспечения для этого. Для получения дополнительной информации см. FAQ.

+
+ +

Для получения дополнительной информации см. Error handling (Express docs).

+ +

Использование баз данных

+ +

Приложения Express могут использовать любой механизм базы данных, поддерживаемый Node (сам по себе Express не определяет каких-либо дополнительных действий / требований для управления базой данных). Есть много вариантов, включая PostgreSQL, MySQL, Redis, SQLite, MongoDB и т. Д.
+
+ Чтобы использовать их, вы должны сначала установить драйвер базы данных, используя NPM. Например, чтобы установить драйвер для популярной NoSQL MongoDB, вы должны использовать команду:

+ +
$ npm install mongodb
+
+ +

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

+ +
var MongoClient = require('mongodb').MongoClient;
+
+MongoClient.connect('mongodb://localhost:27017/animals', function(err, db) {
+  if (err) throw err;
+
+  db.collection('mammals').find().toArray(function (err, result) {
+    if (err) throw err;
+
+    console.log(result);
+  });
+});
+ +

Другим популярным подходом является косвенный доступ к вашей базе данных с помощью Object Relational Mapper («ORM»). При таком подходе вы определяете свои данные как «объекты» или «модели», и ORM отображает их в базовый формат базы данных. Этот подход имеет то преимущество, что как разработчик вы можете продолжать думать с точки зрения объектов JavaScript, а не семантики базы данных, и что есть очевидное место для выполнения проверки и проверки входящих данных. Подробнее о базах данных мы поговорим в следующей статье.

+ +

Для получения дополнительной информации см. Database integration (Express docs).

+ +

Рендеринг данных (просмотров)

+ +

Механизмы шаблонов (в Express называемые «механизмами просмотра») позволяют указывать структуру выходного документа в шаблоне, используя заполнители для данных, которые будут заполняться при создании страницы. Шаблоны часто используются для создания HTML, но могут также создавать другие типы документов. В Express есть поддержка ряда шаблонных движков, и здесь есть полезное сравнение более популярных движков: Сравнение шаблонизаторов JavaScript: Jade, Mustache, Dust и More.
+
+ В своем коде настроек приложения вы задаете механизм шаблонов для использования и место, где Express должен искать шаблоны, используя настройки «views» и «engine», как показано ниже (вам также нужно будет установить пакет, содержащий вашу библиотеку шаблонов). !)

+ +
var express = require('express');
+var app = express();
+
+// Set directory to contain the templates ('views')
+app.set('views', path.join(__dirname, 'views'));
+
+// Set view engine to use, in this case 'some_template_engine_name'
+app.set('view engine', 'some_template_engine_name');
+
+ +

Внешний вид шаблона будет зависеть от того, какой движок вы используете. Предполагая, что у вас есть файл шаблона с именем «index. <Template_extension>», который содержит заполнители для переменных данных с именами «title» и «message», вы должны вызвать Response.render () в функции обработчика маршрута для создания и отправки ответа HTML. :

+ +
app.get('/', function(req, res) {
+  res.render('index', { title: 'About dogs', message: 'Dogs rock!' });
+});
+ +

Для получения дополнительной информации см. Using template engines with Express (Express docs).

+ +

Файловая структура

+ +

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

+ +

В более поздней теме мы будем использовать Express Application Generator, который создает модульный каркас приложения, который мы можем легко расширить для создания веб-приложений.

+ + + +

Резюме

+ +

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

+ +

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

+ + + +
{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}
+ +
+ +
+
+
+
+ +
+ +
+
+ +
+
+
diff --git a/files/ru/learn/server-side/express_nodejs/mongoose/index.html b/files/ru/learn/server-side/express_nodejs/mongoose/index.html new file mode 100644 index 0000000000..18cdb9922a --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/mongoose/index.html @@ -0,0 +1,800 @@ +--- +title: 'Учебник Express часть 3: Использование базы данных (с помощью Mongoose)' +slug: Learn/Server-side/Express_Nodejs/mongoose +translation_of: Learn/Server-side/Express_Nodejs/mongoose +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}
+ +

В этой статье дается краткое введение в базы данных, и методика их использования в приложнениях Node/Express. Затем мы покажем, как можно использовать Mongoose для доступа к базе данных веб-сайта  LocalLibrary. Мы объясним, как объявляются схемы и модели объектов, укажем основные типы полей, и методику базовой валидации. В статье также кратко показаны  основные методы доступа к данным модели.

+ + + + + + + + + + + + +
Предварительные сведения:Express Tutorial Part 2: Creating a skeleton website
Цель:Уметь спроектировать и создать свои модели, используя Mongoose.
+ +

Обзор

+ +

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

+ +

Express-приложения могут использовать различные базы данных, и есть несколько подходов, которые можно использовать для выполнения операций Create, Read, Update and Delete (CRUD) (создать, прочесть, обновить, удалить). В руководстве дан краткий обзор некоторых доступных опций, и детально рассмотрены некоторые механизмы работы.

+ +

Какие базы данных можно использовать?

+ +

Express-приложение может использовать  любые базы данных, поддерживаемые Node (сам по себе Express не определяет каких-либо конкретных дополнительных свойств и требований для управления базами данных). Есть много популярных вариантов -- PostgreSQL, MySQL, Redis, SQLite, и MongoDB.

+ +

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

+ +

Более подробно о вариантах смотрите в: Database integration (Express docs).

+ +

Каков наилучший способ взаимодействия с базой данных?

+ +

Существует два подхода при работе с базой данных:

+ + + +

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

+ +

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

+ +
+

Совет:  Применение ODM / ORMs часто приводит к снижению затрат на разработку и обслуживание! Если Вы не очень хорошо знакомы с родным языком запросов или если производительность имеет первостепенное значение, следует серьезно рассмотреть возможность применения ODM.

+
+ +

Какую модель ORM/ODM следует использовать?

+ +

Есть много ODM/ORM доступных решений на сайте менеджера пакетов NPM (проверьте теги по подгруппе odm и orm).

+ +

Популярные решения на момент написания статьи:

+ + + +

Как правило, при выборе решения следует учитывать как предоставляемые функции, так и  "деятельность сообщества" ( загрузки, вклад, отчеты об ошибках, качество документации, и т.д. ) . На момент написания статьи Mongoose являлась очень популярной ORM, и разумно, если вы выбрали MongoDB.

+ +

Применение Mongoose и MongoDb для LocalLibrary

+ +

В примере LocalLibrary (и до конца раздела) мы будем использовать Mongoose ODM для доступа к данным нашей библиотеки. Mongoose является интерфейсом для MongoDB, NoSQL-базы данных с открытым исходным кодом, в которой использована документо-ориентированная модель данных. В MongoDB «коллекции» и «документы» -- это аналоги «таблиц» и «строк» в реляционных БД.

+ +

Это сочетание ODM и БД весьма популярно в сообществе Node, частично потому, что система хранения документов и запросов очень похожа на JSON и поэтому знакома разработчикам JavaScript.

+ +
+

Совет: Не обязательно знать MongoDB, чтобы использовать Mongoose, хотя   документацию Mongoose легче использовать и понимать, если вы уже знакомы с MongoDB.

+
+ +

Далее показано, как определить и получить доступ к схеме и моделям Mongoose для примера веб-сайта LocalLibrary.

+ +

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

+ +

 

+ +

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

+ +

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

+ +

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

+ +

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

+ +

Как только мы определились с моделями и полями, следует подумать об отношениях между ними.

+ +

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

+ +

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

+ +
+

Заметка: Как показано в примереMongoose primer ниже, часто лучше иметь поле, определяющее отношение между документами (моделями), только в одной модели (обратное отношение можно найти по присвоенному идентификатору _id в другой модели). Ниже мы предпочли задать отношения между Book/Genre и между Book/Author в схеме  Book, а отношение между  Book/BookInstance -- в схеме BookInstance. Этот выбор в некотором смысле был произвольным -- таким же хорошим мог бы быть выбор другого поля в другой схеме.

+
+ +

Mongoose Library Model  with correct cardinality

+ +
+

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

+
+ +

Mongoose Справочник

+ +

В этом разделе кратко описано как подключиться к базе MongoDB с помощью Mongooseе, как определить схемы и модели, как сформировать основные запросы.

+ +
+

 Примечание: На этот  пример значительно повлияли документы Mongoose quick start на npm и официальная документация.

+
+ +

Установка Mongoose и MongoDB

+ +

Mongoose устанавливается в ваш проект (package.json) как и другие зависимости - при помощи NPM. Команда установки (выполняется из каталога проекта): 

+ +
npm install mongoose
+
+ +

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

+ +
+

Примечание: В примере для хранения базы данных мы используем облачный сервис sandbox tier ("песочницу"). This is suitable for development, and makes sense for the tutorial because it makes "installation" operating system independent (database-as-a-service is also one approach you might well use for your production database).

+
+ +

Подключенние к MongoDB

+ +

Mongoose требует подключение к MongoDB. Вы можете использовать require() и подключится к локальной БД при помощи mongoose.connect(), как показано ниже.

+ +
// Импортировать модуль mongoose
+var mongoose = require('mongoose');
+
+// Установим подключение по умолчанию
+var mongoDB = 'mongodb://127.0.0.1/my_database';
+mongoose.connect(mongoDB);
+// Позволим Mongoose использовать глобальную библиотеку промисов
+mongoose.Promise = global.Promise;
+// Получение подключения по умолчанию
+var db = mongoose.connection;
+
+// Привязать подключение к событию ошибки  (получать сообщения об ошибках подключения)
+db.on('error', console.error.bind(console, 'MongoDB connection error:'));
+
+ +

При помощи mongoose.connection можно получить стандартный объект Connection. После подключения в экземпляре Connection возникает событие open (открыт).

+ +
+

Tip: Если необходимо создать дополнительные подключения, можно использовать mongoose.createConnection(). При этом будут применены те же БД URI (хост, БД, порт, опции и т.д.), что и в connect() и будет возвращен объект Connection.

+
+ +

Определение и создание моделей

+ +

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

+ +

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

+ +
+

Заметка: Каждой модели соответствует коллекция документов в ДБ MongoDB. Документы будут содержать поля тех типов, которые заданы в модели Schema.

+
+ +

Определение схем данных

+ +

Код ниже показывает, как можно задать простую схему. Сначала при помощи require() создается объект mongoose, затем конструктор Schema создает новый экземпляр схемы, при этом различные поля задаются как параметры конструктора.

+ +
//Требуется Mongoose
+var mongoose = require('mongoose');
+
+//Определяем схему
+var Schema = mongoose.Schema;
+
+var SomeModelSchema = new Schema({
+    a_string: String,
+    a_date: Date
+});
+
+ +

В примере созданы два поля, типа String и типа Date. В следующем разделе будут примеры полей других типов, их валидации и примеры других методов.

+ +

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

+ +

Модели создаются из схем методом mongoose.model():

+ +
// Определяем схему
+var Schema = mongoose.Schema;
+
+var SomeModelSchema = new Schema({
+    a_string: String,
+    a_date: Date
+});
+
+// Компилируем модель из схемы
+var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
+ +

Первый аргумент - уникальное имя создаваемой для модели коллекции(Mongoose создаст коллекцию для модели SomeModel), второй аргумент - схема, которая используется для создания модели.

+ +
+

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

+
+ +

Типы схемы (поля)

+ +

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

+ +
var schema = new Schema(
+{
+  name: String,
+  binary: Buffer,
+  living: Boolean,
+  updated: { type: Date, default: Date.now },
+  age: { type: Number, min: 18, max: 65, required: true },
+  mixed: Schema.Types.Mixed,
+  _someId: Schema.Types.ObjectId,
+  array: [],
+  ofString: [String], // You can also have an array of each of the other types too.
+  nested: { stuff: { type: String, lowercase: true, trim: true } }
+})
+ +

Большинство типов в SchemaTypes (указаны после “type:” или после имен полей) достаточно очевидны. Исключения:

+ + + +

Код содержит также два способа объявления полей:

+ + + +

Дополнительная информация - в SchemaTypes (документация Mongoose).

+ +

Валидация (проверка допустимости)

+ +

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

+ +

Встроенные валидаторы включают:

+ + + +

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

+ +

+    var breakfastSchema = new Schema({
+      eggs: {
+        type: Number,
+        min: [6, 'Too few eggs'],
+        max: 12
+        required: [true, 'Why no eggs?']
+      },
+      drink: {
+        type: String,
+        enum: ['Coffee', 'Tea', 'Water',]
+      }
+    });
+
+ +

Подробная информация по валидации полей - в разделе Validation (документация Mongoose).

+ +

Виртуальные свойства

+ +

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

+ +
+

Замечание: В библиотеке виртуальное свойство будет применено для определения уникального URL каждой записи в модели по пути и по значению _id записи.

+
+ +

Подробная информация - в разделе  Virtuals (документация Mongoose).

+ +

Методы и помощники запросов

+ +

В схеме можно также задать методы экземпляра (instance methods), статические (static) методы и  помощники запросов. Статические методы и методы экземпляра аналогичны, но различаются тем, что методы экземпляра связаны с конкретной записью и имеют доступ к текущему объекту. Помощники запросов позволяют расширить  API построителя цепочек запросов (например, можно добавить запрос "byName"  ("по имени") в дополнение к методам find(), findOne() и findById()).

+ +

Применение моделей

+ +

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

+ +

Ниже предлагается краткий обзор. Более подробно смотрите в Models (документация Mongoose).

+ +

Создание и изменение документов

+ +

Чтобы создать запись, следует определить экземпляр модели и вызвать метод save().  В примере ниже SomeModel -- это модель с единственным полем "name",  которую мы создадим из нашей схемы.

+ +
// Создать экземпляр модели SomeModel
+var awesome_instance = new SomeModel({ name: 'awesome' });
+
+// Сохранить новый экземпляр, передав callback
+awesome_instance.save(function (err) {
+  if (err) return handleError(err);
+  // сохранили!
+});
+
+ +

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

+ +

Можно использовать метод create() для создании экземпляра модели при его сохранении. Тогда callback-функция вернет ошибку (или null) как первый аргумент и только что созданный экземпляр как второй аргумент.

+ +
SomeModel.create({ name: 'also_awesome' }, function (err, awesome_instance) {
+  if (err) return handleError(err);
+  // сохранили!
+});
+ +

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

+ +

Поля в новой записи могут быть получены и изменены с применением dot (точка)-синтаксиса. Для сохранения изменений служат методы save() и update().

+ +
// Доступ к полям модели в dot-нотации
+console.log(awesome_instance.name); //вывод в консоль 'also_awesome'
+
+// Изменить запись, модифицируя поля, потом вызвать save().
+awesome_instance.name="New cool name";
+awesome_instance.save(function (err) {
+   if (err) return handleError(err); // сохранили!
+   });
+
+ +

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

+ +

При поиске записей методами запросов, условия поиска следует задавать как документ JSON. Приведенный фрагмент кода (ниже) показывает, как в БД найти имена (name) и возраст (age) всех спортсменов-теннисистов. Соответствие будет определяться по одному полю (sport), но можно добавить критерии поиска, задав, например, регулярное выражение, или удалить все критерии, чтобы получить список всех спортсменов.

+ +
var Athlete = mongoose.model('Athlete', yourSchema);
+
+// найти всех теннисистов, выбирать поля 'name' и 'age'
+Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) {
+  if (err) return handleError(err);
+  // 'athletes' содержит список спортсменов, соответствующих критерию.
+})
+ +

Если задать callback-функцию так, как показано выше, запрос будет выполнен немедленно. Однако callback-функция будет вызвана только после завершения поиска.

+ +
+

Заметка: Все callbacks-функции в Mongoose используют образец callback(error, result). Если при выполнении запроса возникает ошибка, параметр error будет содержать объект error, а result будет null. При успешном запросе параметр error будет null, а result будет содержать результат запроса.

+
+ +

Если не задать callback-функцию, API вернет переменную типа Query. Можно использовать объект запроса, чтобы создать и выполнить свой запрос (с callback-функцией) позже, при помощи метода exec().

+ +
// найти всех теннисистов
+var query = Athlete.find({ 'sport': 'Tennis' });
+
+// выбрать поля 'name' и 'age'
+query.select('name age');
+
+// ограничить результат 5 элементами
+query.limit(5);
+
+// сортировать по возрасту
+query.sort({ age: -1 });
+
+// выполнить запрос позже
+query.exec(function (err, athletes) {
+  if (err) return handleError(err);
+  // athletes содержит упорядоченный список из 5 теннисистов
+})
+ +

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

+ +
Athlete.
+  find().
+  where('sport').equals('Tennis').
+  where('age').gt(17).lt(50).  //Дополнительное условие
+  limit(5).
+  sort({ age: -1 }).
+  select('name age').
+  exec(callback); // callback - имя нашей callback-функции.
+ +

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

+ + + +
+

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

+
+ +

Запросы полезны и во многих других случаях. Дополнительная информация - в Queries (документация Mongoose).

+ +

Работа со связанными документами — загрузка

+ +

Один документ (экземпляр модели) может ссылаться на другой документ при помощи поля ObjectId схемы, или на много других документов, используя массив идентификаторов ObjectIds. Идентификатор соответствующей модели хранится в поле. При необходимости получить действительное содержимое связанного документа, следует использовать в запросе метод populate(), который заменит идентификатор в запросе действительными данными.

+ +

Например, в следующей схеме определены авторы и рассказы. У каждого автора может быть несколько рассказов, которые представим массивом ссылок of ObjectId. У каждого рассказа может быть только один автор. Ссылка "ref" (выделена жирным) указывает в схеме, какая модель должна быть связана с этим полем.

+ +
var mongoose = require('mongoose')
+  , Schema = mongoose.Schema
+
+var authorSchema = Schema({
+  name    : String,
+  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
+});
+
+var storySchema = Schema({
+  author : { type: Schema.Types.ObjectId, ref: 'Author' },
+  title    : String
+});
+
+var Story  = mongoose.model('Story', storySchema);
+var Author = mongoose.model('Author', authorSchema);
+ +

Можно сохранить ссылки в связанном документе, используя значение идентификатора _id. Ниже создается автор, затем   рассказ, и значение идентификатора id автора сохраняется в поле "author" рассказа.

+ +
var bob = new Author({ name: 'Bob Smith' });
+
+bob.save(function (err) {
+  if (err) return handleError(err);
+
+  //автор Bob создан, создаем рассказ
+  var story = new Story({
+    title: "Bob goes sledding",
+    author: bob._id    // присваиваем полю значение идентификатора Боба. Идентификатор создается по умолчанию!
+  });
+
+  story.save(function (err) {
+    if (err) return handleError(err);
+    // У Боба теперь есть рассказ!
+  });
+});
+ +

Теперь документ "story" ссылается на автора по идентификатору документа "author". Для получения информации об авторе применяется метод populate() (показано ниже).

+ +
Story
+.findOne({ title: 'Bob goes sledding' })
+.populate('author') //подменяет идентификатор автора информацией об авторе!
+.exec(function (err, story) {
+  if (err) return handleError(err);
+  console.log('The author is %s', story.author.name);
+  // выводит "The author is Bob Smith"
+});
+ +
+

Заметка: Внимательные читатели заметили, что автор добавлен к рассказу, но ничего не сделано, чтобы добавить рассказ к массиву рассказов stories автора. Как же тогда получить список всех рассказов конкретного автора? Один из возможных вариантов - добавить автора в массив рассказов, но при этом пришлось бы хранить данные об авторах и рассказах в двух местах и поддерживать их актуальность.

+ +

Лучше получить _id нашего автора author, и применить find() для поиска этого идентификатора в поле "author" всех рассказов.

+ +
Story
+.find({ author : bob._id })
+.exec(function (err, stories) {
+  if (err) return handleError(err);
+  // возвращает все рассказы, у которых идентификатор Боба.
+});
+
+
+ +

Это почти все, что следует знать для работы со связанными данными в нашем руководстве. Более полную информацию можно найти в Population (документация Mongoose).

+ +

Одна схема (модель) - один файл

+ +

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

+ +
// Файл: ./models/somemodel.js
+
+//Требуется Mongoose
+var mongoose = require('mongoose');
+
+//Определяем схему
+var Schema = mongoose.Schema;
+
+var SomeModelSchema = new Schema({
+    a_string          : String,
+    a_date            : Date,
+});
+
+//экспортируется функция для содания класса модели "SomeModel"
+module.exports = mongoose.model('SomeModel', SomeModelSchema );
+ +

You can then require and use the model immediately in other files. Below we show how you might use it to get all instances of the model.

+ +
//Создаем модель SomeModel просто вызовом модуля из файла
+var SomeModel = require('../models/somemodel')
+
+// Используем объект SomeModel (модель) для поиска всех записей в SomeModel
+SomeModel.find(callback_function);
+ +

Установка базы данных MongoDB

+ +

Мы уже немного понимаем,  что может делать Mongoose и как следует проектировать  модели. Теперь самое время начать работу над сайтом  LocalLibrary. Самое первое, что мы должны сделать - установить базу данных MongoDb, в которой будут храниться данные нашей   библиотеки.

+ +

В этом руководстве мы будем использовать базу данных в "песочнице" ("sandbox") - бесплатный облачный сервис, предоставляемый  mLab. Такая база не очень подходит для промышленных вебсайтов, поскольку не имеет избыточности, но она очень удобна для разработки и прототипирования. Мы используем ее, так как она бесплатна, ее легко установить, и потому что mLab - популярный поставщик  базы данных как сервиса, и это может быть разумным выбором для промышленной базы данных (на данный момент другие известные возможности включают Compose, ScaleGrid и MongoDB Atlas).

+ +
+

Заметка: При желании можно установить БД  MongoDb локально, загрузив и установив подходящие для вашей системы двоичные файлы. В этом случае приводимые ниже инструкции не изменятся, за исключением URL базы данных, который нужно будет задать для установки соединения.

+
+ +

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

+ +

После входа в систему вы увидите главную страницу home:

+ +
    +
  1. Щелкните Create New в разделе MongoDB Deployments для создания новой БД.
  2. +
  3. Откроется экран Cloud Provider Selection -  раздела провайдера облака.
    + MLab - screen for new deployment
    + +
      +
    • Выберите план SANDBOX (Free) из раздела Plan Type (тип плана). 
    • +
    • Выберите любого провайдера в разделе Cloud Provider (провайдер облака). Разные провайдеры обслуживают разные регионы (показаны под выбранным типом плана).
    • +
    • Щелкните кнопку Continue.
    • +
    +
  4. +
  5. Откроется экран выбора региона Select Region. +

    Select new region screen

    + +
      +
    • +

      Выберите ближайщий к Вам регион и щелкните кнопку Continue.

      +
    • +
    +
  6. +
  7. +

    Откроется экран Final Details для ввода названия БД.

    + +
      +
    • +

      Введите имя новой базы -  local_library и нажмите Continue.

      +
    • +
    +
  8. +
  9. +

    Откроется экран подтверждения заказа Order Confirmation.
    + Order confirmation screen

    + +
      +
    • +

      Щелкните Submit Order (подтвердить заказ), чтобы создать БД.

      +
    • +
    +
  10. +
  11. +

    Вы вернетесь на главный (home) экран. Щелкните по вновь созданной базе, чтобы открыть экран с детальной информацией. Как видно, в БД нет коллекций (данных).
    + mLab - Database details screen
    +  
    + На форме выше обведен URL для соединения с вашей БДthat you need to use to access your database is displayed on the form above (shown for this database circled above). Чтобы его использовать, необходимо создать пользователя БД, который позже введет этот URL.

    +
  12. +
  13. Щелкните по вкладке Users и выберите кнопку Add database user (добавить пользователя БД).
  14. +
  15. Введите имя пользователя и пароль (дважды), затем нажмите Create (создать). Не отмечайте Make read only (только для чтения)!
    +
  16. +
+ +

Теперь БД создана, и для доступа к ней есть URL, имя пользователя и пароль. Это должно выглядеть примерно так: mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library.

+ +

Установка Mongoose

+ +

Откройте окно команд и перейдите в каталог, в котором создан  каркас вебсайта Local Library. Введите команду install, чтобы установить Mongoose (и ее зависимости), а также добавьте ее в файл package.json, если вы еще не сделали этого ранее, при чтении примера Mongoose Primer.

+ +
npm install mongoose
+
+ +

Подключение к MongoDB

+ +

Откройте /app.js (в корне проекта) и скопируйте приведенный ниже текст, в котором  объявляется объект приложения Express (после строки var app = express();). Замените строку url БД ('insert_your_database_url_here') тем URL, который представляет вашу БД  (т.е. используйте информацию, полученную от mLab).

+ +
//Устанавливаем соединение с mongoose
+var mongoose = require('mongoose');
+var mongoDB = 'insert_your_database_url_here';//замените url!!!
+mongoose.connect(mongoDB);
+mongoose.Promise = global.Promise;
+var db = mongoose.connection;
+db.on('error', console.error.bind(console, 'MongoDB connection error:'));
+ +

Как указано ранее в примере Mongoose primer, этот код задает соединение по умолчанию с привязкой события ошибки error (так что ошибки будут выведены в консоль). 

+ +

Определение схемы LocalLibrary

+ +

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

+ +
/express-locallibrary-tutorial  //the project root
+  /models
+    author.js
+    book.js
+    bookinstance.js
+    genre.js
+
+ +

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

+ +

Скопируйте код схемы автора Author (приведен ниже) в файл ./models/author.js . В схеме определено, что у автора есть обязательные поля имени и фамилии типа String  длиной не более 100 символов, и поля типа Date дата рождения и дата смерти.

+ +
var mongoose = require('mongoose');
+
+var Schema = mongoose.Schema;
+
+var AuthorSchema = new Schema(
+  {
+    first_name: {type: String, required: true, max: 100},
+    family_name: {type: String, required: true, max: 100},
+    date_of_birth: {type: Date},
+    date_of_death: {type: Date},
+  }
+);
+
+// Виртуальное свойство для полного имени автора
+AuthorSchema
+.virtual('name')
+.get(function () {
+  return this.family_name + ', ' + this.first_name;
+});
+
+// Виртуальное свойство - URL автора
+AuthorSchema
+.virtual('url')
+.get(function () {
+  return '/catalog/author/' + this._id;
+});
+
+//Export model
+module.exports = mongoose.model('Author', AuthorSchema);
+
+
+ +

Мы объявим также в схеме AuthorSchema виртуальное свойство "url" , которое позволит получить абсолютный  URL конкретного экземпляра модели — используем это свойство  в шаблонах, если потребуется получить связь с конкретным автором.

+ +
+

Заметка: Объявить  в схеме URL как виртуальные свойства - хорошая идея,  т.к. URL отдельного элемента при необходимости изменения требует коррекции только в одном месте.
+ Сейчас связь при помощи этого URL еще не работает, так как у нас еще нет кода, поддерживающего маршруты для экземпляров модели.  Мы построим его в следующей статье!

+
+ +

В конце модуля экспортируется модель.

+ +

Модель книги Book

+ +

Скопируйте код схемы Book (приведен ниже) в файл ./models/book.js. Большая часть кода подобна коду для модели автора — объявляется схема с рядом строковых полей, с виртуальным свойством URL для получения  URL конкретных книг, затем модель экспортируется.

+ +
var mongoose = require('mongoose');
+
+var Schema = mongoose.Schema;
+
+var BookSchema = new Schema(
+  {
+    title: {type: String, required: true},
+    author: {type: Schema.ObjectId, ref: 'Author', required: true},
+    summary: {type: String, required: true},
+    isbn: {type: String, required: true},
+    genre: [{type: Schema.ObjectId, ref: 'Genre'}]
+  }
+);
+
+// Virtual for book's URL
+BookSchema
+.virtual('url')
+.get(function () {
+  return '/catalog/book/' + this._id;
+});
+
+//Export model
+module.exports = mongoose.model('Book', BookSchema);
+
+ +

Основное отличие в том, что созданы две ссылки на другие модели:

+ + + +

Модель экземпляра книги BookInstance

+ +

Наконец, скопируйте код схемы BookInstance (приведен ниже) в файл ./models/bookinstance.js. Схема BookInstance представляет конкретный экземпляр книги, которую можно одолжить на время, и содержит информацию о доступности экземпляров книги, о дате возврата одолженной книги, о деталях версии или печатного экземпляра.

+ +
var mongoose = require('mongoose');
+
+var Schema = mongoose.Schema;
+
+var BookInstanceSchema = new Schema(
+  {
+    book: { type: Schema.ObjectId, ref: 'Book', required: true }, //ссылка на книгу
+    imprint: {type: String, required: true},
+    status: {type: String, required: true, enum: ['Available', 'Maintenance', 'Loaned', 'Reserved'], default: 'Maintenance'},
+    due_back: {type: Date, default: Date.now}
+  }
+);
+
+// Virtual for bookinstance's URL
+BookInstanceSchema
+.virtual('url')
+.get(function () {
+  return '/catalog/bookinstance/' + this._id;
+});
+
+//Export model
+module.exports = mongoose.model('BookInstance', BookInstanceSchema);
+ +

В этой схеме новыми являются опции поля:

+ + + +

Все остальное знакомо по предыдущим схемам.

+ +

Модель жанра Genre - проверьте себя!

+ +

Откройте файл ./models/genre.js  и создайте схему для хранения жанра (категории книги, т.е. художественная или научная, романтика или военная история и т.д.).

+ +

Определение будет подобно другим моделям:

+ + + +

Тестирование — создаем элементы БД

+ +

Вот так. У нас теперь есть все модели для создания сайта!

+ +

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

+ +
    +
  1. Загрузите (или создайте) файл populatedb.js в каталоге express-locallibrary-tutorial (на том же уровне, что и package.json). + +
    +

    Заметка: Не обязательно понимать, как работает populatedb.js; он просто помещает некоторые данные в базу данных.

    +
    +
  2. +
  3. Введите в корне проекта команду для установки модуля async, который потребуется скрипту populatedb.js (роль async обсудим в следующих руководствах) +
    npm install async
    +
  4. +
  5. Запустите скрипт из командной строки, используя node. В качестве параметра укажите URL вашей БД MongoDB (тот самый, которым вы ранее заменили insert_your_database_url_here в файле app.js ): +
    node populatedb <your mongodb url>​​​​
    +
  6. +
  7. Скрипт должен выполниться до конца, выводя в терминал создаваемые элементы.
  8. +
+ +
+

Совет: Откройте свою базу на Lab. Вы сможете увидеть коллекции Book, Author, Genre, BookInstance (книги, авторы, жанры, экземпляры книг) и просмотреть содержащиеся в них документы.

+
+ +

Итог

+ +

В этой статье мы познакомились с БД и ОРМ (объектно-реляционными моделями) в системе Node/Express, узнали, как определяются схемы и модели Mongoose. Мы применили эти знания при проектировании и реализации моделей Book, BookInstance, Author и Genre для вебсайта LocalLibrary.

+ +

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

+ +

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

+ + + +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}

+ +

 

+ +

In this module

+ + + +

 

diff --git a/files/ru/learn/server-side/express_nodejs/routes/index.html b/files/ru/learn/server-side/express_nodejs/routes/index.html new file mode 100644 index 0000000000..c8610eba1b --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/routes/index.html @@ -0,0 +1,655 @@ +--- +title: 'Учебник Express часть 4: Маршруты и контроллеры' +slug: Learn/Server-side/Express_Nodejs/routes +translation_of: Learn/Server-side/Express_Nodejs/routes +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}
+ +

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

+ + + + + + + + + + + + +
+ + + + + + +
Предварительные знания:
+
Прочесть  введение в Express/Node . Завершить предыдущие уроки (включая Express Tutorial Part 3: Using a Database (with Mongoose)).
Цель:Понять, как создать простые маршруты. Настроить конечные точки URL.
+ +

Обзор

+ +

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

+ +

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

+ +

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

+ + + +

+ +

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

+ +

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

+ +

Маршруты - основы

+ +

Маршруты - это часть кода Express, связывающая HTTP действия (GET, POST, PUT, DELETE, etc.), URL пути (шаблона), и функцию, которая обрабатывает этот шаблон.

+ +

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

+ +
+

Заметка: Маршруты приложения Express уже кратко рассматривались в Express Introduction > Creating route handlers (Введение -> Создание обработчиков маршрутов).  Применение Router обеспечивает лучшую поддержку модульности (как обсуждается в первой подсекции ниже), а в остальном очень похоже на определение маршрутов непосредственно в объекте приложения Express.

+
+ +

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

+ +

Определение и использование отдельных модулей маршрутов

+ +

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

+ +

Первым делом в модуле wiki.js создадим маршруты для wiki .  Код сначала импортирует объект приложения Express, использует его для получения объекта Router и затем, применяя метод get(), добавляет к объекту пару маршрутов. В завершение модуль экспортирует объект Router .

+ +
// wiki.js - Wiki route module.
+
+var express = require('express');
+var router = express.Router();
+
+// Home page route.
+router.get('/', function (req, res) {
+  res.send('Wiki home page');
+})
+
+// About page route.
+router.get('/about', function (req, res) {
+  res.send('About this wiki');
+})
+
+module.exports = router;
+
+
+ +
+

Заметка: В примере callback-функции обработчиков маршрутов определены непосредственно в функциях роутеров. А в LocalLibrary мы определим эти callback-функции в отдельном модуле контроллера.

+
+ +

Чтобы использовать модуль роутера в главном приложении, прежде всего следует выполнить require() модуля маршрута (wiki.js). Потом вызовем use() для  приложения Express с аргументом, в котором указан URL-путь 'wiki', что добавит  Router к пути обработки промежуточного слоя.

+ +
var wiki = require('./wiki.js');
+// ...
+app.use('/wiki', wiki);
+ +

После этого два маршрута, определенные в нашем модуле маршрутов wiki, станут доступны из /wiki/ и /wiki/about/.

+ +

Функции Route

+ +

В модуле выше определена пара типовых функций маршрута. Маршрут "about" (еще раз показан ниже) определен при помощи метода Router.get(), который отвечает только на  HTTP GET-запросы. Первый аргумент метода - URL-путь, а второй -  callback-функция, которая будет вызвана, если получен HTTP GET-запрос с указанным путем.

+ +
router.get('/about', function (req, res) {
+  res.send('About this wiki');
+})
+
+ +

Эта callback-функция имеет три аргумента takes three arguments (обычно именуемых как указано: req, res, next), которые соответствуют объекту HTTP запроса, ответу HTTP, и следующей 
+ функции в цепочке промежуточных элементов.

+ +
+

Заметка: Фукции в Router - это промежуточный слой (middleware) are Express , что означает, что они должны или завершить (ответить на) запрос reqили вызвать следующую (next) функцию в цепочке. В нашем случае запрос завершается вызовом send(), поэтому аргумент next не нужен (и поэтому не указан).

+ +

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

+
+ +

Здесь, когда приходит GET-запрос с путем ('/about') callback-функция при ответе вызывает send() , возвращая строку "About this wiki". Существует  ряд других методов ответа , завершающих цикл запрос-ответ. Например, можно вызвать res.json() , чтобы послать ответ JSON, или res.sendFile(), чтобы послать файл. Метод ответа, который будет использован чаще всего при построении нашей библиотеки - это  render(), создающий, на основе шаблонов и данных, и возвращающий  HTML-файлы —мы поговорим об этом подробнее в следующей статье!

+ +

HTTP глаголы (действия)

+ +

Рассмотренный пример использует метод Router.get() для ответа на  HTTP GET- запросы с указанным путем.

+ +

Кроме того, Router обеспечивает также методы маршрутизации для других HTTP глаголов, которые обычно используются точно таким же способом: post(), put(), delete(), options(), trace(), copy(), lock(), mkcol(), move(), purge(), propfind(), proppatch(), unlock(), report(), ​​​​​​ mkactivity(), checkout(), merge(), m-search(), notify(), subscribe(), unsubscribe(), patch(), search(), и connect().

+ +

Например, код ниже делает то же, что и предыдущий, с путем /about, но отвечает на  HTTP POST-запросы.

+ +
router.post('/about', function (req, res) {
+  res.send('About this wiki');
+})
+ +

Маршруты путей

+ +

Маршруты путей определяют конечные точки, в которых могут быть сделаны запросы.  В уже рассмотренных примерах это были просто строки, которые использовались точно так, как были записаны: '/', '/about', '/book', '/any-random.path'.

+ +

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

+ + + +

Пути маршрутов могут быть также регулярными выражениями  JavaScript. Например, путь маршрутов (ниже) будет соответствовать catfish и dogfish, но не catflap, catfishhead, и так далее. Отметим, регулярное выражение как путь использует синтаксис регулярных выражений (это не строка в кавычках, как в предыдущих случаях).

+ +
app.get(/.*fish$/, function (req, res) {
+  ...
+})
+ +
+

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

+
+ +

Параметры маршрутов

+ +

Параметры маршрутов - это именованные сегменты URL , которые используются для выбора значений из указанной позиции  URL. Именованные сегменты начинаются двоеточием, после которого следует имя (например, /:your_parameter_name/.  Выбранные значения сохраняются в объекте req.params, причем имя параметра  используется как ключ (т.е. req.params.your_parameter_name).

+ +

Предположим, например, что URL содержит информацию о пользователях и книгах:  http://localhost:3000/users/34/books/8989. Можно извлечь эту информацию (см. ниже) в параметры userId и bookId пути:

+ +
app.get('/users/:userId/books/:bookId', function (req, res) {
+  // доступ к userId через: req.params.userId
+  // доступ к bookId через: req.params.bookId
+  res.send(req.params);
+})
+
+ +

Имена параметров пути должны состоять из “символов слова” (A-Z, a-z, 0-9, и _).

+ +
+

Заметка: URL /book/create будет соответствовать маршрутам вида /book/:bookId (и 'create' станет значением "bookId"). Будет использован первый маршрут, соответствующий введенному  URL, поэтому, если необходимо обрабатывать URL вида /book/create отдельно, обработчик этого маршрута должен быть расположен до маршрута /book/:bookId .

+
+ +

Для начала этих сведений достаточно - если потребуется, можно найти дополнительную информацию в документации  Express: Basic routing (основы маршрутизации) и Routing guide (руководство по маршрутизации).  В следующем разделе показано, как задать маршруты и контроллеры для нашей библиотеки LocalLibrary.

+ +

Маршруты, необходимые для библиотеки LocalLibrary

+ +

Те URL, котрые в итоге будут нужны для наших страниц, показаны ниже.  Слово object должно быть заменено на имя каждой из наших моделей (book, bookinstance, genre, author),  слово objects - множественное число для object, а id - уникальное значение для поля(_id), которое Mongoose создает по умолчанию для каждого экземпляра модели.

+ + + +

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

+ +

В противоположность этому, другие URL используются для работы с  определенными экземплярами документов и моделей— индивидуальность элементов кодируется в  URL (как <id> выше). Параметры путей используются для извлечения информации и передачи ее в обработчик пути (и в следующей статье мы применим этот прием для того, чтобы динамически определять, какую информацию следует получить из БД). By encoding the information in our URL we only need one route for every resource of a particular type (e.g. one route to handle the display of every single book item).

+ +
+

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

+
+ +

Далее мы создадим callback-функции обработчиков маршрутов и код маршрутов для всех указанных выше URL.

+ +

Создаем callback-функции обработчиков маршрутов

+ +

Перед определением маршрутов сначала создадим фиктивные (каркасные) callback-функции, которые они будут вызывать. Эти функции будут храниться в отдельных модулях -"контроллерах" для моделей Book, BookInstance, Genre, и Author (можно использовать любую структуру моделей и файлов, но кажется, что выбранная обеспечивает приемлемую модульность нашего проекта).

+ +

Начнем с создания каталога для контроллеров в корне проекта (/controllers), а затем создадим отдельные файлы (модули) контроллеров для работы с моделями:

+ +
/express-locallibrary-tutorial  //корень проекта
+  /controllers
+    authorController.js
+    bookController.js
+    bookinstanceController.js
+    genreController.js
+ +

Контроллер автора

+ +

Скопируем следующий код в файл /controllers/authorController.js:

+ +
var Author = require('../models/author');
+
+// Показать список всех авторов.
+exports.author_list = function(req, res) {
+    res.send('NOT IMPLEMENTED: Author list');
+};
+
+// Показать подробную страницу для данного автора.
+exports.author_detail = function(req, res) {
+    res.send('NOT IMPLEMENTED: Author detail: ' + req.params.id);
+};
+
+// Показать форму создания автора по запросу GET.
+exports.author_create_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Author create GET');
+};
+
+// Создать автора по запросу POST.
+exports.author_create_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Author create POST');
+};
+
+// Показать форму удаления автора по запросу GET.
+exports.author_delete_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Author delete GET');
+};
+
+// Удалить автора по запросу POST.
+exports.author_delete_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Author delete POST');
+};
+
+// Показать форму обновления автора по запросу GET.
+exports.author_update_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Author update GET');
+};
+
+// Обновить автора по запросу POST.
+exports.author_update_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Author update POST');
+};
+
+ +

В модуле сначала подключается (requires) модель, которая далее будет использована для получения данных и их обновления. Далее экспортируются функции для каждого URL, который мы хотим обрабатывать (операции create-создать, update-обновить и delete-удалить используют формы, следовательно, должны быть дополнительные методы для обработки post-запросов от форм - эти методы обсуждаются далее, в статье "forms article" ("формы")).

+ +

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

+ +

BookInstance controller

+ +

Скопируйте следующий код в файл /controllers/bookinstanceController.js  (он построен по образцу модуля контроллера для автора Author ):

+ +
var BookInstance = require('../models/bookinstance');
+
+// Display list of all BookInstances.
+exports.bookinstance_list = function(req, res) {
+    res.send('NOT IMPLEMENTED: BookInstance list');
+};
+
+// Display detail page for a specific BookInstance.
+exports.bookinstance_detail = function(req, res) {
+    res.send('NOT IMPLEMENTED: BookInstance detail: ' + req.params.id);
+};
+
+// Display BookInstance create form on GET.
+exports.bookinstance_create_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: BookInstance create GET');
+};
+
+// Handle BookInstance create on POST.
+exports.bookinstance_create_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: BookInstance create POST');
+};
+
+// Display BookInstance delete form on GET.
+exports.bookinstance_delete_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: BookInstance delete GET');
+};
+
+// Handle BookInstance delete on POST.
+exports.bookinstance_delete_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: BookInstance delete POST');
+};
+
+// Display BookInstance update form on GET.
+exports.bookinstance_update_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: BookInstance update GET');
+};
+
+// Handle bookinstance update on POST.
+exports.bookinstance_update_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: BookInstance update POST');
+};
+
+ +

Контроллер жанра

+ +

Скопируйте следующий код в файл /controllers/genreController.js (он построен по образцу модулей контроллеров для автора Author и экземпляра книги BookInstance):

+ +
var Genre = require('../models/genre');
+
+// Display list of all Genre.
+exports.genre_list = function(req, res) {
+    res.send('NOT IMPLEMENTED: Genre list');
+};
+
+// Display detail page for a specific Genre.
+exports.genre_detail = function(req, res) {
+    res.send('NOT IMPLEMENTED: Genre detail: ' + req.params.id);
+};
+
+// Display Genre create form on GET.
+exports.genre_create_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Genre create GET');
+};
+
+// Handle Genre create on POST.
+exports.genre_create_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Genre create POST');
+};
+
+// Display Genre delete form on GET.
+exports.genre_delete_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Genre delete GET');
+};
+
+// Handle Genre delete on POST.
+exports.genre_delete_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Genre delete POST');
+};
+
+// Display Genre update form on GET.
+exports.genre_update_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Genre update GET');
+};
+
+// Handle Genre update on POST.
+exports.genre_update_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Genre update POST');
+};
+
+ +

Контроллер книги

+ +

Скопируйте следующий код в файл /controllers/bookController.js. Он построен по образцу других модулей контроллеров, но еще содержит функцию index() для вывода странички с приветствием:

+ +
var Book = require('../models/book');
+
+exports.index = function(req, res) {
+    res.send('NOT IMPLEMENTED: Site Home Page');
+};
+
+// Display list of all books.
+exports.book_list = function(req, res) {
+    res.send('NOT IMPLEMENTED: Book list');
+};
+
+// Display detail page for a specific book.
+exports.book_detail = function(req, res) {
+    res.send('NOT IMPLEMENTED: Book detail: ' + req.params.id);
+};
+
+// Display book create form on GET.
+exports.book_create_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Book create GET');
+};
+
+// Handle book create on POST.
+exports.book_create_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Book create POST');
+};
+
+// Display book delete form on GET.
+exports.book_delete_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Book delete GET');
+};
+
+// Handle book delete on POST.
+exports.book_delete_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Book delete POST');
+};
+
+// Display book update form on GET.
+exports.book_update_get = function(req, res) {
+    res.send('NOT IMPLEMENTED: Book update GET');
+};
+
+// Handle book update on POST.
+exports.book_update_post = function(req, res) {
+    res.send('NOT IMPLEMENTED: Book update POST');
+};
+
+ +

Создание модуля для маршрута catalog

+ +

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

+ +

Каркас приложения уже содержит каталог ./routes, в котором есть маршруты для index и users. Внутри этого каталога создадим еще один файл маршрутов — catalog.js ( см. ниже).

+ +
/express-locallibrary-tutorial //the project root
+  /routes
+    index.js
+    users.js
+    catalog.js
+ +

Скопируйте приведенный ниже код в файл /routes/catalog.js :

+ +
var express = require('express');
+var router = express.Router();
+
+// Требующиеся модули контроллеров.
+var book_controller = require('../controllers/bookController');
+var author_controller = require('../controllers/authorController');
+var genre_controller = require('../controllers/genreController');
+var book_instance_controller = require('../controllers/bookinstanceController');
+
+/// BOOK ROUTES МАРШРУТЫ КНИГ///
+
+// GET catalog home page.
+router.get('/', book_controller.index);
+
+// GET request for creating a Book. NOTE This must come before routes that display Book (uses id).
+// GET запрос для создания книги. Должен появиться до маршрута, показывающего книгу(использует id)
+router.get('/book/create', book_controller.book_create_get);
+
+// POST request for creating Book.
+router.post('/book/create', book_controller.book_create_post);
+
+// GET request to delete Book.
+router.get('/book/:id/delete', book_controller.book_delete_get);
+
+// POST request to delete Book.
+router.post('/book/:id/delete', book_controller.book_delete_post);
+
+// GET request to update Book.
+router.get('/book/:id/update', book_controller.book_update_get);
+
+// POST request to update Book.
+router.post('/book/:id/update', book_controller.book_update_post);
+
+// GET request for one Book.
+router.get('/book/:id', book_controller.book_detail);
+
+// GET request for list of all Book items.
+router.get('/books', book_controller.book_list);
+
+/// AUTHOR ROUTES ///
+
+// GET request for creating Author. NOTE This must come before route for id (i.e. display author).
+// GET-запрос для создания автора. Должен появиться до маршрута для id (для вывода автора)
+router.get('/author/create', author_controller.author_create_get);
+
+// POST request for creating Author.
+router.post('/author/create', author_controller.author_create_post);
+
+// GET request to delete Author.
+router.get('/author/:id/delete', author_controller.author_delete_get);
+
+// POST request to delete Author.
+router.post('/author/:id/delete', author_controller.author_delete_post);
+
+// GET request to update Author.
+router.get('/author/:id/update', author_controller.author_update_get);
+
+// POST request to update Author.
+router.post('/author/:id/update', author_controller.author_update_post);
+
+// GET request for one Author.
+router.get('/author/:id', author_controller.author_detail);
+
+// GET request for list of all Authors.
+router.get('/authors', author_controller.author_list);
+
+/// GENRE ROUTES ///
+
+// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
+// GET-запрос для создания жанра. Должен появиться до маршрута, выводящего жанр (( с использованием id)
+router.get('/genre/create', genre_controller.genre_create_get);
+
+//POST request for creating Genre.
+router.post('/genre/create', genre_controller.genre_create_post);
+
+// GET request to delete Genre.
+router.get('/genre/:id/delete', genre_controller.genre_delete_get);
+
+// POST request to delete Genre.
+router.post('/genre/:id/delete', genre_controller.genre_delete_post);
+
+// GET request to update Genre.
+router.get('/genre/:id/update', genre_controller.genre_update_get);
+
+// POST request to update Genre.
+router.post('/genre/:id/update', genre_controller.genre_update_post);
+
+// GET request for one Genre.
+router.get('/genre/:id', genre_controller.genre_detail);
+
+// GET request for list of all Genre.
+router.get('/genres', genre_controller.genre_list);
+
+/// BOOKINSTANCE ROUTES ///
+
+// GET request for creating a BookInstance. NOTE This must come before route that displays BookInstance (uses id).
+// GET-запрос для создания экземпляра книги. Должен появиться до маршрута, выводящего BookInstance с использованием id
+router.get('/bookinstance/create', book_instance_controller.bookinstance_create_get);
+
+// POST request for creating BookInstance.
+router.post('/bookinstance/create', book_instance_controller.bookinstance_create_post);
+
+// GET request to delete BookInstance.
+router.get('/bookinstance/:id/delete', book_instance_controller.bookinstance_delete_get);
+
+// POST request to delete BookInstance.
+router.post('/bookinstance/:id/delete', book_instance_controller.bookinstance_delete_post);
+
+// GET request to update BookInstance.
+router.get('/bookinstance/:id/update', book_instance_controller.bookinstance_update_get);
+
+// POST request to update BookInstance.
+router.post('/bookinstance/:id/update', book_instance_controller.bookinstance_update_post);
+
+// GET request for one BookInstance.
+router.get('/bookinstance/:id', book_instance_controller.bookinstance_detail);
+
+// GET request for list of all BookInstance.
+router.get('/bookinstances', book_instance_controller.bookinstance_list);
+
+module.exports = router;
+
+ +

Модуль загружает Express и использует его для создания объекта Router . В маршутизаторе задаются маршруты и производится их экспорт.

+ +

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

+ +

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

+ +

Обновление модуля маршрута index

+ +

Все новые маршруты заданы, а маршрут на начальную страницу остался без изменения. Давайте перенаправим его на новую страницу "index", которая создана в каталоге  '/catalog'.

+ +

Откройте /routes/index.js и замените существущий маршрут нприведенную ниже.

+ +
// GET home page.
+router.get('/', function(req, res) {
+  res.redirect('/catalog');
+});
+ +
+

Заметка: Это первое использование метода ответа redirect() . Он делает перенаправление на указанную страницу, и по умолчанию устанавливает код возврата HTTP в "302 Found" (найдено). Если требуется, можно изменить код возврата. Путь можно задавать как абсолютный или как относительный.

+
+ +

Обновление app.js

+ +

Завершающий шаг - добавление маршрутов в цепочку промежуточного слоя. Это будет сделано в app.js.

+ +

Откройте файл app.js и поместите require для маршрута каталог  ниже других маршрутов (добавьте третью строку. показанную ниже, после имеющихся двух строк):

+ +
var indexRouter = require('./routes/index');
+var usersRouter = require('./routes/users');
+var catalogRouter = require('./routes/catalog');  //Import routes for "catalog" area of site
+ +

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

+ +
app.use('/', indexRouter);
+app.use('/users', usersRouter);
+app.use('/catalog', catalogRouter);  // Add catalog routes to middleware chain.
+ +
+

Заметка: Мы добавили модуль каталога в путь'/catalog'.  Этот путь будет предшествовать всем путям, определенным в модуле каталога. Например, для доступа к списку книг URL будет таким: /catalog/books/.

+
+ +

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

+ +

Проверка маршрутов

+ +

Чтобы проверить маршруты, сначала запустим веб-сайт обычным способом

+ + + +

После запуска перейдите к совокупности URL нашей LocalLibrary, и проверьте, что не появляется страница ошибки (HTTP 404). Небольшая часть наших URL для удобства приводится ниже:

+ + + +

Итог

+ +

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

+ +

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

+ +

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

+ + + +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}

+ + + +

In this module

+ + diff --git a/files/ru/learn/server-side/express_nodejs/skeleton_website/index.html b/files/ru/learn/server-side/express_nodejs/skeleton_website/index.html new file mode 100644 index 0000000000..51af5515d4 --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/skeleton_website/index.html @@ -0,0 +1,507 @@ +--- +title: 'Учебник Express часть 2: Создание скелета сайта' +slug: Learn/Server-side/Express_Nodejs/skeleton_website +translation_of: Learn/Server-side/Express_Nodejs/skeleton_website +--- +
{{LearnSidebar}}
+ +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}

+ +

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

+ + + + + + + + + + + + +
Необходимые знания:Установить среду разработки Node. Просмотреть учебник Express.
Задача:Научиться запускать свои проекты используя Express Application Generator.
+ +

Обзор

+ +

В этой статье показано, как создать каркас сайта с помощью средства Express Application Generator. Каркас затем можно будет заполнить с помощью путей сайта, шаблонов/представлений и обращений к базе данных. Мы используем это средство для создания основы нашего сайта Local Library. К основе будет добавлен код, необходимый сайту. Создание каркаса чрезвычайно просто -- требуется только вызвать генератор в командной строке, указав имя нового проекта, дополнительно можно указать также движок шаблона сайта и генератор CSS.

+ +

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

+ +
+

ЗамечаниеExpress Application Generator — не единственный генератор Express-приложений, и созданный проект --не единственный жизнеспособный способ организации ваших файлов и каталогов. Однако созданный сайт имеет модульную структуру, которую легко понять и расширить. О минимальном Express приложении смотрите Hello world example в документации Express.

+
+ +

Применение генератора приложений

+ +

Вы уже должны были устанавить express-generator, читая статью установка среды разработки Node. Напомним, что генератор установлен с помощью менеджера пакетов NPM, при выполнении команды:

+ +
npm install express-generator -g
+
+ +

Express-generator имеет ряд параметров, которые можно увидеть, выполнив команду express --help (или express -h):

+ +
> express --help
+
+  Usage: express [options] [dir]
+
+  Options:
+
+    -h, --help           output usage information (информация по применению)
+        --version        output the version number (номер версии express)
+    -e, --ejs            add ejs engine support (добавить поддержку движка ejs)
+        --pug            add pug engine support (добавить поддержку движка pug)
+        --hbs            add handlebars engine support (добавить поддержку движка handlebar)
+    -H, --hogan          add hogan.js engine support (добавить поддержку движка hogan.js)
+    -v, --view <engine>  add view <engine> support (ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
+                         (добавить поддержку движков представлений. По умолчанию - jade)
+    -c, --css <engine>   add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
+                         (добавить поддержку движков стилей, по умолчанию - простой CSS)
+        --git            add .gitignore (добавить поддержку .gitignore)
+    -f, --force          force on non-empty directory (работать в каталоге с файлами)
+
+ +

Команда express создаст проект в текущем каталоге с использованием (устаревшего) движка представления Jade и обычного CSS. Если указать express name, проект будет создан в подкаталоге name текущего каталога. 

+ +
express
+ +

Можно выбрать движок представления (шаблон), используя --view; параметр --css позволяет выбрать движок для создания CSS.

+ +
+

Заметка: Другие опции (--hogan, --ejs, --hbs и пр.) для выбора шаблонизатора устарели. Используйте --view (или -v)!

+
+ +

Какой движок представлений следует использовать?

+ +

Express-generator дает возможность сконфигурировать несколько популярных движков, включая EJS, Hbs, Pug (Jade), Twig, и Vash, но по умолчанию  выбран Jade. Экспресс сразу после установки может поддерживать большое количество и других шаблонизаторов.

+ +
+

Заметка: При желании использовать шаблонизатор, который не поддерживается генератором,  просмотрите  документацию Using template engines with Express  и документацию для нужного шаблонизатора.

+
+ +

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

+ + + +
+

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

+
+ +

Для этого проекта мы  используем шаблонизатор Pug (в прошлом назывался Jade) -- один из популярнейших Express/JavaScript шаблонизаторов, который поддерживается в Express-generator "из коробки".

+ +

Какие шаблонизаторы CSS следует использовать?

+ +

Express Application Generator позволяет создавать проекты, настроенные для применения шаблонизаторов CSS: LESS, SASS, Compass, Stylus.

+ +
+

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

+
+ +

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

+ +

Какую базу данных следует использовать?

+ +

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

+ +

Мы обсудим взаимодействие с базой данных в следующей статье.

+ +

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

+ +

Разрабатывая пример - приложение Local Library, мы построим проект с именем express-locallibrary-tutorial. Используем библиотеку шаблонов Pug, а движок CSS применять не будем.

+ +

Выберем место для нового проекта — каталог express-locallibrary-tutorial - и выполним команду:

+ +
express express-locallibrary-tutorial --view=pug
+
+ +

Будет создан каталог express-locallibrary-tutorial и выведен список созданных внутри каталога проектных файлов.

+ +
   create : express-locallibrary-tutorial
+   create : express-locallibrary-tutorial/package.json
+   create : express-locallibrary-tutorial/app.js
+   create : express-locallibrary-tutorial/public/images
+   create : express-locallibrary-tutorial/public
+   create : express-locallibrary-tutorial/public/stylesheets
+   create : express-locallibrary-tutorial/public/stylesheets/style.css
+   create : express-locallibrary-tutorial/public/javascripts
+   create : express-locallibrary-tutorial/routes
+   create : express-locallibrary-tutorial/routes/index.js
+   create : express-locallibrary-tutorial/routes/users.js
+   create : express-locallibrary-tutorial/views
+   create : express-locallibrary-tutorial/views/index.pug
+   create : express-locallibrary-tutorial/views/layout.pug
+   create : express-locallibrary-tutorial/views/error.pug
+   create : express-locallibrary-tutorial/bin
+   create : express-locallibrary-tutorial/bin/www
+
+   install dependencies:
+     > cd express-locallibrary-tutorial && npm install
+
+   run the app:
+     > SET DEBUG=express-locallibrary-tutorial:* & npm start
+ +

После списка файлов генератор выведет инструкции для установки зависимостей (указанных в файле package.json) и запуска приложения (инструкции предназначены для Windows; для Linux/Mac OS X они могут слегка отличаться).

+ +

Запускаем каркас сайта

+ + + +

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

+ + + +
    +
  1. Прежде всего установим зависимости (команда install запросит все пакеты зависимостей, указанные в файле package.json). + +
    cd express-locallibrary-tutorial
    +npm install
    +
  2. +
  3. Затем запустим приложение. +
      +
    • В Windows используйте команду: +
      SET DEBUG=express-locallibrary-tutorial:* & npm start
      +
    • +
    • В Mac OS X или Linux используйте команду: +
      DEBUG=express-locallibrary-tutorial:* npm start
      +
      +
    • +
    +
  4. +
  5. Откроем http://localhost:3000/ в браузере. Мы должны увидеть такую страницу:
  6. +
+ +

Browser for default Express app generator website

+ +

У нас получилось веб-приложение на базе Express, работающее по адресу localhost:3000.

+ +
+

Заметка: Можно также запустить приложение командой npm start. Переменная DEBUG, указанная в примере, включает логгирование в консоль для дальнейшей отладки. Так, при посещении страницы веб-приложения, вы увидите похожий вывод в консоль:

+ +
>SET DEBUG=express-locallibrary-tutorial:* & npm start
+
+> express-locallibrary-tutorial@0.0.0 start D:\express-locallibrary-tutorial
+> node ./bin/www
+
+  express-locallibrary-tutorial:server Listening on port 3000 +0ms
+GET / 200 288.474 ms - 170
+GET /stylesheets/style.css 200 5.799 ms - 111
+GET /favicon.ico 404 34.134 ms - 1335
+
+ +

Обеспечиваем
+ перезапуск сервера при изменении файлов

+ +

Любые изменения, внесенные на веб-сайт Express, не будут отображаться до перезапуска сервера. Остановка (Ctrl-C) и перезапуск сервера каждый раз после внесения изменений быстро становится раздражающей, поэтому стоит автоматизировать перезапуск.

+ +

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

+ +
npm install --save-dev nodemon
+ + + +

Если вы предпочитаете установить nodemon глобально, не только для этого проекта, надо выполнить команду

+ + + +
npm install -g nodemon
+ + + +

В файле package.json проекта появится  новый раздел с этой зависимостью (на вашей машине номер версии nodemon может бытьдругим) :

+ +
  "devDependencies": {
+    "nodemon": "^1.11.0"
+  }
+
+ +

Поскольку nodemon не установлен глобально, его нельзя запустить из командной строки (пока мы не добавим его в путь), но его можно вызвать из сценария NPM, так как NPM знает все об установленных пакетах.  Раздел  scripts в файле package.json исходно будет содержать одну строку, которая начинается с  "start". Обновите его, поставив запятую в конце строки, и добавьте строку "devstart",  показанную ниже:

+ +
  "scripts": {
+    "start": "node ./bin/www",
+    "devstart": "nodemon ./bin/www"
+  },
+
+ +

Теперь можно запустить сервер почти так же, как и ранее, но командой npm run devstart:

+ + + +
SET DEBUG=express-locallibrary-tutorial:* & npm run devstart
+ + + +
DEBUG=express-locallibrary-tutorial:* npm run devstart
+ +
+

Заметка: Сейчас после изменения любого файла проекта сервер будет перезапускаться (или можно самостоятельно перезапустить его, введя rs в командной строке). Вам все равно придется обновить страницу в браузере .

+ +

Теперь мы должны выполнять команду "npm run <scriptname>" а не просто  npm start, поскольку "start", это, по сути, команда NPM, сопоставленная сценарию в файле package.json. Можно заменить команду в сценарии "start", но, так как мы хотим использовать nodemon только во время разработки, разумно создать новую команду сценария.

+
+ +

Созданный проект

+ +

Давайте посмотрим на созданный проект.

+ +

Структура каталогов

+ +

После установки зависимостей проект имеет такую структуру файлов (файлы - это элементы без префикса"/"). Файл package.json определяет имя файла с приложением, сценарии запуска, зависимости и др.  Сценарий запуска задает точку входа приложения, у нас -- файл JavaScript /bin/www. Этот файл настраивает некоторые обработчики ошибок приложения, а затем загружает  app.js для выполнения остальной работы. Пути приложения хранятся в отдельных модулях каталога routes/.  Шаблоны хранятся в каталоге /views.

+ +
/express-locallibrary-tutorial
+    app.js
+    /bin
+        www
+    package.json
+    /node_modules
+        [about 4,500 subdirectories and files]
+    /public
+        /images
+        /javascripts
+        /stylesheets
+            style.css
+    /routes
+        index.jsusers.js
+    /views
+        error.pug
+        index.puglayout.pug
+
+
+ +

Далее файлы описаны более подробно.

+ +

package.json

+ +

Файл package.json указывает зависимости приложения и содержит другие данные:

+ +
{
+  "name": "express-locallibrary-tutorial",
+  "version": "0.0.0",
+  "private": true,
+  "scripts": {
+    "start": "node ./bin/www",
+    "devstart": "nodemon ./bin/www"
+  },
+  "dependencies": {
+    "body-parser": "~1.15.2",
+    "cookie-parser": "~1.4.3",
+    "debug": "~2.2.0",
+    "express": "~4.14.0",
+    "morgan": "~1.7.0",
+    "pug": "~2.0.0-beta6",
+    "serve-favicon": "~2.3.0"
+  },
+  "devDependencies": {
+    "nodemon": "^1.11.0"
+  }
+}
+ +

Зависимости включают пакет express и пакет для выбранного движка представления (pug). Кроме того, указаны пакеты, полезные во многих веб-приложениях:

+ + + +

Раздел "scripts" определяет скрипт" start", выполняемый при запуске сервера командой npm start.  Можно видеть, что самом деле выполняется команда node ./bin/www.  Кроме того, определяется script "devstart", который вызывается командой npm run devstart. Запускается тот же файл ./bin/www ,но командой nodemon вместо node.

+ +
"scripts": {
+    "start": "node ./bin/www",
+    "devstart": "nodemon ./bin/www"
+  },
+ +

Файл www

+ +

Файл /bin/www – это входная точка приложения. Сначала в файле создается объект основного приложения, расположенного в app.js — выполняется app=require(./app).

+ +
#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+
+ +
+

Заметка: require() -- это глобальная функция node  для импорта модулей в текущий файл.  Для модуля app.js указан относительный путь, а расширение файла по умолчанию (.js) опущено.

+
+ +

Оставшаяся часть кода настраивает порт сервера node для HTTP (определен в переменной среды или 3000, если не определен), и начинает прослушивание и протоколирование соединений и ошибок сервера. Сейчас вам не требуется дополнительных сведений о коде (все в этом файле шаблонно), но, при желании, его можно посмотреть.

+ +

Файл app.js

+ +

Этот файл создает объект приложения  express (с именемapp, по соглашению), настраивает приложение и промежуточное ПО, а затем экспортирует приложение из модуля. В приведенном ниже коде показаны только те части файла, которые создают и экспортируют объект приложения:

+ +
var express = require('express');
+var app = express();
+...
+module.exports = app;
+
+ +

Именно этот экспортированный объект использован в рассмотренном ранее файле www.

+ +

Рассмотрим детали файла app.js. Сначала при помощи require(...) выполняется импорт некоторых полезных библиотек node: express, serve-favicon, morgan, cookie-parse, body-parser (они ранее были загружены для нашего приложения командой npm install), а также path из основной библиотеки node (применяется для разбора путей каталогов и файлов).

+ +
var express = require('express');
+var path = require('path');
+var favicon = require('serve-favicon');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+
+ +

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

+ +
var index = require('./routes/index');
+var users = require('./routes/users');
+
+ +
+

Заметка: Здесь мы только импортируем модули. В действительности эти пути еще не используются — это произойдет в файле несколько позже.

+
+ +

Далее, импортированные модули express применяются для создания объекта app, который потом устанавливает движки-шаблоны представления. Установка движков состоит их двух частей. В первой мы задаем значение 'view', указывая папку, в которой будут размещаться шаблоны (у нас это /views). Во второй мы задаем значение движка 'view engine', указывая на библиотеку шаблона (у нас — "pug").

+ +
var app = express();
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'pug');
+
+ +

Следующие строки вызывают app.use(...), чтобы добавить промежуточные (middleware) библиотеки в цепочку обработки запросов. Кроме сторонних библиотек, импортированных ранее, используем библиотеку Express.static, что позволит обрабатывать статические файлы из папки /public корня проекта.

+ +
// uncomment after placing your favicon in /public
+//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(logger('dev'));
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+app.use(cookieParser());
+app.use(express.static(path.join(__dirname, 'public')));
+
+ +

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

+ +
app.use('/', index);
+app.use('/users', users);
+
+ +
+

Заметка: . пути, указанные выше ('/' и '/users') рассматриваются как префиксы путей, определенных в импортированных файлах. Так, например, если импортированный модуль users определяет путь для /profile, для доступа следует указать /users/profile.  Мы поговорим подробнее о путях в последующей статье.

+
+ +

Последняя в файле промежуточная библиотека добавляет методы обработки ошибок и ответов 404 от HTTP.

+ +
// catch 404 and forward to error handler
+app.use(function(req, res, next) {
+  var err = new Error('Not Found');
+  err.status = 404;
+  next(err);
+});
+
+// error handler
+app.use(function(err, req, res, next) {
+  // set locals, only providing error in development
+  res.locals.message = err.message;
+  res.locals.error = req.app.get('env') === 'development' ? err : {};
+
+  // render the error page
+  res.status(err.status || 500);
+  res.render('error');
+});
+
+ +

Объект app приложения Express теперь полностью настроен. Остался последний шаг - добавить его к экпортируемым элементам модуля (это позволит импортировать его в файле /bin/www).

+ +
module.exports = app;
+ +

Пути (Routes)

+ +

Файл путей /routes/users.js приведен ниже (файлы путей имеют сходную структуру, поэтому нет необходимости приводить также index.js). Сначала загружается модуль Express, затем он используется для получения объекта express.Router. После этого для этого объекта задается путь, и, наконец, объект-роутер экспортируется из модуля (именно это позволяет импортировать файл в app.js):.

+ +
var express = require('express');
+var router = express.Router();
+
+/* GET users listing. */
+router.get('/', function(req, res, next) {
+  res.send('respond with a resource');
+});
+
+module.exports = router;
+
+ +

Путь определяет функцию обратного вызова (далее — callback-функцию), которая будет вызвана, когда обнаружится HTTP GET-запрос корректного вида. Образец для сопоставления пути задается при импорте модуля -- ('/users') плюс что-то, определяемое в этом файле ('/'). Иными словами, этот путь будет использован, когда получен URL-запрос /users/.

+ +
+

Совет: запустите сервер и задайте в браузере URL http://localhost:3000/users/. Вы должны увидеть сообщение: 'respond with a resource'.

+
+ +

Стоит отметить, что callback-функция имеет третий аргумент - 'next', т. е. является не простой callback-функцией, а callback-функцией промежуточного модуля. Пока третий аргумент не используется, но будет полезен в дальнейшем, если мы захотим создать несколько обработчиков пути '/'.

+ +

Представления (шаблоны)

+ +

Файлы преставлений (шаблонов) хранятся в каталоге /views (это указано в app.js) и имеют расширение .pug. Метод Response.render() выполняет указанный шаблон, передавая объекту значение именованной переменной, и затем посылает результат как ответ. В коде из /routes/index.js (приводится ниже) можно увидеть, что роут отвечает, используя шаблон "index" с переданным значением переменной "title" из шаблона.

+ +
/* GET home page. */
+router.get('/', function(req, res) {
+  res.render('index', { title: 'Express' });
+});
+
+ +

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

+ +
extends layout
+
+block content
+  h1= title
+  p Welcome to #{title}
+
+ +

Мини-тест

+ +

Создайте новый путь в /routes/users.js, чтобы выводить текст "You're so cool" или "Ну, вы крутой!" по URL /users/cool/. Проверьте его, запустив сервер и посетив в браузере http://localhost:3000/users/cool/.

+ + + +

Итоги

+ + + +

Сейчас создан каркас проекта Local Library. Мы проверили, что он запускается с использованием Node. Но главное, что вы поняли структуру проекта, и знаете, где и как добавить пути и представления для нашей локальной библиотеки.

+ +

Далее мы изменим каркас, чтобы он работал как библиотечный вебсайт

+ +

Смотри также

+ + + +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}

diff --git "a/files/ru/learn/server-side/express_nodejs/\321\203\321\207\320\265\320\261\320\275\320\270\320\272_\321\201\320\260\320\271\321\202_local_library/index.html" "b/files/ru/learn/server-side/express_nodejs/\321\203\321\207\320\265\320\261\320\275\320\270\320\272_\321\201\320\260\320\271\321\202_local_library/index.html" new file mode 100644 index 0000000000..c7e821248e --- /dev/null +++ "b/files/ru/learn/server-side/express_nodejs/\321\203\321\207\320\265\320\261\320\275\320\270\320\272_\321\201\320\260\320\271\321\202_local_library/index.html" @@ -0,0 +1,73 @@ +--- +title: 'Учебник Express: сайт Local Library' +slug: Learn/Server-side/Express_Nodejs/Учебник_сайт_local_library +tags: + - Express + - Node + - nodejs + - Введение + - Для начинающих + - Серверная часть + - Учебник +translation_of: Learn/Server-side/Express_Nodejs/Tutorial_local_library_website +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}
+ +

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

+ + + + + + + + + + + + +
Необходимые знания:Прочтите Введение в Express. Для следования статьям вам также надо будет установить среду разработки Node
Задача:Представить пример приложения, используемого в этом учебнике, и позволить читателям понять, какие темы будут рассмотрены. 
+ +

Обзор

+ +

Добро пожаловать в учебник MDN «Local Library» Express (Node), в котором мы разрабатываем веб-сайт, который может использоваться для управления каталогом локальной библиотеки.

+ +
В этой серии обучающих статей вы будете:
+ + + +

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

+ +

Сайт LocalLibrary

+ +

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

+ +

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

+ + + +

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

+ +

Я застрял, где я могу посмотреть код?

+ +

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

+ +

Если вы застряли, вы можете найти полностью разработанную версию вебсайта на Github.

+ +

Резюме

+ +

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

+ +

{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}

diff --git a/files/ru/learn/server-side/first_steps/client-server_overview/index.html b/files/ru/learn/server-side/first_steps/client-server_overview/index.html new file mode 100644 index 0000000000..7961d46a24 --- /dev/null +++ b/files/ru/learn/server-side/first_steps/client-server_overview/index.html @@ -0,0 +1,325 @@ +--- +title: Клиент-сервер +slug: Learn/Server-side/First_steps/Client-Server_overview +tags: + - Вступление + - Вступление + - Начинающий + - Начинающий + - Программирование на стороне сервера + - Программирование на стороне сервера + - Руководство + - Руководство + - Сервер + - Сервер + - Учить + - Учить +translation_of: Learn/Server-side/First_steps/Client-Server_overview +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}
+ +

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

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

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

+ +

Веб-серверы и HTTP (для начинающих)

+ +

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

+ +

Этот запрос включает:

+ + + +

Веб-серверы ожидают сообщений с запросами от клиентов, обрабатывают их, когда они приходят и отвечают веб-браузеру через сообщение с HTTP-ответом. Ответ содержит Код статуса HTTP-ответа, который показывает, был ли запрос успешным (например, «200 OK» означает успех, «404 Not Found» если ресурс не может быть найден, «403 Forbidden», если пользователь не имеет права просматривать ресурс, и т. д.). Тело успешного ответа на запрос GET будет содержать запрашиваемый ресурс.

+ +

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

+ +

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

+ +

Пример GET запроса/ответа

+ +

Вы можете сформировать простой GET запрос кликнув по ссылке или через поиск по сайту (такой как страница поисковой системы). Например, HTTP-запрос, отправленный во время выполнения запроса "client server overview" на сайте MDN, будет во многом похож на текст ниже (он не будет идентичным, потому что части сообщения зависят от вашего браузера/настроек).

+ +
+

Формат HTTP сообщения определён в «веб-стандарте» (RFC7230). Вам не нужно знать этот уровень детализации, но, по крайней мере, теперь вы знаете, откуда это появилось!

+
+ +

Запрос

+ +

Каждая строка запроса содержит информацию о запросе. Первая часть называется заголовок и содержит важную информацию о запросе, точно так же, как HTML head содержит важную информацию о HTML-документе (но не содержимое документа, которое расположено внутри тэга "body"):

+ +
GET https://developer.mozilla.org/en-US/search?q=client+server+overview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev HTTP/1.1
+Host: developer.mozilla.org
+Connection: keep-alive
+Pragma: no-cache
+Cache-Control: no-cache
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Referer: https://developer.mozilla.org/en-US/
+Accept-Encoding: gzip, deflate, sdch, br
+Accept-Language: en-US,en;q=0.8,es;q=0.6
+Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _gat=1; _ga=GA1.2.1688886003.1471911953; ffo=true
+
+ +

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

+ + + +

Последняя строка содержит информацию о клиентских куки — в данном случае можно увидеть куки, включающие id для управления сессиями (Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; ...).

+ +

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

+ + + +

HTTP-запрос может также содержать body, но в данном случае этого нет.

+ +

Ответ

+ +

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

+ + + +

В конце сообщения мы видим содержимое body, содержащее HTML-код возвращаемого ответа.

+ +
HTTP/1.1 200 OK
+Server: Apache
+X-Backend-Server: developer1.webapp.scl3.mozilla.com
+Vary: Accept,Cookie, Accept-Encoding
+Content-Type: text/html; charset=utf-8
+Date: Wed, 07 Sep 2016 00:11:31 GMT
+Keep-Alive: timeout=5, max=999
+Connection: Keep-Alive
+X-Frame-Options: DENY
+Allow: GET
+X-Cache-Info: caching
+Content-Length: 41823
+
+
+
+<!DOCTYPE html>
+<html lang="en-US" dir="ltr" class="redesign no-js"  data-ffo-opensanslight=false data-ffo-opensans=false >
+<head prefix="og: http://ogp.me/ns#">
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
+  <script>(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);</script>
+  ...
+
+ +

Остальная часть заголовка ответа содержит информацию об ответе (например, когда он был сгенерирован), сервере и о том, как он ожидает, что браузер обработает страницу (например, строка X-Frame-Options: DENY говорит браузеру не допускать внедрения этой страницы, если она будет внедрена в {{htmlelement ("iframe")}} на другом сайте).

+ +

Пример POST запроса/ответа

+ +

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

+ +

Запрос

+ +

В приведённом ниже тексте показан HTTP-запрос, сделанный когда пользователь загружает новые данные профиля на этом сайте. Формат запроса почти такой же, как пример запроса GET, показанный ранее, хотя первая строка идентифицирует этот запрос как POST.

+ +
POST https://developer.mozilla.org/en-US/profiles/hamishwillee/edit HTTP/1.1
+Host: developer.mozilla.org
+Connection: keep-alive
+Content-Length: 432
+Pragma: no-cache
+Cache-Control: no-cache
+Origin: https://developer.mozilla.org
+Upgrade-Insecure-Requests: 1
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
+Content-Type: application/x-www-form-urlencoded
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
+Referer: https://developer.mozilla.org/en-US/profiles/hamishwillee/edit
+Accept-Encoding: gzip, deflate, br
+Accept-Language: en-US,en;q=0.8,es;q=0.6
+Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; _gat=1; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _ga=GA1.2.1688886003.1471911953; ffo=true
+
+csrfmiddlewaretoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT&user-username=hamishwillee&user-fullname=Hamish+Willee&user-title=&user-organization=&user-location=Australia&user-locale=en-US&user-timezone=Australia%2FMelbourne&user-irc_nickname=&user-interests=&user-expertise=&user-twitter_url=&user-stackoverflow_url=&user-linkedin_url=&user-mozillians_url=&user-facebook_url=
+ +

Основное различие заключается в том, что URL-адрес не имеет параметров. Как вы можете видеть, информация из формы закодирована в теле запроса (например, новое полное имя пользователя устанавливается с использованием: &user-fullname=Hamish+Willee).

+ +

Ответ

+ +

Ответ от запроса показан ниже. Код состояния «302 Found» сообщает браузеру, что сообщение обработано, и что необходим второй HTTP-запрос для загрузки страницы, указанной в поле Location. В остальном информация аналогична информации для ответа на запрос GET .

+ +
HTTP/1.1 302 FOUND
+Server: Apache
+X-Backend-Server: developer3.webapp.scl3.mozilla.com
+Vary: Cookie
+Vary: Accept-Encoding
+Content-Type: text/html; charset=utf-8
+Date: Wed, 07 Sep 2016 00:38:13 GMT
+Location: https://developer.mozilla.org/en-US/profiles/hamishwillee
+Keep-Alive: timeout=5, max=1000
+Connection: Keep-Alive
+X-Frame-Options: DENY
+X-Cache-Info: not cacheable; request wasn't a GET or HEAD
+Content-Length: 0
+
+ +
+

На заметку: HTTP-ответы и запросы, показанные в этих примерах, были захвачены с помощью приложения Fiddler, но вы можете получить аналогичную информацию с помощью веб-снифферов (например, http://web-sniffer.net/) или с помощью расширений браузера, таких как HttpFox. Вы можете попробовать это сами. Воспользуйтесь любым из предложенных инструментов, а затем перейдите по сайту и отредактируйте информацию профиля, чтобы увидеть различные запросы и ответы. В большинстве современных браузеров также есть инструменты, которые отслеживают сетевые запросы (например, инструмент Network Monitor в Firefox).

+
+ +

Статические сайты

+ +

Статический сайт — это тот, который возвращает тот же жёсткий кодированный контент с сервера всякий раз, когда запрашивается конкретный ресурс. Например, если у вас есть страница о товаре в /static/myproduct1.html, эта же страница будет возвращена каждому пользователю. Если вы добавите ещё один подобный товар на свой сайт, вам нужно будет добавить ещё одну страницу (например, myproduct2.html) и так далее. Это может стать действительно неэффективным — что происходит, когда вы попадаете на тысячи страниц товаров? Вы повторяли бы много кода на каждой странице (основной шаблон страницы, структуру и т. д.), И если бы вы захотели изменить что-либо в структуре страницы — например, добавить новый раздел «связанные товары» — тогда вам придётся менять каждую страницу отдельно.

+ +
+

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

+
+ +

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

+ +

A simplified diagram of a static web server.

+ +

Когда пользователь хочет перейти на страницу, браузер отправляет HTTP-запрос GET с указанием URL-адреса его HTML-страницы. Сервер извлекает запрошенный документ из своей файловой системы и возвращает HTTP-ответ, содержащий документ и код состояния HTTP Response status code 200 OK (успех). Сервер может вернуть другой код состояния, например, «404 Not Found», если файл отсутствует на сервере или «301 Moved Permanently», если файл существует, но был перемещён в другое место.

+ +

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

+ +

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

+ +

Динамические сайты

+ +

Динамический сайт — это тот, который может генерировать и возвращать контент на основе конкретного URL-адреса запроса и данных (а не всегда возвращать один и тот же жёсткий код для определённого URL-адреса). Используя пример сайта товара, сервер будет хранить «данные» товара в базе данных, а не отдельные HTML-файлы. При получении GET-запроса для товара сервер определяет идентификатор товара, извлекает данные из базы данных и затем создаёт HTML-страницу для ответа, вставляя данные в HTML-шаблон. Это имеет большие преимущества перед статическим сайтом:

+ +

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

+ +

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

+ +

Анатомия динамического запроса

+ +

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

+ +

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

+ +

This is a diagram of a simple web server with step numbers for each of step of the client-server interaction.

+ +

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

+ +
    +
  1. Веб-браузер отправит HTTP-запрос GET на сервер с использованием базового URL-адреса ресурса (/best) и кодирования номера команды и игрока в форме URL-параметров (например, /best?team=my_team_name&show=11) или как часть URL-адреса (например, /best/my_team_name/11/). Запрос GET используется, потому что речь идёт только о запросе выборки данных (а не об их изменении).
  2. +
  3. Веб-сервер определяет, что запрос является «динамическим» и пересылает его в веб-приложение для обработки (веб-сервер определяет, как обрабатывать разные URL-адреса на основе правил сопоставления шаблонов, определённых в его конфигурации).
  4. +
  5. Веб-приложение определяет, что цель запроса состоит в том, чтобы получить «лучший список команд» на основе URL (/best/) и узнать имя команды и количество игроков из URL-адреса. Затем веб-приложение получает требуемую информацию из базы данных (используя дополнительные «внутренние» параметры, чтобы определить, какие игроки являются «лучшими», и, возможно, определяя личность зарегистрированного тренера из файла cookie на стороне клиента).
  6. +
  7. Веб-приложение динамически создаёт HTML-страницу, помещая данные (из базы данных) в заполнители внутри HTML-шаблона.
  8. +
  9. Веб-приложение возвращает сгенерированный HTML в веб-браузер (через веб-сервер) вместе с кодом состояния HTTP 200 («успех»). Если что-либо препятствует возврату HTML, веб-приложение вернёт другой код, например, «404», чтобы указать, что команда не существует.
  10. +
  11. Затем веб-браузер начнёт обрабатывать возвращённый HTML, отправив отдельные запросы, чтобы получить любые другие файлы CSS или JavaScript, на которые он ссылается (см. шаг 7).
  12. +
  13. Веб-сервер загружает статические файлы из файловой системы и возвращает их непосредственно в браузер (опять же, правильная обработка файлов основана на правилах конфигурации и сопоставлении шаблонов URL).
  14. +
+ +

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

+ +

Выполнение другой работы

+ +

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

+ +

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

+ +

Возвращение чего-то другого, кроме HTML

+ +

Серверный код сайта может возвращать не только HTML-фрагменты и файлы в ответе. Он может динамически создавать и возвращать другие типы файлов (текст, PDF, CSV и т. д.) или даже данные (JSON, XML и т. д.).

+ +

Идея вернуть данные в веб-браузер, чтобы он мог динамически обновлять свой собственный контент ({{glossary("AJAX")}}) существует довольно давно. Совсем недавно «Одностраничные приложения» стали популярными, где весь сайт написан с одним HTML-файлом, который динамически обновляется по мере необходимости. Веб-сайты, созданные с использованием приложений такого рода, переносят большие вычислительные затраты с сервера на веб-браузер и приводят к тому, что веб-сайты, ведут себя больше как нативные приложения (очень отзывчивые и т. д.).

+ +

Веб-фреймворки упрощают веб-программирование на стороне сервера

+ +

Веб-фреймворки на стороне сервера делают написание кода для обработки описанных выше операций намного проще.

+ +

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

+ +

Для примера рассмотрим следующий код Django (Python), который связывает два URL-шаблона с двумя функциями просмотра. Первый шаблон проверяет, что HTTP-запрос с URL-адресом ресурса /best будет передан функции с именем index() в модуле views. Запрос, который имеет шаблон «/best/junior», вместо этого будет передан функции просмотра junior().

+ +
# file: best/urls.py
+#
+
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+    # example: /best/
+    url(r'^$', views.index),
+    # example: /best/junior/
+    url(r'^junior/$', views.junior),
+]
+ +
+

На заметку: Первые параметры в функциях url() могут выглядеть немного необычно (например, r'^junior/$', потому что они используют метод сопоставления шаблонов под названием «регулярные выражения» (RegEx или RE). Вам не нужно знать, как работают регулярные выражения на этом этапе, кроме того, что они позволяют нам сопоставлять шаблоны в URL-адресе (а не жёстко закодированные значения выше) и использовать их в качестве параметров в наших функциях просмотра. В качестве примера, действительно простой RegEx может говорить «соответствовать одной заглавной букве, за которой следуют от 4 до 7 строчных букв».

+
+ +

Веб-фреймворк также упрощает функцию просмотра для получения информации из базы данных. Структура наших данных определяется в моделях, которые являются классами Python, которые определяют поля, которые должны храниться в основной базе данных. Если у нас есть модель с именем Team с полем «team_type», мы можем использовать простой синтаксис запроса, чтобы получить все команды, имеющие определённый тип.

+ +

В приведённом ниже примере представлен список всех команд, у которых есть точный (с учётом регистра) team_type «junior» («младший») — обратите внимание на формат: имя поля (team_type), за которым следует двойной знак подчёркивания, а затем тип соответствия для использования (в этом случае exact («точное»)). Существует много других типов соответствия, и мы можем объединить их. Мы также можем контролировать порядок и количество возвращаемых результатов.

+ +
#best/views.py
+
+from django.shortcuts import render
+
+from .models import Team
+
+
+def junior(request):
+    list_teams = Team.objects.filter(team_type__exact="junior")
+    context = {'list': list_teams}
+    return render(request, 'best/index.html', context)
+
+ +

После того, как функция junior() получает список младших команд, она вызывает функцию render(), передавая исходный HttpRequest, HTML-шаблон и объект «context», определяющий информацию, которая должна быть включена в шаблон. Функция render() — это функция удобства, которая генерирует HTML с использованием контекста и HTML-шаблона и возвращает его в объект HttpResponse.

+ +

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

+ +

Резюме

+ +

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

+ +

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

+ +

{{PreviousMenuNext ("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}

diff --git a/files/ru/learn/server-side/first_steps/index.html b/files/ru/learn/server-side/first_steps/index.html new file mode 100644 index 0000000000..91512b5957 --- /dev/null +++ b/files/ru/learn/server-side/first_steps/index.html @@ -0,0 +1,49 @@ +--- +title: Первые шаги в программировании веб-сайтов на стороне сервера +slug: Learn/Server-side/First_steps +tags: + - Beginner + - CodingScripting + - Guide + - Intro + - Landing + - Learn + - NeedsTranslation + - Server-side programming + - TopicStub +translation_of: Learn/Server-side/First_steps +--- +
{{LearnSidebar}}
+ +

В этом модуле, посвященном программированию на стороне сервера, мы ответим на несколько фундаментальных вопросов о программировании серверной части: «что это такое?», «чем оно отличается от программирования клиентской части?» и «почему оно так полезно?». Затем последует обзор некоторых самых популярных веб-фреймворков для серверной части и руководство по выбору наиболее подходящего фреймворка для создания вашего первого сайта. Наконец, мы завершим этот модуль вводной статьей о безопасности веб-сервера.

+ +

Прежде чем начать

+ +

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

+ +

Вам нужно понимать "как работает веб". Мы рекомендуем вам сперва прочесть следующие темы:

+ + + +

С этими базовыми знаниями вы будете готовы работать с модулями этого раздела.

+ +

Руководства

+ +
+
Введение в серверную часть
+
Добро пожаловать на курс программирования серверной части MDN для начинающих! В этой первой статье мы посмотрим на программирование серверной части на высоком уровне, отвечая на вопросы такие как: "что это такое?", "чем оно отличается от программирования клиентской части?" и "почему это настолько востребовано?". После прочтения этой статьи вы будуте понимать всю дополнительную мощь, доступную веб-сайтам посредством программирования на стороне сервера.
+
Обзор Клиент-Сервера
+
Теперь, когда вы познакомились с целью и потенциальными преимуществами программирования серверной части, мы собираемся узнать в подробностях, что случится, когда сервер получит "динамический запрос" от браузера. Так как большинство программ серверной части обрабатывает запросы и ответы практически одинаково, это поможет вам понять, что нужно делать при написании собственного кода.
+
Фреймворки серверной части
+
Последняя статья рассказывает о том, что нужно делать веб-приложению серверной стороны для ответа на запросы от веб-браузера. Мы покажем здесь, как веб-фреймворки могут упростить эти задачи и поможем вам подобрать подходящий фреймворк для вашего первого серверного веб-приложения.
+
Безопасность веб-сайта
+
Безопасность веб-сайта требует бдительности на всех этапах проектирования сайта и его использования. Эта вводная статья не сделает из вас гуру безопасности сайтов, но поможет узнать, какие первые важные шаги вы можете предпринять для повышения устойчивости вашего веб-приложения против наиболее распространенных угроз.
+
+ +

Аттестация

+ +

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

diff --git a/files/ru/learn/server-side/first_steps/introduction/index.html b/files/ru/learn/server-side/first_steps/introduction/index.html new file mode 100644 index 0000000000..967d1157a7 --- /dev/null +++ b/files/ru/learn/server-side/first_steps/introduction/index.html @@ -0,0 +1,217 @@ +--- +title: Введение в серверную часть +slug: Learn/Server-side/First_steps/Introduction +tags: + - Бэкенд + - Введение + - Начинающий + - Руководство + - Сервер + - Серверная часть +translation_of: Learn/Server-side/First_steps/Introduction +--- +
{{LearnSidebar}}
+ +
{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}
+ +

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

+ + + + + + + + + + + + +
Перед стартом:Базовая компьютерная грамотность. Базовое понимание, что такое веб-сервер.
Цель:Ознакомиться с тем, что такое программирование серверной части, на что оно способно и чем отличается от программирования клиентской части.
+ +

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

+ +

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

+ +

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

+ +

В современном мире веб-разработки настоятельно рекомендуется узнать о разработке на стороне сервера.

+ +

Что такое программирование серверной части сайта?

+ +

Веб-браузеры взаимодействуют с веб-серверами при помощи гипертекстового транспортного протокола ({{glossary("HTTP")}}). Когда вы нажимаете на ссылку на веб-странице, заполняете форму или запускаете поиск,  HTTP-запрос  отправляется из вашего браузера на целевой сервер.

+ +

Запрос включает в себя URL, определяющий затронутый ресурс, метод, определяющий требуемое действие (например, получить, удалить или опубликовать ресурс) и может включать дополнительную информацию, закодированную в параметрах URL (пары поле-значение, оправленные как строка запроса), как POST запрос (данные, отправленные методом HTTP POST) или в {{glossary("Cookie", "куки-файлах")}}.

+ +

Веб-серверы ожидают сообщений с клиентскими запросами, обрабатывают их по прибытию и отвечают веб-браузеру при помощи ответного HTTP сообщения (HTTP-ответ). Ответ содержит строку состояния, показывающую, был ли запрос успешным или нет (например, «HTTP/1.1 200 OK» в случае успеха).

+ +

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

+ +

Статические сайты

+ +

Схема ниже показывает базовую архитектуру веб-сервера для статического сайта (статический сайт — это тот, который возвращает одно и то же жёстко закодированное содержимое с сервера всякий раз, когда запрашивается конкретный ресурс). Когда пользователь хочет перейти на страницу, браузер отправляет HTTP-запрос «GET» с указанием его URL. 

+ +

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

+ +

A simplified diagram of a static web server.

+ +

Динамические сайты

+ +

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

+ +

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

+ +

Большая часть кода для поддержки динамического веб-сайта должна выполняться на сервере. Создание этого кода известно, как «программирование серверной части» (или иногда «программирование бэкенда»).

+ +

Схема ниже показывает простую архитектуру динамического сайта. Как и на предыдущей схеме, браузеры отправляют HTTP-запросы на сервер, затем сервер обрабатывает запросы и возвращает соответствующие HTTP-ответы.

+ +

Запросы статических ресурсов обрабатываются так же, как и для статических сайтов (статические ресурсы — это любые файлы, которые не меняются, обычно это: CSS, JavaScript, изображения, предварительно созданные PDF-файлы и прочее).

+ +

A simplified diagram of a web server that uses server-side programming to get information from a database and construct HTML from templates. This is the same diagram as is in the Client-Server overview.

+ +

Запросы динамических данных отправляются (2) в код серверной части (показано на диаграмме как Веб-приложение). Для «динамических запросов» сервер интерпретирует запрос, читает необходимую информацию из базы данных (3), комбинирует извлечённые данные с шаблонами HTML и возвращает ответ, содержащий сгенерированный HTML (5, 6).

+ +
+

Одинаково ли программирование серверной части и клиентской?

+
+ +

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

+ + + +

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

+ +

Код клиентской части написан с использованием HTML, CSS и JavaScript — он запускается в веб-браузере и практически не имеет доступа к базовой операционной системе (включая ограниченный доступ к файловой системе).

+ +

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

+ +

Код серверной части может быть написан на любом количестве языков программирования — примеры популярных языков серверной части включают в себя PHP, Python, Ruby, C# и NodeJS (JavaScript). Код серверной части имеет полный доступ к операционной системе сервера, и разработчик может выбрать какой язык программирования (и какую версию) он хотел бы использовать.

+ +

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

+ +

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

+ +
+

На заметку: Фреймворки клиентской части часто используются для ускорения написания кода клиентской части, но вы также можете решить писать весь код руками; на самом деле, написание кода руками может быть более быстрым и эффективным, если вам нужен небольшой простой веб-сайт UI.

+ +

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

+
+ +
+

Что можно сделать в серверной части?

+ +

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

+ +

Компании, такие как Amazon, используют программирование серверной части для построения исследовательских результатов для товаров, формирования целевого предложения, основанного на предпочтениях клиента и предыдущих покупках, упрощения заказов и т. д. Банки используют программирование серверной части, чтобы хранить учётную информацию и позволять только авторизованным пользователям просматривать и совершать транзакции. Другие сервисы, такие как Facebook, Twitter, Instagram и Wikipedia используют бэкэнд, чтобы выделять, распространять и контролировать доступ к интересному контенту.

+
+ +

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

+ +

Эффективное хранение и доставка информации

+ +

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

+ +

Программирование серверной части позволяет вместо этого хранить информацию в базе данных и динамически создавать и возвращать HTML и другие типы файлов (например, PDF, изображения, и т. д.). Также есть возможность просто вернуть данные ({{glossary("JSON")}}, {{glossary("XML")}}, и т. д.) для отображения, используя подходящий фреймворк клиентской части (это уменьшает загрузку процессора на сервере и количество передаваемых данных).

+ +

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

+ +

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

+ +
+

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

+ +
    +
  1. Зайдите на Amazon или в другой интернет-магазин.
  2. +
  3. Введите в поиск несколько ключевых слов и заметьте, как структура страницы не изменилась, тогда как результаты изменились.
  4. +
  5. Откройте два или три разных товара. Заметьте, что они имеют схожую структуру и внешний вид, но содержимое для разных товаров было вставлено из базы данных.
  6. +
+ +

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

+
+ +

Настраиваемый пользовательский опыт взаимодействия

+ +

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

+ +

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

+ +
+

На заметку: Google Maps сохраняет вашу историю поиска и посещений. Часто посещаемые или часто вводимые в поиск локации выделяются больше, чем остальные.

+ +

Результаты поиска Google оптимизируются на основе прыдыдущего поиска.

+ +
    +
  1.  Перейдите в поиск Google.
  2. +
  3.  Произведите поиск по слову «футбол».
  4. +
  5.  Теперь попробуйте ввести «любимое» в поисковой строке и понаблюдайте, как работают подсказки автозаполнения поиска.
  6. +
+ +

Стечение обстоятельств? Нет!

+
+ +

Контролируемый доступ к контенту

+ +

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

+ +

Реальные примеры:

+ + + +
+

На заметку: Рассмотрим другие реальные примеры, где доступ к контенту контролируется. Например, что вы можете увидеть, если зайдёте на сайт вашего банка? Авторизуйтесь через вашу учётную запись, и какую дополнительную информацию вы можете просматривать и редактировать? Что за информацию вы можете увидеть, которую может редактировать только банк?

+
+ +

Хранение информации о сессии/состоянии

+ +

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

+ +

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

+ +
+

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

+
+ +

Уведомления и средства связи

+ +

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

+ +

Вот несколько примеров:

+ + + +
+

На заметку: Самый распространённый вид уведомлений – это «подтверждение регистрации». Возьмите почти любой интересующий вас крупный сайт (Google, Amazon, Instagram и т. п.) и создайте новую учётную запись, используя ваш адрес электронной почты. Вскоре вы получите письмо, подтверждающее факт вашей регистрации или содержащее информацию о необходимости активировать вашу учётную запись.

+
+ +

Анализ данных

+ +

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

+ +

Например, и Amazon, и Google рекламируют товары на основании предыдущих поисков (и покупок).

+ +
+

На заметку: Если вы пользуетесь Facebook, зайдите на вашу стену и посмотрите на ряд постов. Заметьте, что некоторые посты не идут по порядку: в частности, посты с большим количеством «лайков» часто находятся выше по списку, чем остальные. Также взгляните на рекламу, которую вам показывают, вы вероятно увидите рекламу товаров, которые искали на других сайтах. Алгоритм Facebook для выделения контента и рекламы может казаться мистикой, но очевидно, что он зависит от ваших лайков и запросов поиска!

+
+ +

Подведение итогов

+ +

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

+ +

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

+ +

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

+ +

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

+ +

{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}

diff --git a/files/ru/learn/server-side/first_steps/web_frameworks/index.html b/files/ru/learn/server-side/first_steps/web_frameworks/index.html new file mode 100644 index 0000000000..af2b467d3f --- /dev/null +++ b/files/ru/learn/server-side/first_steps/web_frameworks/index.html @@ -0,0 +1,306 @@ +--- +title: Выполняемые на сервере веб фреймворки +slug: Learn/Server-side/First_steps/Web_frameworks +translation_of: Learn/Server-side/First_steps/Web_frameworks +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}
+ +

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

+ + + + + + + + + + + + +
Необходимые навыки:Базовая компьютерная грамотность. Высокое понимание того, как серверный код обрабатывает и отвечает на HTTP-запросы (см. Client-Server overview).
Цели:Понять, как веб-интерфейсы могут упростить разработку / обслуживание кода на стороне сервера и заставить читателей задуматься о выборе структуры для собственной разработки.
+ +

Следующие разделы иллюстрируют некоторые моменты, используя фрагменты кода, взятые из реальных веб-интерфейсов. Не беспокойтесь, если не всё сразу понятно; мы будем работать над кодом вместе в модулях, посвящённых отдельным фреймворкам.

+ +

Обзор

+ +

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

+ +

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

+ +

Что может сделать веб-фреймворк для вас ?

+ +

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

+ +

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

+ +

Работайте напрямую с HTTP-запросами и ответами

+ +

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

+ +

Пример ниже показывает, как это работает в веб-фреймворке Django (Python). Каждая функция «view» (обработчик запроса) получает объект HttpRequest, содержащий информацию о запросе, и должна вернуть объект HttpResponse с форматированным выводом (в этом случае строка).

+ +
# Django view function
+from django.http import HttpResponse
+
+def index(request):
+    # Get an HttpRequest (request)
+    # perform operations using information from the request.
+    # Return HttpResponse
+    return HttpResponse('Output string to return')
+
+ +

Запросы маршрута к соответствующему обработчику

+ +

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

+ +

Различные фреймворки используют различные механизмы для сопоставления. Например, веб-фреймворк Flask (Python) добавляет маршруты для просмотра функций используя декораторы.

+ +
@app.route("/")
+def hello():
+    return "Hello World!"
+ +

Django ожидает, что разработчики определят список сопоставлений URL-адресов между шаблоном URL-адреса и функцией просмотра.

+ +
urlpatterns = [
+    url(r'^$', views.index),
+    # example: /best/myteamname/5/
+    url(r'^best/(?P<team_name>\w.+?)/(?P<team_number>[0-9]+)/$', views.best),
+]
+
+ +

Упростите доступ к данным в запросе

+ +

Данные могут быть закодированы в HTTP-запросе разными способами. Для получения файлов или данных с сервера, HTTP-запрос GET может кодировать, какие данные требуются в URL-параметрах или в структуре URL. HTTP-запрос POST для обновления ресурса на сервере вместо этого будет включать обновлённую информацию как «POST данные» внутри тела запроса. HTTP-запрос может также включать информацию о текущей сессии или пользователе в cookie со стороны клиента.

+ +

Веб-фреймворки предоставляют соответствующие языку программирования механизмы доступа к этой информации. Например, объект HttpRequest, который Django передаёт каждой функции «view», содержит методы и свойства для доступа к целевому URL, типу запроса (например, HTTP GET), параметрам GET или POST, файлам cookie и данным сеанса и т. д. Django также может передавать информацию, закодированную в структуре URL, путём определения «шаблонов захвата» в преобразователе URL (см. последний фрагмент кода в разделе выше).

+ +

Абстрагируйте и упростите доступ к базе данных

+ +

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

+ +

Использование ORM имеет два преимущества:

+ + + +

Например, веб-фреймворк Django предоставляет ORM и ссылается на объект, используемый для определения структуры записи в качестве модели. Модель задаёт типы полей, которые должны быть сохранены, что может обеспечить проверку на уровне поля того, какая информация может быть сохранена (например, поле электронной почты будет разрешать только действительные адреса электронной почты). В определениях полей также можно указать их максимальный размер, значения по умолчанию, параметры списка выбора, текст справки для документации, текст метки для форм и т. д. Модель не содержит никакой информации о базе данных, поскольку это параметр конфигурации, который может быть изменён отдельно от нашего кода.
+
+ Первый фрагмент кода ниже показывает очень простую модель Django для объекта
 Team. Это сохраняет название команды и уровень команды как символьные поля и определяет максимальное количество символов для каждой записи. team_level  это поле выбора, поэтому здесь мы связываем варианты значений на выбор с сохраняемыми данными, а также значение по умолчанию.

+ +
#best/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'),
+        ...  #list our other teams
+    )
+    team_level = models.CharField(max_length=3,choices=TEAM_LEVELS,default='U11')
+
+ +

Модель Django предоставляет простой API запросов для поиска в базе данных. Это может соответствовать нескольким полям одновременно, используя различные критерии (например, exact (точный), case-insensitive (без учёта регистра), greater than (больше чем) и т. п.), а также может поддерживать сложные операторы (например, вы можете указать поиск для команд U11, в которых есть команда имя, которое начинается с «Fr» или заканчивается на «al»).
+
+ Второй фрагмент кода показывает функцию представления (обработчик ресурсов) для отображения всех наших команд U09. В этом случае мы указываем, что мы хотим фильтровать для всех записей, где поле
 team_level имеет в точности текст «U09» (обратите внимание ниже, как этот критерий передаётся функции filter() в качестве аргумента с именем поля и типом соответствия, отделённым двойным подчёркиванием: team_level__exact).

+ +
#best/views.py
+
+from django.shortcuts import render
+from .models import Team
+
+def youngest(request):
+    list_teams = Team.objects.filter(team_level__exact="U09")
+    context = {'youngest_teams': list_teams}
+    return render(request, 'best/index.html', context)
+
+ +
+
+ +

Отрисовка данных

+ +

Веб-фреймворки часто предоставляют системы шаблонов. Они позволяют вам указать структуру выходного документа, используя заполнители для данных, которые будут добавлены при создании страницы. Шаблоны часто используются для создания HTML, но могут также создавать другие типы документов.
+
+ Веб-фреймворки часто предоставляют механизм, позволяющий легко создавать другие форматы из хранимых данных, включая {{glossary ("JSON")}} и {{glossary ("XML")}}.
+
+ Например, система шаблонов Django позволяет вам задавать переменные с использованием синтаксиса «двойных велосипедных рулей» (например,
 {имя_переменной }}), которые будут заменены значениями, передаваемыми из функции «view» при отрисовке страницы. Система шаблонов также обеспечивает поддержку выражений (с синтаксисом: {% выражение %}), которые позволяют шаблонам выполнять простые операции, такие как повторение значений списка, передаваемых в шаблон.

+ +
+

На заметку. Многие другие системы шаблонов используют аналогичный синтаксис, например: Jinja2 (Python), handlebars (JavaScript), moustache (JavaScript) и т. п.

+
+ +

Фрагмент кода ниже показывает, как это работает. Продолжая пример «самой молодой команды» из предыдущего раздела, HTML-шаблон передаёт представлению переменную списка youngest_teams. Внутри скелета HTML у нас есть выражение, которое сначала проверяет, существует ли переменная youngest_teams, а затем повторяет её в цикле for. На каждой итерации шаблон отображает значение team_name команды в элементе списка.

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

Как выбрать веб-фреймворк

+ +

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

+ +

Вот некоторые из факторов, которые могут повлиять на ваше решение:

+ + + +

Существует также много других возможных влияющих факторов, включая вопросы лицензирования, зависимость от того, находится ли фреймворк в процессе активной разработки и т. д.
+
+ Если вы абсолютный новичок в программировании, вы, вероятно, выберете свою среду на основе «простоты обучения». В дополнение к «простоте использования» самого языка, ваши самые ценные ресурсы — это высококачественная документация / учебные пособия и активное сообщество, помогающее новым пользователям. Мы выбрали Django (Python) и Express (Node/JavaScript) для написания наших примеров далее в курсе, главным образом потому, что они просты в освоении и имеют хорошую поддержку.

+ +
+

На заметку: Давайте перейдём к основным веб-сайтам для Django (Python) и Express (Node/JavaScript) и ознакомимся с их документацией и сообществом.

+ +
    +
  1. Перейдите к основным сайтам (ссылки выше) + +
      +
    • Нажмите на ссылки меню «Документация» (такие вещи, как «Документация, Руководство, Справочник по API, Начало работы»).
    • +
    • Можете ли вы увидеть темы, показывающие, как настроить маршрутизацию URL, шаблоны и базы данных / модели?
    • +
    • Понятны ли эти документы?
    • +
    +
  2. +
  3. Перейдите к спискам рассылки для каждого сайта (доступно по ссылкам сообщества). +
      +
    • Сколько вопросов было опубликовано за последние несколько дней?
    • +
    • У скольких есть ответы?
    • +
    • Есть ли у них активное сообщество?
    • +
    +
  4. +
+
+ +

Несколько хороших веб-фреймворков?

+ +

Давайте продолжим и обсудим несколько конкретных серверных веб-фреймворков.
+
+ Фреймворки на стороне сервера, представленные ниже, представляют собой несколько самых популярных из доступных на момент написания. Все они имеют всё, что вам нужно для продуктивной работы
они с открытым исходным кодом, находятся в процессе активной разработки, имеют полные энтузиазма сообщества, создающие документацию и помогающие пользователям на форумах, и используются на большом количестве выдающихся веб-сайтов. Существует также много других замечательных серверных фреймворков, которые вы можете найти с помощью обычного поиска в Интернете.

+ +
+

На заметку: Описания взяты (частично) с веб-сайтов фреймворка!

+
+ +

Django (Python)

+ +

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

+ +

Популярные сайты, использующие Django (с домашней страницы Django), включают в себя: Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, Open Stack.

+ +

Flask (Python)

+ +

Flask это микрофреймворк для Python.
+
+ И хотя Flask минималистичен, он может создавать серьезные веб-сайты из коробки. Он содержит сервер разработки и отладчик, а также поддерживает шаблоны Jinja2, безопасные файлы cookie, модульное тестирование и диспетчеризацию запросов RESTful. У него хорошая документация и активное сообщество.

+ +

Flask стал чрезвычайно популярным, особенно для разработчиков, которым необходимо предоставлять веб-сервисы в небольших системах с ограниченными ресурсами (например, запуск веб-сервера на Raspberry Pi, контроллеры Drone и т. п.).

+ +

Express (Node.js/JavaScript)

+ +

Express — быстрый, непринуждённый, гибкий и минималистский веб-фреймворк для Node.js (node — это серверная среда для запуска JavaScript). Он обеспечивает надёжный набор функций для веб и мобильных приложений и предоставляет полезные HTTP-утилиты и middleware (промежуточные интерфейсы).

+ +

Express чрезвычайно популярен, частично потому, что он облегчает миграцию клиентских веб-программистов JavaScript в разработку на стороне сервера, а частично потому, что он ресурсоэффективен (базовая среда узлов использует легкую многозадачность в потоке, а не порождает отдельные процессы для каждого новый веб-запрос).
+
+ Поскольку Express является минималистской веб-инфраструктурой, он не включает в себя все компоненты, которые вы, возможно, захотите использовать (например, доступ к базе данных и поддержка пользователей и сеансов предоставляются через независимые библиотеки). Есть много отличных независимых компонентов, но иногда бывает сложно решить, какой из них лучше всего подходит для конкретной цели!
+
+ Многие популярные серверные и полные стеки (включая серверные и клиентские) основаны на Express, включая Feathers, ItemsAPI, KeystoneJS, Kraken, LEAN-STACK, LoopBack, MEAN и Sails.
+
+ Многие крупные компании используют Express, в том числе: Uber, Accenture, IBM и т. Д. (Список приведен здесь).

+ +

Ruby on Rails (Ruby)

+ +

Rails (обычно называемый «Ruby on Rails») - это веб-фреймворк, написанный для языка программирования Ruby.
+
+ Rails следует очень похожей философии дизайна на Django. Как и Django, он предоставляет стандартные механизмы для маршрутизации URL, доступа к данным из базы данных, генерации HTML из шаблонов и форматирования данных как {{glossary ("JSON")}} или {{glossary ("XML")}}. Точно так же поощряется использование шаблонов проектирования, таких как DRY («не повторяйте себя» - пишите код только один раз, если это возможно), MVC (модель-представление-контроллер) и ряд других.
+
+ Конечно, есть много различий, связанных с конкретными проектными решениями и характером языков.
+
+ Rails использовался для крупных сайтов, в том числе: Basecamp, GitHub, Shopify, Airbnb, Twitch, SoundCloud, Hulu, Zendesk, Square, Highrise.

+ +

ASP.NET

+ +

ASP.NET - это веб-инфраструктура с открытым исходным кодом, разработанная Microsoft для создания современных веб-приложений и сервисов. С ASP.NET вы можете быстро создавать веб-сайты на основе HTML, CSS и JavaScript, масштабировать их для использования миллионами пользователей и легко добавлять более сложные возможности, такие как веб-API, формы поверх данных или коммуникации в режиме реального времени.
+
+ Одним из отличий для ASP.NET является то, что он построен на Common Language Runtime (CLR), что позволяет программистам писать код ASP.NET с использованием любого поддерживаемого языка .NET (C #, Visual Basic и т. д.). Как и многие продукты Microsoft, он обладает отличными инструментами (часто бесплатными), активным сообществом разработчиков и хорошо написанной документацией.
+
+ ASP.NET используется Microsoft, Xbox.com, Stack Overflow и многими другими.

+ +

Mojolicious (Perl)

+ +

Mojolicious - это веб-фреймворк следующего поколения для языка программирования Perl.
+
+ Еще в первые дни Интернета многие люди изучали Perl из-за замечательной библиотеки Perl под названием CGI. Это было достаточно просто, чтобы начать, не зная много о языке и достаточно мощный, чтобы держать вас в курсе. Mojolicious реализует эту идею, используя новейшие технологии.
+
+ Некоторые из функций, предоставляемых Mojolicious: веб-инфраструктура в режиме реального времени, позволяющая легко превращать отдельные файловые прототипы в хорошо структурированные веб-приложения MVC; RESTful маршруты, плагины, команды, шаблоны Perl-ish, согласование контента, управление сеансами, проверка форм, структура тестирования, статический файловый сервер, обнаружение CGI / PSGI, поддержка Unicode первого класса; Реализация полного стека HTTP и WebSocket клиент / сервер с IPv6, TLS, SNI, IDNA, HTTP / SOCKS5 прокси, сокет домена UNIX, Comet (длинный опрос), поддержка активности, пул соединений, тайм-аут, cookie, поддержка нескольких частей и сжатия gzip; Парсеры и генераторы JSON и HTML / XML с поддержкой селекторов CSS; Очень чистый, портативный и объектно-ориентированный чистый Perl API без скрытой магии; Свежий код, основанный на многолетнем опыте, бесплатный и открытый исходный код.

+ +

Резюме

+ +

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

+ +

Для следующей статьи в этом модуле мы немного изменим направление и рассмотрим веб-безопасность.

+ +

{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}

+ +

В этом модуле

+ + + +
+ +
+
+
+
+ +
+ +
+
+ +
+
+
diff --git "a/files/ru/learn/server-side/first_steps/\320\262\320\265\320\261_\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\276\321\201\321\202\321\214/index.html" "b/files/ru/learn/server-side/first_steps/\320\262\320\265\320\261_\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\276\321\201\321\202\321\214/index.html" new file mode 100644 index 0000000000..6caa9b2aa2 --- /dev/null +++ "b/files/ru/learn/server-side/first_steps/\320\262\320\265\320\261_\320\261\320\265\320\267\320\276\320\277\320\260\321\201\320\275\320\276\321\201\321\202\321\214/index.html" @@ -0,0 +1,169 @@ +--- +title: Веб-безопасность +slug: Learn/Server-side/First_steps/Веб_Безопасность +translation_of: Learn/Server-side/First_steps/Website_security +--- +
{{LearnSidebar}}
+ +
{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}
+ +

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

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

Что такое безопасность сайта?

+ +

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

+ +

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

+ +

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

+ +

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

+ +
+

Примечание: Это вводная статья, призванная помочь вам задуматься о безопасности веб-сайта, но она не является исчерпывающей.

+
+ +

Угрозы бесопасности сайта

+ +

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

+ +

Межсайтовый скриптинг (XSS)

+ +

XSS (Cross-Site Scripting - Межсайтовый скриптинг) это термин, используемый для описания типа атак, которые позволяют злоумышленнику внедрять вредоносный код через веб-сайт в браузеры других пользователей. Поскольку внедренный код поступает в браузер с сайта, он является доверенным и может выполнять такие действия, как отправка авторизационного файла cookieпользователя злоумышленнику. Когда у злоумышленника есть файл cookie, он может войти на сайт, как если бы он был пользователем, и сделать все, что может пользователь, например, получить доступ к данным кредитной карты, просмотреть контактные данные или изменить пароли.

+ +
+

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

+
+ +

Уязвимости XSS делятся на отраженные и хранимые, в зависимости от того, как сайт возвращает внедренный код в браузер.

+ + + +

SQL injection

+ +

Уязвимости SQL-инъекций позволяют злоумышленникам выполнять произвольный код SQL в базе данных, позволяя получать, изменять или удалять данные независимо от разрешений пользователя. Успешная инъекционная атака может подделать удостоверения, создать новые удостоверения с правами администратора, получить доступ ко всем данным на сервере или уничтожить / изменить данные, чтобы сделать их непригодными для использования.
+
+ Типы внедрения SQL включают внедрение SQL на основе ошибок, внедрение SQL на основе логических ошибок и внедрение SQL на основе времени.
+
+ Эта уязвимость присутствует, если пользовательский ввод, который передается в базовый оператор SQL, может изменить смысл оператора. Например, следующий код предназначен для перечисления всех пользователей с определенным именем (userName), которое было предоставлено из формы HTML:

+ +
statement = "SELECT * FROM users WHERE name = '" + userName + "';"
+ +

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

+ +
SELECT * FROM users WHERE name = 'a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't';
+
+ +

Модифицированный оператор создает действительный оператор SQL, который удаляет таблицу пользователей и выбирает все данные из таблицы userinfo (которая раскрывает информацию о каждом пользователе). Это работает, потому что первая часть введенного текста (a ';) завершает исходное утверждение.
+
+ Чтобы избежать такого рода атак, вы должны убедиться, что любые пользовательские данные, которые передаются в запрос SQL, не могут изменить природу запроса. Один из способов сделать это - экранировать все символы пользовательского ввода, которые имеют особое значение в SQL.

+ +
+

Примечание. Инструкция SQL обрабатывает символ ' как начало и конец строкового литерала. Поместив обратную косую черту перед этим символом (\ '), мы экранируем символ и говорим SQL вместо этого трактовать его как символ (только часть строки).

+
+ +

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

+ +
SELECT * FROM users WHERE name = 'a\';DROP TABLE users; SELECT * FROM userinfo WHERE \'t\' = \'t';
+
+
+ +

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

+ +
+

Примечание: этот раздел в значительной степени основан на информации из Wikipedia.

+
+ +

Подделка межсайтовых запросов (CSRF)

+ +

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

+ +

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

+ +

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

+ +

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

+ +
+

Примечание: Уловка здесь в том, что Джону не нужен доступ к файлам cookie пользователя (или учетным данным). Браузер пользователя сохраняет эту информацию и автоматически включает ее во все запросы к соответствующему серверу.

+
+ +

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

+ +

Веб-фреймворки часто включают такие механизмы предотвращения CSRF.

+ +

Прочие угрозы

+ +

Другие распространенные атаки / уязвимости включают:

+ + + +

Полный список угроз безопасности веб-сайтов см. Category: Web security exploits (Wikipedia) и Category: Attack (Open Web Application Security Project).

+ +

Несколько ключевых сообщений

+ +

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

+ +
+

Внимание! Самый важный урок, который вы можете извлечь о безопасности веб-сайтов: никогда не доверяйте данным из браузера. Это включает, помимо прочего, данные в параметрах URL-адресов запросов GET, запросов POST, заголовков HTTP и файлов cookie, а также файлов, загруженных пользователем. Всегда проверяйте и дезинфицируйте все входящие данные. Всегда предполагайте худшее!

+
+ +

Вы можете предпринять ряд других конкретных шагов:

+ + + +

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

+ +

Резюме

+ +

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

+ +

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

+ +

{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}

+ +

В этом модуле

+ + diff --git a/files/ru/learn/server-side/index.html b/files/ru/learn/server-side/index.html new file mode 100644 index 0000000000..306d52ad79 --- /dev/null +++ b/files/ru/learn/server-side/index.html @@ -0,0 +1,54 @@ +--- +title: Серверное программирование веб-сайтов +slug: Learn/Server-side +tags: + - Beginner + - CodingScripting + - Intro + - Landing + - Learn + - NeedsTranslation + - Server + - Server-side programming + - Topic + - TopicStub +translation_of: Learn/Server-side +--- +
{{LearnSidebar}}
+ +

Тема Динамические веб-сайты – серверное программирование состоит из ряда модулей, рассматривающих создание динамических веб-сайтов; сайтов, которые доставляют персонализированную информацию в ответ на HTTP запрос. Этот модуль предоставляет общее введение в серверное программирование, наряду со специальными руководствами начального уровня о том, как использовать Django (Python) и Express (Node.js/JavaScript) веб-фреймворки для создания простых приложений.

+ +

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

+ +

В современном мире веб-разработки крайне рекомендуется изучить разработку на стороне сервера.

+ +

Программа обучения

+ +

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

+ +

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

+ +

Вам потребуется понимать "как работает веб". Мы рекомендуем вам сначала ознакомиться с темами:

+ + + +

С этим базовым набором знаний вы будете готовы освоить модули в этой секции.

+ +

Модули

+ +

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

+ +
+
Первые шаги в программировании веб-сайтов на стороне сервера
+
Этот модуль посвящен информации о технологиях программирования веб-сайтов на стороне сервера, попутно отвечая и на фундаментальные вопросы о серверном программировании — "что это такое", "чем оно отличается от программирования на стороне клиента", и "почему оно так востребовано" — и обозревая некоторые из наиболее популярных серверных веб-фреймворков, а также объясняя как выбрать подходящий для вашего сайта. Напоследок мы организуем вводный раздел о безопасности веб-сервера.
+
Веб-фреймворк Django (Python)
+
Django является чрезвычайно популярным и полнофункциональным серверным веб-фреймворком, написанным на Python. Этот модуль объяснит почему Django настолько хороший серверный веб-фреймворк, как установить среду разработки и как с его помощью можно выполнять привычные задачи.
+
Веб-фреймворк Express (Node.js/JavaScript)
+
Express - популярный веб-фреймворк, написанный на JavaScript и размещенный в среде окружения node.js. Модуль объясняет некоторые из ключевых преимуществ этой структуры, как настроить среду разработки и как выполнять общие задачи для веб-разработки и развертывания.
+
Сервер на Node без фреймворков
+
В этой статье представлен простой статический файловый сервер, построенный с использованием чистого Node.js, для тех из вас, кто не хочет использовать фреймворк.
+
diff --git a/files/ru/learn/server-side/node_server_without_framework/index.html b/files/ru/learn/server-side/node_server_without_framework/index.html new file mode 100644 index 0000000000..087627c20a --- /dev/null +++ b/files/ru/learn/server-side/node_server_without_framework/index.html @@ -0,0 +1,84 @@ +--- +title: Node.js server without a framework +slug: Learn/Server-side/Node_server_without_framework +translation_of: Learn/Server-side/Node_server_without_framework +--- +
{{LearnSidebar}}
+ +

Здесь вы найдете описание простого статического сервера, который построен сугубо на Node.js без использования какого-либо фреймворка .

+ +

Node.js может использовать множество фреймворков, которые могут помочь создать сервер

+ +

Наиболее популярные:

+ + + +

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

+ +

Пример

+ +

Вот так выглядит  статический сервер на Node.js:

+ +
var http = require('http');
+var fs = require('fs');
+var path = require('path');
+
+http.createServer(function (request, response) {
+    console.log('request ', request.url);
+
+    var filePath = '.' + request.url;
+    if (filePath == './') {
+        filePath = './index.html';
+    }
+
+    var extname = String(path.extname(filePath)).toLowerCase();
+    var contentType = 'text/html';
+    var mimeTypes = {
+        '.html': 'text/html',
+        '.js': 'text/javascript',
+        '.css': 'text/css',
+        '.json': 'application/json',
+        '.png': 'image/png',
+        '.jpg': 'image/jpg',
+        '.gif': 'image/gif',
+        '.wav': 'audio/wav',
+        '.mp4': 'video/mp4',
+        '.woff': 'application/font-woff',
+        '.ttf': 'application/font-ttf',
+        '.eot': 'application/vnd.ms-fontobject',
+        '.otf': 'application/font-otf',
+        '.svg': 'application/image/svg+xml'
+    };
+
+    contentType = mimeTypes[extname] || 'application/octet-stream';
+
+    fs.readFile(filePath, function(error, content) {
+        if (error) {
+            if(error.code == 'ENOENT'){
+                fs.readFile('./404.html', function(error, content) {
+                    response.writeHead(200, { 'Content-Type': contentType });
+                    response.end(content, 'utf-8');
+                });
+            }
+            else {
+                response.writeHead(500);
+                response.end('Sorry, check with the site admin for error: '+error.code+' ..\n');
+                response.end();
+            }
+        }
+        else {
+            response.writeHead(200, { 'Content-Type': contentType });
+            response.end(content, 'utf-8');
+        }
+    });
+
+}).listen(8125);
+console.log('Server running at http://127.0.0.1:8125/');
+ +

Задание

+ +

Попробуйте добавить в этот код описание как работает этот код. Как вариант еще можно добавить функционал динамических запросов.

-- cgit v1.2.3-54-g00ecf