The words you are searching are inside this book. To get more targeted content, please make full-text search by clicking here.

Django 3.0 Praktika sozdania Web-saytov na Python 2021

Discover the best professional documents and content resources in AnyFlip Document Base.
Search
Published by Pronko.yuri, 2022-09-19 04:40:55

Django 3.0 Praktika sozdania Web-saytov na Python

Django 3.0 Praktika sozdania Web-saytov na Python 2021

Keywords: Phyton

650 Часть IV. Практическое занятие: разработка веб-сайта

if formset.is_valid() :
formset.save()
messages.add_message(request, messages.SUCCESS,
’Объявление добавлено’)
return redirect('main:profile')

else:
form = BbForm (initial={'author': request.user.pk})
formset = AIFormSetO

context = {'form': form, 'formset': formset}
return render(request, 'main/profile_bb_add.html', context)

Здесь нужно отметить три важных момента. Во-первых, при создании формы перед
выводом страницы сохранения мы заносим в поле author формы ключ текущего
пользователя, который станет автором объявления.
Во-вторых, во время сохранения введенного объявления, при создании объектов
формы и набора форм, мы должны передать конструкторам их классов вторым по­
зиционным параметром словарь со всеми полученными файлами (он хранится
в атрибуте files объекта запроса). Если мы не сделаем этого, то отправленные
пользователем иллюстрации окажутся потерянными.
В-третьих, при сохранении мы сначала выполняем валидацию и сохранение формы
самого объявления. Метод save о в качестве результата возвращает сохраненную
запись, и эту запись мы должны передать через параметр instance конструктору
класса набора форм. Это нужно для того, чтобы все дополнительные иллюстрации
после сохранения оказались связанными с объявлением.
Напишем маршрут, который укажет на страницу добавления, поместив его перед
маршрутом, указывающим на страницу профиля:

from .views import profile_bb_add

uripatterns = [

path (' accounts/profile/add/ •, profile_bb_add, name=' profile_bb_add') ,
path('accounts/profile/<int:pk>/', profile_bb_detail,

name='profile_bb_detail'),

]

Займемся шаблоном main\profile_bb_add.html, который создаст страницу добавления
объявления. Его код приведен в листинге 34.12.

{% extends "layout/basic.html" %}

{% load bootstrap4 %}

Гпава 34. Объявления 651

{% block title %}Добавление объявления - Профиль пользователя!% endblock %}

{% block content %}
<Ь2>Добавление объявления</Ъ2>
<form method=”post” enctype="multipart/form-data">

{% csrf_token %}
{% bootstrap_form form layout=’horizontal' %}
{% bootstrap_formset formset layout='horizontal' %}
{% buttons submit='Добавить’ %}{% endbuttons %}
</form>
{% endblock %}

Обязательно укажем у формы метод кодирования данных muitipart/form-data.
Если этого не сделать, то занесенные в форму файлы не будут отправлены. А набор
форм выведем с помощью тега шаблонизатора bootstrap formset.

Наконец, в шаблон страницы профиля main\profile.html добавим гиперссылку на
страницу добавления объявления:

<р><а href="{% url 'main:profile_bb_add' %}">Добавить объявление</а></р>

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

Код контроллеров profile_bb_change() И profile bb delete (), которые соответст­
венно правят и удаляют объявление, приведен в листинге 34.13.

@login_required
def profile_bb_change(request, pk):

bb = get_object_or_404(Bb, pk=pk)
if request.method == ’POST’:

form = BbForm(request.POST, request.FILES, instance=bb)
if form.is_valid():

bb = form.save()
formset = AiFormSet(request.POST, request.FILES, instance=bb)
if formset.is_valid():

formset.save()
messages.add_message(request, messages.SUCCESS,

’Объявление исправлено’)
return redirect('main:profile’)
else:
form = BbForm (instance=bb)
formset = AiFormSet(instance=bb)
context = {'form’: form, 'formset': formset}
return render(request, 'main/profile_bb_change.html', context)

652 Часть IV. Практическое занятие: разработка веб-сайта

@login_fcequired
def profile_bb_delete(request, pk):

bb = get_object_or_404(Bb, pk=pk)
if request.method == ’POST’:

bb.delete()
messages.add_message(request, messages.SUCCESS,

'Объявление удалено’)
return redirect(’main:profile’)
else:
context = {’bb’: bb}
return render(request, ’main/profile_bb_delete.html’, context)

Для простоты на странице удаления объявления не будем выводить дополнитель­
ные иллюстрации — они там не особо нужны.
Объявим необходимые маршруты:

from .views import profile_bb_change, profile_bb_delete

urlpatterns = [

path (’ acoounts/profile/change/<int: pk>/ ’, profile_bb_change,
name-'profile_bb_change'),

path (' accounts/profile/delete/<int: pk>/', profile_bb_delete,
name= 'profile_hb_delete’) ,

path(’accounts/profile/add/’, profile_bb_add, name=’profile_bb_add’),

]

Шаблоны main\profile_bb_change.html и main\profile_bb_delete.html вы можете написать
самостоятельно, использовав в качестве основы ранее написанные шаблоны.
В шаблон main\profile.html нужно добавить код, создающий гиперссылки для правки
и удаления каждого из занесенных пользователем в базу объявлений:

<div class=”media-body">
<р>Рубрика: {{ bb.rubric }}</р>

<р class=” text-right mt-2’’>
<a href="{% url ’main:profile_bb_change' pk=bb.pk %}”>
Исправить</а>
<a href="{% url 'main:profile_bb_delete' pk^ib.pk %}">Удалить</а>

</p>
</div>

Напоследок следует проверить в действии реализованную функциональность по
правке и удалению объявлений.

ГЛАВА 35

Комментарии

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

35.1. Подготовка к выводу CAPTCHA

Сделаем так, чтобы зарегистрированные пользователи могли оставлять коммента­
рии беспрепятственно, а гости должны были дополнительно ввести CAPTCHA. Так
мы хоть как-то обезопасим сайт от атаки служб рассылки спама.
Установим библиотеку Django Simple Captcha:

pip install django-simple-captcha

Добавим в список зарегистрированных в проекте приложение captcha— про­
граммное ядро этой библиотеки:

INSTALLED_APPS = [

’captcha’,
]

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

uripatterns = [

path('captcha/1, include('captcha.urls’)),
path('', include('main.urls')),
1

Миграции пока выполнять не будем — еще нужно объявить модель для хранения
комментариев.

654 Часть IV. Практическое занятие: разработка веб-сайта

35.2. Модель комментария

Модель, хранящую комментарии, мы назовем comment. Ее структура приведена
в табл. 35.1.

Таблица 35.1. Структура модели Comment

Имя Тип Дополнительные Описание
bb ForeignKey параметры
Объявление, к которому
author CharField Каскадное удаление оставлен комментарий
content TextField разрешено
is active BooleanField Имя автора
Длина — 30 символов
created at DateTimeFieid Содержание
Значение по умолчанию —
True, индексированное Признак, показывать ли
комментарий в списке
Подставляется текущее
значение даты и времени, Дата и время публикации
индексированное комментария

Код, объявляющий модель comment, приведен в листинге 35.1.

class Comment(models.Model):
bb = models.ForeignKey(Bb, on_delete=models.CASCADE,
verbose_name=’Объявление’)
author = models.CharField(max_length=30, verbose_name= 'Автор')
content = models.TextField(verbose_name='Содержание')
is_active = models.BooleanField(default=True, db_index=True,
verbose_name='Выводить на экран?')
created_at = models.DateTimeFieid(auto_now_add=True, db_index=True,
verbose_name='Опубликован')

class Meta:
verbose_name_plural = 'Комментарии'
verbose_name = 'Комментарий'
ordering = ['created_at']

Для комментариев указываем сортировку по увеличению временной отметки их
добавления. В результате более старые комментарии будут располагаться в начале
списка, а более новые — в его конце.
Создадим миграции, после чего выполним их (при этом также будут выполнены
миграции из библиотеки Django Simple Captcha).

Гпава 35. Комментарии 655

35.3. Вывод и добавление комментариев

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

Объявим связанные С моделью Comment формы UserCommentForm И GuestComment Form,
в которые будут заносить комментарии, соответственно, зарегистрированные поль­
зователи, выполнившие вход, и гости. Код этих форм приведен в листинге 35.2.

from captcha.fields import CaptchaFieid

from .models import Comment

class UserCommentForm(forms.ModelForm) :
class Meta:
model = Comment
exclude = (’is_active1,)
widgets = {'bb’: forms.Hiddenlnput}

class GuestComment Form (forms. ModelForm) :
captcha = CaptchaFieid(label='Введите текст с картинки’,
error_messages={’invalid’: 'Неправильный текст’})

class Meta:
model = Comment
exclude = (’is_active',)
widgets = {’bb': forms.Hiddenlnput}

Поле is active (признак, будет ли комментарий выводиться на странице) уберем из
форм, поскольку оно требуется лишь администрации сайта. У поля ьь, хранящего
ключ объявления, с которым связан комментарий, укажем в качестве элемента
управления скрытое поле.
Теперь необходимо существенно обновить код контроллера-функции detail о, на­
писанного в главе 34. реализовав в нем вывод комментариев и добавление нового
комментария. Код обновленного контроллера приведен в листинге 35.3.

from .models import Comment
from . forms import UserCommentForm, GuestCommentForm

def detail(request, rubric_pk, pk):
bb = Bb.objects.get(pk=pk)
ais = bb.additionalimage_set.all()

656 Часть IV. Практическое занятие: разработка веб-сайта

comments = Comment.objects.filter(bb=pk, is_active=True)

initial = {’bb’: bb.pk}

if request.user.is_authenticated:

initial['author’] = request.user.username

form_class = UserCommentForm

else:

form_class = GuestCommentForm

form = form_class(initial=initial)

if request.method == ’POST’:

c_form = form_class(request.POST)

if c_form.is_valid():

c_form. save ()

messages.add_message(request, messages.SUCCESS,

’Комментарии добавлен’)

else:

form = c_form

messages.add_message(request, messages.WARNING,

’Комментарий не добавлен’)

context = {’bb’: bb, ’ais’: ais, ’comments’: comments, ’form’: form}

return render(request, ’main/detail.html’, context)

В поле ьь создаваемой формы ввода комментария заносим ключ выводящегося на
странице объявления — с ним будет связан добавляемый комментарий. Если теку­
щий пользователь выполнил вход на сайт, заносим его имя в поле author этой фор­
мы комментария. Наконец, если текущий пользователь выполнил вход на сайт, то
создаем форму на основе класса UserCommentForm, а если не выполнил — на основе
класса Guestcommentuser. Все эти действия выполняет фрагмент кода:

initial = {’bb’: bb.pk}
if request.user.is_authenticated:

initial[’author’] = request.user.username
form_class = UserCommentForm
else:
form_class = GuestCommentForm
form = form_class(initial=initial)

Объект формы сохраним в переменной с именем form. Форма из этой переменной
впоследствии будет выведена на странице сведений об объявлении.

Далее, если полученный запрос был отправлен HTTP-методом POST, т. е. посети­
тель ввел комментарий и отправил его на сохранение, создаем еще один объект
формы, передав конструктору полученные данные. Второй объект формы сохраня­
ется в переменной c form. После этого выполняем валидацию второй формы.

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

Гпава 35. Комментарии 657

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

В шаблоне main\detail.html отыщем тег шаблонизатора block content . . . endblock
и вставим перед закрывающим тегом код, выводящий комментарии:

{% block content %}

<h4 class="mt-5">HoBbi& комментарий</Ь4>
<form method="post">

{% csrf_token %}
{% bootstrap_form form layout=’horizontal’ %}
{% buttons submit=' Добавить’ %} {% endbuttons %}
</form>
{% if comments %}
<div class=’’mt-5”>
{% for comment in comments %}
<div class="my-2 p-2 border">

<h5>{{ comment.author }}</h5>
<p>{{ comment.content }}</p>
<p class-"text-right font-italic">{ { comment.createdjat }}</p>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}

Стилевой класс my-2 задает небольшие внешние отступы сверху и снизу, а стилевой
класс р-2 — небольшие внутренние отступы со всех сторон. Мы используем их,
чтобы создать просветы между отдельными комментариями.

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

Осталось добавить список комментариев на административную страницу сведений
об объявлении. Сделайте это самостоятельно. Форму для ввода комментария и
соответствующую функциональность в контроллере можно не делать— вряд ли
автор объявления будет комментировать его сам...

35.4. Отправка уведомлений
о новых комментариях

Отправлять уведомления о появлении новых комментариев будем в обработчике
сигнала post save, возникающего после сохранения записи в модели comment.

658 Часть IV. Практическое занятие: разработка веб-сайта

Откроем модуль utilities.py пакета приложения и добавим в него объявление функ­
ции send_new_comment_notification (), которая и выполнит отправку уведомления.
Ее КОД ПОХОЖ на КОД аналогичной функции send_activation_notification()
(см. листинг 32.18) и приведен в листинге 35.4.

def send_new_comment_notification(comment):
if ALLOWED_HOSTS:
host = ’http://’ + ALLOWED_HOSTS[0]
else:
host = ’http://localhost:8000'
author = comment.bb.author
context = {’author’: author, ’host’: host, ’comment’: comment}
subject = render_to_string(’email/new_comment_letter_subject.txt’, context)
body_text = render_to_string('email/new_comment_letter_body.txt’, context)
author.email_user(subject, body_text)

Шаблоны email\new_comment_letter_subject.txt и email\new_comment_letter_body.txt, кото­
рые сформируют тему и тело электронного письма, вы, уважаемые читатели, соз­
дайте самостоятельно. Их можно написать на основе аналогичных шаблонов
email\activation_letter_subject.txt и email\activation_letter_body.txt (см. листинги 32.19 и 32.20).
В письме нужно указать интернет-адрес административной страницы сведений об
объявлении, к которому был оставлен новый комментарий.
Осталось лишь йривязать к сигналу post save обработчик, вызывающий функцию

send_new_comment_notification() ПОСЛе Добавления комментария. Код, ВЫПОЛНЯЮ­

ЩИЙ привязку, поместим в модуль models.ру пакета приложения. Вот этот код:

from django.db.models.signals import post_save

from .utilities import send_new_comment_notification

def post_save_dispatcher(sender, **kwargs):
author = kwargs[’instance’].bb.author
if kwargs[’created’] and author.send_messages:
send_new_comment_notification(kwargs[’instance’])

post_save.connect(post_save_dispatcher, sender=Comment)

Перед тем как отправлять оповещение, следует проверить, не запретил ли пользо­
ватель их отправку, т. е. не хранит ли поле send_messages модели пользователя
AdvUser значение False.
Попробуем еще раз добавить комментарий к какому-либо объявлению и удостове­
римся, что уведомление о новом комментарии было отправлено.
Осталось лишь объявить редактор для модели comment и зарегистрировать его на
административном сайте Django. Автор полагает, что читатели сделают это само­
стоятельно.

ГЛАВА 36

Веб-служба REST

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

36.1. Веб-служба

36.1.1. Подготовка к разработке веб-службы

Установим библиотеки Django REST framework и django-cors-headers, для чего на­
берем команды:

pip install djangorestframework
pip install django-cors-headers

Сразу же создадим новое приложение api, в котором и реализуем функциональ­
ность веб-службы:

manage.py startapp api

Добавим приложения rest_framework И corsheaders — Программные Ядра ТОЛЬКО
что установленных библиотек, а также только что созданное приложение api
в список зарегистрированных в проекте:

INSTALLED_APPS = [

’rest_framework',
’corsheaders’,
’api.apps.ApiConf ig’,

]
Добавим в список зарегистрированных в проекте необходимый для работы по­
средник:

660 Часть IV. Практическое занятие: разработка веб-сайта

MIDDLEWARE = [

* corsheaders .middleware.CorsMiddleware',
’ dj ango. middleware. common. CommonMiddleware ’,

]

He забудем указать там же, в модуле settings.py пакета конфигурации, настройки,
разрешающие доступ к веб-службе с любого домена:

CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = r’A/api/.*$’

36.1.2. Список объявлений

Создадим в пакете приложения api модуль serializers.py. В нем сохраним код сериа­
лизатора BbSerializer, формирующего список объявлений (листинг 36.1).

from rest_framework import serializers

from main.models import Bb

class BbSerializer(serializers.ModelSerializer): 'created_at’)
class Meta:
model = Bb
fields = (’id', ’title', ’content’, ’price’,

В составе сведений о каждом объявлении ради компактности будем отправлять
лишь ключ, название, описание, цену товара и временную отметку создания объ­
явления. Интернет-адрес основной иллюстрации и контакты отправим в составе
сведений о выбранном объявлении.

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

from rest—framework.response import Response
from rest—framework.decorators import api_view

from main.modeIs import Bb
from .serializers import BbSerializer

@api_view([’GET’])
def bbs(request):

if request.method = ’GET’:
bbs = Bb.objects.filter(is_active=True)[:10]

Гпава 36. Веб-служба REST____________________________________________________661

serializer = BbSerializer(bbs, many=True)
return Response(serializer.data)

Откроем список маршрутов уровня проекта (модуль urls.py пакета конфигурации)
и добавим маршрут, указывающий на приложение api:

uripatterns = [

path(1api/', include(1api.urls')),
path(’’, include(’main.urls’)),
1

В пакете приложения api создадим модель urls.py, в который запишем список мар­
шрутов уровня этого приложения (листинг 36.3).

from django.urls import path

from .views import bbs

uripatterns = [
path(’bbs/’, bbs),

1

Пока что он содержит лишь маршрут, указывающий на только что написанный
нами контроллер bbs ().
Сохраним код, запустим отладочный веб-сервер и попробуем получить список
объявлений, перейдя по интернет-адресу http://localhost:8000/api/bbs/. Если мы все
сделали правильно, то увидим веб-представление, показывающее последние
10 объявлений, которые были оставлены посетителями сайта.

36.1.3. Сведения о выбранном объявлении

В состав сведений о выбранном объявлении, помимо ключа записи, названия, опи­
сания, цены товара и даты создания объявления, следует включить контакты и ин­
тернет-адрес основной иллюстрации.
Учтем это при написании сериализатора BbDetaiiserializer, выдающего сведения
об объявлении (листинг 36.4). Занесем его код в модуль serializers.py пакета прило­
жения.

class BbDetaiiserializer(serializers.ModelSerializer):
class Meta:
model = Bb
fields = ('id’, ’title’, ’content’, ’price’, ’created_at’,
’contacts’, ’image’)

662 Часть IV. Практическое занятие: разработка веб-сайта

Контроллер, ВЫДаЮЩИЙ Сведения О ВЫбраННОМ Объявлении, Назовем BbDetailView
и реализуем в виде класса, производного от класса RetrieveAPiview. Его код, весьма
компактный, приведен в листинге 36.5.

from rest_framework.generics inport RetrieveAPiview

from .serializers import BbDetailSerializer

class BbDetailView(RetrieveAPiview):
queryset = Bb.objects.filter(is_active=True)
serializer_class = BbDetailSerializer

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

from .views import BbDetailView

urlpatterns = [

path (' bbs/<int: pk>/', BbDetailView. as_view ()) ,

path(’bbs/', bbs),
]
Сохраним код и попытаемся получить сведения об объявлении с ключом 1, для
чего выполним переход по интернет-адресу http://localhost:8000/api/bbs/l/. Далее
запросим сведения о паре других объявлений.

36.1.4. Вывод и добавление комментариев

Код сериализатора Commentserializer, который будет отправлять список коммента­
риев и добавлять новый комментарий, приведен в листинге 36.6.

from main.models import Comment

class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = (’bb’, ’author’, ’content’, ’created_at’)

Он отправит клиенту ключ объявления, с которым связан комментарий, имя автора,
содержимое и временную отметку создания комментария.
Код контроллера-функции comments (), выдающего список комментариев и добав­
ляющего новый комментарий, приведен в листинге 36.7.

Гпава 36. Веб-служба REST 663

from rest_framework.decorators import permission_classes
from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST
from rest—framework.permissions import IsAuthenticatedOrReadOnly

from main.models import Comment
from .serializers import CommentSerializer

@api_view([’GET’, ’POST’])
@permission_classes((IsAuthenticatedOrReadOnly,))
def comments(request, pk):

if request.method == ’POST’:
serializer = CommentSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=HTTP_201_CREATED)
else:
return Response(serializer.errors,
status=HTTP_400_BAD—REQUEST)

else:
comments = Comment.objects.filter(is_active=True, bb=pk)
serializer = CommentSerializer(comments, many=True)
return Response(serializer.data)

Ранее мы условились, что разрешим добавлять комментарии только зарегистриро­
ванным пользователям, а просматривать их, напротив, позволим всем. Поэтому мы
пометили контроллер декоратором permission ciasses о, в котором указали класс
разграничения доступа IsAuthenticatedOrReadOnly.

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

from .views import comments

uripatterns = [
path (’ bbs/<int: pk>/comments/' , comments) ,
path(’bbs/<int:pk>/’, BbDetailView.as_view()),

]

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

664 Часть IV. Практическое занятие: разработка веб-сайта

36.2. Тестовый фронтенд

Для тестирования нашей веб-службы создадим простой фронтенд, применив по­
пулярный клиентский веб-фреймворк Angular.

36.2.1. Введение в Angular

Angular — веб-фреймворк, предназначенный для написания клиентской части веб­
сайтов, в том числе фронтендов. Сайт, написанный с применением Angular, пред­
ставляет собой совокупность компонентов, служб, метамодулей, маршрутизатора
и одной-единственной веб-страницы.

□ Компонент— представляет пользовательский интерфейс фронтенда или его
часть. Непосредственно взаимодействует с пользователем. Может включать
в свой состав другие компоненты.

Компоненты Angular независимы и изолированы друг от друга. Чтобы реализо­
вать обмен данными между компонентами, следует явно предусмотреть в них
соответствующие средства.

Каждый компонент связан с определенным парным тегом (тегом компонента).
Чтобы вывести компонент на экран, достаточно вставить этот тег в HTML-код.

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

□ Компонент приложения— выводится на экран сразу при открытии Angular-
сайта (приложением в терминологии Angular называется сам сайт). Генерирует­
ся при создании нового проекта Angular.

Как правило, компонент приложения выводит другие компоненты, непосредст­
венно отображающие какие-либо данные.

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

□ Метамодуль (в оригинальной документации — ngModule) — выполняет две за­
дачи:

• объединяет компоненты и службы, составляющие сайт или его раздел (по­
добно приложению Django);

• инициализирует входящие в его состав компоненты, службы и прочие сущ­
ности, готовя их к работе.

Проект Angular-сайта может включать произвольное количество метамодулей,
как минимум, один.

Сущности — компоненты и службы, — объявленные в одном метамодуле, не
доступны в других. Чтобы сделать какую-либо сущность доступной в других

Гпава 36. Веб-служба REST____________________________________________________665

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

Сам Angular реализован в виде набора метамодулей, содержащих ключевые
службы.

□ Метамодуль приложения — стартует сразу при открытии Angular-сайта, запус­
кает ключевые службы и выводит на экран компонент приложения. Генерирует­
ся при создании нового проекта Angular.

□ Маршрутизатор — при переходе по интернет-адресу, совпадающему с опреде­
ленным шаблонным путем, выводит на экран соответствующий компонент (как,
собственно, и в Django). Реализован в виде службы в одном из метамодулей
Angular.

□ Стартовая веб-страница— единственная веб-страница, которая входит в со­
став Angular-сайта и на которой выводятся все входящие в его состав компо­
ненты.

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

Программный код Angular-сайтов пишется на языке TypeScript — дальнейшем раз­
витии JavaScript.

В состав любого проекта Angular входят компилятор, транслирующий TypeScript-
код в обычный JavaScript, и отладочный веб-сервер.

На заметку

Полное описание фреймворка Angular находится на сайте https://angular.io/, а описа­
ние языка TypeScript — на сайте https://www.typescriptlang.org/.

36.2.2. Подготовка к разработке фронтенда

Сначала установим исполняющую среду Node.js. Ее дистрибутивные комплекты
можно отыскать по интернет-адресу https://nodejs.org/en/dowiiload/current/.

Далее установим утилиту командной строки ng, с помощью которой выполняется
создание Angular-проектов, программных модулей различных типов, запуск отла­
дочного веб-сервера Angular и ряд других служебных задач. Установить ng можно,
набрав в командной строке команду:

npm install -g @angular/cli

Теперь создадим проект нашего Angular-фронтенда, который назовем bbciient.
Пользуясь командной строкой, перейдем в папку, в которой будет располагаться
папка проекта, и подадим команду:

ng new bbciient —defaults —skip-git —skip-tests

666 Часть IV. Практическое занятие: разработка веб-сайта

Ключ —defaults указывает создать проект с настройками по умолчанию— без
создания маршрутизации (мы сами ее сделаем) и с поддержкой таблиц стилей, на­
писанных на языке CSS. Ключи -skip-git и -skip-tests указывают не создавать
локальный репозиторий GIT и тестовые модули (они нам не нужны).

В результате будет создана папка проекта bbclient, содержащая с десяток служебных
файлов и три папки. Откроем папку src, в которой хранятся файлы с исходным ко­
дом сайта. Там находится файл index.html, содержащий HTML-код стартовой веб­
страницы. Откроем этот файл и посмотрим на код, создающий секцию тела страни­
цы (тег <body>):

<body>
<app-rootx/app-root>

</body>

Парный тег <app-root> — это тег компонента приложения, носящего имя
AppComponent. Встретив его, программное ядро Angular создаст объект этого компо­
нента и выведет его в данном месте страницы.

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

Изначально проект Angular включает лишь компонент приложения и метамодуль
приложения. Нам понадобятся еще два компонента и служба.

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

□ компонента списка объявлений BbListComponent:

ng generate component bb-list —flat

Флаг —flat задает размещение файлов с модулем непосредственно в папке
src\app, а не во вложенной папке (проект у нас несложный, и разносить его код
по разным вложенным папкам не имеет смысла);
□ компонента сведений О выбранном объявлении BbDetailComponent (он же выве­
дет список комментариев и форму для добавления нового комментария):

ng generate component bb-detail —flat

□ службы BbService — для ’’общения” с бэкендом, написанным в разд. 36.1:

ng generate service bb

36.2.3. Метамодуль приложения AppModule.
Маршрутизация в Angular

Метамодуль запускает в работу компоненты и службы, зарегистрированные в нем
(подробности — в разд. 36.2.1). Метамодуль приложения стартует непосредственно
при открытии сайта, запускает и выводит на экран компонент приложения.

Гпава 36. Веб-служба REST 667

Код метамодуля приложения хранится в файле app.module.ts (не забываем, что все
ключевые TypeScript-модули находятся в папке src\app). Класс метамодуля прило­
жения носит имя AppModuie. Его изначальный код приведен в листинге 36.8.

import { BrowserModule } from ’@angular/platform-browser’;
import { NgModule } from ’@angular/core’;

import { AppComponent } from /app.component’;
import { BbListComponent } from /bb-list.component';
import { BbDetailComponent } from /bb-detail.component’;

@NgModule({
declarations: [
AppComponent,
BbListComponent,
BbDetailComponent
],
imports: [
ВrowserModule

b

providers: [],
bootstrap: [AppComponent]
})
export class AppModuie { }

Языковые конструкции import . . . from выполняют импорт сущностей из раз­
личных модулей (язык TypeScript является модульным, как и Python).

Класс метамодуля практически всегда пуст — не имеет ни свойств, ни методов. Все
параметры метамодуля указываются в вызове декоратора NgModule из TypeScript-
модуля @anguiar/core, в виде объекта класса object со свойствами:

□ declarations — массив классов компонентов, регистрируемых в метамодуле.
Отметим, что там уже присутствуют, помимо компонента приложения
AppComponent, еще И Созданные Вручную компоненты BbListComponent и
BbDetailComponent. Их добавила туда утилита ng сразу при их создании;

□ providers — массив классов регистрируемых служб (у нас — пуст);
□ bootstrap— массив компонентов приложения. Практически всегда содержит

лишь компонент AppComponent (как и в нашем случае);
□ imports — массив метаимпортируемых метамодулей.

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

668 Часть IV. Практическое занятие: разработка веб-сайта

Нам нужно сделать следующее:
□ зарегистрировать службу вь, чтобы она заработала. Для этого добавим в мета­

модуль следующий код:

import { BbServioe } from './ЬЬ.service';

0NgModule({

providers: [
BbServioe

1,
bootstrap: [AppComponent]
})
export class AppModule { }

□ ВЫПОЛНИТЬ метаимпорт метамодуля FormsModule ИЗ модуля 0angular/forms И
метамодуля HttpciientModuie из модуля @anguiar/common/http. Первый обеспе­
чивает работу веб-форм (включая двустороннее связывание данных, о котором
поговорим позже), второй — взаимодействие с бэкендом. Добавим следующий код:

import { FormsModule ) from 1 0angular/forms1;
import { HttpClientModule } from ' 0 angular/common/http' ;

0NgModule({

imports: [
BrowserModule,
HttpClientModule,
FormsModule

1,

})
export class AppModule { }

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

import { registerLocaleData } from ’0angular/common';
import localeRu from ' 0angular/conmon/locales/ru1;
import localeRuExtra from ’0angular/common/locales/extra/ru’;
import { LOCALE_ID } from 10 angular/core1;

registerLocaleData(localeRu, 'ru', localeRuExtra);

0NgModule({
providers: [
BbService,

Гпава 36. Веб-служба REST 669

(provide: LOCALE_ID, useValue: ' ru'}

],
bootstrap: [AppComponent]
})
export class AppModule { }

Служба locale id из модуля @anguiar/core хранит обозначение текущего языка,
по умолчанию — 'en-us' (американский английский). Указав в массиве
providers объект класса object, свойство provide которого хранит ссылку на эту
службу, а свойство usevalue— строку ’ru’, мы изменим текущий язык на рус­
ский.

Функция registerLocaleData() ИЗ модуля 0angular/common непосредственно за­
дает модули, откуда Angular будет брать настройки, характерные для выбран­
ного языка. В первом параметре мы указали модуль locaieRu из пакета
@anguiar/common/iocaies/ru, содержащий основные настройки, во втором —
строковое обозначение нужного языка ’ru’, в третьем — модуль с дополнитель­
ными настройками localeRuExtra, объявленный В пакете 0angular/common/

locales/extra/ru;

□ написать список маршрутов, связав:

• шаблонный путь 1<КЛЮЧ объявления^--- С компонентом BbDetailComponent,
который выведет сведения об объявлении с заданным ключом;

• ’’корень” сайта — компонентом списка объявлений BbListComponent.

Добавим в метамодуль такой код:

import ( Routes } from ’0angular/router';

const appRoutes: Routes = [
(path: *:pk', component: BbDetailComponent},

(path: * 1, caapcment'. BbListComponent}

J;

0NgModule({

})
export class AppModule { }

Список маршрутов в виде массива мы сохранили в константе appRoutes. Каждый
маршрут описывается объектом класса object со свойствами path (шаблонный
путь в виде строки) и component (связанный с ним компонент). В шаблонном
пути первого маршрута ключ объявления будет передаваться в URL-параметре
с именем pk.
У константы мы указали тип Routes из модуля 0angular/router, записав его
после двоеточия. TypeScript поддерживает статическую типизацию, как в языках
C++, С#, Delphi и др. Тип Routes описывает значение как массив объектов класса

670 Часть IV. Практическое занятие: разработка веб-сайта

object, содержащих свойства path, component и некоторые другие, необязатель­
ные, не используемые здесь;
□ инициализировать маршрутизатор, вставив код:

import { RouterModule } from ' @angular/гоиter ’ ;

@NgModule({

imports: [
RouterModule.forRoot(appRoutes)r
BrowserModule,

1,

I)
export class AppModuie { }

У метамодуля RouterModule ИЗ модуля Gangular/router МЫ вызвали статический
метод forRoot (), передав ему созданный ранее список маршрутов. Метод вернет
объект программно сгенерированного метамодуля с готовым к работе маршру­
тизатором, который мы метаимпортировали в метамодуль приложения.

36.2.4. Компонент приложения AppComponent

Компонент— это часть интерфейса сайта, написанного на Angular. Компонент
приложения выводится на стартовой странице сразу после открытия сайта. Как
правило, он включает другие компоненты, выводящие различные данные.
Код компонента приложения хранится в модуле app.component.ts. Класс этого ком­
понента называется AppComponent. Его изначальный код приведен в листинге 36.9.

import { Component } from ’@angular/core’;

@Component({
selector: ’app-root’,
templateUrl: './app.component.html',
styleUrls: [’./app.component.css’]

})
export class AppComponent {

title = ’bbciient’;
}

Класс компонента может содержать свойства, значения которых будут выводиться
на экран, и методы, которые вычисляют выводимые на экран значения или выпол­
няют какие-либо иные действия. Изначально класс компонента AppComponent со­
держит лишь свойство title, хранящее имя проекта.

Гпава 36. Веб-служба REST 671

Параметры компонента указываются в вызове декоратора component, объявленного
в модуле @anguiar/core, в виде объекта класса object со свойствами:

□ selector — тег компонента;

□ tempiateuri — путь к файлу с шаблоном компонента;

□ styieUris — массив путей к файлам таблиц стилей, определяющих представле­
ние компонента. Изначально там присутствует одна таблица стилей — ’’пустая”,
т. е. не содержащая никакого кода.

Удалим из компонента свойство title, нам совершенно не нужное:

export class AppComponent {

—title - 'bbclient'f

}

Шаблон компонента, хранящийся в файле app.component.html в той же папке src\app,
довольно велик, поскольку формирует сложную страницу приветствия.

Нам нужно, чтобы компонент приложения выводил заголовок ’’Доска объявлений”,
под которым будет находиться выпуск (outlet) — место на странице, куда маршру­
тизатор Angular станет выводить тот или иной компонент в процессе навигации по
сайту. Откроем файл его шаблона и переделаем согласно листингу 36.10.

<header>
<Ы>Доска объявлений</Ы>

</header>
<router-outletx/router-outlet>

Выпуск обозначается парным тегом <router-outiet>.

36.2.5. Служба BbServioe.
Внедрение зависимостей. Объекты-обещания

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

Код, объявляющий класс службы BbService, хранится в файле bb.service.ts. Его изна­
чальный вид приведен в листинге 36.11.

import { Injectable } from ’@angular/core’;

@Injectable({
providedln: ’root’

672__________________________ Часть IV. Практическое занятие: разработка веб-сайта

export class BbService {

constructor() { }
}

Класс службы содержит лишь ’’пустой” конструктор. Декоратор injectable, объяв­
ленный в модуле @anguiar/core, собственно, помечает этот класс как службу
Angular. Еще он указывает у нее параметр providedin со значением ’ root • — это
значит, что служба будет обрабатываться основным обработчиком фреймворка
(такой режим работы подходит в большинстве случаев).
Над классом службы мы проделаем следующие действия:
□ объявим частное строковое свойство url, хранящее интернет-адрес хоста, на ко­

тором работает бэкенд:

export class BbService {
private url: String = 'http://localhost:8000';

}

Ключевое слово private делает свойство частным — доступным только внутри
объектов текущего класса;
□ создадим частное свойство http, хранящее объект службы HttpService (объявле­
на В модуле 0angular/common/http), ПОСреДСТВОМ КОТОрОЙ будет ВЫПОЛНЯТЬСЯ
взаимодействие с бэкендом. Сделать это проще всего, исправив код конструкто­
ра следующим образом:

import { HttpClient } from '@angular/common/http';

export class BbService {

constructor (private http: HttpClient) { }
}

Встретив в конструкторе класса такое объявление параметра, Angular проверит,
был ли ранее создан объект класса HttpService, и, если не был, создаст его.
После этого фреймворк создаст в текущем объекте частное свойство http и при­
своит ему данный объект класса.
Отметим, что если служба HttpService требуется для работы сразу нескольким
другим службам, то все они будут использовать один-единственный ее объект.
А как только необходимость в службе отпадет, ее объект будет удален.
Так работает система внедрения зависимостей (dependency injection), встроенная
в Angular. Она обрабатывает единым образом все службы — и встроенные во
фреймворк, и написанные разработчиком сайта. Необходимо лишь пометить
класс службы декоратором injectable и либо зарегистрировать службу в мета­
модуле, либо выполнить метаимпорт метамодуля, в котором она зарегистриро­
вана;

Гпава 36. Веб-служба REST 673
□ объявим метод getBbs (), получающий и выдающий перечень объявлений:

import { Observable } from 'rxjs';

export class BbService {

getBbs () : Observable<Object [ ] > {
return this. http. get<Object [ ] > (this. url + ' /api/bbs/') ;

}

Метод get (<интернет-адрес>) класса HttpService отправляет запрос НТТР-мето-
дом GET по заданному интернет-адресу и возвращает загруженные данные в ка­
честве результата. Метод имеет две важные особенности.

Во-первых, результат он возвращает в виде объекта класса observable из модуля
rxjs. Этот класс представляет значение, которое будет получено не прямо сей­
час, а позже, спустя неопределенный промежуток времени. Назовем этот объект
обещанием.

Во-вторых, в вызове метода get () обязательно следует указать тип возвращае­
мого значения, представляемого объектом-обещанием. Тип указывается после
имени метода в угловых скобках. Мы указали тип <object[]>— массив объек­
тов класса object (именно в таком виде бэкенд и отправит перечень объяв­
лений).

В объявлении метода getBbs (), после имени самого метода и двоеточия, мы ука­
зали тип возвращаемого значения: observabie<object[]> — массив объектов
класса object, представляемый обещанием — объектом класса observable;

□ объявим метод getBb(<Kn»v о&ьявления>), получающий и выдающий объявление
с заданным ключом,
export class BbService {

getBb(pk: Number) : Observable<Object> {
return this. http. get<Object> (this. url + ' /api/bbs/ • + pk) ;

)

}

Результатом этого метода станет единичный объект класса object, представляе­
мый обещанием;
□ объявим служебный метод handieError (), который понадобится для обработки
ошибок, которые могут возникнуть при добавлении новых комментариев:

import { of } from 'rxjs';

export class BbService {

674 Часть IV. Практическое занятие: разработка веб-сайта

handleError () {
return (error: any) : Observable<Object> => {
window. alert (error. message) ;
return of(null);
}

)

}

Метод, обрабатывающий ошибки, должен возвращать в качестве результата
функцию, которая принимает в качестве параметра объект ошибки, возвращает
объект-обещание с каким-либо значением, которое будет использоваться в даль­
нейших вычислениях, и, собственно, каким-то образом обрабатывает ошибку.
У возвращаемой методом handleError () функции МЫ указали параметр error,
принимающий значения любого — any — типа, и возвращаемый результат в ви­
де объекта класса object, заключенного в обещание. Сама функция выводит ок­
но с текстовым описанием возникшей ошибки и возвращает обещание с ’’нуле­
вой” ссылкой null. Заключить значение в обещание можно с помощью функции
of () из модуля rxjs;
□ объявим метод addcomment (), вызываемый в формате:
addComment (<ключ объявления^ <имя пользователя^ <пароль>,

«содержание комментария^

и добавляющий новый комментарий:

import { catchError } from 'rxjs/operators';
import { HttpHeaders } from ' @ angular/common/http';

export class BbService {

addcomment(bb: String, author: String, password: String,
content: String): Observable<Object> {
const connnent = {'bb': bb, 'author': author, 'content': content);
const options = (headers: new HttpHeaders(
{'Content-Type': 'application/j son',
'Authorization' : 'Basic ' + window.btoa(author + ' : ' +
password)})};
return this. http. post<Object>( this, url + '/api/bbs/' + bb +
'/comments/', comment,
options) .pipe(catchError (this.handleError ())) ;

)

}

В константе comment сохраняем объект класса object, содержащий все необхо­
димые сведения для создания нового комментария. В константе options сохра­
няем объект того же класса, содержащий параметры отправляемого запроса.
У нас этот объект содержит лишь свойство headers, которое хранит заголовки

Гпава 36. Веб-служба REST___________________________________________________ 675

запроса, формируемые объектом класса HttpHeaders ИЗ модуля @angular/common/
http. Далее вызываем метод post (<интернет-адрес>, <отправляемые данные>
[, <параметры запроса>]) класса HttpService, КОТОрЫЙ ОТПраВИТ Запрос НТТР-
методом POST.
Метод post () также возвращает обещание, заключающее в себе полученные от
бэкенда данные. У этого обещания в вызове метода pipe (<операция>) посредст­
вом функции catchErrorO из модуля rxjs/operators задаем объявленный ранее
метод handieError () в качестве обработчика ошибок;
□ объявим метод getcomments (<ключ объявления^, загружающий список коммен­
тариев к объявлению с заданным ключом.

export class BbService {

getCommants(pk: Number): Observable<Object[]> {
return this.http.get<Object[]>(this.url + ’/api/bbs/' + pk +
' /comments/') ;

)
}

36.2.6. Компонент списка объявлений BbListComponent.
Директивы. Фильтры. Связывание данных

Класс компонента списка объявлений BbListComponent хранится в файле bb-list.
component.ts. Откроем его и посмотрим на код, непосредственно объявляющий класс
(остальной код аналогичен таковому у компонента AppComponent):

export class BbListComponent implements Onlnit {
constructor() { }

ngOnlnit() {
}
}

Этот класс реализует интерфейс Onlnit из модуля @anguiar/core. Интерфейсом
в TypeScript называется описание набора методов — их имена, принимаемые пара­
метры и возвращаемый результат (если таковые есть), — которые обязательно
должны быть объявлены в классе, реализующем этот интерфейс.
Интерфейс onlnit содержит описание метода ngOninito. Следовательно, класс
BbListComponent, реализующий этот интерфейс, должен содержать метод ngOnlnit ()
(кстати, в коде класса уже имеется сгенерированная утилитой ng "заготовка" для
его написания).
Программное ядро Angular, инициализируя очередной компонент, проверяет, реа­
лизует ли он интерфейс onlnit, и, если это так, вызывает у компонента метод
ngOnlnit () сразу после вызова конструктора. Этот метод — наилучшее место для
выполнения кода, инициализирующего компонент.

676 Часть IV. Практическое занятие: разработка веб-сайта

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

□ объявим частное свойство bbs типа ’’массив объектов класса object” для хране­
ния списка объявлений:

export class BbListComponent implements Onlnit {
private bbs: Object[];

}
□ укажем в конструкторе, что нам понадобится частное свойство bbservice с объ­

ектом службы Bbservice, написанной в разд. 36.2.5:

import { BbServioe } from ' . /bb. service';

export class BbListComponent implements Onlnit {

constructor (private bbservice: BbServioe) { }

}
Внедрение зависимостей работает также и в случае служб, написанных разра­
ботчиками сайтов;
□ напишем в методе ngoninit () код, загружающий перечень объявлений:

export class BbListComponent implements Onlnit {

ngOnlnit() {
this. bbservice. getBbs () . subscribe (
(bbs: ObjectCJ) => {this.bbs = bbs;}
);

}
}
Метод getBbs () службы Bbservice возвращает объект-обещание с массивом объ­
явлений. Чтобы извлечь этот массив из обещания, воспользуемся методом
subscribe (<функция>) класса observable. Он задает функцию, которая выполнит­
ся после того, как значение, заключенное в обещании, будет реально получено,
и примет это значение в единственном параметре. У нас эта функция присвоит
полученный массив объявлений свойству bbs компонента, объявленному ранее.
Займемся шаблоном компонента BbListComponent. Откроем хранящий его файл
bb-listcomponent.html и удалим весь имеющийся там код. Создадим в шаблоне
вот что:
□ заголовок второго уровня ’’Последние 10 объявлений” и блок, формирующий
одно объявление:

<Н2>Последние 10 объявлений^/h2>
<div>
</div>

Гпава 36. Веб-служба REST 677

□ сделаем так, чтобы блок повторялся столько раз, сколько объявлений присутст­
вует в полученном от бэкенда массиве:

<div *ngFor=”let bb of bbs”>
</div>

Директива *ngFor Angular по назначению аналогична тегу for . . . endfor
шаблонизатора Django. Заданное у нее значение let bb of bbs указывает ей по­
вторить HTML-тег, в котором она присутствует, столько раз, сколько элементов
имеется в массиве bbs, и на каждой итерации занести очередной элемент масси­
ва в переменную ьь, доступную внутри содержащего ее тега;

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

<div *ngFor=”let bb of bbs">
<h3>{{bb.title}}</h3>
<p>{ {bb. content} }</p>

</div>

Директива {{<значение>}} Angular, подобно аналогичной директиве Django, вы­
водит заданное значение в том месте шаблона, в котором присутствует. В каче­
стве значения может быть указано свойство компонента, константа или про­
стейшее выражение TypeScript.

Если значение свойства компонента, выводимого этой директивой, было про­
граммно изменено, то оно будет выведено повторно. Говорят, что директива
{{<значение>}} создает связь между помеченным ей местом в шаблоне и свойст­
вом компонента в направлении ’’свойство место в шаблоне” {одностороннее
связывание данных);

□ выведем абзац с ценой товара в рублях:

<div *ngFor="let bb of bbs”>

<p class="price">{ {bb. price | currency: ’ RUR• } }</p>
</div>

Фильтр currency выводит число в виде денежной суммы в формате, обозначение
которого указано в параметре (у нас: »rur’ — российские рубли);
□ выведем абзац с временной отметкой публикации объявления:

<div *ngFor="let bb of bbs">

<p class="date">{ {bb. created_at | date: 1 medium'} }</p>
</div>

678 Часть IV. Практическое занятие: разработка веб-сайта

Фильтр date со значением ’medium’ выводит временную отметку в формате:

<число> Сокращенное название месяца> <год> г.,
< часы>:<минуты>:< секунды>

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

<div *ngFor=’’let bb of bbs’’>
<h3Xa [routerLink]=" [bb.id] ">{ {bb. title} }</aX/h3>

</div>

Директива [routerLink] вставляет в тег <a> атрибут href с интернет-адресом, со­
ставленным из элементов массива, который задан в качестве ее значения. У нас
этот массив содержит всего один элемент — ключ объявления, извлекаемый из
свойства id объекта, хранящегося в свойстве ьь компонента, поэтому интернет-
адреса будут иметь формат 1<ключ объявления^.

На заметку

Директивы и фильтры Angular также объявляются в виде классов, помеченных осо­
быми декораторами. Разработчик сайта может создать свои директивы и фильтры,
зарегистрировать их в метамодуле и использовать наряду со встроенными.

Осталось оформить наш компонент. Откроем файл bb-list.component.css, где хранится
его таблица стилей, и запишем туда код из листинга 36.12.

div {
margin: Юрх Орх;
padding: Орх Юрх;
border: grey thin solid;

}
div p.price {

font-size: larger;
font-weight: bold;
text-align: right;
}
div p.date {
font-style: italic;
text-align: right;
}

36.2.7. Компонент сведений
об объявлении BbDetailComponent.
Двустороннее связывание данных

Модуль с объявлением класса компонента BbDetailComponent хранится в файле
bb-detail.component.ts. Откроем его и сделаем следующее:

Гпава 36. Веб-служба REST 679

□ объявим в классе частные свойства ьь (объект со сведениями об объявлении, для
простоты укажем у него любой — any — тип) и comments (массив объектов, со­
держащий перечень комментариев):

export class BbDetailComponent implements Onlnit {

private bb: any;
private comments: Object[];

}

□ объявим в классе частные строковые свойства author (имя пользователя, кото­
рый станет автором добавляемого комментария), password (пароль) и content
(содержание добавляемого комментария):

export class BbDetailComponent implements Onlnit {

private author: String = '’;
private password: String = ’ ' ;
private content: String = ’ ’;

}
□ укажем в конструкторе создать частные свойства bbservice с объектом службы

BbService И аг — С объектом службы ActivatedRoute ИЗ модуля Gangular/router:

import { ActivatedRoute } from '@angular/router' ;

import { BbService } from /bb.service';

export class BbDetailComponent implements Onlnit {

constructor(private bbservice: BbService,

private ar: ActivatedRoute) { }

}
Служба ActivatedRoute позволяет получить сведения об интернет-адресе, по ко­
торому был выполнен переход, включая значения присутствующих в нем URL-
параметров;
□ объявим метод getcomments (), загружающий перечень комментариев:

export class BbDetailComponent implements Onlnit {

getCommants () {
this. bbservice. getcomments (this. bb. id) . subscribe (
(comments: Object [ ]) => (this. comments = comments;}
);

)

680 Часть IV. Практическое занятие: разработка веб-сайта

Ключ объявления, комментарии к которому нужно загрузить, извлекаем из
свойства id объекта, хранящегося в свойстве ьь компонента, объявленном ранее;
□ напишем в методе ngOninit () код, загружающий с бэкенда объявление с полу­
ченным в URL-параметре pk ключом и список комментариев к нему:

export class BbDetailComponent implements Onlnit {

ngOninit() {

const pk = this.ar.snapshot.params.pk;
this. bbservice. getBb (pk) . subscribe ((bb: Object) => {

this.bb = bb;
this. getComments () ;
});

}

}
Объект службы ActivatedRoute содержит свойство snapshot, хранящее объект со
сведениями об интернет-адресе, по которому был выполнен переход. Этот
объект, в свою очередь, поддерживает свойство params с объектом, хранящим
все URL-параметры.
После получения сведений об объявлении вызываем ранее объявленный метод
getComments (), чтобы загрузить комментарии к объявлению;
□ объявим метод submitcomment (), вызываемый в формате:
submitComment (<ключ объявления^ <имя пользователя^ <пароль>,

<содержание комментария^-}

и добавляющий новый комментарий:

export class BbDetailComponent implements Onlnit {

submitComment () {
this, bbservice. addComment (this.bb. id, this.author, this.password,
this. content) . subscribe ((cooment: Object) => (
if (comment) {
this. content = ' ' ;
this. getComments () ;
}
I);

1
}
После успешного добавления комментария очищаем свойство content, хранящее
его содержание, и перезагружаем перечень комментариев.
Шаблон этого компонента хранится в файле bb-detail.component.html. Откроем файл
и занесем в него код из листинга 36.13.

Гпава 36. Веб-служба REST 681

<div class="image"ximg src="{{bb.image}}"x/div>
<div class="others">

<h2>{{bb.title}}</h2>
<p>{{bb.content}}</p>
<p class="price">{{bb.price|currency:'RUR'}}</p>
<p>{{bb.contacts}}</p>
<p class="date">{{bb.created_at(date:’medium’}}</p>
</div>
<ЬЗ>Новый комментарий</ЬЗ>
<form>
<р>Имя: <input name=”author" requiredx/p>
<р>Пароль: <input type="password" name="password" requiredx/p>
<р>Содержание:<br><textarea name="content" requiredx/textarea></p>
<pxinput type="submit" value="flo6aBHTb"x/p>
</form
<div class="comment" *ngFor="let comment of comments’’>
<h4>{{comment.author}}</h4>
<p>{{comment.content}}</p>
<p class="date">{{comment.created_at|date:’medium’ }}</p>

</div>

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

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

Исправим все это следующим образом:

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

<ng-container *nglf="bb">
<div class=’’image*’ximg src="{{bb. image}}"x/div>
<div class="others’’>

</div>
</ng-oontainer>
<ЬЗ>Новый комментарий/h3>

682 Часть IV. Практическое занятие: разработка веб-сайта

Парный тег <ng-container> создает псевдоконтейнер Angular, объединяющий
произвольное количество элементов страницы, но не преобразующийся при
выводе на экран в какой-либо HTML-тег. Директива *ngif выводит на экран
элемент, в котором присутствует, только в том случае, если заданное для нее
значение равно true. В результате верхняя часть шаблона будет выведена только
после того, как свойство ьь получит значение, отличное от undefined;
□ свяжем поля ввода Имя, Пароль и область редактирования Содержание формы
со свойствами author, password и content соответственно:

<form>
<р>Имя: <input [ (ngModel) ]="author" name=”author" requiredx/p>
<р>Пароль: <input type="password" [ (ngModel) ] ="password"
name="password" requiredx/p>
<р>Содержание:cbrxtextarea [ (ngModel) ]="content" name="content"
requi redx / textareax /p>
<pxinput type="submit" value="flo6aBHTb"x/p>

</form>

Директива [ (ngModel) ] связывает элемент управления, в теге которого присутст­
вует, со свойством компонента, имя которого задано в качестве ее значения. При
изменении значения в элементе управления оно сразу же копируется в свойство,
а при программном изменении значения свойства— копируется в элемент
управления (двустороннее связывание данных, которое можно обозначить как
’’свойство <—> элемент управления”);

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

<fо rm (ngSubmit) =’’ submitComment () ">

</form>

Директива (ngSubmit) задает для элемента, в котором присутствует, обработчик
события submit. У нас — это вызов метода submitcomment () компонента.
Осталось написать таблицу стилей компонента BbDetailComponent. Она хранится
в файле bb-detail.component.css и изначально ’’пуста”. Сделайте это самостоятельно,
оформив компонент по своему вкусу.
Как упоминалось ранее, Angular-проект включает в свой состав отладочный веб­
сервер. Запустить его можно, перейдя в папку проекта и задав в командной строке
команду:

ng serve

Гпава 36. Веб-служба REST 683

Поскольку тестовый Angular-сайт в процессе работы "общается" с веб-службой,
написанной на Django и Django REST framework, также, необходимо запустить
отладочный веб-сервер Django.

Отладочный веб-сервер Angular работает через ТСР-порт 4200. Следовательно, для
перехода на наш сайт нужно набрать интернет-адрес http://localhost:4200/.

Проверим наш небольшой фронтенд в действии. После чего завершим работу обо­
их отладочных серверов. Отладочный сервер Angular останавливается нажатием
комбинации клавиш <Ctrl>+<Break>, как и сервер Django.

Заключение

Вот и закончена книга о программировании веб-сайтов с помощью великолепного
веб-фреймворка Django. Мы изучили практически все, что нужно для создания сай­
тов, и даже сделали в качестве практического занятия сайт доски объявлений.
И теперь можем с уверенностью и гордостью именовать себя настоящими про­
граммистами!
Автор описал в книге все основные возможности Django, без которых не обойтись.
Однако нельзя объять необъятное, и кое-что все-таки осталось "за кадром". В част­
ности, не были описаны:
□ подсистема комментирования;
□ подсистема GeoDjango, предназначенная для разработки геоинформационных

систем;
□ подсистема для разработки средствами фреймворка обычных, статических веб­

сайтов;
□ средства для создания карты сайта;
□ средства для формирования лент новостей в формате RSS и Atom;
□ инструменты для экспорта данных в форматах CSV и Adobe PDF;
□ средства для написания своих миграций;
□ всевозможные вспомогательные инструменты;
□ множество полезных дополнительных библиотек;
□ разработка дополнительных библиотек для Django.
Обо всем этом рассказано в официальной документации, представленной на
домашнем сайте фреймворка. И любой желающий ознакомиться с этими инстру­
ментами всегда может обратиться к ней. Что касается дополнительных библиотек,
то их в огромном количестве можно найти в PyPI— стандартном репозитории
Python.
О да, на изучение всех возможностей Django стоит потратить время. Фреймворк
Django, как и язык Python, на котором он написан, с честью выдержал проверку

Заключение 685

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

И — автор полностью уверен в этом — Django еще долгое время будет применять­
ся в веб-строительстве, и если и уйдет когда-нибудь, так сказать, в отставку, то
лишь после появления достойного конкурента. Пока их не предвидится...

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

Таблица 3.1. Интернет-ресурсы, посвященные Django

Интернет-адрес Описание
https://www.djangoproject.com/
Официальный сайт фреймворка Django. Дистрибутивы,
https://djangopackages.org/ документация, поддержка

https://www.djbook.ru/ Подборка дополнительных библиотек и утилит
https://vk.com/django_framework для Django
https://www.python.org/
Русскоязычная документация по Django
https://pypi.org/
Группа "ВКонтакте", посвященная Django
https://vk.com/itcookies/
Официальный сайт языка Python. Дистрибутивы,
https://pythonworld.ru/ документация, поддержка

Официальный репозиторий Python, содержащий
огромное количество дополнительных библиотек

Группа "ВКонтакте", посвященная программированию,
в том числе и на Python

Русскоязычный сайт для Python-программистов

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

Исходные коды разработанного в части IV книги веб-сайта электронной доски
объявлений находятся в сопровождающем книгу электронном архиве, который
можно скачать с FTP-сервера издательства ’’БХВ-Петербург” по ссылке ftp://
ftp.bhv.ru/9785977566919.zip или со страницы книги на сайте www.bhv.ru
(см. приложение).

На этом все. Автор книги прощается и желает вам успехов в веб-программи-
ровании.

Владимир Дронов

ПРИЛОЖЕНИЕ

Описание электронного архива

Электронный архив к книге выложен на FTP-сервер издательства «БХВ-Петер-
бург» по интернет-адресу: ftp://ftp.bhv.ru/9785977566919.zip. Ссылка на него дос­
тупна и со страницы книги на сайте www.bhv.ru.

Содержимое архива описано в табл. П. 1.

Каталог, Таблица П.1. Содержимое электронного архива
файл
bboard Описание

bbclient Папка с исходным кодом веб-сайта электронной доски объявлений,
разрабатываемого на протяжении части IV книги на Python и Django
readme.txt
Папка с исходным кодом тестового фронтенда, используемого для отладки
веб-службы и написанного с применением веб-фреймворка Angular
Файл с описанием архива и инструкциями по развертыванию обоих
веб-сайтов

Предметный указатель

_str_() 108 ArchivelndexView 218
ArrayAgg 374
А ArrayField 356, 366, 370
ArrayMaxLengthValidator 364
Abs 162 ArrayMinLengthvaiidator 363
АВSOLUTEURLOVERRIDES 107 as_manager() 339
AbstractUser 444 as_p() 275, 288
AccessMixin 316 as_table() 275,289
ACos 162 as_ul() 275, 288
actions 532 as_view() 173, 197
actionsonbottom 518 asctime 555
actionsontop 518 ASGI 580
actionsselectioncounter 518 ASin 162
add 244 ATan 162
add() 128, 129, 326,495 ATan2 163
add_message() 462 atomic() 342
add_never_cache_headers() 507 ATOMIC-REQUEST 341
addslashes 246 attach() 479
ADJACENTTO 360 attach_altemative() 481
ADMINS 484 attach_file() 479
aggregate() 149 AUTH_PASSWORD_VALIDATORS 433
all() 138 AUTH_USER_MODEL 433
aiiow empty 206, 217 authenticate() 435
allowfuture 216 AUTHENTICATION_BACKENDS 296, 433
AllowAny 552 authentication_form 301
ALLOWED HOSTS 571 AuthenticationForm 301
AmbiguousTimeError 160 AUTOCOMMIT 341, 343
Angular 664 autocomplete_fields 523
annotate() 150,154 autoescape 230,238
AnonymousUser 312 AutoField 91
api_view() 537 Avg 153
APIView 547
APP_DIRS 230 в
appname 176,195
AppConfig 83 BACKEND 229,487
AppDirectoriesFinder 249 BadSignature 455, 465
BASE_DIR 75
BaseDateListView 217

688 Предметный указатель

BaseFormSet 346 cache_control() 505
BaseGenericInlineFormSet 331 CACHEMIDDLEWAREALIAS 491
BaselnlineFormSet 292 CACHE_MIDDLEWARE_KEY_PREFIX 491
BaseModelFormSet 283 CACHE MIDDLEWARE SECONDS 491
BBCode 388 cache_page() 491
BBCODEALLOWCUSTOMTAGS 395 caches 495
BBCODEALLOWSMILIES 395 CACHES 486
BBCODEDISABLEBUILTINTAGS 394 CallbackFilter() 557
BBCODEESCAPEHTML 394 can delete 529
BBCODENEWLINE 394 capfirst 243
BBCodeTextField 391 CAPTCHA 350
BICField 386 captcha.helpers.mathchallenge 352
BICFormField 387 captcha.helpers.randomcharchallenge 352
BigAutoField 91 captcha.helpers.wordchallenge 352
BigIntegerField 90 CAPTCHA_BACKGROUND_COLOR 353
BiglntegerRangeField 355 CAPTCHA_CHALLENGE_FUNCT 352
BinaryField 91 captcha clean 354
BitAnd 375 captcha_create_pool 354
BitOr 375 CAPTCHA DICTIONARY MAX LENGTH
block 247
block.super 248 353
body 187 CAPTCHA-DICTIONARYMINLENGTH
BoolAnd 375
BooleanField 90, 262 353
BoolOr375 CAPTCHA_FONT_PATH 353
Bootstrap 395 CAPTCHA FONT SIZE 353
bootstrapalert 400 CAPTCHA_FOREGROUND_COLOR 353
bootstrapbutton 399 CAPTCHAJMAGE-SIZE 353
bootstrapcss 396 CAPTCHA_LENGTH 353
bootstrapfield 399 CAPTCHA_LETTER_ROTATION 353
bootstrapform 397 CAPTCHA_MATH_CHALLENGE_OPERA
bootstrap form errors 398
bootstrapformset 398 TOR 353
bootstrap formset errors 399 CAPTCHA_TIMEOUT 353
bootstrapJavascript 396 CAPTCHA_WORDS_DICTIONARY 353
bootstrap label 400 CaptchaField 351
bootstrapmessages 400 Case 164
bootstrap_pagination 401 Cast 157
BoundField 276 Ceil 161
Brinlndex 359 center 246
BtreeGinExtension 362 changeddata 274
BtreeGistExtension 362 changedobjects 286
BTreelndex 358 changepassword 297
build_absolute_uri() 188 CharField 89, 262
builtins 230 charset 182
bulk_create() 131 check 573
bulkupdate 132 check_password() 434
buttons 399 Checkboxinput 268
CheckboxSelectMultiple 269
c CheckConstraint 105
ChoiceFieid 264
cache 495 Choices 95
cache ... endcache 494 Chr 159
chunks() 423
CICharField 357

Предметный указатель 689

CIEmailField 357 create_user() 434
CITextExtension 362 CreateAPIView 549
CITextField 357 createcachetable 489
class 558 created 555
classes 529 CreateExtension 362
clean() 116,280 createsuperuser 296
clean_savepoints() 343 CreateView 213
cleaneddata 273 CryptoExtension 362
clear() 130, 327, 497 CSRF COOKIE SECURE 577
clear_expired() 459 csrf token 238
ClearableFilelnput 418 CULLFREQUENCY 488
clearsessions 460 currentapp 190
close() 480,497 cut 243
closed 182 cycle 236
Coalesce 156 cycle_key() 460
collectstatic 581
comment 239 D
commit() 343
CommonPasswordValidator() 437 data 544
Concat 157 DATAUPLOADMAXMEMORYSIZE
condition() 503
conditional_escape() 405 354
CONN MAX AGE 77 DATAUPLOADMAXNUMBERFIELDS
connect() 468
ConnectionError 501 354
CONTAINED BY 360 DATABASES 76
CONTAINS 360 date 240
content 182 datefield 216
content_params 186 DATE FORMAT 81
content type 186, 200 datehierarchy 517
ContentType 328 DATE INPUT FORMATS 81
contextdata 185 dateJoined 310
context object name 202, 207 date list_period 217
context_processors 230 DateDetailView 224
ContextMixin 199 DateField 90, 263
Cookie 453 datefmt 556
0 подписанный 454 Dateinput 267
0 сессии 456 DateMixin 216
COOKIES 453 DateRange 365
Corr 375 DateRangeField 356, 383
CORS ORIGIN ALLOW ALL 534 dates() 169
CORS ORIGIN REGEX WHITELIST 535 DATETIME FORMAT 81
CORS_ORIGIN_WHITELIST 534 DATETIME INPUT FORMATS 82
CORS URLS REGEX 535 DateTimeField 91,263
Cos 162 DateTimelnput 268
Cot 162 DateTimeRangeFieid 356, 383
count 253 datetimes() 169
Count 152 DateTimeTZRange 365
count() 140 day 223
CovarPop 376 dayformat 223
create() 124,128, 130, 326 DayArchiveView 223
create_superuser() 434 DayMixin 223
debug 230, 240
DEBUG 75

690 Предметный указатель

debug() 462 django.contrib.messages.middleware.
DECIMAL SEPARATOR 80 MessageMiddleware 79
DecimalField 90, 263
DecimalRangeField 356,383 django.corttrib.messages.storage.cookie.
DecimalValidator 112 CookieStorage 460
decr() 496
decr_version() 497 django.contrib.messages.storage.fallback.
default 242 Fallbackstorage 460
DEFAULT CHARSET 75
DEFAULT FILE STORAGE 413 django.contrib.messages.storage.session.
DEFAULTFROMEMAIL 476 SessionStorage 460
default if none 243
DEFAULT MESSAGE LEVELS 464 django.contrib.postgres 355
DEFAULT PASSWORD LIST PATH 437 django.contrib.sessions 78
DEFAULT USER ATTRIBUTES 437 django.contrib.sessions.backends.cache 456
DefaultRouter 550 django.contrib.sessions.backends.cacheddb
defer() 324
Degrees 163 456
DELETE 543 django.contrib.sessions.backends.db 456
delete() 108,126,133,421,496 django.contrib.sessions .backends.file 456
delete_cookie() 454 django.contrib.sessions.backends.signed_
delete_many() 497
delete_pattem() 500 cookies 457
delete_test_cookie() 458 django.contrib.sessions.middleware.
deleted forms 347
deleted objects 286 SessionMiddleware 79
DeleteView 215 django.contrib.sessions.serializers.
DeletionMixin 215
DestroyAPIView 549 JSONSerializer 457
DetailView 203 django.contrib.sessions.serializers.
dictsort 244
dictsortreversed 245 PickleSerializer 457
DIRS 230 django.contrib.staticfiles 78
disable existing loggers 555 django.core.cache.backends.db.DatabaseCache
disabled 261
disconnect) 470 487
dispatch() 198 django.core.cache.backends.dummy.
distinct() 147
divisibleby 244 DummyCache 487
django 563 django.core.cache.backends.filebased.
Django REST framework 533
Django Simple Captcha 350 FileBasedCache 487
django.contrib.admin 78 django.core.cache.backends.locmem.
django.contrib.auth 78
django.contrib.auth.context_processors.auth LocMemCache 487
django.core.cache.backends.memcached.
231
django.contrib.auth.middleware. MemcachedCache 487
django.core.mail.backends.console.
AuthenticationMiddleware 79
django.contrib.contenttypes 78 EmailBackend 476
django.contrib.messages 78 django.core.mail.backends.dummy.
django.contrib.messages.context_processors.
EmailBackend 476
messages 231 django.core.mail.backends.filebased.

EmailBackend 476
django.core.mail.backends, locmem.

EmailBackend 476
django.core.mail.backends.smtp.

EmailBackend 476
django.db.backends 563
django.db.backends.schema 563
django.middleware.cache.

FetchFromCacheMiddleware 447
django.middleware.cache.

UpdateCacheMiddleware 447
django.middleware.clickjacking.

XFrameOptionsMiddleware 79

Предметный указатель 691

django.middleware.common. dumps() 467
CommonMiddleware 79 duration 563
DurationField 91, 264
django.middleware.csrf.CsrfViewMiddleware
79 E

django.middleware.gzip.GZipMiddleware 446 earliest() 139
django.middleware.http. easy-thumbnails 426
elif 235
ConditionalGetMiddleware 447, 502 else 235
django.middleware.security. email 310
EMAIL_BACKEND 476
SecurityMiddleware 79 EMAIL_FILE_PATH 478
django.request 563 EMAILHOST 477
django.security.<iaiacc исключения> 564 EMAIL HOST PASSWORD 477
django.security.csrf 564 EMAILHOSTUSER 477
django.server 563 EMAILPORT 477
django.template 563 EMAIL SSL CERTFILE 477
dj ango.template.backends.dj ango. EMAIL SSL KEYFILE 477
EMAILSUBJECTPREFIX 484
DjangoTemplates 229 email template name 305
dj ango.template.backends.j inja2.Jinj a2 229 EMAILTIMEOUT 477
django.template.context_processors.csrf 231 EMAIL USE LOCALTIME 477
django.template.context_processors.debug 231 EMAIL USE SSL 477
django.template.context_processors.media 231 EMAIL USE TLS 477
django.template.context_processors.request email_user() 483
EmailField 89, 262
231 Emailinput 267
django.template.context_processors.static 231, EmailMessage 478
EmailMultiAlternatives 481
250 EmailValidator 111
django.template.context_processors.tz 231 empty vaiue display 518
django.utils.log.AdminEmailHandler() 561 EmptyPage 253
djangoredis.cache.RedisCache 498 encoding 186
djangoredis.compressors.identity. end_index() 254
endautoescape 238
Identitycompressor 501 endblock 247
django_redis.compressors.lz4. endbuttons 399
endcomment 239
Lz4Compressor 501 endfilter 238
django_redis.compressors.lzma. endfor 234
endif235
LzmaCompressor 501 endifchanged 235
djangoredis.compressors.zlib.ZlibCompressor endspaceless 238
endverbatim 239
501 endwith 237
DJANGO_REDIS_IGNORE_EXCEPTIONS ENGINE 76
EQUAL 360
502 error() 462
DJANGO_REDIS_LOG_IGNORED_ error css class 348,402
error messages 261
EXCEPTIONS 502 errors 271, 277
DJANGO REDIS LOGGER 502
django-admin 26
django-bootstrap4 395
django-cleanup 425
django-cors-headers 534
django-localflavor 385
DjangoModelPermissions 552
DjangoModelPermissionsOrAnonReadOnly

552
django-precise-bbcode 388
django-redis 497
DoesNotExist 140
DOS 354

692 Предметный указатель

escape 246 FileSystemFinder 249
escape() 405 filter 238
escapejs 246 filter() 142,403
etag() 504 filter horizontal 523
excinfo 555 filter vertical 524
exclude 520 filters 554, 558, 564
exclude() 142 findstatic 582
Exclusionconstraint 359 first 244
Exists 166 first() 138
exists() 140 FIRSTDAYOFWEEK 82
Exp 163 firstname 310
expire() 500 firstof 237
ExpressionWrapper 155 fk name 528
extends 247 FloatField 90, 263
extra 528 floatformat 243
extra context 199, 300, 302-305, 307-309 Floor 161
extraemailcontext 305 flush() 182, 458
extratags 463 for 234
Extract 159 forceescape 246
ExtractDay 159 ForeignKey 95
ExtractHour 159 form 522, 530
ExtractlsoYear 159 Form 345
ExtractMinute 159 form class 209, 303, 305, 308
ExtractMonth 159 form_invalid() 210
ExtractQuarter 159 form_valid() 210,213
ExtractSecond 159 format 556
ExtractWeek 159 formatter 558
ExtractWeekDay 159 formatters 554
ExtractYear 159 formfield overrides 524
FormMixin 209
F FormParser 547
forms 291
F 146 formset 529
FieldFile 420 formset factoryO 346
fields 212, 519 FormView 211
fieldsets 521 fromemail 305
filecharset 230 from_queryset() 339
FILE CHARSET 76 full_clean() 133
FILE_UPLOAD_DIRECTORY_ FULLY GT 360
FULLY LT 360
PERMISSIONS 413 funcName 555
FILE UPLOAD HANDLERS 413 func 195
FILEUPLOADMAXMEMORYSIZE 413
FILE UPLOAD PERMISSIONS 413 G
FILEUPLOADTEMPDIR 413
FileExtensionValidator 417 generic_inlineformset_factory() 330
FileField 415, 417 GenericForeignKey 328
Fileinput 418 GenericIPAddressField 91, 265
filename 555 GenericRelation 330
FilePathFieid 421,422 GET 186, 542
FileResponse 192 get() 140,211,496
FILES 186 get_<HMa вторичной модели>_оМег() 131
filesizeformat 244

Предметный указатель 693

get<HMa ii(irui>_display() 136 get_next_in_order() 141
get_absolute_url() 107 get_next_month() 221
get_all_permissions() 312 get_next_week() 222
get_allow_empty() 206 get_next_year() 219
get_allow_future() 216 get_object() 202
get_autocommit() 343 get_object_or_404() 194
get_autocomplete_fields() 523 get_or_create() 124
get_connection() 480 get_or_set() 496
get_context_data() 199, 202, 207, 210 get_ordering() 205, 514
get_context_object_name() 202, 207 get_page() 253
get_date_field() 216 get_paginate_by() 206
get_date_list() 217 get_paginate_orphans() 206
get_date_list_period() 217 get_paginator() 206, 519
get_dated_items() 217 get_parser() 390
get_dated_queryset() 217 get_password_validators() 439
get_day() 223 get_permission_denied_message() 316
get_day_format() 223 get_permission_required() 317
getdigit 247 get_port() 188
get_exclude() 520 get_prepopulated_fields() 525
get_expire_at_browser_close() 459 get_previous_by_<HMfl поля>() 141
get_expiry_age() 459 get_previous_day() 223
get_expiry_date() 459 get_previous_in_order() 141
get_extra() 528 get_previous_month() 220
get_fields() 520 get_previous_week() 222
get_fieldsets() 522 get_previous_year() 219
get_form() 210, 522 get_queryset() 201, 205, 335, 514
get_form_class() 209, 212 get_readonly_fields() 521
get_form_kwargs() 210, 213 get_redirect_field_name() 316
get_formset() 530 get_redirect_url() 226
get_full_name() 312 get_search_fields() 515
get_full_path() 188 get_short_name() 312
get_group_permissions() 311 get_signed_cookie() 455
get_help_text() 438 get slug_field() 202
get_host() 188 get_sortable_by() 514
get_initial() 209 get_static_prefix 250
get_inlines() 530 get_success_message() 463
get_list_display() 512 get_success_url() 215
get_list_display_links() 512 get_template() 183
get_list_filter() 517 get_template_names() 200,203,208
get_list_or_404() 194 get_user_permissions() 311
get_list_select_related() 514 get_usemame() 312
get_login_url() 316 get_week() 222
get_make_object_list() 220 get_week_format() 222
get_many() 497 get_year() 219
get_max age() 507 get_year_format() 219
get_max_num() 529 getlist() 420
get_messages() 464 Ginindex 359
get_min_num() 529 Gistindex 358
get_month() 220 gotrequestexception 473
get_month_format() 220 Greatest 156
get next by <HMa поля>() 141 Group 310
get_next_day() 223 groups 310
gzip_page() 196

694 Предметный указатель

н in_bulk() 170
include() 174,177
handlers 554, 564 inclusion_tag() 407
has_changed() 274 incr() 496
has_header() 182 incr_version() 497
has_key() 496 Index 103,357
has_next() 254 info() 462
has_no_permission() 316 initial 209, 261
has_other_pages() 254 inlineformset_factory() 291
has_perm() 310 inlines 521, 530
has_perms() 311 INSTALLED APPS 77, 83
has_previous() 254 int_list_validator() 113
has_usable_password() 435 IntegerChoices 94
Hashindex 359 IntegerField 90, 263
headers 187 IntegerRangeField 355,383
height 420 IntegrityError 87, 97
helptext 261,277 intersection() 167
hidden_fields() 277 InvalidCacheBackendError 495
Hiddenlnput 267 iriencode 246
horizontallabelclass 401 isactive 310
HOST 77 is_ajax() 188
HStoreExtension 362 is anonymous 310
HStoreField 357, 367,371,384 isauthenticated 310
htmlemailtemplatename 305 is_bound() 271
HTTP_201_CREATED 544 is hidden 277
HTTP_204_NO-CONTENT 544 is_multipart() 276
HTTP_400_BAD_REQUEST 544 is_secure() 188
httpmethodnames 198 isstaff 310
http_method_not_allowed() 199 issuperuser 310
Http404 191 is_valid() 271
HttpRequest 179, 186 IsAdminUser 552
HttpResponse 179,182 IsAuthenticated 552
HttpResponseBadRequest 191 IsAuthenticatedOrReadOnly 552
HttpResponseForbidden 191 iter_keys() 500
HttpResponseGone 191
HttpResponseNotAllowed 191 J
HttpResponseNotFound 190
HttpResponseNotModified 191 join 244
HttpResponsePermanentRedirect 189 JSON 193
HttpResponseRedirect 188 JSONBAgg 375
HttpResponseServerError 191 JSONField 357, 367, 372, 384
JSONParser 547
I JsonResponse 193

IBANField 386 К
IBANFormField 387
if 235 KEY-FUNCTION 488
ifchanged 235 KEY_PREFIX 488
IGNORE_EXCEPTIONS 502 keys() 500
ImageClearableFilelnput 432 KeysVaiidator 364
ImageFieid 416,417 kwargs 195,198
ImageFieldFile 420

Предметный указатель 695

L logging.NullHandler 563
logging.StreamHandler 558
label 83,261,277 login() 435
labelsuffix 261 LOGIN REDIRECT URL 295
labeltag 277 login_required() 314
LANGUAGE CODE 80 login url 316
last 244 LOGIN URL 295
last() 139 LoginRequiredMixin 317
last login 310 LoginView 300
last_modified() 504 logout() 435
lastname 310 LOGOUTREDIRECTURL 296
latest() 139 LogoutView 302
Least 157 LogRecord 555
Left 158 lookups() 516
length 244 lower 243, 366
Length 157 Lower 157
lengthis 244 lower inc 366
level 463, 558, 564 lowerinf 366
leveltag 463 LPad 158
levelname 555 LTrim 158
levelno 555
libraries 231 M
Library 403
linebreaks 245 m2m_changed 472
linebreaksbr 245 mail_admins() 484
lineno 555 mail_managers() 484
linenumbers 247 makelist 244
list display 510 makeobj ectlist 219
list display links 512 makemigrations 118
listeditable 513 manage.py 26
listfilter 516 managementform 289
list max show all 518 Manager 124, 335
list_per_page 518 MANAGERS 484
listselectrelated 513 ManyToManyField 99
ListAPiview 549 mark_safe() 405
ListCreateAPIView 548 Max 153
ListView 208 MAX_ENTRIES 488
ljust 246 max num 529
Ln 163 MaxLengthValidator 110
load 239 MaxValueValidator 112
loaders 230 MD5 163
loads() 467 MEDIA_ROOT 412
LOCATION 487 MEDIA_URL 412
Lock 501 Memcached 489
lock() 501 MemoryFileUploadHandler 413
Log 163 message 463, 555
loggers 555 Message 463
LOGGING 554 message() 479
logging.FileHandler 558 messagedict 134
logging.handlers.RotatingFileHandler 559 MESSAGE_LEVEL 461
logging.handlers.SMTPHandler 562 MESSAGE-STORAGE 460
logging.handlers.TimedRotatingFileHandler 560 MESSAGE_TAGS 461

696 Предметный указатель

message_user() 531 non_atomic_requests() 342
MessageFailure 462 NON_FIELD_ERRORS 117,271
messages 463 non_field_errors() 277
Meta 101, 258, 445 non_formeiTors() 289
META 186 NOT_EQUAL 360
method 186 NOT_LT 360
MIDDLEWARE 78 NOT-GT 360
MiddlewareNotUsed 449 NotSupportedError 132
migrate 120 now 238
Migration 361 Now 159
Min 153 NullBooleanField 90, 262
min num 529 NullBooleanSelect 269
MinimumLengthValidator() 437 Nulllf 161
MinLengthValidator 110 num_pages 253
MinVaiueVaiidator 112 number 254
Mod 161 NUMBER-GROUPING 80
mod wsgi 583 Numberinput 267
model 201,205,212,528 NumericRange 365
Model 86
ModelAdmin 510 о
ModelChoiceField 264
ModeiForm 258, 276, 277 object-list 254
modelform factoryO 256 objects 124, 138
ModelFormMixin 212 on_commit() 344
modelformset factoryO 281 OneToOneField 98
ModelMultipleChoiceField 264 only() 324
ModelSerializer 535 open() 480
ModelViewSet 549 operations 361
module 555 OPTIONS 77, 230, 488
month 220 options() 199
MONTH DAY FORMAT 81 Ord 159
monthformat 220 order_by() 148
MonthArchiveView 221 ordered-forms 347
MonthMixin 220 ordering 205, 514
msecs 555 ORDERING_FIELD_NAME 287
MultiPartParser 547 orderingwidget 349
multiple_chunks() 422 OuterRef 166
MultipleChoiceField 265 OVERLAPS 360
MultipleObjectMixin 205
MuitipieObjectsRetumed 125,140 p
MuitipieObjectTemplateResponseMixin 207
Page 254
N page() 253
pagekwarg 206
name 83, 420, 556 pagerange 253
NAME 76, 229 PageNotAnlnteger 253
namespace 195 paginateby 206
never_cache() 506 paginateorphans 206
newobjects 285 paginate_queryset() 206
next_page 302 paginator 254
next_page_number() 254 Paginator 252
ng 665 paginatorclass 206

Предметный указатель 697

parameter name 516 Prefetch 323
password 310 prefetch_related() 322
PASSWORD 77, 499 prefix 209
password_changed() 439 prepopulated fields 524
PASSWORDRESETTIMEOUTDAYS 296 preserve filters 517
password_validators_help_texts() 439 previous_page_number() 254
password_validators_help_texts_html() 439 process 555
PasswordChangeDoneView 304 process_exception() 450
PasswordChangeForm 303 process_template_response() 450
PasswordChangeView 303 process_view() 450
Passwordinput 267 ProcessFormView 210
PasswordResetCompleteView 309 processName 556
PasswordResetConfirmView 307 ProhibitNullCharactersValidator 112
PasswordResetDoneView 307 propagate 564
PasswordResetForm 305 ProtectedError 96
PasswordResetTokenGenerator 305 PUT 542
PasswordResetView 305 put() 211
PATCH 543 Python Social Auth 440
patch_cache_control() 506 python-memcached 489
patch_response_headers() 506
patch_vary_headers() 507 Q
path 83,186
path() 173, 175,176 Q 146
pathinfo 186 query_pk_and_slug 202
pathname 555 querystring 226
pattemname 226 queryset 201, 205, 548, 549
permanent 226 QuerySet 138, 338
permissionclasses 553 queryset() 516
permission_classes() 552
permissiondeniedmessage 316 R
permissionrequired 317
permission_required() 315 Radians 163
PermissionDenied 191 radio fields 523
PermissionRequiredMixin 317 RadioSelect 269
perms 318 RAISEERROR 455
persist() 500 raiseexception 316
Pi 162 random 244
pk 135 RandomUUID 377
pkurlkwarg 201 RangeMaxValueValidator 363
PORT 77 RangeMinValueValidator 362
PositivelntegerField 90 RangeOperators 360
PositiveSmalllntegerField 90 RangeWidget 385
POST 186, 542 raw id fields 525
post() 211 re_path() 178
postdelete 471 read() 423
postinit 470 readonly fields 520
postresetlogin 308 ReadOnlyModelViewSet 551
postsave 471 reason_phrase 182, 192
Power 161 receiver() 469
predelete 471 recipients() 479
preinit 470 redirect() 194
presave 471 redirectauthenticateduser 300

698 Предметный указатель

redirect-field name 300,302, 316 reverse_lazy() 190
redirect_to_login() 314 Right 158
RedirectView 225 rjust 246
Redis 497 rollback() 343
RegexField 262 ROOT URLCONF 75, 172
RegexValidator 111 Round 161
register() 526, 527, 550 route 195
RegrAvgX 376 RPad 158
RegrAvgY 376 RTrim 159
RegrCount 376 RUAlienPassportNumberField 387
Regrintercept 376 RUCountySelect 387
regroup 237 runserver 84
RegrR2 377 RUPassportNumberField 387
RegrSlope 377 RUPostalCodeField 387
RegrSXX 377 RURegionSelect 387
RegrSXY 377
RegrSYY 377 s
RelatedManager 127
relativeCreated 555 safe 246
remove() 130, 327 SafeMIMEText 479
render() 184,193, 390 safeseq 246
render_to_response() 200 SafeText 405
render_to_string() 184 save() 108, 125, 272
rendered 391 save as 526
Repeat 158 saveascontinue 526
Replace 158 save_m2m() 272
request 198, 563 saveontop 526
requestfmished 473 savepoint() 343
request_started 473 savepoint_commit() 343
require_get() 196 savepoint_rollback() 343
require_http_methods() 196 scheme 186
require_post() 196 search fields 515
require_safe() 196 SearchQuery 379
required 261 SearchRank 380
required_css_class 348, 401 SearchVector 378
RequireDebugFalse 557 SECRET_KEY 75
RequireDebugTrue 557 SECURE-BROWSER XSS FILTER 576
reseturltoken 308 SECURE_CONTENT_TYPE_NOSNIFF 576
resetcycle 236 SECURE_HSTS_INCLUDE_SUBDOMAINS
resolve() 195
resolvermatch 187 575
Resolver404 195 SECURE_HSTS_PRELOAD 576
ResolverMatch 195 SECURE HSTS SECONDS 575
Response 537 SECUREPROXYSSLHEADER 578
responseclass 200 SECURE_REDIRECT_EXEMPT 574
REST 533 SECURE_REFERRER_POLICY 576
RetrieveAPiview 549 SECURE_SSL_HOST 575
RetrieveDestroyAPIView 548 SECURE_SSL_REDIRECT 574
RetrieveUpdateAPIView 548 Select 268
RetrieveUpdateDestroyAPIView 548 select_related() 321
Reverse 160 select_template() 183
reverseQ 149, 189 SelectDateWidget 267
SelectMultiple 269

Предметный указатель 699

send() 474,479 Signer 465
send_mail() 482 simpletagO 406
send_mass_mail() 482 SimpieArrayFieid 383
send_messages() 481 SimpleListFilter 516
send_robust() 475 Sin 162
serializer class 548, 550 SingleObjectMixin 201
serve()414, 569, 579,580 SingleObjectTemplateResponseMixin 203
SERVER EMAIL 484 size 420
SESSION CACHE ALIAS 458 slice 244
SESSION COOKIE AGE 457 slugfield 202
SESSIONCOOKIEDOMAIN 457 siugurlkwarg 202
SESSIONCOOKIEHTTPONLY 457 SlugField 89, 262
SESSIONCOOKIENAME 457 slugify 243
SESSIONCOOKIEPATH 457 SmallAutoField 91
SESSIONCOOKIESAMESITE 457 SmalllntegerField 90
SESSIONCOOKIESECURE 457 SMILIESUPLOADTO 395
SESSIONENGINE 456 SOCKET_TIMEOUT 501
SESSIONEXPIREATBROWSERCLOSE sortableby 514
SOCKET_CONNECT_TIMEOUT 501
457 spaceless 238
SESSION FILE PATH 458 SpGistlndex 358
SESSIONSAVEEVERYREQUEST 457 SplitArrayField 383
SESSIONSERIALIZER 457 SplitDateTimeField 263
sessions 458 SplitDateTimeWidget 268
set() 130, 326,495, 499 sql 563, 564
set_<имя вторичной модели>_о^ег() 131 Sqrt 161
set_autocommit() 343 squashmigrations 121
set_cookie() 453 SSL 477
set_expiry() 459 stack info 555
set_many() 497 Stackedlnline 527
set_password() 434 start_index() 254
setsignedcookieO 455 startapp 83
settestcookieO 458 startproject 74
set_unusable_password() 435 static 250
setdefault() 182 static() 414
SetPasswordForm 308 STATIC_ROOT 248, 581
setup() 198 STATIC URL 248
SHA1 164 STATICFILES_DIRS 249
SHA244 164 STATICFILES_FINDERS 249
SHA256 164 STATICFILES_STORAGE 249
SHA384 164 StaticFilesStorage 249
SHA512 164 status code 182, 192, 563
shell 38 StdDev 153
SHORT DATE FORMAT 80 streaming 182, 192
SHORT DATETIME FORMAT 81 streamingcontent 192
short description 109, 511, 531 StreamingHttpResponse 192
showchangelink 529 Strindex 157
showfullresultcount 515 stringifinvalid 230
showmigrations 121 StringAgg 373
Sign 163 stringfilter 404
sign() 465,466 stringformat 243
Signal 468, 474 striptags 246
SignatureExpired 455, 466


Click to View FlipBook Version