Django — PythonRu https://pythonru.com Изучайте Python на русском: учебные руководства Python для разработчиков с разным уровнем знаний, рекомендации книг и курсов Python, новости, примеры кода, статьи и уроки Tue, 15 Jun 2021 08:47:16 +0000 ru-RU hourly 1 https://pythonru.com/wp-content/uploads/2018/11/cropped-pythonru-icon-32x32.png Django — PythonRu https://pythonru.com 32 32 Запуск Django-приложения в Docker контейнере https://pythonru.com/uroki/docker-django Fri, 11 Jun 2021 07:00:00 +0000 https://pythonru.com/?p=5680 Если вы занимаетесь программированием некоторое время, то наверняка слышали о Docker или о таком термине, как контейнеры. В этом руководстве разберемся с его значением как раз на примере Docker, а также рассмотрим, как контейнеризировать простое Django-приложение. В конце темы вы будете знать следующее:

  • Виртуализация
  • Контейнеризация (с помощью Docker)
  • Docker
  • Создание Dockerfile
  • Docker Compose
  • Настройка Django приложения в среде Docker с помощью Dockerfile и docker-compose

Условия

Чтобы справиться с этим руководством вам нужно иметь следующее:

  • Git/GitHub
  • PyCharm (или любой другой редактор кода)
  • Опыт работы с Django

Готовый репозиторий с Django-приложением, как всегда на GitLab: https://gitlab.com/PythonRu/django-docker.

Что такое виртуализация

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

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

Что такое контейнеры, и что в них такого особенного?

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

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

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

Введение в Docker

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

Установка Docker

Для установки Docker на компьютере, воспользуйтесь инструкцией с официального сайта. У каждой операционной системы есть своя версия приложения.

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

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

На ПК с установленным git перейдите в выбранную папку и клонируйте следующий репозиторий из GitLab:

git clone https://gitlab.com/PythonRu/django-docker.git

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

cd django-docker && code .
Запуск Django-приложения в Docker контейнере

В этой папке создайте файл Dockerfile (регистр играет роль) без формата. Внутри него будут находиться настройки контейнера. Инструкции из него компьютер будет выполнять каждый раз при запуске команды docker build.

Следующий этап — создание файла requirements.txt, в котором перечислены все зависимости. Позже он будет использован для Dockerfile, в котором также требуется указывать все требуемые зависимости.

В файле requirements.txt добавьте Django версии 3.1.2 в таком формате:

Django==3.1.2

Что такое Dockerfile

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

  1. Базовый образ, на основе которого требуется построить свой. Он выступает своего рода фундаментом для вашего приложения. Это может быть операционная система, язык программирования (Python в нашем случае) или фреймворк.
  2. Пакеты и дополнительные инструменты для образа.
  3. Скрипты и файлы, которые требуется скопировать в образ. Обычно это и есть исходный код приложения.

При чтении или написании такого файла удобно держать в голове следующее:

  • Строки с инструкциями обычно начинаются с ключевого слова, например: RUN, FROM, COPY, WORKDIR и так далее.
  • Комментарии начинаются с символа #. При выполнении инструкций из файла такие комментарии обычно игнорируются.

Создание Dockerfile

Приложение будет работать на основе официального образа 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 использует файл 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 в браузере, чтобы увидеть приложение в действии:

Запуск Django-приложения в Docker контейнере

Для закрытия контейнера используется команда docker-compose down.

Выводы

Репозиторий проекта: https://gitlab.com/PythonRu/django-docker.

В этом руководстве вы познакомились с виртуализацией, контейнеризацией и другими терминами из мира Docker. Также вы теперь знаете, что такое Dockerfile, как его создавать для запуска контейнеризированного Django-приложения. Наконец, разобрались с настройкой docker-compose с помощью файла docker-compose.yml для сервисов, от которых зависит самое приложения.

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

]]>
Django + AJAX: как использовать AJAX в шаблонах Django https://pythonru.com/primery/django-ajax Wed, 05 May 2021 07:15:00 +0000 https://pythonru.com/?p=5496 AJAX или асинхронный JavaScript и XML — это набор методов веб-разработки, использующих веб-технологии на стороне клиента для создания асинхронных веб-запросов.

Проще говоря, 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

Выполнение AJAX GET запросов с помощью Django и JQuery

Метод 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, вы также можете загрузить ее локально.

Затем у нас есть еще один скрипт с методом 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 в действии.

Выполнение AJAX POST запросов с помощью Django и JQuery

Метод 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-адресу. В случае успеха мы показываем диалоговое окно с сообщением, сгенерированным на основе полученного имени пользователя.

Выполнение AJAX POST запросов с использованием представлений на основе классов

Нам нужно просто вернуть объект 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)
]]>
Создаем API блога на Django REST Framefork https://pythonru.com/uroki/django-rest-api Wed, 07 Apr 2021 09:45:00 +0000 https://pythonru.com/?p=5351 API-сервисы позволяют приложениям общаться с другими приложения с помощью данных, передаваемых в формате JSON. Достаточно создать и использовать его данные из любого API-клиента или фронтенд-приложения.

Django REST Framework — это набор инструментов для создания REST API с помощью Django. В этом руководстве рассмотрим, как правильно его использовать. Создадим эндпоинты(точки доступа к ресурсам) для пользователей, постов в блоге, комментариев и категорий.

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

Вот чему вы научитесь:

  • Добавлять новые и существующие модели Django в API.
  • Сериализовать модели с помощью встроенных сериализаторов для распространенных API-паттернов.
  • Создавать представления и URL-паттерны.
  • Создавать отношения многие-к-одному и многие-ко-многим.
  • Аутентифицировать пользовательские действия.
  • Использовать созданный API Django REST Framework.

Код урока можно скачать в репозитории 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
Создание нового 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», чтобы увидеть пользователя и добавить новых при необходимости.

админ-панель сайта

Создание API для пользователей

Теперь с пользователем «admin» можно переходить к созданию самого API. Это предоставит доступ только для чтения списку пользователей из списка API-эндпоинтов.

Сериализатор для User

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 можно на официальном сайте.

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

Есть несколько способов создавать представления в 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.

Пути к эндпоинтам будут настроены на следующем шаге.

URL-паттерны

С моделью, сериализатором и набором представлений для 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, чтобы увидеть список пользователей приложения.

Django REST framework User List

В этом руководстве используется графическое представление API из Django REST Framework для демонстрации эндпоинтов. Интерфейс предоставляет элементы аутентификации и формы, имитирующие фронтенд-клиент. Для тестирования API также можно использовать cURL или httpie.

Обратите внимание на то, что значение пользователя admin равно 1. Можете перейти к нему, открыв для этого http://127.0.0.1:8000/users/1.

Django REST framework User Detail

В итоге класс модели Django сериализуется с помощью UserSerializaer. Он предоставляет данные представлениям UserList и UserDetail, доступ к которым можно получить с помощью паттернов users/ и users/<int:pk>.

Создание API для Post

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

Модель 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

Чтобы добавить модель 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

Следующий шаг — создать набор представлений для 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).

URL-паттерны Post

Чтобы закончить с эндпоинтами для 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.

Django REST framework Post Detail

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

Django REST framework Post List

Настройка разрешений

Для удобства добавим кнопку «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 и другие, чтобы проверить, какие действия доступны аутентифицированным и анонимным пользователям.

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

Django REST framework Log Out

Создание API для Comments

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

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

Модель Comment

Сначала создайте модель в 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

Сериализатор Comment

Для создания 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.

URL-Паттерны комментариев

Чтобы закончить 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 можете увидеть список существующих комментариев и создавать новые.

Django REST framework Comment List

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

Создание API для Category

Финальный элемент блога — система категорий.

Пост может принадлежать к одной или нескольким категориям. Также одна категория может принадлежать нескольким постам, значит это отношение многие-ко-многим.

Модель категории

Создайте модель 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

Процесс создания похож на описанный в прошлых шагах. Сперва создайте сериализатор для 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]

По аналогии с прошлыми.

URL-паттерны категорий

Наконец, добавьте этот код в 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 создайте пост и выберите для него категории.

Django REST framework Categories

Выводы

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

Чтобы расширить возможности своего API, переходите к официальной документации Django REST Framework. А ссылку на код этого урока можно найти в начале статьи.

]]>
Как настроить Celery в Django https://pythonru.com/primery/django-celery Tue, 16 Mar 2021 08:30:00 +0000 https://pythonru.com/?p=5162 В этом руководстве по использованию Celery совместно с Django я расскажу:

  1. Как настроить Celery с Django.
  2. Как протестировать Celery-задачу в Django-оболочке.
  3. Где контролировать работу Celery-приложения.

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

Зачем приложению на Django нужен Celery

Celery нужен для запуска задач в отдельном рабочем процессе (worker), что позволяет немедленно отправить HTTP-ответ пользователю в веб-процессе (даже если задача в рабочем процессе все еще выполняется). Цикл обработки запроса не будет заблокирован, что повысит качество взаимодействия с пользователем.
Ниже приведены некоторые примеры использования Celery:

  • Вы создали приложение с функцией отправки комментариев, в которых пользователь может использовать символ @, чтобы упомянуть другого пользователя, после чего последний получит уведомление по электронной почте. Если пользователь упоминает 10 человек в своем комментарии, веб-процессу необходимо обработать и отправить 10 электронных писем. Иногда это занимает много времени (сеть, сервер и другие факторы). В данном случае Celery может организовать отправку писем в фоновом режиме, что в свою очередь позволит вернуть HTTP-ответ пользователю без ожидания.
  • Нужно создать миниатюру загруженного пользователем изображения? Такую задачу стоит выполнить в рабочем процессе.
  • Вам необходимо делать что-то периодически, например, генерировать ежедневный отчет, очищать данные истекшей сессии. Используйте Celery для отправки задач рабочему процессу в назначенное время.

Когда вы создаете веб-приложение, постарайтесь сделать время отклика не более, чем 500мс (используйте New Relic или Scout APM), если пользователь ожидает ответа слишком долго, выясните причину и попытайтесь устранить ее. В решении такой проблемы может помочь Celery.

Celery или RQ

RQ (Redis Queue) — еще одна библиотека Python, которая решает вышеуказанные проблемы.
Логика работы RQ схожа с Celery (используется шаблон проектирования производитель/потребитель). Далее я проведу поверхностное сравнение для лучшего понимания, какой из инструментов более подходит для задачи.

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

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

Брокер сообщений и бэкенд результатов

Брокер сообщений — это хранилище, которое играет роль транспорта между производителем и потребителем.
Из документации Celery рекомендуемым брокером является RabbitMQ, потому что он поддерживает AMQP (расширенный протокол очереди сообщений).

Так как во многих случаях нам не нужно использовать AMQP, другой диспетчер очереди, такой как Redis, также подойдет.

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

Здесь рекомендуется использовать Redis.

Как настроить Celery

Celery не работает на Windows. Используйте Linux или терминал Ubuntu в Windows.

Далее я покажу вам, как импортировать Celery worker в ваш Django-проект.

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

Используйте Docker для подготовки среды разработки

Если вы работаете в Linux или Mac, у вас есть возможность использовать менеджер пакетов для настройки Redis (brew, apt-get install), однако я хотел бы порекомендовать вам попробовать применить Docker для установки сервера redis.

  1. Вы можете скачать Docker-клиент здесь.
  2. Затем попробуйте запустить службу Redis $ docker run -p 6379: 6379 --name some-redis -d redis

Команда выше запустит Redis на 127.0.0.1:6379.

  1. Если вы намерены использовать RabbitMQ в качестве брокера сообщений, вам нужно изменить только приведенную выше команду.
  2. Закончив работу с проектом, вы можете закрыть Docker-контейнер — окружение вашей рабочей машины по-прежнему будет чистым.

Теперь импортируем Celery в наш Django-проект.

Создание 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.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

Файл __init__.py

Давайте продолжим изменять проект, в celery_django/__init__.py добавьте.

from __future__ import absolute_import, unicode_literals

# Это позволит убедиться, что приложение всегда импортируется, когда запускается Django
from .celery import app as celery_app

__all__ = ('celery_app',)

Дополнение settings.py

Поскольку 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:

  1. app.config_from_object('django.conf: settings', namespace = 'CELERY') сообщает Celery, чтобы он считывал значение из пространства имен CELERY, поэтому, если вы установите просто broker_url в своем файле настроек Django, этот параметр не будет работать. Правило применяется для всех ключей конфигурации в документации Celery.
  2. Некоторые конфигурационные ключи различаются между Celery 3 и Celery 4, так что, пожалуйста, загляните в документацию при настройке.

Отправка заданий 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)

Рассмотрим некоторые моменты:

  1. Мы используем xxx.delay для отправки сообщения брокеру. Рабочий процесс получает эту задачу и выполняет ее.
  2. Когда вы нажимаете клавишу enter для ввода task = add.delay(1, 2), кажется, что команда быстро завершает выполнение (отсутствие блокировки), но метод добавления все еще активен в рабочем процессе Celery.
  3. Если вы проверите вывод терминала, где был запущен 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.
Мы можем использовать его следующим образом:

  1. Проверить состояние задачи.
  2. Узнать возвращенное значение (результат) или сведения об исключении.
  3. Получить другие метаданные.

Мониторинг Celery с помощью Flower

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

URL-адрес панели управления: http://127.0.0.1:5555/. Откройте страницу задач — Tasks.

Как настроить Celery в Django


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

Заключение

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

]]>
Настройка и подключение статических файлов в Django https://pythonru.com/uroki/django-static Sat, 23 Jan 2021 15:33:10 +0000 https://pythonru.com/?p=4562 Если вы искали способ добавить изображения, стили и js в свой проект на Django, то пришли по адресу.

Что такое статические файлы в Django?
Изображения, JS и CSS-файлы называются статическими файлами или ассетами проекта Django.

Код из урока: https://gitlab.com/PythonRu/django_static

1. Создадим папку для статических файлов

В папке проекта Django создайте новую папку «static». В примере выше она находится в директории «dstatic».

Создадим папку для статических файлов

2. Укажите статический URL Django в настройках

Теперь убедитесь, что статические файлы 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 файлы.

3. Создайте папку для изображений

Создайте папку в «static» специально для изображений. Назовите ее «img». Главное после этого правильно ссылаться на нее в шаблонах.

Создайте папку для изображений

4. Загрузите статический файл в свой HTML-шаблон

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

Загрузите статический файл в свой HTML-шаблон

Важно добавить {% load static %} до того, как добавлять изображение. В противном случае оно не будет загружено.

Использование тега 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. Результат:

Настройка и подключение статических файлов в Django

5. Создание папки и файла JavaScript

Если нужно добавить кастомные JS-файлы в проект, создайте папку «js» внутри «static».

Можно также использовать элемент <script> внутри шаблона, но создание отдельного JS-файла улучшит организацию проекта и поможет проще находить все скрипты в одном месте.

Создание файла 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");
    }
});

6. Загрузите скрипт в шаблон

Теперь для подключения 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>

7. Создание папки и файла для CSS

Также можно подключить CSS-файлы. Для этого создайте папку «css» внутри «static». Вы также можете использовать элемент <style> и вложить все стили туда.

Создание папки и файла для CSS

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

Создание файла стилей css

Создайте файл «stylesheet.css» в static > css. Там будут храниться все ваши стили.

.custom {
    color:#007bff;
    background:#000000;
    font-size:20px;
}

8. Загрузите ссылку на CSS в шаблон

Для подключения собственных стилей к проекту, добавьте 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 вы увидите изменения:

Загрузите ссылку на CSS в шаблон

Ошибки, связанные с загрузкой статических файлов Django

Если вы получили ошибку «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' %}"> {# верно #}
]]>
Отправка писем с формы в Django 3.0 https://pythonru.com/primery/otpravka-pisem-s-formy-v-django Sat, 03 Oct 2020 12:51:51 +0000 https://pythonru.com/?p=3636 В этом материале создадим простую контактную форму для отправки электронных сообщений с сайта Django 3.0. Воспользуемся возможностями встроенной поддержки email, что позволит без лишних усилий отправлять электронные сообщения, используя сервис SendGrid.

Архив с проектом 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, то должно появиться приблизительно следующее:

Первоначальная настройка

Обновление settings.py

Созданное приложение теперь нужно явно добавить в проект 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'

Обновление urls.py

После добавления приложения в проект 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'),
]

Создание forms.py

Внутри приложения 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 для быстрого создания трех полей.

Создание views.py

Создадим представление, которое будет выполнять основную работу для контактной формы. Обновим существующий файл 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>

Email-сервис

Для реальной отправки сообщений нужно настроить сервис: SendGrid, mailgun или SES. К счастью, в Django их очень легко использовать.

Для использования SendGrid нужно создать бесплатный аккаунт и выбрать вариант SMTP. Его легче настраивать при работе с Web API.

Необходимо выполнить верификацию отправителя. Инструкции доступны по этой ссылке. Если раньше можно было отправлять сообщения с бесплатных адресов (gmail.com или yahoo.com), но теперь это не будет работать из-за протокола email-аутентификации DMARC. Поэтому для реальной отправки сообщений нужно почту на своем домене, владение которой придется подтвердить.

Отправка писем с формы в Django 3.0

После добавления отправителя на странице https://app.sendgrid.com/settings/sender_auth/senders/new, подтвердите почту и переходите к настройке Web API https://app.sendgrid.com/guide/integrate/langs/python.

Следующее окно требует указать название для API-ключа. После этого нужно нажать на Create Key.

Отправка писем с формы в Django 3.0

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

Отправка писем с формы в Django 3.0

Дальше обновим файл 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 3.0

Выводы

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

]]>
Блог на Django #35: Поисковые алгоритмы в Django https://pythonru.com/primery/blog-na-django-35-poiskovye-algoritmy-v-django Thu, 13 Feb 2020 12:13:49 +0000 https://pythonru.com/?p=2079

Сокращение и ранжирование результатов

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.4body: совпадения в заголовке будут преобладать над таковыми в теле. Отфильтруем результаты для отображения только тех, у которых вес больше 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/.

]]>
Блог на Django #34: Создание представления поиска https://pythonru.com/primery/blog-na-django-34-sozdanie-predstavlenija-poiska Tue, 11 Feb 2020 11:08:36 +0000 https://pythonru.com/?p=2076

Теперь нужно создать представление для того, чтобы пользователи могли осуществлять поиск по постам. В первую очередь нужна форма поиска. Отредактируйте файл 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 #33: Добавление поиска по блогу https://pythonru.com/primery/blog-na-django-33-dobavlenie-poiska-po-blogu Sun, 09 Feb 2020 16:15:20 +0000 https://pythonru.com/?p=2073

Добавим поисковые возможности в блог. 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.

]]>
Блог на Django #32: Установка PostgreSQL https://pythonru.com/primery/blog-na-django-32-ustanovka-postgresql Sat, 08 Feb 2020 14:25:16 +0000 https://pythonru.com/?p=2070

Сейчас для проекта используется 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 #31: Создание RSS-ленты блога https://pythonru.com/primery/blog-na-django-31-sozdanie-rss-lenty-bloga Thu, 06 Feb 2020 14:50:58 +0000 https://pythonru.com/?p=2027

В 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/ в браузере и посмотрите на сайдбар. Ссылка должна вести на ленту блога:

Ссылка для подписки на RSS в Django

]]>
Блог на Django #30: Добавление карты сайта https://pythonru.com/primery/blog-na-django-30-dobavlenie-karty-sajta Wed, 05 Feb 2020 14:36:31 +0000 https://pythonru.com/?p=2024

В 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 для sitemap в локальной среде

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

]]>
Блог на Django #29: Создание собственных шаблонных фильтров https://pythonru.com/primery/blog-na-django-29-sozdanie-sobstvennyh-shablonnyh-filtrov Mon, 03 Feb 2020 12:51:43 +0000 https://pythonru.com/?p=2014

В 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/)

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

Разметка Markdown в постах блога на Django

Как можно видеть на скриншоте, собственные фильтры шаблонов очень удобны для форматирования. Больше об этой теме здесь: https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/#writing-custom-template-filters.

]]>
Блог на Django #28: Создание тегов шаблонизатора https://pythonru.com/primery/blog-na-django-28-sozdanie-tegov-shablonizatora Sat, 25 Jan 2020 16:49:57 +0000 https://pythonru.com/?p=1990

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/.

]]>
Блог на Django #27: Получение похожих постов https://pythonru.com/primery/blog-na-django-27-poluchenie-pohozhih-postov Sat, 18 Jan 2020 10:52:51 +0000 https://pythonru.com/?p=1949

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

Для получения похожих постов нужно совершить следующие шаги:

  1. Получить все теги текущего поста.
  2. Получить все посты, к которым проставлены любые из этих же тегов.
  3. Исключить текущий материал, чтобы не рекомендовать его же.
  4. Отсортировать результаты по количеству общих тегов с текущим постом.
  5. Если тегов два или более, рекомендовать самый последний.
  6. Ограничить количество рекомендуемых постов.

Эти шаги превращаются в сложный 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]

Этот код выполняет следующее:

  1. Получаем список Python с ID тегов текущего поста. QuerySet values_list() возвращает кортеж со значениями для заданных полей. Передаем flat=True, чтобы получить список в формате [1, 2, 3, ...].
  2. Получаем все посты с одним из этих тегов, не включая текущий.
  3. Используем функцию агрегации Count для генерации поля same_tags, которое содержит количество общих тегов.
  4. Сортируем результаты по количество общих тегов (в порядке убывания) по 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

Теперь можно рекомендовать похожие посты пользователям. django-taggit также включает менеджер similar_objects(), который можно использовать для получения постов с общими тегами. Посмотреть на все менеджеры django-taggit можно здесь: https://django-taggit.readthedocs.io/en/latest/api.html.

Также можно добавить список тегов на страницу поста по принципу шаблона blog/post/list.html.

]]>
Блог на Django #26: Добавление системы тегов https://pythonru.com/primery/blog-na-django-26-dobavlenie-sistemy-tegov Sat, 04 Jan 2020 12:37:05 +0000 https://pythonru.com/?p=1864

После создания системы комментариев пришло время реализовать теги для постов. Сделаем это с помощью интеграции стороннего приложения 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:

админ-страница со списком объектов Tag

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

 Посты теперь включают поле 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 работает следующим образом:

  1. Оно принимает опциональный параметр tag_slug со значением по умолчанию None. Он будет в URL.
  2. В представлении создается первый QuerySet, который получает все опубликованные посты. Если slug указан, то объект Tag можно получить через него с помощью get_object_or_404().
  3. Затем список фильтруется, чтобы остались только те, что включают тег. Это отношение многое-ко-многим, поэтому фильтровать нужно по тегам в списке, который в этом случае содержит всего один элемент.

Стоит напомнить, что 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/ в браузере и нажмите на ссылку тега. Появится список постов с этим тегом:

список постов с тегом "jazz"

]]>
Блог на Django #25: Добавление комментариев в шаблон поста https://pythonru.com/primery/blog-na-django-25-dobavlenie-kommentariev-v-shablon-posta Wed, 01 Jan 2020 11:14:00 +0000 https://pythonru.com/?p=1856

Функциональность для управления комментариями поста уже готова. Теперь нужно адаптировать шаблон 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/ в браузере и кликните на пост, чтоб открыть его. Появится следующее:
Добавление комментария на сайт Django

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

Несколько комментариев на сайт Django

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

Управление комментариями из админ-панели

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

]]>
Блог на Django #24: Создание и обработка форм из моделей https://pythonru.com/primery/blog-na-django-24-sozdanie-i-obrabotka-form-iz-modelej Mon, 30 Dec 2019 09:29:13 +0000 https://pythonru.com/?p=1850

Все еще нужна форма, чтобы пользователи могли оставлять комментарии к записям. В 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, потому что это единственное, что нужно заполнять.

Обработка ModelForms в представлениях

Для простоты представление поста будет использоваться для создания экземпляра формы и ее обработки. Отредактируйте файл 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(). Если форма неверна, рендерится шаблон с ошибками проверки. Если правильная — выполняются следующие действия:

  1. Создается новый объект Comment посредством вызова метода формы save() и присваивания его переменной new_comment следующим образом:
    new_comment = comment_form.save(commit=False)
    

    Метод save() создает экземпляр модели, к которой привязана форма и сохраняет ее в базу данных. Если ее вызвать с помощью commit=False, то экземпляр будет создан, но сохранение в базу данных не состоится. Это удобно, когда нужно изменить объект перед сохранением. А это следующий этап.

    Метод save() доступен для ModelForm, но не для экземпляров Form, потому что они не привязаны ни к одной модели.

  2. Текущий пост присваивается созданному комментарию:
    new_comment.post = post
    

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

  3. Наконец, новый комментарий сохраняется в базу данных через метод save():
    new_comment.save()
    

Представление готово отображать и обрабатывать новые комментарии.

]]>
Блог на Django #23: Создание системы комментариев https://pythonru.com/primery/blog-na-django-23-sozdanie-sistemy-kommentariev Sat, 28 Dec 2019 09:29:03 +0000 https://pythonru.com/?p=1847

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

  1. Создать модель для сохранения комментариев.
  2. Создать форму для отправки комментариев и проверки введенных данных.
  3. Добавить представление, которое будет обрабатывать форму и сохранять комментарий в системе.
  4. Отредактировать шаблон поста для отображения списка комментариев и формы для добавления нового.

Сначала создадим модель для сохранения комментариев. Откройте файл 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, как на скриншоте:

новая модель отображается в разделе BLOG

Модель зарегистрирована в административном сайте, а это значит, что экземплярами Comment можно управлять с помощью простого интерфейса.

]]>
Блог на Django #22: Рендеринг форм в шаблонах https://pythonru.com/primery/blog-na-django-22-rendering-form-v-shablonah Wed, 25 Dec 2019 11:19:56 +0000 https://pythonru.com/?p=1836

После создания формы и представления, а также добавления 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/ в браузере. Нажмите на название любого поста, под ним будет добавленная ссылка:

Добавление "поделиться" в пост Django

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

страница с формой Django

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

Если данные неверные, форма отрендерится заново с ошибками проверки.

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

форма не отправлена, браузер отобразил ошибку

Форма чтобы делиться постами через email готова. Дальше создадим систему комментариев для блога.

]]>
Блог на Django #21: Отправка email https://pythonru.com/primery/blog-na-django-21-otpravka-email Mon, 23 Dec 2019 10:38:42 +0000 https://pythonru.com/?p=1831

Отправлять электронные письма с помощью 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 нужно активировать следующий пункт:

отправка email в Django через Gmail

Теперь необходимо добавить функциональность представлению:

Отредактируйте представление 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'), 
]
]]>
Блог на Django #20: Обработка форм в представлениях https://pythonru.com/primery/blog-na-django-20-obrabotka-form-v-predstavlenijah Sat, 21 Dec 2019 10:38:36 +0000 https://pythonru.com/?p=1828

Необходимо создать новое представление, которое обрабатывает форму и отправляет 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'.

Дальше процесс отображения и обработки формы:

  1. Когда представление изначально загружается с запросом GET, создается новый экземпляр form, который будет использоваться для отображения пустой формы в шаблоне:
    form = EmailPostForm()
    
  2. Пользователь заполняет форму и отправляет ее с помощью POST. Затем создается экземпляр формы с помощью отправленных данных, которые хранятся в request.POST:
    if request.method == 'POST': 
        # Форма была отправлена 
        form = EmailPostForm(request.POST)
    
  3. После этого отправленные данные проверяются с помощью метода формы is_valid(). Он проверяет данные и возвращает True, если все поля содержат прошедшие проверку данные. Если где-то указана неправильная информация, метод is_valid() возвращает False. Список ошибок проверки можно посмотреть, получив доступ к form.errors.
  4. Если форма не прошла проверку, то она снова рендерится в шаблоне с новыми данными. Ошибки проверки отображаются в шаблоне.
  5. Если проверка пройдена, доступ к данным можно получить с помощью form.cleaned_data. Этот атрибут является словарем, где его значения — это поля формы.

Если форма не прошла проверку, в cleaned_data будут только те поля, данные которых подошли.

Теперь нужно узнать, как с помощью Django отправлять email.

]]>
Блог на Django #19: Создание форм, для отправки email https://pythonru.com/primery/blog-na-django-19-sozdanie-form-dlja-otpravki-email Wed, 18 Dec 2019 12:08:08 +0000 https://pythonru.com/?p=1822

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

  • Отправка email с помощью Django
  • Создание форм и обработка их в представлениях
  • Создание форм из моделей
  • Интеграция сторонних приложений
  • Построение сложных QuerySet

Делиться постами

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

  • Создать форму для пользователей, где они будут указывать свои имя и электронный адрес, email получателя и дополнительные комментарии.
  • Создать представление в файле views.py, которое будет обрабатывать данные и отправлять сообщение.
  • Добавить URL-шаблон для нового представления в файле urls.py приложения блога.
  • Создать шаблон для отображения формы.

Создание форм в Django

Начнем с создания формы, используемой чтобы делиться постами. В 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 для каждого приложения.

Тип поля nameCharField. Он отрисовывается как 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/.

]]>
Блог на Django #18: Использование представлений, основанных на классах https://pythonru.com/primery/blog-na-django-18-ispolzovanie-predstavlenij-osnovannyh-na-klassah Mon, 16 Dec 2019 10:41:57 +0000 https://pythonru.com/?p=1819

Представления, основанные на классах, — это альтернативный способ внедрять представления в виде объектов, а не функций Python. Поскольку представление — это вызываемый объект, принимающий веб-запрос и возвращающий ответ, его можно определить в виде методов класса. Django предоставляет базовые классы представлений для этого. Они все наследуют класс View, который обрабатывает HTTP метод направления и другие функции.

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

  • Код, связанный с методами 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, а не получать все объекты. Вместо определения атрибута 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 #17: Добавление пагинации https://pythonru.com/primery/blog-na-django-17-dobavlenie-paginacii Sat, 14 Dec 2019 10:31:36 +0000 https://pythonru.com/?p=1815

Добавляя контент на блог, вы быстро придете к выводу, что список постов лучше делить на несколько страниц. В 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})

Вот как работает этот класс:

  1. Создается экземпляр класса Paginator с количеством объектов, которые нужно отображать на одной странице.
  2. Получаем параметр page GET, который указывает на текущую страницу.
  3. Получаем объекты для нужной страницы, вызывая метод page() метода Paginator.
  4. Если параметр page — это не целое число, возвращаем первую страницу результатов. Если оно больше последней страницы результатов, возвращаем последнюю.
  5. Передаем шаблону номер страницы и полученные объекты.

Теперь нужно создать шаблон для отображения пагинатора так, чтобы он мог использоваться в любом шаблоне с пагинацией. В папке 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/ в браузере. Вы увидите элементы навигации по страницам в нижней части и сможете перемещаться по ним.

Элементы навигации по страницам django

]]>
Блог на Django #16: Создание шаблонов для представлений https://pythonru.com/primery/blog-na-django-16-sozdanie-shablonov-dlja-predstavlenij Wed, 11 Dec 2019 12:12:26 +0000 https://pythonru.com/?p=1807

Итак, представления и 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. Появится следующее:
Отображение постов в django

Теперь нужно отредактировать файл 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 %}

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

Отображение поста в django

И посмотрите на URL — его структура должны быть приблизительно такой /blog/2019/11/23/who-was-django-reinhardt/. Это значит, что ссылки подходят для SEO-оптимизации.

]]>
Блог на Django #15: Добавление URL-шаблонов в представления https://pythonru.com/primery/blog-na-django-15-dobavlenie-url-shablonov-v-predstavlenija Mon, 09 Dec 2019 11:00:30 +0000 https://pythonru.com/?p=1801

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 для моделей

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

]]>
Блог на Django #14: Создание представлений (views) https://pythonru.com/primery/blog-na-django-14-sozdanie-predstavlenij-views Sat, 07 Dec 2019 10:35:47 +0000 https://pythonru.com/?p=1798

Начнем с создания представления для отображения списка постов. Отредактируйте файл 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() для отрисовки полученного поста с помощью шаблона.

]]>
Блог на Django #13: Создание менеджеров модели https://pythonru.com/primery/blog-na-django-13-sozdanie-menedzherov-modeli Fri, 06 Dec 2019 15:02:00 +0000 https://pythonru.com/?p=1784

Как уже упоминалось, 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')
]]>
Блог на Django #12: Методы QuerySet https://pythonru.com/primery/blog-na-django-12-metody-queryset Wed, 04 Dec 2019 14:48:00 +0000 https://pythonru.com/?p=1781

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()

Метод filter()

Чтобы отфильтровать 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()

Можно исключить некоторые результаты из запроса с помощью метода менеджера exclude(). Например, можно получить все посты за 2017 год, названия которых не начинаются с Why:

Post.objects.filter(publish__year=2017) \
	    .exclude(title__startswith='Why')

Метод order_by()

Результаты можно отсортировать по полям с помощью метода менеджера 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 можно объединить несколько фильтров, но они не коснутся базы данных, до тех пор пока QuerySet не будет выполнен. А он исполняется в следующих условиях:

  • При первой итерации по QuerySet.
  • При получении среза, например Post.objects.all()[:3].
  • При сериализации или кэшировании объектов.
  • При вызове repr() или len().
  • При прямом вызове list().
  • При проверке QuerySet в других выражениях, например bool(), or, and или if.
]]>
Блог на Django #11: Создание и обновление объектов https://pythonru.com/primery/blog-na-django-11-sozdanie-i-obnovlenie-obektov Mon, 02 Dec 2019 13:32:00 +0000 https://pythonru.com/?p=1778

Теперь, когда есть полноценный административный сайт для управления контентом из блога, пришло время узнать, как получать информацию из базы данных и взаимодействовать с ней. В 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().

]]>
Блог на Django #10: Настройка отображения модели https://pythonru.com/primery/blog-na-django-10-nastrojka-otobrazhenija-modeli Sat, 30 Nov 2019 13:15:35 +0000 https://pythonru.com/?p=1774

Рассмотрим, как изменить внешний вид списка объектов модели в админ-панели. Отредактируйте файл 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')

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

Отредактированная админ-панель Django

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

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

поле author с виджетом поиска

Всего парой строк кода удалось изменить способ отображения модели в административной панели. Есть еще много способов изменения внешнего вида и расширения возможностей списка объектов модели в админ-панели Django.

]]>
Блог на Django #9: Добавление моделей в админ-панель https://pythonru.com/primery/blog-na-django-9-dobavlenie-modelej-v-admin-panel Fri, 29 Nov 2019 16:38:00 +0000 https://pythonru.com/?p=1769

Добавим модели блога на административный сайт. Отредактируйте файл 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. Вы должны быть перенаправлены на страницу списка постов с подтверждением добавления поста, как показано ниже:

Страницу списка постов

]]>
Блог на Django #8: Создание админ-панели для моделей https://pythonru.com/primery/blog-na-django-8-sozdanie-admin-paneli-dlja-modelej Wed, 27 Nov 2019 15:41:00 +0000 https://pythonru.com/?p=1762

Теперь, когда модель 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.

Админ-панель Django

Запустить сервер разработки можно с помощью команды python manage.py runserver. Дальше нужно открыть https://127.0.0.1:8000/admin/ в браузере. Отобразится страница авторизации как на скриншоте:

страница авторизации django

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

стартовая страница админ-панели django

Модели Group и User — это элементы фреймворка аутентификации Django, которые расположены в django.contrib.auth. Если нажать на Users, вы увидите созданного пользователя. Модель Post приложения blog связана с моделью User. Запомните, что отношение определяется полем author.

]]>
Блог на Django #7: Создание и применение миграций https://pythonru.com/primery/blog-na-django-7-sozdanie-i-primenenie-migracij Mon, 25 Nov 2019 14:57:00 +0000 https://pythonru.com/?p=1757

Теперь, когда есть модель данных для постов в блоге, нужна таблица базы данных. В 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, чтобы синхронизировать базу данных с моделями.

]]>
Блог на Django #6: Разработка схемы блога https://pythonru.com/primery/blog-na-django-6-razrabotka-shemy-bloga Sat, 23 Nov 2019 14:27:02 +0000 https://pythonru.com/?p=1754

Начинать разработку дизайна схемы блога необходимо с определения моделей данных блога. Модель — это класс в 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.

  • publishdatetime определяет, когда пост был опубликован. По умолчанию используется метод 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 #5: Создание и активация приложения https://pythonru.com/primery/blog-na-django-5-sozdanie-i-aktivacija-prilozhenija Tue, 19 Nov 2019 14:51:00 +0000 https://pythonru.com/?p=1741

В руководствах по этому фреймворку вы будете встречать слова «проект» и «приложение». В 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 знает, что оно активно и сможет загружать его модели.

]]>
Блог на Django #4: Настройки проекта https://pythonru.com/primery/blog-na-django-4-nastrojki-proekta Sat, 16 Nov 2019 14:42:00 +0000 https://pythonru.com/?p=1737

Запустим файл 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 #3: Запуск сервера https://pythonru.com/primery/blog-na-django-3-zapusk-servera Sat, 16 Nov 2019 14:16:03 +0000 https://pythonru.com/?p=1733

В 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 #2: Создание проекта https://pythonru.com/primery/blog-na-django-2-sozdanie-proekta Mon, 11 Nov 2019 15:14:41 +0000 https://pythonru.com/?p=1728

В 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 #1: Установка Django 2.0 https://pythonru.com/primery/blog-na-django-1-ustanovka-django-2 Sat, 09 Nov 2019 14:53:02 +0000 https://pythonru.com/?p=1725

Если 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.

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

Рекомендуется использовать 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/.

Установка Django с помощью pip

Система управления пакетами 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/.

]]>