Чтобы справиться с этим руководством вам нужно иметь следующее:
Готовый репозиторий с Django-приложением, как всегда на GitLab: https://gitlab.com/PythonRu/django-docker.
Обычно при развертывании веб-приложения в хостинге (например, DigitalOcean или Linode) вы настраиваете виртуальную машину или виртуальный компьютер, куда будет перенесен весь код с помощью git, FTP или другими средствами. Это называется виртуализацией.
Со временем разработчики начали видеть недостатки такого процесса — как минимум затраты на приспосабливание к изменениям в операционной системе. Им хотелось объединить среды разработки и производственную, вследствие чего и появилась идея контейнеризации.
Контейнер, если говорить простыми словами, — это место для среды разработки, то есть, вашего приложения и тех зависимостей, которые требуются для его работы.
Контейнеры позволяют разработчику упаковывать приложение со всеми зависимостями и передавать его между разными средами без каких-либо изменений.
Поскольку контейнеризация — куда более портативное, масштабируемое и эффективное решение, такие платформы, как Docker, становятся популярным выбором разработчиков.
Docker — это набор инструментов, с помощью которого можно создавать, управлять и запускать приложения в контейнерах. Он позволяет запросто упаковывать и запускать приложения в виде портативных, независимых и легких контейнеров, которые способны работать где угодно.
Для установки Docker на компьютере, воспользуйтесь инструкцией с официального сайта. У каждой операционной системы есть своя версия приложения.
Для этого руководства используем репозиторий приложения для опросов, написанного на Django. По мере продвижения в этом руководстве настроим Dockerfile, в котором будут обозначены инструкции для контейнера, внутри которого приложение и будет работать. После этого же настроим и файл docker-compose.yml для упрощения всего процесса.
На ПК с установленным git перейдите в выбранную папку и клонируйте следующий репозиторий из GitLab:
git clone https://gitlab.com/PythonRu/django-docker.git
После этого перейдите в корень этой папки и откройте ее в редакторе с помощью такой команды:
cd django-docker && code .

В этой папке создайте файл Dockerfile (регистр играет роль) без формата. Внутри него будут находиться настройки контейнера. Инструкции из него компьютер будет выполнять каждый раз при запуске команды docker build.
Следующий этап — создание файла requirements.txt, в котором перечислены все зависимости. Позже он будет использован для Dockerfile, в котором также требуется указывать все требуемые зависимости.
В файле requirements.txt добавьте Django версии 3.1.2 в таком формате:
Django==3.1.2
Идея написания Dockerfile может показаться сложной, но не забывайте, что это всего лишь инструкция (набор шагов) для создания собственных образов (images). Dockerfile будет содержать следующее:
При чтении или написании такого файла удобно держать в голове следующее:
RUN, FROM, COPY, WORKDIR и так далее.#. При выполнении инструкций из файла такие комментарии обычно игнорируются.Приложение будет работать на основе официального образа Python. Напишем следующие инструкции:
# Указывает Docker использовать официальный образ python 3 с dockerhub в качестве базового образа
FROM python:3
# Устанавливает переменную окружения, которая гарантирует, что вывод из python будет отправлен прямо в терминал без предварительной буферизации
ENV PYTHONUNBUFFERED 1
# Устанавливает рабочий каталог контейнера — "app"
WORKDIR /app
# Копирует все файлы из нашего локального проекта в контейнер
ADD ./app
# Запускает команду pip install для всех библиотек, перечисленных в requirements.txt
RUN pip install -r requirements.txt
Docker Compose — это отличный инструмент, помогающий определять и запускать приложения, для которых требуются несколько сервисов.
Обычно Docker Compose использует файл docker-compose.yml для настройки сервисов, которые приложение будет использовать. Запускаются эти сервисы с помощью команды docker-compose up. Это создает и запускает все сервисы из файла. В большинстве веб-приложений нужны, веб-сервер (такой как nginx) и база данных (например, PostgreSQL). В этом приложении будем использовать SQLite, поэтому внешняя база данных не потребуется.
Для использования особенностей Docker Compose нужно создать файл docker-compose.yml в той же папке, где находится Dockerfile и добавить туда следующий код:
version: '3.8'
services:
web:
build: .
command: python manage.py runserver localhost:8000
ports:
- 8000:8000
Дальше разберем содержимое файла построчно:
version: '3.8'
Эта строка сообщает Docker, какая версия docker-compose должна быть использована для запуска файла. На момент написания руководства последняя версия — 3.8, но обычно синтаксис не сильно меняется по мере выхода последующих.
После настройки docker-compose откройте терминал и запустите команду docker-compose up -d для запуска приложения. Дальше открывайте ссылку localhost:8000 в браузере, чтобы увидеть приложение в действии:

Для закрытия контейнера используется команда docker-compose down.
Репозиторий проекта: https://gitlab.com/PythonRu/django-docker.
В этом руководстве вы познакомились с виртуализацией, контейнеризацией и другими терминами из мира Docker. Также вы теперь знаете, что такое Dockerfile, как его создавать для запуска контейнеризированного Django-приложения. Наконец, разобрались с настройкой docker-compose с помощью файла docker-compose.yml для сервисов, от которых зависит самое приложения.
Не существует единого правильного способа использовать Docker в Django-приложении, но считается хорошей практикой следовать официальным инструкциями, чтобы максимально обезопасить свое приложение.
]]>Проще говоря, AJAX позволяет обновлять веб-страницы асинхронно, негласно обмениваясь данными с веб-сервером. Это означает, что обновление частей веб-страницы возможно без перезагрузки всей страницы.
Мы можем делать запросы AJAX из шаблонов Django, используя JQuery. AJAX методы библиотеки jQuery позволяют нам запрашивать текст, HTML, XML или JSON с удаленного сервера, используя как HTTP Get, так и HTTP Post. Полученные данные могут быть загружены непосредственно в выбранные HTML-элементы вашей веб-страницы.
В этом руководстве мы узнаем, как выполнять AJAX HTTP GET и POST запросы из шаблонов Django.
Я предполагаю, что у вас есть базовые знания о Django. Поэтому я не буду вдаваться в настройку проекта. Это очень простой проект с приложением под названием AJAX, в котором я использую bootstrap и Django crispy form для стилизации.
Репозиторий Gitlab — https://gitlab.com/PythonRu/django-ajax
Метод HTTP GET используется для получения данных с сервера.
В этом разделе мы создадим страницу регистрации, где мы будем проверять доступность имени пользователя с помощью JQuery и AJAX в шаблонах Django. Это очень распространенное требование для большинства современных приложений.
# ajax/views.py
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import login, authenticate
from django.shortcuts import render, redirect
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
@login_required(login_url='signup')
def home(request):
return render(request, 'home.html')
class SignUpView(CreateView):
template_name = 'signup.html'
form_class = UserCreationForm
success_url = reverse_lazy('home')
def form_valid(self, form):
valid = super().form_valid(form)
login(self.request, self.object)
return valid
def validate_username(request):
"""Проверка доступности логина"""
username = request.GET.get('username', None)
response = {
'is_taken': User.objects.filter(username__iexact=username).exists()
}
return JsonResponse(response)
Итак, у нас есть три представления, первым является home, которое отображает довольно простой шаблон домашней страницы.
Далее идет SignUpView, унаследованный от класса CreateView. Он нужен для создания пользователей с помощью встроенного Django-класса UserCreationForm, который предоставляет очень простую форму для регистрации. При успешном ее прохождении происходит вход в систему, и пользователь перенаправляется на домашнюю страницу.
Наконец, validate_username — это наше AJAX представление, которое возвращает объект JSON с логическим значением из запроса, проверяющего, существует ли введенное имя пользователя.
Класс JsonResponse возвращает HTTP-ответ с типом содержимого application/json, преобразуя переданный ему объект в формат JSON. Поэтому, если имя пользователя уже существует в базе данных, он вернет JSON-объект, показанный ниже.
{'is_taken': true}
# ajax/urls.py
from django.urls import path
from .views import home, SignUpView, validate_username
urlpatterns = [
path('', home, name='home'),
path('signup', SignUpView.as_view(), name='signup'),
path('validate_username', validate_username, name='validate_username')
]
# dj_ajax/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('ajax/', include('ajax.urls'))
]
{# home.html #}
<h1>Привет, {{ user.username }}!</h1>
{# signup.html #}
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body>
<div class="container mt-5 w-50">
<form id="signupForm" method="POST">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="signupSubmit" class="btn btn-success btn-lg" />
</form>
</div>
{% block javascript %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
// отслеживаем событие отправки формы
$('#id_username').keyup(function () {
// создаем AJAX-вызов
$.ajax({
data: $(this).serialize(), // получаяем данные формы
url: "{% url 'validate_username' %}",
// если успешно, то
success: function (response) {
if (response.is_taken == true) {
$('#id_username').removeClass('is-valid').addClass('is-invalid');
$('#id_username').after('<div class="invalid-feedback d-block" id="usernameError">This username is not available!</div>')
}
else {
$('#id_username').removeClass('is-invalid').addClass('is-valid');
$('#usernameError').remove();
}
},
// если ошибка, то
error: function (response) {
// предупредим об ошибке
console.log(response.responseJSON.errors)
}
});
return false;
});
})
</script>
{% endblock javascript %}
</body>
</html>
Для лучшего понимания давайте подробно разберем все части представленного шаблона.
Внутри тега head мы загружаем bootstrap, используя CDN. Вы также можете сохранить библиотеку себе на диск и отдавать ее клиенту из статических папок.
<form id="signupForm" method="POST">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="signupSubmit" class="btn btn-success btn-lg" />
</form>
Затем мы создаем форму Django, используя для стилизации тег crispy.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
Далее внутри блока javascript мы запрашиваем JQuery от google CDN, вы также можете загрузить ее локально.
$(document).ready(function () {
( .... )
})
Затем у нас есть еще один скрипт с методом ready(). Код, написанный внутри метода $(document).ready(), будет запущен, когда DOM страницы будет готов для выполнения JavaScript.
$('#id_username').keyup(function () {
// создаем AJAX-вызов
$.ajax({
data: $(this).serialize(), // получаяем данные формы
url: "{% url 'validate_username' %}",
// если успешно, то
success: function (response) {
if (response.is_taken == true) {
$('#id_username').removeClass('is-valid').addClass('is-invalid');
$('#id_username').after('<div class="invalid-feedback d-block" id="usernameError">Это имя пользователя недоступно!</div>')
}
else {
$('#id_username').removeClass('is-invalid').addClass('is-valid');
$('#usernameError').remove();
}
},
// если ошибка, то
error: function (response) {
// предупредим об ошибке
console.log(response.responseJSON.errors)
}
});
return false;
});
Метод ajax запускается функцией keyup. Он принимает на вход объект с параметрами запроса. После успешного завершения запросов запускается одна из колбэк-функций success или error. При успешном вызове мы используем условный оператор для добавления и удаления классов is-valid/is-invalid поля ввода. А return false в конце скрипта предотвращает отправку форм, таким образом останавливая перезагрузку страницы.
Сохраните файлы и запустите сервер, вы должны увидеть AJAX в действии.
Метод HTTP POST используется для отправки данных на сервер.
В этом разделе мы узнаем, как делать POST-запросы с помощью JQuery и AJAX в шаблонах Django.
Мы создадим контактную форму и сохраним данные, предоставленные пользователем, в базу данных с помощью JQuery и AJAX.
# ajax/models.py
from django.db import models
class Contact(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
message = models.TextField()
def __str__(self):
return self.name
# ajax/forms.py
from django import forms
from .models import Contact
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = '__all__'
# ajax/urls.py
...
from .views import home, SignUpView, validate_username, contact_form
urlpatterns = [
...
path('contact-form/', contact_form, name='contact_form')
]
# ajax/views.py
...
from .forms import ContactForm
...
def contact_form(request):
form = ContactForm()
if request.method == "POST" and request.is_ajax():
form = ContactForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
form.save()
return JsonResponse({"name": name}, status=200)
else:
errors = form.errors.as_json()
return JsonResponse({"errors": errors}, status=400)
return render(request, "contact.html", {"form": form})
В представлении мы проверяем ajax-запрос с помощью метода request.is_ajax(). Если форма корректно заполнена, мы сохраняем ее в базе данных и возвращаем объект JSON с кодом состояния и именем пользователя. Для недопустимой формы мы отправим клиенту найденные ошибки с кодом 400, что означает неверный запрос (bad request).
{# contact.html #}
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact us</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body>
<div class="container mt-5 w-50">
<form id="contactForm" method="POST">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="contact-submit" class="btn btn-success btn-lg" />
</form>
</div>
{% block javascript %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
// отслеживаем событие отправки формы
$('#contactForm').submit(function () {
// создаем AJAX-вызов
$.ajax({
data: $(this).serialize(), // получаем данные формы
type: $(this).attr('method'), // GET или POST
url: "{% url 'contact_form' %}",
// если успешно, то
success: function (response) {
alert("Спасибо, что обратились к нам " + response.name);
},
// если ошибка, то
error: function (response) {
// предупредим об ошибке
alert(response.responseJSON.errors);
console.log(response.responseJSON.errors)
}
});
return false;
});
})
</script>
{% endblock javascript %}
</body>
</html>
Давайте разобьем шаблон на более мелкие модули, чтобы лучше понять его.
Сначала мы импортируем bootstrap в head, используя CDN. Затем внутри body мы создаем форму с тегом crispy для стилизации.
После этого в первом javascript-блоке мы загружаем JQuery из CDN. Далее внутри функции $(document).ready() мы добавили наш AJAX метод.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
// отслеживаем событие отправки формы
$('#contactForm').submit(function () {
// создаем AJAX-вызов
$.ajax({
data: $(this).serialize(), // получаем данные формы
type: $(this).attr('method'), // GET или POST
url: "{% url 'contact_form' %}",
// если успешно, то
success: function (response) {
alert("Спасибо, что обратились к нам " + response.name);
},
// если ошибка, то
error: function (response) {
// предупредим об ошибке
alert(response.responseJSON.errors);
console.log(response.responseJSON.errors)
}
});
return false;
});
})
</script>
При отправке формы мы вызываем метод ajax(), который сериализует ее данные и отправляет их по заданному URL-адресу. В случае успеха мы показываем диалоговое окно с сообщением, сгенерированным на основе полученного имени пользователя.
Нам нужно просто вернуть объект JSON из метода form_valid() класса FormView. Вы также можете использовать другие стандартные представления, основанные на классах, переопределив метод post().
# ajax/views.py
...
from django.views.generic.edit import CreateView, FormView
...
class ContactFormView(FormView):
template_name = 'contact.html'
form_class = ContactForm
def form_valid(self, form):
"""
Если форма валидна, вернем код 200
вместе с именем пользователя
"""
name = form.cleaned_data['name']
form.save()
return JsonResponse({"name": name}, status=200)
def form_invalid(self, form):
"""
Если форма невалидна, возвращаем код 400 с ошибками.
"""
errors = form.errors.as_json()
return JsonResponse({"errors": errors}, status=400)
Django REST Framework — это набор инструментов для создания REST API с помощью Django. В этом руководстве рассмотрим, как правильно его использовать. Создадим эндпоинты(точки доступа к ресурсам) для пользователей, постов в блоге, комментариев и категорий.
Также рассмотрим аутентификацию, чтобы только залогиненный пользователь мог изменять данные приложения.
Вот чему вы научитесь:
Код урока можно скачать в репозитории https://gitlab.com/PythonRu/blogapi.
У вас в системе должен быть установлен Python 3, желательно 3.8. Также понадобится опыт работы с REST API. Вы должны быть знакомы с реляционными базами данными, включая основные и внешние ключи, модели баз данных, миграции, а также отношения многие-к-одному и многие-ко-многим.
Наконец, потребуется опыт работы с Python и Django.
Для создания нового API-проекта для начала создайте виртуальную среду Python в своей рабочей директории. Для этого запустите следующую команду в терминале:
python3 -m venv env
source env/bin/activate
В Windows это будет source env\Scripts\activate.
Не забывайте запускать все команды из этого руководства в виртуальной среде. Убедиться в том, что она активирована можно благодаря надписи (env) в начале строки приглашения к вводу в терминале.
Чтобы деактивировать среду, введите deactivate.
После этого установите Django и REST Framework в среду:
pip install django==3.1.7 djangorestframework==3.12.4
Создайте новый проект «blog» и новое приложение «api»:
django-admin startproject blog
cd blog
django-admin startapp api

Из корневой директории «blog» (там где находится файл «manage.py»), синхронизируйте базу данных. Это запустит миграции для admin, auth, contenttypes и sessions.
python manage.py migrate
Вам также понадобится пользователь admin для взаимодействия с панелью управления Django и API. Из терминала запустите следующее:
python manage.py createsuperuser --email admin@example.com --username admin
Установите любой пароль (у него должно быть как минимум 8 символов). Если вы введете слишком простой пароль, то можете получить ошибку.
Для настройки, добавьте rest_framework и api в файл конфигурации (blog/blog/settings.py):
INSTALLED_APPS = [
...
'rest_framework',
'api.apps.ApiConfig',
]
Добавление ApiConfig позволит добавлять параметры конфигурации в приложение. Другие настройки для этого руководства не потребуются.
Наконец, запустите локальный сервер с помощью команды python manage.py runserver.
Перейдите по ссылке http://127.0.0.1:8000/admin и войдите в админ-панель сайта. Нажмите на «Users», чтобы увидеть пользователя и добавить новых при необходимости.

Теперь с пользователем «admin» можно переходить к созданию самого API. Это предоставит доступ только для чтения списку пользователей из списка API-эндпоинтов.
REST Framework Django использует сериализаторы, чтобы переводить наборы запросов и экземпляры моделей в JSON-данные. Сериализация также определяет, какие данные вернет API в ответ на запрос клиента.
Пользователи Django создаются из модели User, которая определена в django.contrib.auth. Для создания сериализатора для модели добавьте следующее в blog/api/serializers.py (файл нужно создать):
from rest_framework import serializers
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username']
По примеру импортируйте модель User из Django вместе с набором сериализаторов из REST Framework Django.
Теперь создайте класс UserSerializer, который должен наследоваться от класса ModelSerializer.
Определите модель, которая должна ассоциироваться с сериализатором (model = User). Массив fields определяет, какие поля модели должны быть включены. Например, можно добавлять поля first_name и last_name.
Класс ModelSerializer генерирует поля сериализатора, которые основаны на соответствующих свойствах модели. Это значит, что не нужно вручную указывать все атрибуты для поля сериализации, поскольку они вытягиваются напрямую из модели.
Этот сериализатор также создает простые методы create() и update(). При необходимости их можно переписать.
Ознакомиться подробнее с работой ModelSerializer можно на официальном сайте.
Есть несколько способов создавать представления в REST Framework Django. Чтобы получить возможность повторного использования кода и избегать повторений, используйте классовые представления.
REST Framework предоставляет несколько обобщенных представлений, основанных на классе APIView. Они представляют собой самые распространенные паттерны.
Например, ListAPIView используется для эндпоинтов с доступом только для чтения. Он предоставляет метод-обработчик get. ListCreateAPIView используется для эндпоинтов с разрешением чтения-записи, а также обработчики get и post.
Для создания эндпоинта только для чтения, который возвращал бы список пользователей, добавьте следующее в blog/api/views.py:
from rest_framework import generics
from . import serializers
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = serializers.UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = serializers.UserSerializer
В первую очередь здесь импортируется generics коллекция представлений, а также модель User и UserSerialized из предыдущего шага. Представление UserList предоставляет доступ только для чтения (через get) к списку пользователей, а UserDetails — к одному пользователю.
Названия представлений должны быть в следующем формате: {ModelName}List и {ModelName}Details для коллекции объектов и одного объекта соответственно.
Для каждого представления переменная queryset содержит коллекцию экземпляров модели, которую возвращает User.objects.all(). Значением serializer_class должно быть UserSerializer, который и сериализует данные модели User.
Пути к эндпоинтам будут настроены на следующем шаге.
С моделью, сериализатором и набором представлений для User финальным шагом будет создание эндпоинтов (которые в Django называются URL-паттернами) для каждого представления.
В первую очередь добавьте следующее в blog/api/urls.py (этот файл тоже нужно создать):
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from . import views
urlpatterns = [
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Здесь импортируется функция path и также коллекция представлений приложения api.
Функция path создает элемент, который Django использует для показа страницы приложения. Для этого Django в первую очередь ищет нужный элемент с соответствующим URL (например, users/) для запрошенного пользователем. После этого он импортирует и вызывает соответствующее представление (то есть, UserList)
Последовательность <int:pk> указывает на целочисленное значение, которое является основным ключом (pk). Django захватывает эту часть URL и отправляет в представление в виде аргумента-ключевого слова.
В этом случае основным ключом для User является поле id, поэтому http://127.0.0.1:8000/users/1 вернет id со значением 1.
Прежде чем можно будет взаимодействовать с этими URL-паттернами (и теми, которые будут созданы позже) их нужно добавить в проект. Добавьте следующее в blog/blog/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('api.urls')),
]
Чтобы убедиться, что все элементы работают корректно, перейдите по ссылке http://127.0.0.1:8000/users, чтобы увидеть список пользователей приложения.

В этом руководстве используется графическое представление API из Django REST Framework для демонстрации эндпоинтов. Интерфейс предоставляет элементы аутентификации и формы, имитирующие фронтенд-клиент. Для тестирования API также можно использовать cURL или httpie.
Обратите внимание на то, что значение пользователя admin равно 1. Можете перейти к нему, открыв для этого http://127.0.0.1:8000/users/1.

В итоге класс модели Django сериализуется с помощью UserSerializaer. Он предоставляет данные представлениям UserList и UserDetail, доступ к которым можно получить с помощью паттернов users/ и users/<int:pk>.
После базовой настройки можно приступать к созданию полноценного API для блога с эндпоинтами для постов, комментариев и категорий. Начнем с API для Post.
В blog/api/models.py создайте модель Post, которая наследуется от класса Model из Django и определите ее поля:
from django.db import models
class Post(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
body = models.TextField(blank=True, default='')
owner = models.ForeignKey('auth.User', related_name='posts', on_delete=models.CASCADE)
class Meta:
ordering = ['created']
Типы полей соответствуют таковым в реляционных базах данные. Можете ознакомиться со страницей Models на официальном сайте фреймворка.
Обратите внимание на то, что тип ForeignKey создает отношение многие-к-одному между текущей моделью и моделью, указанной в первом аргументе (auth.User — то есть, модель User, с которой вы работаете).
В этом случае пользователь может иметь много статей, но у поста может быть всего один владелец. Поле owner может быть использовано во фронтенд-приложении для получения пользователя и отображения его имени в качестве автора поста.
Аргумент related_name позволяет задать другое имя доступа к текущей модели (posts) вместо стандартного (post_set). Список постов будет добавлен в сериализатор User на следующем шаге для завершения отношения многие-к-одному.
Каждый раз при обновлении модели запускайте следующие команды для обновления базы данных:
python manage.py makemigrations api
python manage.py migrate
Поскольку мы работаем с моделями Django, таким как User, посты можно изменить из административной панели Django, зарегистрировав ее в blog/api/admin.py:
from django.contrib import admin
from .models import Post
admin.site.register(Post)
Позже их можно будет создавать и через графическое представление API.
Перейдите на http://127.0.0.1:8000/admin, кликните на Posts и добавьте новые посты. Вы заметите, что поля title и body в форме соответствуют типам CharField и TextField из модели Post.
Также можно выбрать owner среди существующих пользователей. При создании поста в API пользователя выбирать не нужно. Owner будет задан автоматически на основе данных залогиненного пользователя. Это настроим в следующем шаге.
Чтобы добавить модель Post в API, нужно повторить шаги добавления модели User.
Сначала нужно сериализовать данные модели Post. В blog/api/serializers.py добавьте следующее:
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Post
class PostSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Post
fields = ['id', 'title', 'body', 'owner']
class UserSerializer(serializers.ModelSerializer):
posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = ['id', 'username', 'posts']
Импортируйте модель Post из приложения api и создайте PostSerializer, который будет наследоваться от класса ModelSerializer. Задайте модель и поля, которые будут использоваться сериализатором.
ReadOnlyField — это класс, возвращающий данные без изменения. В этом случае он используется для возвращения поля username вместо стандартного id.
Дальше добавьте поле posts в UserSerializer. Отношение многие-к-одному между постами и пользователями определено моделью Post в прошлом шаге. Название поля (posts) должно быть равным аргументу related_field поля Post.owner. Замените posts на post_set (значение по умолчанию), если вы не задали значение related_field в прошлом шаге.
PrimaryKeyRelatedField представляет список публикаций в этом отношении многие-к-одному (many=True указывает на то, что постов может быть больше чем один).
Если не задать read_only=True поле posts будет иметь права записи по умолчанию. Это значит, что будет возможность вручную задавать список статей, принадлежащих пользователю при его создании. Вряд ли это желаемое поведение.
Перейдите по ссылке http://127.0.0.1:8000/users, чтобы увидеть поле posts каждого пользователя.
Обратите внимание на то, что список
posts— это, по сути, список id. Вместо этого можно возвращать список URL с помощьюHyperLinkModelSerializer.
Следующий шаг — создать набор представлений для Post API. Добавьте следующее в blog/api/views.py:
...
from .models import Post
...
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = serializers.PostSerializer
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = serializers.PostSerializer
ListCreateAPIView и RetrieveUpdateDestroyAPIView предоставляют самые распространенные обработчики API-методов: get и post для списка (ListCreateAPIView) и get, update и delete для одной сущности (RetrieveUpdateDestroyAPIView).
Также нужно перезаписать функцию по умолчанию perform_create, чтобы задать поле owner текущего пользователя (значение self.request.user).
Чтобы закончить с эндпоинтами для Post API создайте URL-паттерны Post. Добавьте следующее в массив urlpatterns в blog/api/urls.py:
urlpatterns = [
...
path('posts/', views.PostList.as_view()),
path('posts/<int:pk>/', views.PostDetail.as_view()),
]
Объединение представлений с этими URL-паттернами создает эндпоинты:
get posts/, post posts/, get posts/<int:pk>/, put posts/<int:pk>/ delete posts/<int:pk>/.Чтобы протестировать их, перейдите на http://127.0.0.1:8000/posts и создайте публикации. Я взял несколько статей из Медиума.
Перейдите на один пост (например, http://127.0.0.1:8000/posts/1 и нажмите DELETE. Чтобы поменять название поста, обновите поле «title» и нажмите PUT.

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

Для удобства добавим кнопку «Log in» в графическое представление API с помощью следующего кода в blog/urls.py:
urlpatterns = [
...
path('api-auth/', include('rest_framework.urls')),
]
Теперь можно заходить под разными аккаунтами, чтобы проверять работу разрешений и изменять посты через интерфейс.
Сейчас можно создать пост, будучи зарегистрированным, но для удаления или изменения данных этого не требуется — даже если пост вам не принадлежит. Попробуйте зайти под другим аккаунтом, и вы сможете удалить посты, принадлежащие admin.
Чтобы аутентифицировать пользователя и быть уверенным в том, что только владелец поста может обновлять и удалять его, нужно добавить разрешения.
Начните с этого кода в blog/api/permisisions.api (файл необходимо создать):
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.owner == request.user
Разрешение IsOwnerOrReadOnly проверяет, является ли пользователь владельцем этого объекта. Таким образом только при этом условии можно будет обновлять или удалять пост. Не-владельцы смогут получать пост, потому что это действие только для чтения.
Также есть встроенное разрешение IsAuthenticatedOrReadOnly. С ним любом аутентифицированный пользователь может выполнять любой запрос, а остальные — только на чтение.
Добавьте эти разрешения в представления Post:
from rest_framework import generics, permissions
...
from .serializers import PostSerializer
from .permissions import IsOwnerOrReadOnly
...
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
Представлению PostList требуется разрешение IsAuthenticatedOrReadOnly, потому что пользователь должен аутентифицироваться, чтобы создать пост, а вот просматривать список может любой пользователь.
Для PostDetails нужны оба разрешения, поскольку обновлять или удалять пост должен только залогиненный пользователь, а также его владелец. Для получения поста прав не нужно. Вернитесь на http://127.0.0.1:8000/posts. Зайдите в учетную запись admin и другие, чтобы проверить, какие действия доступны аутентифицированным и анонимным пользователям.
Будучи разлогиненным, вы не должны иметь возможность создавать, удалять или обновлять посты. При аутентификации вы не должны иметь право удалять или редактировать чужие посты.

Теперь у вас есть базовый API для постов. Можно добавить в систему комментарии.
Комментарий — это текст, который пользователь добавляет в ответ на пост другого пользователя. К одному можно оставить несколько комментариев, а у поста может быть несколько комментариев от разных пользователей. Это значит, что нужно настроить две пары отношений многие-к-одному: между комментариями и пользователями, а также между комментариями и постами.
Сначала создайте модель в blog/api/models.py:
...
class Comment(models.Model):
created = models.DateTimeField(auto_now_add=True)
body = models.TextField(blank=False)
owner = models.ForeignKey('auth.User', related_name='comments', on_delete=models.CASCADE)
post = models.ForeignKey('Post', related_name='comments', on_delete=models.CASCADE)
class Meta:
ordering = ['created']
Модель Comment похожа на Post и имеет отношение многие-к-одному с пользователями через поле owner. У комментария есть отношение многие-к-одному с одним постом через поле post.
Запустите миграции базы данных:
python manage.py makemigrations api
python manage.py migrate
Для создания API комментариев нужно добавить модель Comment в PostSerializer и UserSerializer, чтобы убедиться, что связанные комментарии отправляются вместе с данными о пользователе и посте.
Обновите код в blog/api/serializers.py:
...
from .models import Post, Comment
class PostSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
comments = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'body', 'owner', 'comments']
class UserSerializer(serializers.ModelSerializer):
posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
comments = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = ['id', 'username', 'posts', 'comments']
Процесс напоминает добавление posts в UserSerializer. Это настраивает часть «многие» отношения многие-к-одному между комментариями и пользователем, а также между комментариями и постом. Список комментариев должен быть доступен только для чтения (read_only=True)
Теперь добавим CommentSerializer в тот же файл:
...
class CommentSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Comment
fields = ['id', 'body', 'owner', 'post']
Обратите внимание на то, что поле post не меняется. После добавления поля post в массив fields он будет сериализоваться по умолчанию (согласно ModelSerializer). Это эквивалентно post=serializers.PrimaryKeyRelatedField(queryset=Post.objects.all()).
Это значит, что у поля post есть право на запись по умолчанию: при создании комментария настраивается, какому посту он принадлежит.
Наконец, создадим представления и паттерны для комментариев. Процесс похож на тот, что был при настройке API Post.
Добавьте этот код в blog/api/views.py:
...
from .models import Post, Comment
...
class CommentList(generics.ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = serializers.CommentSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class CommentDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Comment.objects.all()
serializer_class = serializers.CommentSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
Представления похожи на PostList и PostDetails.
Чтобы закончить API комментариев, определите URL-паттерны в blog/api/urls.py:
urlpatterns = [
...
path('comments/', views.CommentList.as_view()),
path('comments/<int:pk>/', views.CommentDetail.as_view()),
]
Теперь по ссылке http://127.0.0.1:8000/comments можете увидеть список существующих комментариев и создавать новые.

Также обратите внимание на то, что при создании нужно выбрать пост из списка существующих.
Финальный элемент блога — система категорий.
Пост может принадлежать к одной или нескольким категориям. Также одна категория может принадлежать нескольким постам, значит это отношение многие-ко-многим.
Создайте модель Category в blog/api/models.py:
urlpatterns = [
...
path('comments/', views.CommentList.as_view()),
path('comments/<int:pk>/', views.CommentDetail.as_view()),
]
Здесь класс ManyToManyField создает отношение многие-ко-многим между текущей моделью и моделью из первого аргумента. Как и в случае с классом ForeignKey отношение завершает сериализатор.
Обратите внимание на то, что verbose_name_plural определяет, как правильно писать название модели во множественном числе. Это нужно, например, для административной панели. Так, вы можете указать, что во множественном числе правильно писать categories, а не categorys.
Запустите миграции базы данных:
python manage.py makemigrations api
python manage.py migrate
Процесс создания похож на описанный в прошлых шагах. Сперва создайте сериализатор для Category, добавив код в blog/api/serializers.py:
...
from .models import Post, Comment, Category
...
class CategorySerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Category
fields = ['id', 'name', 'owner', 'posts']
class PostSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
comments = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Post
fields = ['id', 'title', 'body', 'owner', 'comments', 'categories']
class UserSerializer(serializers.ModelSerializer):
posts = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
comments = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
categories = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = User
fields = ['id', 'username', 'posts', 'comments', 'categories']
Не забудьте добавить имя поля categories в список полей PostSerializer и UserSerializer. Обратите внимание на то, что UserSerializer.categories можно отметить как read_only=True. Это поле представляет список всех созданных категорий.
С другой стороны, поле PostSerializer.categories будет иметь право на запись по умолчанию. То же самое, что указать categories = serializers.PrimaryKeyRelatedField(many=True, queryset=Category.objects.all()). Это позволит пользователю выбирать одну или нескольких категорий для поста.
Дальше создайте представления в blog/api/views.api:
...
from .models import Post, Comment, Category
...
class CategoryList(generics.ListCreateAPIView):
queryset = Category.objects.all()
serializer_class = serializers.CategorySerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Category.objects.all()
serializer_class = serializers.PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
По аналогии с прошлыми.
Наконец, добавьте этот код в blog/api/urls.py:
...
from .models import Post, Comment, Category
urlpatterns = [
...
path('categories/', views.CategoryList.as_view()),
path('categories/<int:pk>/', views.CategoryDetail.as_view()),
]
Теперь отправляйтесь на http://127.0.0.1:8000/categories и создайте пару категорий. А на http://127.0.0.1:8000/posts создайте пост и выберите для него категории.

Теперь у вас есть API блога с аутентификацией и многими распространенными паттернами API. Есть эндпоинты для получения, создания, обновления и удаления постов, категорий и комментариев. Также настроены отношения многие-к-одному и многие-ко-многим между этими ресурсами.
Чтобы расширить возможности своего API, переходите к официальной документации Django REST Framework. А ссылку на код этого урока можно найти в начале статьи.
]]>Вы можете использовать на исходный код проекта из этого репозитория.
Celery нужен для запуска задач в отдельном рабочем процессе (worker), что позволяет немедленно отправить HTTP-ответ пользователю в веб-процессе (даже если задача в рабочем процессе все еще выполняется). Цикл обработки запроса не будет заблокирован, что повысит качество взаимодействия с пользователем.
Ниже приведены некоторые примеры использования Celery:
Когда вы создаете веб-приложение, постарайтесь сделать время отклика не более, чем 500мс (используйте New Relic или Scout APM), если пользователь ожидает ответа слишком долго, выясните причину и попытайтесь устранить ее. В решении такой проблемы может помочь Celery.
RQ (Redis Queue) — еще одна библиотека Python, которая решает вышеуказанные проблемы.
Логика работы RQ схожа с Celery (используется шаблон проектирования производитель/потребитель). Далее я проведу поверхностное сравнение для лучшего понимания, какой из инструментов более подходит для задачи.
Я предпочитаю Celery, поскольку он замечательно подходит для решения многих проблем. Данная статья написана мной, чтобы помочь читателю (особенно новичку) быстро изучить Celery!
Брокер сообщений — это хранилище, которое играет роль транспорта между производителем и потребителем.
Из документации Celery рекомендуемым брокером является RabbitMQ, потому что он поддерживает AMQP (расширенный протокол очереди сообщений).
Так как во многих случаях нам не нужно использовать AMQP, другой диспетчер очереди, такой как Redis, также подойдет.
Бэкенд результатов — это хранилище, которое содержит информацию о результатах выполнения Celery-задач и о возникших ошибках.
Здесь рекомендуется использовать Redis.
Celery не работает на Windows. Используйте Linux или терминал Ubuntu в Windows.
Далее я покажу вам, как импортировать Celery worker в ваш Django-проект.
Мы будем использовать Redis в качестве брокера сообщений и бэкенда результатов, что немного упрощает задачу. Но вы свободны в выборе любой другой комбинации, которая удовлетворяет требованиям вашего приложения.
Если вы работаете в Linux или Mac, у вас есть возможность использовать менеджер пакетов для настройки Redis (brew, apt-get install), однако я хотел бы порекомендовать вам попробовать применить Docker для установки сервера redis.
$ docker run -p 6379: 6379 --name some-redis -d redisКоманда выше запустит Redis на 127.0.0.1:6379.
Теперь импортируем Celery в наш Django-проект.
Рекомендую создать отдельное виртуальное окружение и работать в нем.
$ pip install django==3.1
$ django-admin startproject celery_django
$ python manage.py startapp polls
Ниже представлена структура проекта.
├── celery_django
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── polls
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
Давайте приступим к установке и настройке Celery.
pip install celery==4.4.7 redis==3.5.3 flower==0.9.7
Создайте файл celery_django/celery.py рядом с celery_django/wsgi.py.
"""
Файл настроек Celery
https://docs.celeryproject.org/en/stable/django/first-steps-with-django.html
"""
from __future__ import absolute_import
import os
from celery import Celery
# этот код скопирован с manage.py
# он установит модуль настроек по умолчанию Django для приложения 'celery'.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celery_django.settings')
# здесь вы меняете имя
app = Celery("celery_django")
# Для получения настроек Django, связываем префикс "CELERY" с настройкой celery
app.config_from_object('django.conf:settings', namespace='CELERY')
# загрузка tasks.py в приложение django
app.autodiscover_tasks()
@app.task
def add(x, y):
return x / y
Давайте продолжим изменять проект, в celery_django/__init__.py добавьте.
from __future__ import absolute_import, unicode_literals
# Это позволит убедиться, что приложение всегда импортируется, когда запускается Django
from .celery import app as celery_app
__all__ = ('celery_app',)
Поскольку Celery может читать конфигурацию из файла настроек Django, мы внесем в него следующие изменения.
CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/0"
Есть кое-что, о чем следует помнить.
При изучении документации Celery вы вероятно увидите, что broker_url — это ключ конфигурации, который вы должны установить для диспетчера сообщений, однако в приведенном выше celery.py:
app.config_from_object('django.conf: settings', namespace = 'CELERY') сообщает Celery, чтобы он считывал значение из пространства имен CELERY, поэтому, если вы установите просто broker_url в своем файле настроек Django, этот параметр не будет работать. Правило применяется для всех ключей конфигурации в документации Celery.После завершение работы с конфигурацией все готово к использованию Celery. Мы будем запускать некоторые команды в отдельном терминале, но я рекомендую вам взглянуть на Tmux, когда у вас будет время.
Сначала запустите Redis-клиент, потом celery worker в другом терминале, celery_django — это имя Celery-приложения, которое вы установили в celery_django/celery.py.
$ celery worker -A celery_django --loglevel=info
-------------- celery@DESKTOP-111111 v4.4.7 (cliffs)
--- ***** -----
-- ******* ---- Linux-4.4.0-19041-Microsoft-x86_64-with-glibc2.27 2021-03-15 15:03:44
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app: celery_django:0x7ff07f818ac0
- ** ---------- .> transport: redis://127.0.0.1:6379/0
- ** ---------- .> results: redis://127.0.0.1:6379/0
- *** --- * --- .> concurrency: 4 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. celery_django.celery.add
Далее запустим приложение в новом терминале, которое поможет нам отслеживать Celery-задачу (я расскажу об этом чуть позже).
$ flower -A celery_django --port=5555
[I 210315 16:11:39 command:135] Visit me at http://localhost:5555
[I 210315 16:11:39 command:142] Broker: redis://127.0.0.1:6379/0
[I 210315 16:11:39 command:143] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap',
'celery_django.celery.add']
[I 210315 16:11:39 mixins:229] Connected to redis://127.0.0.1:6379/0
Затем откройте http://localhost:5555/. Вы должны увидеть информационную панель, на которой отображаются детали выполнения рабочего процесса Celery.
Теперь войдем в Django shell и попробуем отправить Celery несколько задач.
$ python manage.py migrate
$ python manage.py shell
...
>>> from celery_django.celery import add
>>> task = add.delay(1, 2)
Рассмотрим некоторые моменты:
xxx.delay для отправки сообщения брокеру. Рабочий процесс получает эту задачу и выполняет ее.task = add.delay(1, 2), кажется, что команда быстро завершает выполнение (отсутствие блокировки), но метод добавления все еще активен в рабочем процессе Celery.[2021-03-15 15:04:32,859: INFO/MainProcess] Received task: celery_django.celery.add[e1964774-fd3b-4add-96ff-116e3578de
de]
[2021-03-15 15:04:32,882: INFO/ForkPoolWorker-1] Task celery_django.celery.add[e1964774-fd3b-4add-96ff-116e3578dede] s
ucceeded in 0.013418699999988348s: 0.5
Рабочий процесс получил задачу в 15:04:32, и она была успешно выполнена.
Думаю, теперь у вас уже есть базовое представление об использовании Celery. Попробуем ввести еще один блок кода.
>>> print(task.state, task.result)
SUCCESS 0.5
Затем давайте попробуем вызвать ошибку в Celery worker и посмотрим, что произойдет.
>>> task = add.delay(1, 0)
>>> type(task)
celery.result.AsyncResult
>>> task.state
'FAILURE'
>>> task.result
ZeroDivisionError('division by zero')
Как видите, результатом вызова метода delay является экземпляр AsyncResult.
Мы можем использовать его следующим образом:
Flower позволяет отобразить информацию о работу Celery более наглядно на веб-странице с дружественным интерфейсом. Это значительно упрощает понимание происходящего, поэтому я хочу обратить внимание на Flower, прежде чем углубиться в дальнейшее рассмотрение Celery.
URL-адрес панели управления: http://127.0.0.1:5555/. Откройте страницу задач — Tasks.

При изучении Celery довольно полезно использовать Flower для лучшего понимания деталей.
Когда вы развертываете свой проект на сервере, Flower не является обязательным компонентом. Я имею в виду, что вы можете напрямую использовать команды Celery, чтобы управлять приложением и проверять статус рабочего процесса.
В этой статье я рассказал об основных аспектах Celery. Надеюсь, что после прочтения вы стали лучше понимать процесс работы с ним. Исходный код проекта доступен по ссылке в начале статьи.
]]>Что такое статические файлы в Django?
Изображения, JS и CSS-файлы называются статическими файлами или ассетами проекта Django.
Код из урока: https://gitlab.com/PythonRu/django_static
В папке проекта Django создайте новую папку «static». В примере выше она находится в директории «dstatic».

Теперь убедитесь, что статические файлы Django django.contrib.staticfiles указаны в списке установленных приложений в settings.py.
# dstatic > dstatic > settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Они должны быть там по умолчанию.
После этого пролистайте в нижнюю часть файла настроек и укажите статический URL, если такого еще нет. Вы также можете указать статическую папку, чтобы Django знал, где искать статические файлы.
# dstatic > dstatic > settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
Не забудьте импортировать библиотеку os после добавления кода выше.
После завершения базовой настройки рассмотрим, как добавлять и показывать изображения в шаблонах, а также как подключить свои JavaScript и CSS файлы.
Создайте папку в «static» специально для изображений. Назовите ее «img». Главное после этого правильно ссылаться на нее в шаблонах.

Теперь в выбранном шаблоне (например, в «home.html») загрузите статический файл в верхней части страницы.

Важно добавить
{% load static %}до того, как добавлять изображение. В противном случае оно не будет загружено.
После этого вы можете использовать тег «static» в шаблоне для работы с источником изображения.
dstatic > templates > home.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<h1 class="text-center">Привет Django</h1>
<img class="w-50 mx-auto d-block" src="{% static 'img/example.png' %}" alt="photo">
</div>
</body>
</html>
Стоит отметить, что этот файл использует также Bootstrap CDN. Результат:

Если нужно добавить кастомные JS-файлы в проект, создайте папку «js» внутри «static».
Можно также использовать элемент <script> внутри шаблона, но создание отдельного JS-файла улучшит организацию проекта и поможет проще находить все скрипты в одном месте.
В папке static > js создайте файл «script.js», в котором будут храниться все функции JavaScript.
$(window).scroll(function() {
if ($(document).scrollTop() > 600 && $("#myModal").attr("displayed") === "false") {
$('#myModal').modal('show');
$("#myModal").attr("displayed", "true");
}
});
Теперь для подключения JS-файла к проекту добавьте файл в «header.html». Файл должен вызываться так же, как и в случае с изображениями.
Не забудьте и о {% load static %} в верхней части страницы для корректной работы тегов.
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="{% static 'js/script.js' %}"></script>
</head>
<body>
<div class="container">
<h1 class="text-center">Привет Django</h1>
<img class="w-50 mx-auto d-block" src="{% static 'img/example.png' %}" alt="photo">
</div>
</body>
</html>
Также можно подключить CSS-файлы. Для этого создайте папку «css» внутри «static». Вы также можете использовать элемент <style> и вложить все стили туда.

Но в случае создания отдельных классовых атрибутов, которые затем используются в разных шаблонах, лучше создавать отдельные папки и файлы.
Создайте файл «stylesheet.css» в static > css. Там будут храниться все ваши стили.
.custom {
color:#007bff;
background:#000000;
font-size:20px;
}
Для подключения собственных стилей к проекту, добавьте HTML-элемент <link> в «header.html». Файл вызывается так же, как изображения и JS-файлы.
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/stylesheet.css'%}" type="text/css">
<script src="{% static 'js/script.js' %}"></script>
</head>
<body>
<div class="container custom">
<h1 class="text-center">Привет Django</h1>
<img class="w-50 mx-auto d-block" src="{% static 'img/example.png' %}" alt="photo">
</div>
</body>
</html>
И снова не забудьте о {% load static %} в верхней части страницы. Если не добавить эту строку, то будет ошибка.
Если класс custom добавить к container вы увидите изменения:

Если вы получили ошибку «Invalid block tag on line 8: ‘static’, expected ‘endblock’. Did you forget to register or load this tag?», то вы наверняка забыли добавить тег загрузки статических файлов в верхней части страницы: {% load static %} до вызова самого изображения.
{% load static %}
<img src="{% static 'img/photo.jpg' %}">
Такая ошибка «Could not parse the remainder: ‘/photo.jpg’ from ‘img/photo.jpg’» значит, что вы забыли добавить кавычки вокруг ссылки на изображения. В этом случае нужно использовать две пары кавычек: одни для всего содержимого src, а вторые — для ссылки на изображение.
<img src="{% static img/photo.jpg %}"> {# ошибка #}
<img src="{% static 'img/photo.jpg' %}"> {# верно #}
«Invalid block tag on line 9: »static/img/photo.jpg», expected ‘endblock’. Did you forget to register or load this tag?» говорит о том, что вы случайно добавили тег static в URL.
<img src="{% 'static/img/photo.jpg' %}"> {# ошибка #}
<img src="{% static 'img/photo.jpg' %}"> {# верно #}
Если страница не загружается, и появляется следующая ошибка: «django.core.exceptions.ImproperlyConfigured: You’re using the staticfiles app without having set the required STATIC_URL setting», то это указывает на то, что вы забыли указать статический URL в файле настроек. Его нужно задать в settings.py и сохранить документ.
STATIC_URL = '/static/'
Наконец, если вы не получаете ошибку, но изображение не отображается, то убедитесь, что вы правильно используете тег.
<img src="{ static img/photo.jpg }"> {# ошибка #}
<img src="{% static 'img/photo.jpg' %}"> {# верно #}
Архив с проектом contact.rar
Если вы уже знаете как создавать джанго-приложение, этот шаг можно пропустить. Создайте проект —
config, приложение —sendemailи виртуальное окружениеcontact.
Первый шаг — создание отдельной папки для проекта. Создайте ее в ручную или с помощью терминала. В командной строке (Mаc или Linux, для Windows некоторые команды отличаются) выполните следующие команды, чтобы перейти в нужную директорию и создать новую папку «contact».
$ cd ~/ПУТЬ_К_ПАПКЕ
$ mkdir contact && cd contact
Теперь можно установить Django и активировать виртуальную среду.
Примечание:
Естьpipenvу вас не установлен, начните сpip install pipenv
$ pipenv install django==3.0.5
$ pipenv shell
Далее создадим новый проект Django под названием config в приложении sendemail:
(contact) $ django-admin startproject config .
(contact) $ python manage.py startapp sendemail
Чтобы убедиться, что все работает, запустим migrate и runserver.
(contact) $ python manage.py migrate
(contact) $ python manage.py runserver
Если теперь открыть ссылку 127.0.0.1:8000, то должно появиться приблизительно следующее:

Созданное приложение теперь нужно явно добавить в проект Django. В файле settings.py следует записать sendemail в разделе INSTALLED_APPS.
# config/settings.py
INSTALLED_APPS [
'sendemail.apps.SendemailConfig', # новая строка
...
]
Затем в этом же файле нужно создать DEFAULT_FROM_EMAIL — ваша@почта.com и RECIPIENTS_EMAIL — список почт получателей по уполчанию. Также стоит обновить EMAIL_BACKEND, указав, какой бэкенд email используется — в данном случае это console. Это нужно, чтобы почта выводилась в командной строке.
# config/settings.py
RECIPIENTS_EMAIL = ['manager@mysite.com'] # замените на свою почту
DEFAULT_FROM_EMAIL = 'admin@mysite.com' # замените на свою почту
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
После добавления приложения в проект Django нужно обновить корневой файл config/urls.py, добавив include в верхней строке, а также новый urlpattern в приложении:
# config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('sendemail.urls')),
]
Дальше создайте файл sendemail/urls.py в приложении вручную или:
touch sendemail/urls.py
Теперь — этот код. Благодаря ему основная контактная форма будет доступна по ссылке email/, а удачная отправка — перенаправлять на success/.
# sendemail/urls.py
from django.contrib import admin
from django.urls import path
from .views import contact_view, success_view
urlpatterns = [
path('contact/', contact_view, name='contact'),
path('success/', success_view, name='success'),
]
Внутри приложения sendemail теперь нужно создать новый файл forms.py.
touch sendemail/forms.py
Он будет содержать все поля для самой формы. Потребуются три: from_email, subject и message.
# sendemail/forms.py
from django import forms
class ContactForm(forms.Form):
from_email = forms.EmailField(label='Email', required=True)
subject = forms.CharField(label='Тема', required=True)
message = forms.CharField(label='Сообщение', widget=forms.Textarea, required=True)
Будем использовать встроенный в Django Forms API для быстрого создания трех полей.
Создадим представление, которое будет выполнять основную работу для контактной формы. Обновим существующий файл sendemail/views.py:
from django.shortcuts import render
from django.core.mail import send_mail, BadHeaderError
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from .forms import ContactForm
from config.settings import RECIPIENTS_EMAIL, DEFAULT_FROM_EMAIL
def contact_view(request):
# если метод GET, вернем форму
if request.method == 'GET':
form = ContactForm()
elif request.method == 'POST':
# если метод POST, проверим форму и отправим письмо
form = ContactForm(request.POST)
if form.is_valid():
subject = form.cleaned_data['subject']
from_email = form.cleaned_data['from_email']
message = form.cleaned_data['message']
try:
send_mail(f'{subject} от {from_email}', message,
DEFAULT_FROM_EMAIL, RECIPIENTS_EMAIL)
except BadHeaderError:
return HttpResponse('Ошибка в теме письма.')
return redirect('success')
else:
return HttpResponse('Неверный запрос.')
return render(request, "email.html", {'form': form})
def success_view(request):
return HttpResponse('Приняли! Спасибо за вашу заявку.')
Сначала импортируем send_email и BadHeaderError для безопасности. После этого добавим ссылку на класс ContactForm, который был создан в файле forms.py.
В качестве финального шага нужно создать шаблоны для сообщения и страницы с информацией об успешной отправке. Для этого используем новую папку templates в директории с проектом.
(contact) $ mkdir templates
(contact) $ touch templates/email.html
Теперь обновим файл settings.py, чтобы Django знал, где искать папку с шаблонами. Обновим настройку DIRS в TEMPLATES.
# config/settings.py
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
},
]
Далее обновим сами файлы шаблонов, используя следующий код:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Пример отправки сообщений</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<h1 class="text-center">Оставьте заявку</h1>
<div class="container text-center">
<div id="form_container">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="form-actions form-group ">
<button type="submit" class="form-control btn btn-success">Отправить</button>
</div>
</form>
</div>
</div>
</body>
</html>
Убедитесь, что сервер запущен с помощью команды python manage.py runserver и откройте http://127.0.0.1:8000/contact в браузере. Заполните форму и нажмите кнопку Отправить.

Программа перенаправит на страницу http://127.0.0.1:8000/success, если сообщение отправилось.

А в консоли должно отобразиться, что сообщение отправилось:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject: =?utf-8?b?0J/RgNC40LLQtdGC?=
From: admin@mysite.com
To: manager@mysite.com
Date: Sat, 03 Oct 2020 11:10:29 -0000
Message-ID: <160172342967.9128.15896163902795968694@DESKTOP-4G7VN>
Для реальной отправки сообщений нужно настроить сервис: SendGrid, mailgun или SES. К счастью, в Django их очень легко использовать.
Для использования SendGrid нужно создать бесплатный аккаунт и выбрать вариант SMTP. Его легче настраивать при работе с Web API.
Необходимо выполнить верификацию отправителя. Инструкции доступны по этой ссылке. Если раньше можно было отправлять сообщения с бесплатных адресов (gmail.com или yahoo.com), но теперь это не будет работать из-за протокола email-аутентификации DMARC. Поэтому для реальной отправки сообщений нужно почту на своем домене, владение которой придется подтвердить.

После добавления отправителя на странице https://app.sendgrid.com/settings/sender_auth/senders/new, подтвердите почту и переходите к настройке Web API https://app.sendgrid.com/guide/integrate/langs/python.
Следующее окно требует указать название для API-ключа. После этого нужно нажать на Create Key.

Следуйте инструкциям. На момент написания нужно установить библиотеку sendgrid и отправить тестовое письмо. Можете воспользоваться этим файлом sg_verify.py. Пройдя все этапы вы увидите сообщение об успешном подключении:

Дальше обновим файл settings.py, поменяв значение console на smtp в переменной EMAIL_BACKEND. Также добавим еще несколько полей. А также EMAIL_HOST_PASSWORD с ключем аккаунта SendGrid.
# config/settings.py
# почты для получения писем
RECIPIENTS_EMAIL = ['manager@mysite.com']
# почта отправителя по умолчанию, та что верифицирована
DEFAULT_FROM_EMAIL = 'admin@myiyte.com'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_HOST_USER = 'apikey'
# ваш уникальный апи-ключ с сайта sendgrid
EMAIL_HOST_PASSWORD = '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'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
Вернемся на http://127.0.0.1:8000/contact/, заполним и отправим форму еще раз. Работает!

После всех этих этапов у вас будет рабочее приложение с функцией отправки реальных сообщений из Django. Часто это нужно при регистрации пользователей, сбросе пароля, быстрых ответов и так далее.
]]>Django предлагает класс SearchQuery для перевода запросов в объект поискового запроса. По умолчанию запросы проходят через алгоритмы сокращения, что позволяет лучше находить совпадения. Также можно отсортировать результаты по релевантности. PostgreSQL предоставляет функцию ранжирования, которая сортирует результаты на основе того, как часто текст запроса появляется и как близко они расположены относительно друг друга. Отредактируйте файл views.py приложения blog и добавьте следующие импорты:
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank
Затем взгляните на эти строки:
results = Post.objects.annotate(
search=SearchVector('title', 'body'),
).filter(search=query)
Замените их этими:
search_vector = SearchVector('title', 'body') search_query = SearchQuery(query)
results = Post.objects.annotate(
search=search_vector,
rank=SearchRank(search_vector, search_query)
).filter(search=search_query).order_by('-rank')
В этом коде создается объект SeatchQuery, после этого результаты фильтруются, а SearchRank используется для их ранжирования. Можете открыть https://127.0.0.1:8000/blog/search/ в браузере и провести несколько тестов, чтобы увидеть, как работает сокращение и ранжирование.
Можно сделать так, чтобы у определенных направлений было больше веса при сортировке по релевантности. Например, такой код подойдет, чтобы ранжировать результаты в первую очередь по заголовкам, а не по телу. Отредактируйте предыдущие строки файла views.py приложения blog, чтобы он выглядел так:
search_vector = SearchVector('title', weight='A') + SearchVector('body', weight='B')
search_query = SearchQuery(query)
results = Post.objects.annotate(
rank=SearchRank(search_vector, search_query)
).filter(rank__gte=0.3).order_by('-rank')
В этом коде используется дополнительный «вес» для направлений поиска в полях title и body. Вес по умолчанию — D, C, B и A. Они соответствуют числам 0.1, 0.2, 0.4 и 1.0 соответственно. Применим вес 1.0 к полю title и 0.4 — body: совпадения в заголовке будут преобладать над таковыми в теле. Отфильтруем результаты для отображения только тех, у которых вес больше 0.3.
Еще один поисковый алгоритм — схожесть триграмм. Триграмма — это группа из трех символов. Можно измерить схожесть двух строк, посчитав количество общих триграмм. Этот подход часто используется для измерения схожести слов во многих языках.
Для использования триграмм в PostgreSQL нужно сперва установить pg_trgm. Используйте следующую команду, чтобы подключиться к базе данных:
psql blog
Затем эту, чтобы установить расширение pg_trgm:
CREATE EXTENSION pg_trgm;
Отредактируйте представление и измените его для поиска триграм. Отредактируйте файл views.py приложения blog и добавьте следующий импорт:
from django.contrib.postgres.search import TrigramSimilarity
Затем замените поисковый запрос Post на следующие строки:
results = Post.objects.annotate(
similarity=TrigramSimilarity('title', query),
).filter(similarity__gt=0.3).order_by('-similarity')
Откройте https://127.0.0.1:8000/blog/search/ в браузере и проверьте разные варианты поиска триграмм. Например, введите yango и получите результаты включающие слово django (есть в блоге есть статьи с таким словом).
Теперь у проекта мощный поисковый движок. Еще больше информации о полнотекстовом поиске можно найти здесь https://docs.djangoproject.com/en/2.0/ref/contrib/postgres/search/.
]]>Теперь нужно создать представление для того, чтобы пользователи могли осуществлять поиск по постам. В первую очередь нужна форма поиска. Отредактируйте файл forms.py приложения blog и добавьте следующую форму:
class SearchForm(forms.Form):
query = forms.CharField()
Будем использовать поле query, чтобы пользователи могли ввести поисковые запросы. Отредактируйте файл views.py и добавьте следующий код:
from django.contrib.postgres.search import SearchVector
from .forms import EmailPostForm, CommentForm, SearchForm
def post_search(request):
form = SearchForm()
query = None
results = []
if 'query' in request.GET:
form = SearchForm(request.GET)
if form.is_valid():
query = form.cleaned_data['query']
results = Post.objects.annotate(
search=SearchVector('title', 'body'),
).filter(search=query)
return render(request,
'blog/post/search.html',
{'form': form,
'query': query,
'results': results})
В этом представлении в первую очередь создается экземпляр формы SearchForm. Она будет приниматься с помощью метода GET, так, чтобы итоговый URL включал параметр query. Чтобы проверить, принята ли форма, проверяем параметр query в словаре request.GET. Когда форма принимается, создаем ее экземпляр с принятыми данными GET и верифицируем. Если она проходит проверку, проводим поиск с помощью SearchVector по полям title и body.
Представление поиска готово. Нужно создать шаблон для отображения формы и результатов поиска. Создайте файл в папке с шаблонами /blog/post/, назовите его search.html и добавьте следующий код:
{% extends "blog/base.html" %}
{% block title %}Search{% endblock %}
{% block content %}
{% if query %}
<h1>Posts containing "{{ query }}"</h1>
<h3>
{% with results.count as total_results %}
Found {{ total_results }} result{{ total_results|pluralize }}
{% endwith %}
</h3>
{% for post in results %}
<h4><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h4>
{{ post.body|truncatewords:5 }}
{% empty %}
<p>There are no results for your query.</p>
{% endfor %}
<p><a href="{% url "blog:post_search" %}">Search again</a></p>
{% else %}
<h1>Search for posts</h1>
<form action="." method="get">
{{ form.as_p }}
<input type="submit" value="Search">
</form>
{% endif %}
{% endblock %}
Как и в представлении поиска, узнать, была ли форма проверена, можно по присутствию параметра query. Перед проверкой отображаем форму и кнопку подтверждения. После проверки, отображаем проведенный поиск и общее количество результатов, а также список постов.
Наконец, отредактируйте файл urls.py приложения blog и добавьте следующий шаблон URL:
path('search/', views.post_search, name='post_search'),
Теперь откройте https://127.0.0.1:8000/blog/search/ в браузере. Вы увидите форму. Введите запрос и нажмите на кнопку «Search».
Теперь у блога есть базовая поисковая система.
]]>Добавим поисковые возможности в блог. Django ORM позволяет проводить базовые операции по поиску совпадений с помощью, например, фильтра contains (или его версии, учитывающей регистр, icontains). Следующий запрос можно использовать для поиска постов, содержащих слово framework в теле:
from blog.models import Post Post.objects.filter(body__contains='framework')
Но если планируется осуществлять сложный поиск с получением результатов, учитывающих схожесть или разный вес у полей, потребуется полнотекстовый поисковый движок.
Django предлагает мощный поиск, созданный на основе полнотекстового поиска из PostgreSQL. Модуль django.contrib.postgres включает функции, которые есть в PostgreSQL, но которых лишены другие базы данных, поддерживаемые Django. Больше об этом поиске можно узнать здесь: https://www.postgresql.org/docs/10/static/textsearch.html.
Хотя Django и способен работать с любой базой данных, он включает модуль, поддерживающий широкий набор возможностей из PostgreSQL, которых нет в других базах данных, поддерживаемых Django.
Отредактируйте файл settings.py проекта и добавьте в пункт INSTALLED_APP строку django.contrib.postgres:
INSTALLED_APPS = [
# ...
'django.contrib.postgres',
]
Теперь с помощью search можно проводить поиск:
from blog.models import Post Post.objects.filter(body__search='django')
Этот запрос использует PostgreSQL для создания направления поиска поля body и поискового запроса «django». Результаты основаны на сопоставлении направления и запроса.
Иногда может потребоваться искать в нескольких полях. В этом случае нужно определить SearchVector. Построим вектор, который позволит искать в полях title и body модели Post:
from django.contrib.postgres.search import SearchVector
from blog.models import Post
Post.objects.annotate(
search=SearchVector('title', 'body'),
).filter(search='django')
С помощью annotate и SearchVector для обоих полей получается функциональность, которая будет искать совпадения в заголовке и теле постов.
]]>Полнотекстовый поиск — это интенсивный процесс. Если он будет осуществляться в более чем нескольких сотнях строк, нужно определить функциональный индекс, который совпадает с используемым поисковым направлением. Django предлагает поле
SearchVectorFieldдля моделей. Подробно об этом можно почитать здесь: https://docs.djangoproject.com/en/2.0/ref/contrib/postgres/search/#perfomance.
Сейчас для проекта используется SQLite. Это необходимо для процесса разработки. Но для развертывания потребуется более мощная база данных: PostgreSQL, MySQL или Oracle. Сделаем выбор в пользу первой, чтобы получить ее функции полнотекстового поиска.
Если вы используете Linux, установите компоненты, необходимые для работы PostgreSQL следующим образом:
sudo apt-get install libpq-dev python-dev
Затем установите саму базу данных:
sudo apt-get install postgresql postgresql-contrib
Если у вас macOS или Windows, загрузите PostgreSQL с сайта https://www.postgresql.org/download/ и установите.
Также потребуется адаптер PostgreSQL под названием Psycopg2 для Python. Эта команда установит его:
pip install psycopg2==2.7.7
Создадим базу данных PostgreSQL. Откройте консоль и введите следующие команды:
su postgres
createuser -dP blog
Дальше нужно будет ввести пароль для нового пользователя. Введите его и создайте базу данных blog, сделав ее владельцем того пользователя, что только что был создан с помощью команды:
createdb -E utf8 -U blog blog
Отредактируйте файл settings.py и измените настройку DATABASES, чтобы она выглядела вот так:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'blog',
'USER': 'blog',
'PASSWORD': '*****', # пароль
}
}
Замените данные выше названием новой базы данных и данными нового пользователя. Новая база данных пустая. Запустите следующую команду, чтобы применить все миграции базы данных:
python manage.py migrate
Наконец, создайте нового суперпользователя:
python manage.py createsuperuser
Можете запустить сервер разработки и перейти на административный сайт https://127.0.0.1:8000/admin/ с помощью новых данных.
Поскольку база данных поменялась, постов здесь нет. Заполните ее с помощью базовых постов так, чтобы можно было осуществлять поиск.
]]>В Django есть встроенный фреймворк для синдикации ленты, которую можно использовать для того, чтобы динамически генерировать ленты RSS или Atom по принципу создания карты сайта. Веб-ленты — это формат данных (обычно XML), который предлагает пользователям часто обновляемый контент. Они могут подписаться на ленту с помощью агрегатора — софта, используемого для чтения лент и получения уведомления о новом контенте.
Создайте новый файл в папке приложения blog и назовите его feeds.py. Добавьте следующие строки:
from django.contrib.syndication.views import Feed
from django.template.defaultfilters import truncatewords
from .models import Post
class LatestPostsFeed(Feed):
title = 'My blog'
link = '/blog/'
description = 'New posts of my blog.'
def items(self):
return Post.published.all()[:5]
def item_title(self, item):
return item.title
def item_description(self, item):
return truncatewords(item.body, 30)
Во-первых, нужно подкласс класса Feed фреймворка синдикации. Атрибуты title, link и description связаны с соответствующими элементами RSS: <title>, <link>, <description>.
Метод items() получает объекты, которые нужно включить в ленту. Здесь их будет 5. Методы item_title() и item_description() получают каждый объект, который возвращает items() и возвращают заголовок и описание соответственно. Встроенный фильтр truncatewords используется для создания описания из первых 30 слов.
Теперь отредактируйте файл blog/urls.py, импортируйте созданный LatestPostsFeed и создайте экземпляр ленты в шаблоне URL:
from .feeds import LatestPostsFeed
urlpatterns = [
# ...
path('feed/', LatestPostsFeed(), name='post_feed'),
]
Откройте https://127.0.0.1:8000/blog/feed/ в браузере. Должна появиться лента RSS из последних пяти постов.
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:atom="https://www.w3.org/2005/Atom">
<channel>
<title>My blog</title>
<link>https://localhost:8000/blog/</link>
<description>New posts of my blog.</description>
<atom:link href="https://localhost:8000/blog/feed/" rel="self"></atom:link>
<language>en-us</language>
<lastBuildDate>Sun, 02 Feb 2020 14:59:07 +0000</lastBuildDate>
<item>
<title>Markdown post</title>
<link>https://localhost:8000/blog/2020/2/2/markdown-post/</link>
<description>This is a post formatted with markdown
--------------------------------------
*This is emphasized* and **this is more emphasized**.
Here is a list: * One * Two * Three And a [link to ...
</description>
<guid>https://localhost:8000/blog/2020/2/2/markdown-post/</guid>
</item>
<item>
<title>The AI Community Needs to Take Responsibility for Its Technology and Its Actions</title>
<link>https://localhost:8000/blog/2019/12/14/ai-community-needs-take-responsibility-its-technology-and-its-actions/</link>
<description>At the opening keynote of a prominent AI research conference,
Celeste Kidd, a cognitive psychologist, challenged the audience
to think critically about the future they want to build.
</description>
<guid>https://localhost:8000/blog/2019/12/14/ai-community-needs-take-responsibility-its-technology-and-its-actions/</guid>
</item>
</channel>
</rss>
Если открыть эту же ссылку в RSS-клиенте, лента будет отображаться в человекочитаемом виде с интерфейсом.
Финальный шаг — добавить ссылку для подписки в ленту в сайдбаре блога. Откройте шаблон blog/base.html и добавьте следующую строку под общим количеством постов в блоке div сайдбара:
<p><a href="{% url "blog:post_feed" %}">Subscribe to my RSS feed</a></p>
Теперь откройте https://127.0.0.1:8000/blog/ в браузере и посмотрите на сайдбар. Ссылка должна вести на ленту блога:

В Django есть фреймворк для карты сайта, который позволяет генерировать ее динамически. Карта сайта — это файл в формате XML, который сообщает поисковым системам о том, какие страницы имеются на сайте, их релевантность и частоту обновления. С ее помощью можно помочь поисковым роботом индексировать весь контент.
Этот фреймворк опирается на django.contrib.sites, который позволяет делать объектами отдельные сайты целого проекта. Это очень удобно, если нужно запустить несколько сайтов с помощью одного проекта Django. Для установки фреймворка нужно активировать приложения sites и sitemap в проекте. Отредактируйте файл settings.py проекта и добавьте в INSTALLED_APPS пункты django.contrib.sites и django.contrib.sitemaps. Также определите новую настройку с SITE_ID:
SITE_ID = 1
# Application definition
INSTALLED_APPS = [
# ...
'django.contrib.sites',
'django.contrib.sitemaps',
]
Теперь запустите эту команду для создания таблиц приложения сайта Django в базе данных:
python manage.py migrate
Вывод должен быть следующим:
Applying sites.0001_initial... OK
Applying sites.0002_alter_domain_unique... OK
Приложение sites теперь синхронизировано с базой данных. Создайте новый файл в папке приложения blog и назовите его sitemap.py. Откройте файл и добавьте следующий код:
from django.contrib.sitemaps import Sitemap
from .models import Post
class PostSitemap(Sitemap):
changefreq = 'weekly'
priority = 0.9
def items(self):
return Post.published.all()
def lastmod(self, obj):
return obj.updated
Создадим собственную карту сайта, наследуя класс Sitemap модуля sitemaps. Атрибуты changefreq и priotiy указывают на частоту изменения страниц с постами и их релевантность на сайте (максимальное значение — 1). Метод items() возвращает QuerySet объектов для включения в базу данных. По умолчанию Django вызывает метод get_absolute_url() для каждого объекта для получения URL. Вспомните о созданном методе для получения канонических URL для постов. Если необходимо определить URL для каждого объекта, можно добавить метод location к классу карты сайта. Метод lastmod получает каждый объект, который вернул метод items() и в свою очередь возвращает дату, когда тот последний раз изменился. И changefreq, и priority могут быть как методами, так и атрибутами. Полностью фреймворк карты сайта описан в документации Django https://docs.djangoproject.com/en/2.0/ref/contrib/sitemaps/.
Осталось лишь добавить URL карты сайта. Отредактируйте главный файл urls.py проекта и добавьте ссылку.
# mysite/urls.py
from django.urls import path, include
from django.contrib import admin
from django.contrib.sitemaps.views import sitemap
from blog.sitemap import PostSitemap
sitemaps = {
'posts': PostSitemap,
}
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls', namespace='blog')),
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap')
]
В этом коде были включены необходимые импорты, а также определен словарь карт сайта. Также определен шаблон URL, который ведет на sitemap.xml и использует представление карты сайта. Запустите сервер разработки и введите в браузере https://127.0.0.1:8000/sitemap.xml. Должен появиться следующий XML-вывод:
<urlset>
<url>
<loc>https://example.com/blog/2020/2/2/markdown-post/</loc>
<lastmod>2020-02-02</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>
https://example.com/blog/2019/12/14/ai-community-needs-take-responsibility-its-technology-and-its-actions/
</loc>
<lastmod>2019-12-28</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
...
</urlset>
URL для каждого поста создается с помощью метода get_absolute_url().
Атрибут lastmod соответствует полю updated поста, как было определено в карте сайта. А атрибуты changefreq и priority берутся из класса PostSitemap. Домен, используемый для создания URL — example.com. Он исходит из объекта Site, который хранится в базе данных. Этот объект по умолчанию создается при синхронизации структуры сайта с базой данных. Откройте https://127.0.0.1:8000/admin/sites/site/ в браузере. Отобразится следующее:

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

URL, отображаемые в ленте, теперь будут строиться с помощью имени хоста. При развертывании сайта нужно будет использовать доменное имя.
]]>В Django есть много разных встроенных фильтров, которые позволяют модифицировать переменные в шаблонах. Это функции Python, принимающие один или два параметра: значение переменной, к которой она будет применена и опциональный аргумент. Они возвращают значение, которое может быть отображено или стать еще одним фильтром. Фильтр выглядит следующим образом {{ variable|my_filter }}. А фильтры с аргументом так — {{ variable|my_filter:"foo" }}. К переменной можно применять сколько угодно фильтров, например, {{ variable|filter1|filter2 }}. Каждый из них будет применен в выводу, который сгенерирует предыдущий.
Создадим собственный фильтр, который позволит использовать разметку Markdown в постах блога и затем конвертировать содержимое в код HTML в шаблонах. Markdown — это синтаксис для форматирования обычного текста, который затем превращается в HTML. С основами этого формата можно ознакомиться по ссылке https://daringfireball.net/project/markdown/basics.
Сперва установите модуль Markdown в Python с помощью pip:
pip install Markdown==2.6.11
Затем отредактируйте файл blog_tags.py и добавьте следующий код:
from django.utils.safestring import mark_safe
import markdown
@register.filter(name='markdown')
def markdown_format(text):
return mark_safe(markdown.markdown(text))
Фильтры шаблона регистрируются так же, как и шаблонные теги. Чтобы избежать конфликта имени функции с модулем markdown первую нужно назвать markdown_format, а фильтр для шаблонов — markdown: {{ variable|markdown }}. Django исключает HTML-код, сгенерированный фильтрами. Функция mark_sage из Django используется, чтобы отметить, какой HTML-код нужно отрендерить стандартным путем. По умолчанию, Django будет исключать любой HTML-код перед выводом. Единственные исключения — отмеченные переменные. Такое поведение предотвращает возможный вывод потенциально опасного кода и позволяет создавать исключения для возврата безопасного HTML-кода.
Теперь загрузите шаблонные теги в шаблоны со списком постов и страницей поста. Добавьте следующую строку в верхней части шаблонов blog/post/list.html и blog/post/detail.html после тега {% extends %}:
{% load blog_tags %}
В шаблоне post/detail.html взгляните на следующую строку:
{{ post.body|linebreaks }}
Замените ее на эту:
{{ post.body|markdown }}
Затем в файле post/list.html замените эту строку:
{{ post.body|truncatewords:30|linebreaks }}
На эту:
{{ post.body|markdown|truncatewords_html:30 }}
Фильтр truncatewords_html обрезает строку после определенного количества слов, избегая незакрытых HTML-тегов.
Теперь откройте https://127.0.0.1:8000/admin/blog/post/add в браузере и добавьте пост со следующим тегом.
This is a post formatted with markdown
--------------------------------------
*This is emphasized* and **this is more emphasized**.
Here is a list:
* One
* Two
* Three
And a [link to the Django website](https://www.djangoproject.com/)
Откройте браузер и посмотрите, как он отрендерился. Должно выглядеть вот так:

Как можно видеть на скриншоте, собственные фильтры шаблонов очень удобны для форматирования. Больше об этой теме здесь: https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/#writing-custom-template-filters.
]]>Django предлагает множество встроенных тегов, таких как {% if %} или {% block %}. Некоторые из них были использованы в ранее рассмотренных шаблонах. С полным списком можно ознакомиться по ссылке: https://docs.djangoproject.com/en/2.0/ref/templates/builtins/.
Тем не менее Django дает возможность создавать собственные шаблонные теги для осуществления любых действий. Они пригодятся в тех случаях, когда требуемая функциональность не найдется среди базового набора Django.
Django предлагает следующие вспомогательные функции, которые позволят с легкостью создавать собственные шаблонные теги:
simple_tag: обрабатывает данные и возвращает строку.inclusion_tag: обрабатывает данные и возвращает отрисованный шаблон.Шаблонные теги могут использоваться только внутри приложений Django.
В папке приложения blog создайте новую папку, назовите ее templatetags и добавьте пустой файл __init__.py. Создайте еще один файл blog_tags.py. Файловая структура приложения блога должна быть следующей:
blog/
__init__.py
models.py
...
templatetags/
__init__.py
blog_tags.py
Важно выбирать правильные названия, поскольку они будут использоваться для загрузки тегов в шаблонах.
Начнем с создания простого тега, который будет получать общее количество опубликованных в блоге постов. Отредактируйте файл blog_tags.py и добавьте следующий код:
from django import template
from ..models import Post
register = template.Library()
@register.simple_tag
def total_posts():
return Post.published.count()
Этот тег возвращает количество опубликованных постов. Каждый модуль с шаблонными тегами, чтобы являться действительной библиотекой тегов, должен включать переменную register. Эта переменная — экземпляр template.Library. Она используется для регистрации собственных шаблонных тегов и фильтров. Дальше нужно определить тег total_posts с помощью функции Python и использовать декоратор @register.simple_tag для регистрации его в качестве простого тега. Django использует имя функции в качестве имени тега. Если хочется выбрать другое имя, это можно сделать с помощью атрибута name, например, @register.simple_tag(name='my_tag').
После добавления нового модуля шаблонных тегов нужно перезагрузить сервер разработки Django, чтобы изменения вступили в силу, а новые теги и фильтры можно было использовать.
Перед тем как начинать использовать собственные шаблонные теги, их нужно сделать доступными с помощью тега {% load %}. Как упоминалось ранее, необходимо использовать имя модуля Python, включающего шаблонные теги и фильтры. Откройте шаблон и добавьте сверху {% load blog_tags %} для загрузки модуля тегов шаблона. Затем используйте созданный тег для отображения общего количества опубликованных постов. Просто добавьте тег {% total_posts %} в шаблон. Он будет выглядеть вот так:
{% load blog_tags %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<link href="{% static 'css/blog.css' %}" rel="stylesheet">
</head>
<body>
<div id="content">
{% block content %}
{% endblock %}
</div>
<div id="sidebar">
<h2>My blog</h2>
<p>This is my blog. I've written {% total_posts %} posts so far.</p>
</div>
</body>
</html>
Нужно перезагрузить сервер, чтобы проект отслеживал все новые файлы. Остановите его комбинацией Ctrl + C и запустите снова с помощью:
python manage.py runserver
Откройте https://127.0.0.1:8000/blog/ в браузере. Отобразится общее количество опубликованных постов:

Преимущество собственных тегов в том, что с их помощью можно обрабатывать любые данные и добавлять в любой шаблон вне зависимости от представления. Можно использовать QuerySet или обрабатывать любые данные для отображения результатов в шаблонах.
Теперь создадим еще один тег для отображения последних постов в сайдбаре. Будем использовать тег inclusion (включения). С его помощью можно рендерить шаблон с контекстными переменными, которые шаблонный тег возвращает. Отредактируйте файл blog_tags.py и добавьте следующий код:
@register.inclusion_tag('blog/post/latest_posts.html')
def show_latest_posts(count=5):
latest_posts = Post.published.order_by('-publish')[:count]
return {'latest_posts': latest_posts}
В этом коде тег шаблона был зарегистрирован с помощью @register.inclusion_tag. Также был определен шаблон, который тег вернет вместе со значениями с помощью blog/post/latest_posts.html. Тег будет принимать опциональный параметр count со значением по умолчанию 5. Он позволяет определять количество постов, которые необходимо выводить. Используем переменную для ограничения результатов запроса Post.published.order_by('-publish')[:count]. Обратите внимание, что функция возвращает словарь, а не одно значение. Включенный тег должен возвращать словарь значений, который используется как контекст для рендеринга определенного шаблона. Созданный тег шаблона позволяет определять количество постов для отображения по желанию: {% show_latest_posts 3 %}.
Теперь нужно создать новый файл шаблона в папке blog/post/ и назвать его latest_posts.html. Добавьте туда следующий код:
<ul>
{% for post in latest_posts %}
<li>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
Этот код отображает неупорядоченный список постов с помощью переменной latest_posts, которую возвращает тег шаблона. Теперь нужно отредактировать шаблон blog/base.html и добавить в него тег шаблона для отображения последних трех постов. Код сайдбара должен выглядеть следующим образом:
<div id="sidebar">
<h2>My blog</h2>
<p>This is my blog. I've written {% total_posts %} posts so far.</p>
<h3>Latest posts</h3>
{% show_latest_posts 3 %}
</div>
Тег шаблона вызывается, передавая количество постов для отображения, а шаблон отрисовывается в нужной позиции с указанным контекстом.
Вернитесь в браузер и обновите странице. Сайдбар будет выглядеть так:

Наконец, создадим просто тег шаблона, который сохраняет результат в переменной так, чтобы его можно было использовать в дальнейшем. Создадим тег для отображения наиболее комментируемых постов. Отредактируйте файл blog_tags.py и добавьте следующий импорт и шаблонный тег:
from django.db.models import Count
# ...
@register.simple_tag
def get_most_commented_posts(count=5):
return Post.published.annotate(
total_comments=Count('comments')
).order_by('-total_comments')[:count]
В нем с помощью функции annotate() создаем QuerySet, который агрегирует общее количество комментариев под постов. Сейчас используются функция агрегации Count для сохранения количества комментариев и поле total_comments каждого объекта Post. Отсортируем QuerySet по значению этого поля в убывающем порядке. Используем переменную count для ограничения общего количество возвращаемых объектов.
В дополнение к Count в Django есть такие функции агрегации, как Avg, Max, Min и Sum. О них можно почитать здесь https://docs.djangoproject.com/en/2.0/topics/db/aggregation/.
Отредактируйте шаблон blog/base.html и добавьте следующий код к элементу сайдбара <div> (после {% show_latest_posts 3 %}):
<h3>Most commented posts</h3>
{% get_most_commented_posts as most_commented_posts %}
<ul>
{% for post in most_commented_posts %}
<li>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
Сохраним результат в переменной с помощью аргумента as и имени переменной. Для тега шаблона используем {% get_most_commented_posts as most_commented_posts %} для сохранения результата шаблонного тега в новой переменной most_commented_posts. Затем отобразим посты в виде неотсортированного списка.
Откройте браузер и перезагрузите страницу, чтобы увидеть финальный результат:

Теперь вы знаете, как самостоятельно создавать шаблонные теги. Подробно о них можно почитать здесь https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/.
]]>Теперь, когда теги реализованы, с ними можно многое делать. С их помощью легко классифицировать посты. Материалы на схожие темы будут содержать общие теги. Создадим функциональность, которая будет отображать похожие посты, отталкиваясь от количества одинаковых тегов. Так, когда пользователь прочтет один материал, ему можно будет порекомендовать другой, связанный с ним тематически.
Для получения похожих постов нужно совершить следующие шаги:
Эти шаги превращаются в сложный QuerySet, который будет включать представление post_detail. Откройте файл views.py приложения блога и добавьте следующий импорт в верхней его части:
from django.db.models import Count
Это функция агрегации Count из ORM Django. Она позволяет осуществлять совокупный подсчет тегов. django.db.models включает следующие функции агрегации:
Avg: среднее значениеMax: максимальное значениеMin: минимальное значениеCount: подсчет объектовУзнать больше об агрегации можно здесь: https://docs.djangoproject.com/en/2.0/topics/db/aggregation/.
Добавьте следующие строки в представление post_detail перед функцией render() со следующим уровнем отступа:
# Список похожих постов
post_tags_ids = post.tags.values_list('id', flat=True)
similar_posts = Post.published.filter(tags__in=post_tags_ids) \
.exclude(id=post.id)
similar_posts = similar_posts.annotate(same_tags=Count('tags')) \
.order_by('-same_tags', '-publish')[:4]
Этот код выполняет следующее:
values_list() возвращает кортеж со значениями для заданных полей. Передаем flat=True, чтобы получить список в формате [1, 2, 3, ...].Count для генерации поля same_tags, которое содержит количество общих тегов.publish, чтобы отображать последние [по дате публикации] посты в числе первых, если у нескольких одинаковое количество общих тегов. Обрезаем результаты, чтобы получить только первые четыре поста.Добавьте объект similar_posts в словарь контекста для функции render():
return render(request,
'blog/post/detail.html',
{'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form,
'similar_posts': similar_posts})
Теперь нужно отредактировать шаблон blog/post/detail.html и добавить следующий код перед списком комментариев к посту:
<h2>Similar posts</h2>
{% for post in similar_posts %}
<p>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</p>
{% empty %}
There are no similar posts yet.
{% endfor %}
Теперь страница поста должна выглядеть так:

Теперь можно рекомендовать похожие посты пользователям. django-taggit также включает менеджер similar_objects(), который можно использовать для получения постов с общими тегами. Посмотреть на все менеджеры django-taggit можно здесь: https://django-taggit.readthedocs.io/en/latest/api.html.
Также можно добавить список тегов на страницу поста по принципу шаблона blog/post/list.html.
После создания системы комментариев пришло время реализовать теги для постов. Сделаем это с помощью интеграции стороннего приложения Django. Модуль django-taggit — это приложение, состоящее из модели Tag и менеджера для добавления тегов к любой модели. Вот его исходный код: https://github.com/jazzband/django-taggit.
Сперва нужно установить django-taggit с помощью pip, воспользовавшись следующей командой:
pip install django_taggit==0.22.2
Затем откройте файл settings.py проекта mysite и добавьте taggit к настройке INSTALLED_APPS:
INSTALLED_APPS = [
# ...
'blog.apps.BlogConfig',
'taggit',
]
Откройте файл models.py приложения blog и добавьте менеджер TaggableManager из django-taggit к модели Post с помощью следующего кода:
from taggit.managers import TaggableManager
class Post(models.Model):
# ...
tags = TaggableManager()
Менеджер tags позволяет добавлять, удалять и получать теги от объектов Post.
Используйте следующую команду для создания миграции для изменений модели:
python manage.py makemigrations blog
Должен появиться следующий вывод:
Migrations for 'blog':
blog\migrations\0003_post_tags.py
- Add field tags to post
Теперь запустите следующую команду для создания требуемых таблиц базы данных для моделей django-taggit и синхронизации изменений модели:
python manage.py migrate
Появится вывод, подтверждающий примененные миграции:
Applying taggit.0001_initial... OK
Applying taggit.0002_auto_20150616_2121... OK
Applying blog.0003_post_tags... OK
База данных теперь готова использовать модели django-taggit. Но сперва нужно разобраться, как работает менеджер tags. Откройте терминал с помощью команды python manage.py shell и введите следующий код. В первую очередь нужно получить один из постов (с ID 1):
>>> from blog.models import Post
>>> post = Post.objects.get(id=1)
Затем добавьте некоторые теги и попробуйте вернуть их, чтобы проверить, были ли они добавлены:
>>> post.tags.add('music', 'jazz', 'django')
>>> post.tags.all()
<QuerySet [<Tag: jazz>, <Tag: music>, <Tag: django>]>
Наконец, удалите их и проверьте список еще раз:
>>> post.tags.remove('django')
>>> post.tags.all()
<QuerySet [<Tag: jazz>, <Tag: music>]>
Это было легко, не так ли? Запустите команду python manage.py runserver для запуска сервера разработки и откройте https://127.0.0.1:8000/admin/taggit/tag в браузере. Отобразится административная страница со списком объектов Tag приложения taggit:

Перейдите на https://127.0.0.1:8000/admin/blog/post/ и кликните по посту, чтобы отредактировать его. Посты теперь включают поле Tags, с помощью которого можно легко их редактировать:

Отредактируем посты в блоге для отображения тегов. Откройте шаблон blog/post/list.html и добавьте следующий код HTML под названием поста:
<p class="tags">Tags: {{ post.tags.all|join:", " }}</p>
Фильтр шаблона join работает так же, как и метод строки join() для объединения элементов с выбранной строкой. Откройте https://127.0.0.1:8000/blog/ в браузере. Теперь под каждым названием поста будет отображаться список тегов:

Отредактируем представление post_list, чтобы пользователи могли посмотреть все посты, связанные с конкретным тегом. Откройте файл views.py приложения blog, импортируйте модель Tag из django-taggit и измените представление post_list, чтобы опционально фильтровать посты по тегу:
from taggit.models import Tag
def post_list(request, tag_slug=None):
object_list = Post.published.all()
tag = None
if tag_slug:
tag = get_object_or_404(Tag, slug=tag_slug)
object_list = object_list.filter(tags__in=[tag])
paginator = Paginator(object_list, 3) # 3 поста на каждой странице
# ...
Представление post_list работает следующим образом:
tag_slug со значением по умолчанию None. Он будет в URL.slug указан, то объект Tag можно получить через него с помощью get_object_or_404().Стоит напомнить, что QuerySet ленивы. Они будут выполнены только при итерации по списку постов при рендеринге шаблона.
Наконец, нужно изменить функцию render() в нижней части представления для передачи тега tag шаблону. В итоге представление будет выглядеть вот так:
def post_list(request, tag_slug=None):
object_list = Post.published.all()
tag = None
if tag_slug:
tag = get_object_or_404(Tag, slug=tag_slug)
object_list = object_list.filter(tags__in=[tag])
paginator = Paginator(object_list, 3) # 3 поста на каждой странице
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
# Если страница не является целым числом, поставим первую страницу
posts = paginator.page(1)
except EmptyPage:
# Если страница больше максимальной, доставить последнюю страницу результатов
posts = paginator.page(paginator.num_pages)
return render(request,
'blog/post/list.html',
{'page': page,
'posts': posts,
'tag': tag})
Откройте файл urls.py приложения blog, закомментируйте URL-шаблон PostListView, основанный на классе и раскомментируйте представление post_list:
path('', views.post_list, name='post_list'),
# path('', views.PostListView.as_view(), name='post_list'),
Добавьте следующий дополнительный URL-шаблон для перечисления постов по тегу:
path('tag/<slug:tag_slug>/',
views.post_list, name='post_list_by_tag'),
Оба шаблона указывают на одно представление, но называются они по-разному. Первое будет вызывать представление post_list без дополнительных параметров, а второй использует tag_slug. Здесь используется конвертер пути slug для сопоставления параметра в качестве строки в нижнем регистре, состоящей из символов ASCII, дефиса и нижнего подчеркивания.
Поскольку используется представление post_list, нужно отредактировать шаблон blog/post/list.html и изменить пагинацию так, чтобы она использовала объект posts:
{% include "../pagination.html" with page=posts %}
Добавьте следующие строки над циклом {% for %}:
{% if tag %}
<h2>Posts tagged with "{{ tag.name }}"</h2>
{% endif %}
Если пользователь будет заходить в блог, он увидит список постов. Если попробует отфильтровать материалы по конкретному тегу — тег, по которому проходит фильтрация. Измените способ отображения тегов:
<p class="tags">
Tags:
{% for tag in post.tags.all %}
<a href="{% url "blog:post_list_by_tag" tag.slug %}">
{{ tag.name }}
</a>
{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
Теперь переберите все теги поста, отображая кастомную ссылку в URL для фильтра постов по этому тегу. URL будет построен с помощью {% url "blog:post_list_by_tag" tag.slug %} с URL и slug в качестве параметров. Теги разделяются запятыми.
Откройте https://127.0.0.1:8000/blog/ в браузере и нажмите на ссылку тега. Появится список постов с этим тегом:

Функциональность для управления комментариями поста уже готова. Теперь нужно адаптировать шаблон post/detail.html, чтобы он делал следующие вещи:
Сначала нужно добавить все комментарии. Откройте шаблон post/detail.html и добавьте следующий код в блок content:
{% with comments.count as total_comments %}
<h2>
{{ total_comments }} comment{{ total_comments|pluralize }}
</h2>
{% endwith %}
Здесь в шаблоне используется ORM Django — она исполняет QuerySet comments.count(). Важно отметить, что язык шаблонов Django не использует скобки для вызова методов. Тег {% with %} позволяет присвоить значение новой переменной. Она будет доступна вплоть до тега {% endwith %}.
Тег шаблона
{% with %}полезен, поскольку позволяет избежать риска изменения базы данных или доступа к методам по несколько раз.
Фильтр шаблона pluralize используется для отображения суффикса множественного числа в слове comment в зависимости от значения total_comments. Фильтры принимают значение переменной, к которой их применяли, в качестве ввода и возвращают вычисленное значение. Фильтрам шаблона будет посвящена отдельная тема.
Фильтр pluralize возвращает строку с символом «s», если значение отличается от 1. Текст отрендерится как 0 comments, 1 comment или N comments. Django включает множество тегов и фильтров шаблона, с помощью которых можно отображать желаемую информацию.
Теперь включим список комментариев. Добавьте следующие строки в шаблон post/detail.html перед предыдущим кодом:
{% for comment in comments %}
<div class="comment">
<p class="info">
Comment {{ forloop.counter }} by {{ comment.name }}
{{ comment.created }}
</p>
{{ comment.body|linebreaks }}
</div>
{% empty %}
<p>There are no comments yet.</p>
{% endfor %}
Тег шаблона {% for %} используется, чтобы перебирать комментарии. Сообщение по умолчанию отображается в том случае, если список comments пустой. Оно говорит о том, что к посту комментарии не оставляли. Перечисляются они с помощью переменной {{ forloop.counter }}, которая содержит счетчик цикла в каждой итерации. Затем отображаются имя автора, дата и тело комментария.
Наконец, нужно отрендерить форму или отобразить сообщение об успехе, если сообщение было проверено. Добавьте следующие строки под предыдущим кодом:
{% if new_comment %}
<h2>Your comment has been added.</h2>
{% else %}
<h2>Add a new comment</h2>
<form action="." method="post">
{{ comment_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Add comment"></p>
</form>
{% endif %}
Он должен быть понятен: если объект new_comment существует, отображается сообщение об успехе. В противном случае рендерится элемент абзаца <p> для каждого поля с включенным CSRF-токеном, который обязателен для запросов POST. Откройте https://127.0.0.1:8000/blog/ в браузере и кликните на пост, чтоб открыть его. Появится следующее:

Добавьте несколько комментариев с помощью формы. Они должны появится под постом в хронологическом порядке:

Откройте https://127.0.0.1:8000/admin/blog/comment/ в браузере. Вы увидите админ-панель со списком созданных комментариев. Нажмите на один из них для редактирования, уберите галочку с Active и нажмите Save. Программа снова перенаправит на список комментариев, и колонка Active покажет неактивную иконку для следующего комментария. Он будет выглядеть как первое сообщение на следующем скриншоте.

Если вернуться на страницу поста, то будет видно, что удаленный комментарий не отображается и не учитывается в общем количестве. Благодаря полю active можно отключать неприемлемые сообщения и не показывать их в постах.
Все еще нужна форма, чтобы пользователи могли оставлять комментарии к записям. В Django есть два базовых класса для построения форм: Form и ModelForm. Первый уже использовался для того, чтобы пользователи могли делиться постами через электронную почту. Сейчас нужно использовать ModelForm, потому что форму необходимо создавать динамически на основе Comment. Отредактируйте файл forms.py приложения blog и добавьте следующие строки:
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body')
Для создания формы на основе модели нужно просто указать, какую модель взять за основу в классе формы Meta. Django исследует модель и строит форму динамически. Каждое поле модели имеет соответствующий тип поля формы по умолчанию. Способ, которым были определены поля модели, учитывается при проверке формы. По умолчанию Django создает поле формы для каждого поля модели. Но можно явно указать фреймворку, какие поля нужны в форме с помощью списка fields или определения того, какие поля нужно исключить с помощью списка полей exclude. Для формы CommentForm будут использоваться name, email и body, потому что это единственное, что нужно заполнять.
Для простоты представление поста будет использоваться для создания экземпляра формы и ее обработки. Отредактируйте файл views.py, добавьте импорты для модели Comment и формы CommentForm и отредактируйте представление post_detail, чтобы оно выглядело вот так:
from .models import Post, Comment
from .forms import EmailPostForm, CommentForm
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post,
status='published',
publish__year=year,
publish__month=month,
publish__day=day)
# Список активных комментариев к этой записи
comments = post.comments.filter(active=True)
new_comment = None
if request.method == 'POST':
# Комментарий был опубликован
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
# Создайте объект Comment, но пока не сохраняйте в базу данных
new_comment = comment_form.save(commit=False)
# Назначить текущий пост комментарию
new_comment.post = post
# Сохранить комментарий в базе данных
new_comment.save()
else:
comment_form = CommentForm()
return render(request,
'blog/post/detail.html',
{'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form})
Разберем, что есть в представлении. Представление post_detail используется для отображения поста и комментариев. С помощью QuerySet можно получить все активные комментарии:
comments = post.comments.filter(active=True)
Начинается этот QuerySet с объекта post. Менеджер для связанных объектов, определенный в comments, используется при помощи атрибута related_name отношения в модели Comment.
То же представление используется для того, чтобы пользователи могли оставлять комментарии. Переменная new_comment инициализируется при передаче ей значения None. Она создается при создании комментария. Экземпляр формы создается с помощью comment_form = CommentForm(), если представление вызывается посредством запроса GET. Если он делается через POST, форма экземпляра создается с помощью отправленных данных и проверяется через метод is_valid(). Если форма неверна, рендерится шаблон с ошибками проверки. Если правильная — выполняются следующие действия:
Comment посредством вызова метода формы save() и присваивания его переменной new_comment следующим образом:
new_comment = comment_form.save(commit=False)
Метод save() создает экземпляр модели, к которой привязана форма и сохраняет ее в базу данных. Если ее вызвать с помощью commit=False, то экземпляр будет создан, но сохранение в базу данных не состоится. Это удобно, когда нужно изменить объект перед сохранением. А это следующий этап.
Метод save() доступен для ModelForm, но не для экземпляров Form, потому что они не привязаны ни к одной модели.
new_comment.post = post
Таким образом отмечается, что этот комментарий принадлежит этому посту.
save():
new_comment.save()
Представление готово отображать и обрабатывать новые комментарии.
]]>Пришло время создать систему комментариев для блога, с помощью которой пользователи смогут делиться мыслями о прочитанных материалах. Для этого понадобится проделать следующие шаги:
Сначала создадим модель для сохранения комментариев. Откройте файл models.py приложения blog и добавьте следующий код:
class Comment(models.Model):
post = models.ForeignKey(Post,
on_delete=models.CASCADE,
related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
ordering = ('created',)
def __str__(self):
return 'Comment by {} on {}'.format(self.name, self.post)
Это модель Comment. Она содержит внешний ключ ForeignKey для ассоциации с конкретным постом. Это отношение многое к одному определено в модели Comment, потому что каждый комментарий предназначен для одной записи, но у поста может быть несколько комментариев. Атрибут related_name позволяет назвать атрибут, используемый для связи объектов. После его определения можно будет получать пост, для которого оставлен комментарий, с помощью comment.post. Все комментарии можно будет получить с помощью post.comments.all(). Если этот атрибут не определить, Django будет использовать имя модели маленькими буквами и __set (например, comment_set). Так будет называться менеджер связанного объекта.
Больше о связи много к одному можно почитать здесь: https://docs.djangoproject.com/en/2.0/topics/db/examples/many_to_one/.
Есть булево поле active, которое используется для ручного отключения неприемлемых комментариев. А поле created нужно для сортировки комментариев в хронологическом порядке по умолчанию.
Новая модель comment не синхронизирована с базой данных. Запустите следующую команду для создания миграции, которая отметит создание новой модели:
python manage.py makemigrations blog
Появится следующий вывод:
Migrations for 'blog':
blog/migrations/0002_comment.py
- Create model Comment
Django сгенерировал файл 0002_comment.py в папке migrations/ приложения blog. Теперь нужно создать схему связанной базы данных и применить изменения к базе данных. Воспользуйтесь командой для применения существующих миграций:
python manage.py migrate
Вывод будет включать следующую строку:
Applying blog.0002_comment... OK
Миграция была применена, а таблица blog_comment существует в базе данных.
Теперь можно добавить новую модель в административный сайт, чтобы управлять комментариями через простой интерфейс. Откройте admin.py приложения blog, импортируйте модель Comment и добавьте следующий класс ModelAmdin:
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'post', 'created', 'active')
list_filter = ('active', 'created', 'updated')
search_fields = ('name', 'email', 'body')
Запустите сервер разработки с помощью python manage.py runserver и откройте ссылку https://127.0.0.1:8000/admin/ в браузере. Вы увидите, что новая модель отображается в разделе BLOG, как на скриншоте:

Модель зарегистрирована в административном сайте, а это значит, что экземплярами Comment можно управлять с помощью простого интерфейса.
После создания формы и представления, а также добавления URL-шаблона не хватает только шаблона для этого представления. Создайте новый файл в папке blog/templates/blog/post/ и назовите его share.html. После этого добавьте следующий код:
{% extends "blog/base.html" %}
{% block title %}Share a post{% endblock %}
{% block content %}
{% if sent %}
<h1>E-mail successfully sent</h1>
<p>
"{{ post.title }}" was successfully sent to {{ form.cleaned_data.to }}.
</p>
{% else %}
<h1>Share "{{ post.title }}" by e-mail</h1>
<form action="." method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="Send e-mail">
</form>
{% endif %}
{% endblock %}
Это шаблон для отображения формы или сообщения об успешной отправке. Как можно заметить, создается HTML-элемент form. Отправка его происходит с помощью метода POST:
<form action="." method="post">
Затем включается актуальный экземпляр формы. Django сообщается, что он должен рендерить поля в HTML-абзацах — элементах <p> с помощью метода as_p. Также формы можно отрендерить в виде ненумерованного списка с помощью as_ul или как HTML-таблицу с помощью as_table. Если нужно отрендерить каждое поле, можно перебрать поля как в этом примере:
{% for field in form %}
<div>
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
Ярлык шаблона {% csrf_token %} представляет собой скрытое поле с автоматически генерируемым токеном, который позволяет избежать атак cross-site request forgery (CSRF, «межсайтовая подделка запроса»). Такие атаки представляют собой сайты или программы злоумышленников, выполняющие нежелательные действия за пользователя на сайте. Подробно этом написано по ссылке https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF).
Этот ярлык генерирует скрытое поля, которые выглядит вот так:
<input type='hidden' name='csrfmiddlewaretoken'
value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR' />
По умолчанию Django проверяет CSRF — токен для всех запросов
POST. Не забывайте включить ярлык_csrf_tokenво все формы, отправляемые с помощьюPOST.
Отредактируйте шаблон blog/post/detail.html и добавьте следующую ссылку в URL поста, которым будут делиться, после переменной {{ post.body|linebreaks }}:
<p>
<a href="{% url 'blog:post_share' post.id %}">
Share this post
</a>
</p>
URL создаются динамически с помощью ярлыка шаблона {% url %} из Django. Используется пространство имен blog и URL post_share. ID поста передается в качестве параметра для создания абсолютного URL.
Запустите сервер разработки с помощью команды python manage.py runserver и откройте https://127.0.0.1:8000/blog/ в браузере. Нажмите на название любого поста, под ним будет добавленная ссылка:

Кликните на «Share this post». Откроется страница с формой для того, чтобы поделиться этим постом через email:

CSS-стили есть в коде примера в static/css/blog.css. После нажатия на SEND E-MAIL форма отправляется и проверяется. Если данные в полях правильные, отобразится сообщение об успехе E-mail successfully sent.
Если данные неверные, форма отрендерится заново с ошибками проверки.
Некоторые современные браузеры не дадут отправлять формы с пустыми или некорректными полями. Это происходит из-за того, что проверка форм браузерами основывается на типах полей и их ограничениях. В этом случае форма не будет отправлена, а браузер отобразит ошибку для некорректных полей.

Форма чтобы делиться постами через email готова. Дальше создадим систему комментариев для блога.
]]>Отправлять электронные письма с помощью Django очень просто. В первую очередь нужен локальный SMTP-сервер или определенная конфигурация внешнего в соответствующих настройках в файле settings.py проекта:
EMAIL_HOST: хост SMTP-сервера, по умолчанию — localhost.EMAIL_PORT: порт SMTP, по умолчанию — 25.EMAIL_HOST_USER: имя пользователя SMTP-сервера.EMAIL_HOST_PASSWORD: пароль SMTP-сервера.EMAIL_USE_TLS: использовать ли безопасное TLS-соединение.EMAIL_USE_SSL: использовать ли безопасное SSL-соединение.Если SMTP-сервер использовать не получается, можно задать Django, чтобы он выводил письма в оболочку. Это удобно для тестирования приложения без сервера.
Если нужно отправлять письма, но локального SMTP-сервера нет, можно использовать сервер провайдера. Следующая конфигурация подойдет для отправки через Gmail с помощью Google-аккаунта:
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'your_account@gmail.com' EMAIL_HOST_PASSWORD = 'your_password'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
Запустите команду python manage.py shell для открытия оболочки и отправьте письма следующим образом:
>>> from django.core.mail import send_mail
>>>> send_mail('Django mail', 'This e-mail was sent with Django.',
'your_account@gmail.com', ['your_account@gmail.com'], fail_silently=False)
Функция send_mail() принимает тему, сообщение, отправителя и список получателей в качестве аргументов. С помощью необязательного аргумента fail_silent=False можно сделать так, чтобы при неудачной попытке отправки было вызвано исключение. Если вывод — 1, значит письмо было отправлено.
Если будет использоваться аккаунт Gmail, в настройках по ссылке https://myaccount.google.com/lesssecureapps нужно активировать следующий пункт:

Теперь необходимо добавить функциональность представлению:
Отредактируйте представление post_share в файле views.py приложения blog:
from django.core.mail import send_mail
def post_share(request, post_id):
# Получить пост по id
post = get_object_or_404(Post, id=post_id, status='published')
sent = False
if request.method == 'POST':
# Форма была отправлена
form = EmailPostForm(request.POST)
if form.is_valid():
# Поля формы прошли проверку
cd = form.cleaned_data
post_url = request.build_absolute_uri(post.get_absolute_url())
subject = '{} ({}) recommends you reading " {}"'.format(cd['name'], cd['email'], post.title)
message = 'Read "{}" at {}\n\n{}\'s comments: {}'.format(post.title, post_url, cd['name'], cd['comments'])
send_mail(subject, message, 'admin@myblog.com', [cd['to']])
sent = True
else:
form = EmailPostForm()
return render(request, 'blog/post/share.html', {'post': post,
'form': form,
'sent': sent})
Объявляем переменную sent и задаем для нее значение True, когда пост отправлен. Эта переменная будет использоваться позже в шаблоне, чтобы отображать сообщение об успешной отправке. Поскольку нужно включать ссылку на пост в email, с помощью метода get_absolute_url() можно будет получить абсолютный путь. Он будет использоваться как ввод для request.build_absolute_uri() для построения URL со схемой HTTP и именем хоста. Тема и тело письма создаются на основе очищенных данных из отправленной формы. Наконец, email отправляется по адресу указанному в поле to формы.
Когда представление готово, нужно добавить новый URL-шаблон. Откройте файл urls.py приложения blog и добавьте URL-шаблон post_share:
urlpatterns = [
# ...
path('<int:post_id>/share/',
views.post_share, name='post_share'),
]
]]>Необходимо создать новое представление, которое обрабатывает форму и отправляет email при успешном принятии. Отредактируйте файл views.py приложения blog и добавьте следующий код:
from .forms import EmailPostForm
def post_share(request, post_id):
# Получить пост по id
post = get_object_or_404(Post, id=post_id, status='published')
if request.method == 'POST':
# Форма была отправлена
form = EmailPostForm(request.POST)
if form.is_valid():
# Поля формы прошли проверку
cd = form.cleaned_data
# ... отправить письмо
else:
form = EmailPostForm()
return render(request, 'blog/post/share.html', {'post': post,
'form': form})
Представление работает следующим образом:
post_share, которое принимает объект request и переменную post_id в качестве параметровget_object_or_404 используется для получения ID поста. Также ярлык проверяет что статус поста — published.request можно понять, были ли данные отправлены с помощью формы или еще нет, а принятие происходит с помощью POST. Предполагаем, что если запрос GET, то форма будет отображаться пустой, а если POST — форма отправляется и обрабатывается. Таким образом для поиска отличий в сценариях используется request.method == 'POST'.Дальше процесс отображения и обработки формы:
GET, создается новый экземпляр form, который будет использоваться для отображения пустой формы в шаблоне:
form = EmailPostForm()
POST. Затем создается экземпляр формы с помощью отправленных данных, которые хранятся в request.POST:
if request.method == 'POST':
# Форма была отправлена
form = EmailPostForm(request.POST)
is_valid(). Он проверяет данные и возвращает True, если все поля содержат прошедшие проверку данные. Если где-то указана неправильная информация, метод is_valid() возвращает False. Список ошибок проверки можно посмотреть, получив доступ к form.errors.form.cleaned_data. Этот атрибут является словарем, где его значения — это поля формы.Если форма не прошла проверку, в
cleaned_dataбудут только те поля, данные которых подошли.
Теперь нужно узнать, как с помощью Django отправлять email.
]]>В прошлом разделе было создано базовое приложение блога. Теперь пришло время превратить его в полнофункциональный блог с продвинутыми функциями, такими как возможность делиться постами по email, добавлять комментарии, проставлять теги и получать похожие посты. Дальше речь пойдет о следующих темах:
В первую очередь нужно предоставить пользователям возможность делиться постами с помощью email. Подумайте о том, как использовать представления, URL и шаблоны для создания такой функциональности. Теперь необходимо разобраться с тем, что понадобится для того, чтобы у пользователей была возможность делиться постами по email. Требуется следующее:
views.py, которое будет обрабатывать данные и отправлять сообщение.urls.py приложения блога.Начнем с создания формы, используемой чтобы делиться постами. В Django есть встроенный фреймворк форм, который позволяет легко их создавать. С его помощью достаточно лишь определить поля, выбрать тип их отображения и указать, как они будут проверять полученные данные. Этот фреймворк обеспечивает гибкий рендеринг форм и обработку данных.
В Django есть два базовых класса для построения форм:
Form: используется для построения стандартных форм.ModelForm: позволяет создавать формы, привязанные к экземплярам модели.Сперва нужно создать файл forms.py в папке приложения blog и добавить следующий код:
from django import forms
class EmailPostForm(forms.Form):
name = forms.CharField(max_length=25)
email = forms.EmailField()
to = forms.EmailField()
comments = forms.CharField(required=False,
widget=forms.Textarea)
Это ваша первая форма Django. Ознакомьтесь с кодом. Форма была создана посредством наследования класса Form. Здесь были использованы другие типы полей, чтобы Django правильно их проверял.
Формы могут быть где угодно в проекте Django. Но принято сохранять их в файле
forms.pyдля каждого приложения.
Тип поля name — CharField. Он отрисовывается как HTML-элемент <input type="text">. У каждого типа поля есть виджет по умолчанию, определяющий, как поле рендерится в HTML. Виджет по умолчанию может быть перезаписан с помощью атрибута widget. В поле comments используется виджет Textarea для отображения HTML-элемента <textarea> вместо стандартного <input>.
Проверка поля зависит от его типа. Например, поля email и to — это поля EmailField. Обе требуют действительной электронной почты. В противном случае будет вызвано исключение forms.ValidationError, и проверка не пройдет. Другие параметры тоже учитываются: максимальная длина поля name может быть 25 символов, а поле comments можно сделать опциональным с помощью required=False. Все это учитывается при проверке. Типы полей в этой форме — это лишь часть из общей массы. С целым списком можно ознакомиться здесь: https://docs.djangoproject.com/en/2.0/ref/forms/fields/.
Представления, основанные на классах, — это альтернативный способ внедрять представления в виде объектов, а не функций Python. Поскольку представление — это вызываемый объект, принимающий веб-запрос и возвращающий ответ, его можно определить в виде методов класса. Django предоставляет базовые классы представлений для этого. Они все наследуют класс View, который обрабатывает HTTP метод направления и другие функции.
Представления, основанные на классах, предлагают свои преимущества над представлениями, основанными на функциях в определенных случаях. У них есть следующие особенности:
GET, POST или PUT, в отдельных методах, а не в ветках условий.mixins).Подробно о представлениях, основанных на классах, можно почитать здесь: https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/.
Поменяем представление post_list на «классовое», чтобы использовать общий ListView из Django. Это базовое представление позволяет отображать объекты любого вида.
Отредактируйте файл views.py приложения blog и добавьте следующий код.
from django.views.generic import ListView
class PostListView(ListView):
queryset = Post.published.all()
context_object_name = 'posts'
paginate_by = 3
template_name = 'blog/post/list.html'
Это классовое представление аналогично ранее используемому post_list. В коде выше ListView сообщается, что он должен:
queryset, можно использовать определенный model = Post, и Django построит общий QuerySet Post.objects.all() автоматически.posts для запроса результатов. Если context_object_name не определена, то переменная по умолчанию — object_list.ListView использует blog/post_list.html.Теперь откройте файл urls.py приложения blog, закомментируйте предыдущий URL-шаблона post_list и добавьте новый с помощью класса PostListView:
urlpatterns = [
# post views
# path('', views.post_list, name='post_list'), path('', views.PostListView.as_view(), name='post_list'),
path('<int:year>/<int:month>/<int:day>/<slug:post>/',
views.post_detail,
name='post_detail'),
]
Чтобы пагинация работала, нужно использовать правильный объект страницы, который передается шаблону. Общее представление Django ListView передает выбранную страницу переменной page_obj, поэтому нужно отредактировать шаблон post/list.html. Это необходимо, чтобы пагинатор использовать правильную переменную:
{% include "pagination.html" with page=page_obj %}
Откройте https://127.0.0.1:8000/blog/ в бразуере и проверьте, чтобы все работало так же, как и с прошлым представлением post_list. Это простой пример представления, основанного на классах, использующего общий класс Django.
Вы узнали основы веб-фреймворка Django и создали базовое приложение блога. Разработали модели данных и применили миграции. Создали представления, шаблоны и URL для проекта, включая пагинацию.
Дальше речь пойдет об улучшении приложения с помощью системы комментариев и функции проставления тегов, а также возможности пользователям делиться постами через электронную почту.
]]>Добавляя контент на блог, вы быстро придете к выводу, что список постов лучше делить на несколько страниц. В Django есть встроенный класс пагинации, который позволяет сделать это очень быстро.
Отредактируйте файл views.py приложения blog, чтобы импортировать класс Paginator и измените представление post_list следующим образом:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def post_list(request):
object_list = Post.published.all()
paginator = Paginator(object_list, 3) # 3 поста на каждой странице
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
# Если страница не является целым числом, поставим первую страницу
posts = paginator.page(1)
except EmptyPage:
# Если страница больше максимальной, доставить последнюю страницу результатов
posts = paginator.page(paginator.num_pages)
return render(request,
'blog/post/list.html',
{'page': page,
'posts': posts})
Вот как работает этот класс:
Paginator с количеством объектов, которые нужно отображать на одной странице.page GET, который указывает на текущую страницу.Paginator.page — это не целое число, возвращаем первую страницу результатов. Если оно больше последней страницы результатов, возвращаем последнюю.Теперь нужно создать шаблон для отображения пагинатора так, чтобы он мог использоваться в любом шаблоне с пагинацией. В папке templates/ приложения blog создайте новый файл и назовите его pagination.html. Добавьте туда следующий код:
<div class="pagination">
<span class="step-links">
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}">Previous</a>
{% endif %}
<span class="current">
Page {{ page.number }} of {{ page.paginator.num_pages }}.
</span>
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">Next</a>
{% endif %}
</span>
</div>
Шаблон пагинации ожидает получить объект Page, чтобы отрисовать ссылки «Previous» и «Next», а также для отображения текущей страницы и общее количество страниц результата. Вернемся к шаблону blog/post/list.html и добавим шаблон pagination.html в нижней части блока {% content %}:
{% block content %}
...
{% include "../pagination.html" with page=posts %}
{% endblock %}
Поскольку объект Page, который передается шаблону, называется posts, передадим шаблон пагинации в шаблон списка постов вместе с параметрами для корректного рендеринга. Этот способ можно применять, чтобы использовать шаблон пагинации в постраничных представлениях разных моделей.
Теперь откройте https://127.0.0.1:8000/blog/ в браузере. Вы увидите элементы навигации по страницам в нижней части и сможете перемещаться по ним.

Итак, представления и URL-шаблоны для приложения blog были созданы. Теперь нужно добавить шаблоны для отображения постов так, чтобы их было удобно читать пользователям.
Создайте следующие директории и файлы в папке приложения blog:
blog/
templates/
blog/
base.html
post/
list.html
detail.html
Эта файловая структура для шаблонов. Файл base.html включает основной HTML-код сайта. В нем содержимое разделено между главной областью с контентом и сайдбаром. Файлы list.html и detail.html будут наследовать base.html для отрисовки списка постов и представления поста соответственно.
В Django есть мощный язык шаблонов, который позволяет обознаиать, как именно данные будут отображаться. Он основан на тегах, переменных и фильтрах шаблона:
{% tag %}.{{ variable }}.{{ variable|filter }}.Все встроенные теги и фильтры шаблона перечислены здесь https://doc.djangoproject.com/en/2.0/ref/templates/builtins/.
Отредактируем файл base.html и добавим следующий код:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<link href="{% static 'css/blog.css' %}" rel="stylesheet">
</head>
<body>
<div id="content">
{% block content %}
{% endblock %}
</div>
<div id="sidebar">
<h2>My blog</h2>
<p>This is my blog.</p>
</div></body>
</html>
{% load static %} сообщает Django, что нужно загрузить теги шаблона static, предоставленные приложением django.contrliv.staticfiles, которое можно найти в INSTALLED_APPS. После загрузки фильтр шаблон {% static %} можно использовать во всем шаблоне. С помощью фильтра можно включать статичные файлы, например файл blog.css. Он находится в коде в папке static/. Скопируйте стили из кода внизу в то же место, где хранится проект, чтобы применить стили CSS.
blog/
static/
css/
blog.css
blog.css
* {
box-sizing:border-box;
}
a {
text-decoration: none;
}
body,
html {
height: 100%;
margin: 0;
}
h1 {
border-bottom: 1px solid lightgrey;
padding-bottom: 10px;
}
.date,
.info {
color: grey;
}
.comment:nth-child(even) {
background-color: whitesmoke;
}
#content,
#sidebar {
display: inline-block;
float: left;
padding: 20px;
height: 100%;
}
#content {
width: 70%;
}
#sidebar {
width: 30%;
background-color: lightgrey;
}
Также используются два тега {% block %}. Они сообщают Django, что в этой области нужно определить блок. Наследуемые шаблоны могут заполнять эти блоки контентом. С их помощью определены блоки title и content.
Отредактируем файл post/list.html, чтобы он выглядел следующим образом:
{% extends "blog/base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
<h1>My Blog</h1>
{% for post in posts %}
<h2>
<a href="{{ post.get_absolute_url }}">
{{ post.title }}
</a>
</h2>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|truncatewords:30|linebreaks }}
{% endfor %}
{% endblock %}
Тег шаблона {% extends %} сообщает Django, чтобы он наследовал шаблон blog/base.html. Затем контентом заполняются блоки title и content базового шаблона. Происходит перебор по постам, в результате чего отображаются их название, дата, автор и тело, включая ссылку в названии к каноническому URL поста. В теле применяются два фильтра шаблона: truncateword обрезает значение до заданного количества слов, а linebreaks конвертирует вывод в строки HTML с переносами. Можно объединять сколько угодно фильтров шаблона. Каждый будет применяться к выводу предыдущего.
Откройте терминал и выполните команду python manage.py runserver для запуска сервера разработки. Откройте https://127.0.0.1:8000/blog/ в бразуере, чтобы увидеть, как все работает. Для отображения постов здесь они должны быть со статусом Published. Появится следующее:

Теперь нужно отредактировать файл post/detail.html:
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<h1>{{ post.title }}</h1>
<p class="date">
Published {{ post.publish }} by {{ post.author }}
</p>
{{ post.body|linebreaks }}
{% endblock %}
Наконец, можно вернуться в браузер и кликнуть по названию любого поста, чтобы посмотреть, как он выглядит:

И посмотрите на URL — его структура должны быть приблизительно такой /blog/2019/11/23/who-was-django-reinhardt/. Это значит, что ссылки подходят для SEO-оптимизации.
URL-шаблоны позволяют связывать URL с представлениями. Шаблон URL состоит из шаблона строки, представления и имени (опционально), с помощью которого можно задать имя для URL всего проекта. Django перебирает каждый шаблон и останавливается на первом, который соответствует запрошенному URL. Затем библиотека импортирует представление совпавшего URL-шаблона и исполняет его, передавая экземпляр класса HttpRequest и ключевое слово или позиционные аргументы.
Создайте файл urls.py в папке приложение blog и добавьте следующие строки:
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
# post views
path('', views.post_list, name='post_list'),
path('<int:year>/<int:month>/<int:day>/<slug:post>/',
views.post_detail,
name='post_detail'),
]
В этом коде было определено пространство имен приложения с помощью переменной app_name. Это позволяет организовать URL по приложениям и использовать имена, ссылаясь на них. В примере два шаблона были определены с помощью функции path(). Первый URL-шаблон не принимает аргументов и привязан к представлению post_list. Второй — принимает следующие 4 аргумента и связан с представлением post_detail:
year: требует целое числоmonth: требует целое числоday: требует целое числоpost: может быть составлен из слов и дефисовУгловые скобки используются для захвата значений из URL. Любое значение, определенное в URL-шаблоне как <parameter>, захватывается как строка. Для точного совпадения и возврата целого числа применяются конвертеры пути, такие как <int:year>. А <slug:post> нужен для совпадения ссылки (строки, состоящей из символов ASCII и чисел, плюс дефис и нижнее подчеркивание). Все конвертеры перечислены здесь: https://docs.djangoproject.com/en/2.0/topics/urls/#path-converters.
Если способ с использованием path() и конвертеров не подходит, можно рассмотреть re_path(). Она применяется для определения сложных URL-шаблонов с помощью регулярных выражений Python. Узнать больше об определении URL-шаблонов с помощью регулярных выражений можно по ссылке https://docs.djangoproject.com/en/2.0/ref/urls/#django.urls.re_path. Если вы не работали с ними раньше, рассмотрите эту тему в разделе HOWTO: https://docs.python.org/3/howto/regex/html_
Создавать файл
urls.pyдля каждого приложения — лучший способ добиться того, чтобы их можно было использовать в других проектах.
Теперь нужно включить URL-шаблоны приложения blog в основные URL-шаблоны проекта. Отредактируйте файл urls.py в папке mysite проекта, чтобы он выглядел следующим образом:
from django.urls import path, include
from django.contrib import admin
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls', namespace='blog')),
]
Новый URL-шаблон, определенный с помощью include, ссылается на URL-шаблоны, определенные в приложении блога. Таким образом они относятся к пути blog/ и включены под пространством имен blog. Пространства имен должны быть уникальными во всем проекте. Позже на URL блога можно будет ссылаться с помощью пространства имен, создавая их, например вот так blog:post_list или blog:post_detail. Больше о пространствах имен URL можно узнать по ссылке https://docs.djangoproject.com/en/2.0/topics/http/urls/#url-namespaces.
Для построения канонических URL для объектов post можно использовать URL post_detail, определенные в предыдущем разделе. По правилам Django нужно добавить метод get_absolute_url() в модель. Он вернет канонический URL объекта. Для этого метода будет использоваться метод reverse(), который позволяет создавать URL согласно их названиям и передаваемым опциональным параметрам. Отредактируйте файл models.py и добавьте следующее:
from django.urls import reverse
class Post(models.Model):
# ...
def get_absolute_url(self):
return reverse('blog:post_detail',
args=[self.publish.year,
self.publish.month,
self.publish.day,
self.slug])
В шаблонах будет использоваться метод get_absolute_url() для связи с конкретными постами.
Начнем с создания представления для отображения списка постов. Отредактируйте файл views.py приложения blog, чтобы он выглядел следующим образом:
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
posts = Post.published.all()
return render(request,
'blog/post/list.html',
{'posts': posts})
Это первое представление Django. Представление post_list принимает объект request в качестве единственного параметра. Он обязателен для всех представлений. В этом представлении можно получить все посты с помощью статуса published из менеджера published, который был создан ранее.
Наконец, используется ярлык render(). Она представляется Django для рендеринга списка постов с заданным шаблоном. Эта функция принимает объект request, путь шаблона и контекстные переменные для отрисовки выбранного шаблона. Она возвращает объект HttpResponce с отрисованным текстом (обычно это код HTML). Ярлык render() учитывает контекст запроса, поэтому любая переменная из контекстного процессора шаблона может использоваться для заданного шаблона. Процессоры контекста для шаблонов — это всего лишь вызываемые объекты, которые назначают переменные для контекста.
Создадим второе представление для отображения одного поста. Добавьте следующую функцию в файл views.py:
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post,
status='published',
publish__year=year,
publish__month=month,
publish__day=day)
return render(request,
'blog/post/detail.html',
{'post': post})
Это представление поста. Оно принимает year, month, day и параметры post для получения опубликованного поста с заданным slug и датой. Стоит обратить внимание, что при создании модели Post в поле slug было добавлено поле unique_for_date. Таким образом можно удостовериться, что будет только один пост с такой ссылкой в указанную дату, а это значит, что с помощью даты и ссылки (slug) всегда можно получить один конкретный пост. В этом представлении используется ярлык get_object_or_404() для получения нужной записи. Функция возвращает объект, параметры которого совпадают с запросом или запускает исключение HTTP 404 (не найдено), если такой не был найдет. В конце используется ярлык render() для отрисовки полученного поста с помощью шаблона.
Как уже упоминалось, objects — это менеджер по умолчанию для каждой модели. Он получает объекты из базы данных. Но можно определять и собственные менеджеры для моделей. Попробуем создать собственный менеджер для получения постов со статусом published.
Их можно добавить двумя способами: добавив дополнительные методы менеджера или изменив оригинальные. Первый предоставляет API QuerySet, такие как Post.objects.my_manager(), а второй — Post.my_manager.all(). Менеджер позволит получать посты с помощью Post.published.all().
Отредактируйте файл models.py приложения blog для добавления кастомного менеджера:
class PublishedManager(models.Manager):
def get_queryset(self):
return super(PublishedManager,
self).get_queryset()\
.filter(status='published')
class Post(models.Model):
# ...
objects = models.Manager() # Менеджер по умолчанию
published = PublishedManager() # Собственный менеджер
Метод менеджера get_queryset() возвращает QuerySet, который и будет исполнен. Перезапишем его, чтобы включить кастомный фильтр в финальный QuerySet. Кастомный менеджер уже определен и добавлен к модели Post; теперь его можно использовать и для осуществления запросов. Проверим.
Запустите сервер разработки с помощью этой команды:
python manage.py shell
Теперь вы можете получить все посты, названия которых начинаются с Who:
Post.published.filter(title__startswith='Who')
ORM в Django основан на QuerySet. QuerySet — это набор объектов из базы данных, который может использовать фильтры для ограничения результатов. Уже известно, как получать один объект из базы данных с помощью метода get(). Получить к нему доступ можно с помощью Post.objects.get(). Каждая модель Django имеет как минимум один менеджер, а менеджер по умолчанию называется objects. Сделать запрос к объекту (QuerySet) можно с помощью менеджера модели. Для получения всех объектов из таблицы нужно просто использовать метод all() в менеджере объектов по умолчанию:
>>> all_posts = Post.objects.all()
Таким образом можно создать QuerySet, который вернет все объекты базы данных. Но важно обратить внимание на то, что он не еще исполнился. QuerySet в Django ленивые. Они исполняются только в том случае, если их заставить. Это поведение делает инструмент крайне эффективным. Если не сохранить QuerySet в переменную, а написать его прямо в оболочку Python, выражение SQL в QuerySet исполнится автоматически, потому что такой была команда:
>>> Post.objects.all()
Чтобы отфильтровать QuerySet, можно использовать метод filter() менеджера. Например, можно получить все посты за 2017 год с помощью такого запроса:
Post.objects.filter(publish__year=2017)
Можно фильтровать и по нескольким полям одновременно. Так, чтобы получить все посты за 2017 год, написанные автором admin, следует использовать команду:
Post.objects.filter(publish__year=2017, author__username='admin')
Это то же самое, что писать QuerySet с цепочкой фильтров:
Post.objects.filter(publish__year=2017) \
.filter(author__username='admin')
Запросы с методами поиска по полям пишутся с двумя нижними подчеркиваниями, например
publish__year, _но такое же написание используется для получения доступа к полям связанных моделей, напримерauthor__username.
Можно исключить некоторые результаты из запроса с помощью метода менеджера exclude(). Например, можно получить все посты за 2017 год, названия которых не начинаются с Why:
Post.objects.filter(publish__year=2017) \
.exclude(title__startswith='Why')
Результаты можно отсортировать по полям с помощью метода менеджера order_by(). Например, можно получить все объекты, отсортированные согласно их названию (title) с помощью такой команды:
Post.objects.order_by('title')
Подразумевается расположение по возрастанию. Чтобы разместить элементы по убыванию необходимо использовать минус в качестве префикса:
Post.objects.order_by('-title')
Если нужно удалить объект, это можно сделать из экземпляра с помощью метода delete():
post = Post.objects.get(id=1)
post.delete()
Обратите внимание, что удаление объектов также удалит все зависимые отношения для объектов
ForeignKey, для которых вCASCADEопределеноon_delete.
Для QuerySet можно объединить несколько фильтров, но они не коснутся базы данных, до тех пор пока QuerySet не будет выполнен. А он исполняется в следующих условиях:
Post.objects.all()[:3].repr() или len().list().bool(), or, and или if.Теперь, когда есть полноценный административный сайт для управления контентом из блога, пришло время узнать, как получать информацию из базы данных и взаимодействовать с ней. В Django есть мощный встроенный API для создания, получения, обновления и удаления объектов. Инструмент ORM (объектно-реляционное отображение) в Django совместим с MySQL, PostgreSQL, SQLite и Oracle. Определить базу данных своего проекта можно в настройке DATABASES в файле settings.py проекта. Django умеет работать с несколькими базами данных одновременно, а вы можете создать роутеры для создания собственным схем роутинга.
После создания моделей данных Django предоставляет бесплатный API для взаимодействия с ними. Вот ссылка на информацию о нем в официальной документации: https://docs.djangoproject.com/en/2.0/ref/models/.
Откройте командную строку и введите следующую команду для открытия оболочки Python:
python manage.py shell
Попробуйте ввести следующие строки:
>>> from django.contrib.auth.models import User
>>> from blog.models import Post
>>> user = User.objects.get(username='admin')
>>> post = Post(title='Another post', slug='another-post', body='Post body.', author=user)
>>> post.save()
Разберем по порядку, за что отвечает этот код. Сначала нужно получить объект user, имя пользователя которого — admin.
user = User.objects.get(username='admin')
Метод get() позволяет получить один объект из базы данных. Он рассчитывает на результат, который бы соответствовал запросу. Если база данных не вернет результат, метод вернет исключение DoesNotExist, а если вернет несколько, то — исключение MultipleObjectsReturned. Оба исключения — атрибуты класса модели, к которой и был направлен запрос.
Затем создается экземпляр Post с названием, текстом ссылки и телом. Добавленный же в прошлом шаге пользователь будет автором поста:
post = Post(title='Another post',
slug='another-post',
body='Post body.',
author=user)
Этот объект хранится в памяти и не представлен в базе данных.
Дальше сохраняется объект Post с помощью метода save():
post.save()
Предыдущее действие отвечает за выражение INSERT в SQL. Вы уже знаете, как сначала создать объект в памяти, а потом перенести его в базу данных, но это же можно сделать и с помощью одной операции — create():
Post.objects.create(title='One more post',
slug='one-more-post',
body='Post body.',
author=user)
Попробуйте поменять название поста и снова сохраните объект:
>>> post.title = 'New title'
>>> post.save()
В этот раз метод save() исполнит выражение UPDATE в SQL.
]]>Сделанные изменения не сохранятся в базе данных до вызова метода
save().
Рассмотрим, как изменить внешний вид списка объектов модели в админ-панели. Отредактируйте файл admin.py приложения блога и измените его следующим образом:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'author', 'publish', 'status')
Таким образом административная панель Django понимает, что модель зарегистрирована с помощью пользовательского класса, наследуемого из ModelAdmin. В него можно включить информацию о том, как отображать модель и как взаимодействовать с ней. Атрибут list_display позволяет настроить поля модели, которые необходимо показывать на странице со списком объектов на сайте. Декоратор @admin.register() выполняет ту же функцию, что и замененная admin.site.register(), регистрируя класс ModeAdmin, который он же и декорирует.
Отредактируем административную панель с помощью следующих настроек:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'author', 'publish', 'status')
list_filter = ('status', 'created', 'publish', 'author')
search_fields = ('title', 'body')
prepopulated_fields = {'slug': ('title',)}
raw_id_fields = ('author',)
date_hierarchy = 'publish'
ordering = ('status', 'publish')
Вернемся в браузер и перезагрузим страницу. Теперь она будет выглядеть вот так:

Можно увидеть, что поля, отображаемые на странице со списком постов — это те, что были определены в атрибуте list_display. Она включает правый сайдбар, с помощью которого можно фильтровать результаты по полям, которые указаны в атрибуте list_filter. На странице появилось и поле поиска. Это потому что был определен список полей, по которым можно осуществлять поиск с помощью search_fields. Под ним есть навигационные ссылки для просмотра разных дат. Также посты упорядочены по умолчанию согласно данным в колонках «Status» (Статус) или «Publish» (Опубликовано). Порядок по умолчанию задан с помощью атрибута ordering.
Теперь кликните по кнопке «Add Post» (Добавить пост). Здесь тоже есть кое-какие изменения. При вводе названия нового поста поле slug будет заполняться автоматически. Django использует для этого атрибут prepopulated_fields и данные из поля title. Также поле author теперь отображается с виджетом поиска, который с большим объемом данных работает лучше чем выпадающее меню.

Всего парой строк кода удалось изменить способ отображения модели в административной панели. Есть еще много способов изменения внешнего вида и расширения возможностей списка объектов модели в админ-панели Django.
]]>Добавим модели блога на административный сайт. Отредактируйте файл admin.py приложения blog, чтобы он выглядел так:
from django.contrib import admin
from .models import Post
admin.site.register(Post)
Теперь перезагрузите сайт в браузере. Там появится модель Post:

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

Django использует разные виджеты форм для каждого типа поля. Даже сложные поля, такие как DateTimeField, отображаются с простым интерфейсомt.
Заполните форму и нажмите кнопку Save. Вы должны быть перенаправлены на страницу списка постов с подтверждением добавления поста, как показано ниже:

Теперь, когда модель Post определена, нужно создать простую админ-панель для постов в блоге. В Django есть встроенный административный интерфейс, который подходит для работы с контентом. Он создается динамически с помощью чтения мета-данных модели. Это приводит к появлению готового интерфейса, который используется для редактирования контента. Можно сразу начинать использовать его, настроив лишь способ отображения моделей.
Приложение django.contrib.admin уже включено в INSTALLED_APPS, поэтому отдельно его не нужно добавлять.
В первую очередь нужно создать пользователя, который сможет управлять админ-панелью. Для этого необходимо использовать команду:
python manage.py createsuperuser
Отобразится следующий вывод. Потребуется ввести имя пользователя, email и пароль:
Username (leave blank to use 'admin'):
admin Email address:
admin@admin.com
Password: ********
Password (again): ********
Superuser created successfully.
Запустить сервер разработки можно с помощью команды python manage.py runserver. Дальше нужно открыть https://127.0.0.1:8000/admin/ в браузере. Отобразится страница авторизации как на скриншоте:

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

Модели Group и User — это элементы фреймворка аутентификации Django, которые расположены в django.contrib.auth. Если нажать на Users, вы увидите созданного пользователя. Модель Post приложения blog связана с моделью User. Запомните, что отношение определяется полем author.
Теперь, когда есть модель данных для постов в блоге, нужна таблица базы данных. В Django есть система миграции, которая отслеживает изменения в моделях и позволяет передавать их в базу данных. Команда migrate применяет миграции для всех приложений в INSTALLED_APPS. Она синхронизирует базу данных с текущими моделями и существующими миграциями.
В первую очередь нужно создать стартовую миграцию для модели Post. В корневой директории проекта необходимо ввести следующую команду:
python manage.py makemigrations blog
Появится следующий вывод:
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Post
Django только что создал файл 0001_initial.py в папке migrations приложения blog. Можно открыть его, чтобы увидеть, как появилась миграция. Миграция определяет зависимости от других миграций и операций, которые нужно провести в базе данных, чтобы синхронизировать ее с изменениями модели.
Посмотрим на код SQL, который исполнится в базе данных для создания таблицы для модели. Команда sqlmigrate возьмет названия миграций и вернет их SQL без исполнения. Выполните следующую команду, чтобы увидеть вывод SQL от первой миграции:
python manage.py sqlmigrate blog 0001
Он должен выглядеть следующим образом:
BEGIN;
--
-- Create model Post
--
CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" varchar(250) NOT NULL, "slug" varchar(250) NOT NULL, "body" text NOT
NULL, "publish" datetime NOT NULL, "created" datetime NOT NULL, "updated"
datetime NOT NULL, "status" varchar(10) NOT NULL, "author_id" integer NOT
NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED
);
CREATE INDEX "blog_post_slug_b95473f2" ON "blog_post" ("slug");
CREATE INDEX "blog_post_author_id_dd7a8485" ON "blog_post" ("author_id");
COMMIT;
Точный вывод зависит от используемой базы данных. Этот пример основан на SQLite. Как можно видеть в этом выводе, Django генерирует имена таблицы объединяя название приложения и название модели в нижнем регистре (blog_post), но можно указать и собственное имя базы данных в классе модели Meta с помощью атрибута db_table. Django создает основной ключ автоматически для каждой модели, но его можно перезаписать, указав primary_key = True в одной из полей модели. Основной ключ по умолчанию — это колонка id. Она состоит из целого числа, которое автоматически инкрементируется. Эта колонка соотносится с полем id, которое автоматически добавляется моделям.
Синхронизируем базу данных с новой моделью. Для этого нужно ввести следующую команду:
python manage.py migrate
Вывод будет такой:
Applying blog.0001_initial... OK
Это применило миграции для приложений в INSTALLED_APPS, включая приложение blog. После применения база данных отображает текущее состояние моделей.
Если отредактировать файл models.py для добавления, удаления или изменения полей существующих моделей или при добавлении новых моделей нужно будет создавать новую миграцию с помощью команды makemigrations. Миграция позволит Django отслеживать изменения модели. Затем нужно будет использовать команду migrate, чтобы синхронизировать базу данных с моделями.
Начинать разработку дизайна схемы блога необходимо с определения моделей данных блога. Модель — это класс в Python, представляющий собой подкласс django.db.models.Model,_ в котором каждый атрибут — это поле базы данных. Django создаст таблицу для каждой модели, определенной в файле models.py. При создании модели Django предоставляет практический API, с помощью которого легко делать запросы к объектам в базе данных.
В первую очередь необходимо определить модель Post. Для этого в файл models.py приложения blog нужно добавить следующие строки:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
STATUS_CHOICES = (
('draft', 'Draft'),
('published', 'Published'),
)
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250,
unique_for_date='publish')
author = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='blog_posts')
body = models.TextField()
publish = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10,
choices=STATUS_CHOICES,
default='draft')
class Meta:
ordering = ('-publish',)
def __str__(self):
return self.title
Это модель данных для постов блога. Рассмотрим подробно каждый из полей в этой модели:
title — поле для заголовка поста. Это поле CharField, которое переводится в колонку VARCHAR в базе данных SQL.
slug — это поле используется в URL. Это короткий маркер, включающий только буквы, числа, нижние подчеркивания и дефисы. Он нужен для создания красивых URL, подходящих для SEO. Для этого поля также добавлен параметр unique_for_date, так что для постов будет использоваться дата их публикации вместе со slug. Django не позволит использовать один и тот же slug для нескольких постов в один день.
author — это поле представлено внешним ключом, определенным отношением многие к одному. Мы сообщаем Django, что каждый пост написан пользователем, и пользователь может написать любое количество постов. Для этого поля Django создаст внешний ключ в базе данных, основываясь на основном ключе связанной модели. В этом случае опираемся на модель User из системы аутентификации Django. Параметр on_delete определяет поведение, когда объект, на который ссылаются, оказывается удален. Это из стандартов SQL. С помощью CASCADE можно указать, что если пользователь удаляется, система удалит и все связанные с ним записи из блога. Все возможные варианты представлены здесь https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.ForeignKey.on_delete. Определить обратное отношение, от User к Post можно с помощью атрибута related_name. Это позволит легко получать доступ к связанным объектам. Речь об этом в подробностях пойдет дальше.
body — тело поста. Это текстовое поле, которое переводится в колонку TEXT в базе данных SQL.
publish — datetime определяет, когда пост был опубликован. По умолчанию используется метод now из timezone Django. Возвращаются текущие дата и время с учетом часового пояса. Этот подход напоминает метод datetime.now стандартной версии Python.
created — datetime указывает, когда пост был создан. Поскольку используется auto_now_add, дата сохранится автоматически при создании объекта.
updated — datetime указывает, когда пост был обновлен в последний раз. Поскольку используется auto_now, дата сохранится автоматически при обновлении объекта.
status — показывает статус поста. Используется параметр choices, поэтому значение поля может быть одним из представленных вариантов.
В Django есть несколько типов полей, которые можно использовать для моделей. Все эти типы полей перечислены здесь: https://docs.djangoproject.com/en/2.0/ref/models/fields/.
Класс Meta в модели содержит мета-данные. По умолчанию Django будет сортировать результаты в поле publish в обратном хронологическом порядке при запросе к базе данных. Обратный хронологический порядок задается с помощью отрицательного индекса. В таком случае новые посты будут появляться первыми.
Метод __str__() — это версия объекта в человекочитаемой форме. Django будет использовать ее во многих местах, например в административном сайте.
Если ранее вы пользовались Python 2.X, обратите внимание, что в_ Python 3 все строки по умолчанию закодированы в Unicode, поэтому используется метод __str__(). Метод __unicode__() устарел.
В руководствах по этому фреймворку вы будете встречать слова «проект» и «приложение». В Django проектом называется установка Django с определенными настройками. Приложение — это группа моделей, представлений, шаблонов и URL.
Приложение взаимодействует с фреймворком для предоставления различных возможностей. Его можно задействовать и в других наработках. Проектом можно считать сайт, которые включает приложения: блог, справочник или форум, которые найдут применение и в других проектах.
Создадим приложение блога на Django. В командной строке из корневого каталога проекта нужно ввести следующее:
python manage.py startapp blog
Это создаст стандартную структуру:
blog/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
Файлы в нем выполняют такие задачи:
admin.py — здесь регистрируются модели, которые будут использоваться в административном сайте Django (его необязательно использовать).apps.py — включает настройку blog.migrations — этот каталог включает миграции базы данных приложения. С их помощью Django сохраняет информацию об изменении моделей и соответственно синхронизирует базу данных.models.py — модели данных приложения. Все приложения Django должны иметь файл models.py, но он может оставаться пустым.tests.py — здесь можно добавлять тесты для приложения.views.py — здесь находится логика приложения. Каждое представление получает HTTP-запрос, обрабатывает его и возвращает ответ.Чтобы Django имел возможность отслеживать приложение и мог создавать для моделей базы данных, приложение нужно активировать. Для этого требуется отредактировать файл settings.py и добавить в INSTALLED_APP пункт blog.apps.BlogConfig. Теперь он должен выглядеть так:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog.apps.BlogConfig',
]
Класс BlogConfig — это настройка целого приложения. Теперь Django знает, что оно активно и сможет загружать его модели.
Запустим файл settings.py, чтобы ознакомиться с настройками. Там содержатся только некоторые из настроек Django. Все, включая их значения по умолчанию можно найти по ссылке https://docs.djangoproject.com/en/2.0/ref/settings/.
Вот на какие пункты рекомендуется обратить внимание в первую очередь:
DEBUG — это булево значение, которое активирует режим отладки. Django будет показывать страницы с детальным описанием ошибок при определенных исключениях. При переходу к рабочей среде обязательно нужно установить значение False. Нельзя разворачивать сайт с активированным режимом отладки, потому что это сделает общедоступными важные для проекта данные.
ALLOWED_HOSTS не работает с активированным режимом отладки или работающих тестах. Когда сайт в рабочем режиме, а значение DEBUG равняется False, этой настройке нужно передать домен/хост, чтобы он взаимодействовал с сайтом.
INSTALLED_APPS — раздел, который обязательно нужно редактировать. Он сообщает Django, какие приложения работают для конкретного сайта. Изначально Django включает следующие:
django.contrib.admin: административный сайтdjango.contrib.auth: фреймворк для аутентификацииdjango.contrib.contenttypes: фреймворк для обработки типов контентаdjango.contrib.sessions: фреймворк для работы с сессиямиdjango.contrib.messages: фреймворк сообщенийdjango.contrib.staticfiles: фреймворк для управления статичными файлами.MIDDLEWARE — список микропрограммных средств, которые будут запущены.
ROOT_URLCONF — указывает на модуль URL, где определены корневые URL-паттерны приложения.
DATABASES — Python-словарь с настройками баз данных проекта. Одна стандартная всегда должна присутствовать. Конфигурация по умолчанию использует SQLite3.
LANGUAGE_CODE — отвечает за настройку кода языка сайта Django по умолчанию.
USE_TZ — сообщает Django, что нужно включить/выключить поддержку часовых поясов. В Django есть встроенная поддержка модуля для работы с датой и временем, которая работает и с часовыми зонами. Она получает значение True, когда новый проект создается командой startproject.
Не волнуйтесь, если многое здесь покажется непонятным. Все пункты будут рассмотрены детально в других материалах.
]]>В Django есть веб-сервер, который нужен для быстрой проверки кода. Благодаря ему не нужно заниматься наладкой полноценного рабочего решения. При его запуске сервер продолжает проверять изменения в коде и самостоятельно перезагружается. Но некоторые вещи он не замечает: такие как появление новых файлов в проекте. В таком случае нужно перезагрузить сервер вручную.
Запустить сервер можно с помощью следующей команды в корневом каталоге:
python manage.py runserver
Появятся приблизительно такие строки:
Performing system checks...
System check identified no issues (0 silenced).
November 16, 2019 - 15:20:27
Django version 2.0.5, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Теперь нужно открыть https://127.0.0.1:8000/ в браузере. Страница сообщит, что проект работает. Как на следующем скриншоте:

Это изображение сообщает, что Django работает. Если взглянуть на консоль, то можно увидеть запрос GET от браузера:
[16/Nov/2019 15:22:45] "GET / HTTP/1.1" 200 16348
Каждый HTTP-запрос регистрируется отдельно. В командной строке будут отображаться все ошибки, которые появятся в процессе работы.
Можно запустить сервер на другом порте или использовать другой файл настроек с помощью таких команд:
python manage.py runserver 127.0.0.1:8001 --settings=mysite.settings
Работая с разными средами, требующими разных настроек, можно создать несколько файлов для каждой из них.
Этот сервер стоит использовать только для разработки, но не для полноценного использования. Чтобы развернуть Django в производственной среде (production) его нужно запустить в качестве WSGI-приложения с помощью реального инструмента: Apache, Gunicorn или uWSGI.
]]>В Django есть команда, которая позволяет создать базовую файловую структуру проекта. Напишите следующее в командной строке:
django-admin startproject mysite
Это создаст проект Django с именем mysite.
Не называйте проекты именами встроенных модулей
PythonилиDjango, чтобы избежать конфликтов.
Рассмотрим структуру проекта:
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
Она включает следующие файлы:
manage.py — это утилита для командной строки, которая используется для взаимодействия с проектом. Это тонкая оболочка для django-admin.py. Редактировать этот файл нельзя.mysite/ — это директория проекта, состоящая из следующих файлов:
__init__.py — пустой файл, который сообщает Python, что mysite нужно воспринимать как модуль Python.settings.py — включает настройки проекта с параметрами по умолчанию.urls.py — место хранения URL паттернов. Каждый определенный здесь URL используется для представления.wsgi.py — конфигурация для запуска проекта в виде приложения Web Server Gateway Interface (WSGI)Сгенерированный файл settings.py содержит настройки проекта, включая базовую конфигурацию для использования базы данных SQLite 3 и список INSTALLED_APPS, с основными приложениями Django. Они добавляются в проект по умолчанию. О них будет рассказано позже в статье “Настройки проекта”.
Чтобы завершить установку проекта, нужно создать таблицы базы данных, которые нужны для приложений, перечисленных в INSTALLED_APPS. Откройте командную строку и используйте следующие команды:
cd mysite
python manage.py migrate
Появится следующий вывод:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying sessions.0001_initial... OK
Эти строки обозначают миграции базы данных Django. Благодаря им создаются таблицы для базовых приложений в базе данных. О команде migrate речь пойдет в статье “Создание и использование миграций”.
Если Django уже установлен, можете пропустить этот раздел и переходить к части «Создание первого проекта». Django — это пакет Python, поэтому он может быть установлен в любой среде Python. Вот как установить фреймворк для локальной разработки.
Для Django 2.0 обязательны Python 3.4 или старше. Дальше будет использоваться Python 3.6.5. Для Linux или macOS, то Python, вероятно уже установлен. Если Windows — то инструкция по установке здесь.
Проверить установлен ли Python на компьютере можно, введя python в командной строке. Если в ответ отобразится что-то подобное, то Python установлен:
Python 3.7.3 (default, Mar 27 2019, 17:13:21)
[MSC v.1915 64 bit (AMD64)] :: Anaconda custom (64-bit) on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
Если он не установлен или установлена версия Python 3.4 или младше, то нужно перейти в раздел “Скачать и установить Python”, найти руководство под свою OS и следовать инструкциям.
Для Python 3 не нужна база данных. Эта версия Python поставляется со встроенной базой данных SQLite. Это облегченная база данных, которая подходит для разработки на Django. Если же нужно будет разворачивать приложение в производственной среде, то понадобится более продвинутое решение: PostgreSQL, MySQL или Oracle. Больше узнать о том, как заставить базу данных работать с Django, можно по этой ссылке: https://docs.djangoproject.com/en/2.0/topics/install/#database-installation.
Рекомендуется использовать virtualenv для создания виртуальной среды Python так, чтобы можно было спокойно использовать разные версии пакетов для разных проектов. Это практичнее, чем устанавливать пакеты в Python напрямую в систему. Еще одно преимущество virtualenv — для установки пакетов Python не нужны права администратора. Запустите следующую команду в командной строке для установки virtualenv:
pip install virtualenv
После установки virtualenv, создайте виртуальную среду с помощью следующей команды:
virtualenv my_env
Это создаст папку my_env вместе со средой Python. Любые библиотеки Python, установленные с активированной виртуальной средой Python, будут установлены в папку my_env/lib/python3.7/site-packages.
Если в системе была предустановлена Python 2.X, а вы установили Python 3.X, то нужно указать
virtualenv, чтобы он работал с последней версией.
Можно указать путь, по которому установлен Python 3 и использовать его для создания виртуальной среды с помощью следующих команд:
$ which python3 /Library/Frameworks/Python.framework/Versions/3.7/bin/python3
$ virtualenv my_env -p
/Library/Frameworks/Python.framework/Versions/3.7/bin/python3
Используйте следующую команду для активации виртуальной среды:
source my_env/bin/activate
Командная строка будет включать название активной виртуальной среды в скобках:
(my_env) username:~$
Отключить виртуальную среду можно с помощью команды deactivate.
Больше о virtualenv можно узнать по ссылке https://virtualenv.pypa.io/en/latest/.
Поверх virtualenv можно также использовать virtualenvwrapper. Этот инструмент предоставляет оболочки, с помощью которых проще создавать и управлять виртуальной средой. Загрузить его можно здесь: https://virtualenvwrapper.readthedocs.io/en/latest/.
Система управления пакетами pip — рекомендуемый способ установки Django. В Python 3.6+ она предустановлена, а инструкции для установки можно найти по ссылке https://pythonru.com/baza-znanij/ustanovka-pip-dlja-python-i-bazovye-komandy.
Используйте следующую команду в оболочке, чтобы установить Django с помощью pip:
pip install Django==2.0.5
Django установится в папку Python под названием site-packages/ активной виртуальной среды.
Теперь нужно проверить, успешно ли прошла установка. Для этого в командной строке необходимо ввести python, импортировать Django и проверить его версию следующим образом:
>>> import django
>>> django.get_version()
'2.0.5'
Если вывод такой, как вверху, значит Django был успешно установлен на компьютере:
]]>Django можно установить и другими способами. Полный гайд по установке можно найти здесь: https://docs.djangoproject.com/en/2.0/topics/install/.