Мои проекты

Комментарии к товарам и другим Django-моделям

Полный пример модели Product, view, адаптера и разрешения гостевой почты.

Урок 17 / 22 · Commentail 1.3.2

Этот урок для проекта, где нужна дискуссия у объекта, который не является Wagtail Page: например, товара. Сначала пройдите обычное подключение из урока 2. Ниже пример отдельного приложения shop; если у вас уже есть Product, адаптируйте существующую модель, не создавайте вторую.

Модель и страница товара

В новом учебном проекте создайте приложение python manage.py startapp shop, добавьте shop в INSTALLED_APPS. В shop/models.py:

python
from django.db import models
from django.urls import reverse


class Product(models.Model):
    site = models.ForeignKey("wagtailcore.Site", on_delete=models.CASCADE, related_name="shop_products")
    title = models.CharField(max_length=200)
    is_public = models.BooleanField(default=False)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("product-detail", args=[self.pk])

В shop/views.py:

python
from django.http import Http404
from django.shortcuts import get_object_or_404, render
from wagtail.models import Site
from shop.models import Product


def product_detail(request, pk):
    site = Site.find_for_request(request)
    if site is None:
        raise Http404
    product = get_object_or_404(Product, pk=pk, site=site)
    if not product.is_public and not request.user.is_staff:
        raise Http404
    return render(request, "shop/product_detail.html", {"product": product})

Site в запросе и Site товара должны совпадать. Проверка страницы товара нужна отдельно от проверки Commentail. В корневом urls.py импортируйте path, если его ещё нет, и добавьте до Wagtail catch-all:

python
from shop.views import product_detail

# Insert before Wagtail's catch-all in the existing urlpatterns.
path("products/<int:pk>/", product_detail, name="product-detail"),

Адаптер и доступ к почте

В shop/comments.py:

python
from shop.models import Product


class ProductAdapter:
    def get_site(self, obj, request):
        return obj.site

    def can_view(self, obj, request):
        return obj.is_public or request.user.is_staff

    def get_url(self, obj, site):
        return obj.get_absolute_url()


def can_send_product_email(recipient, target, site):
    """Allow guest mail only for this Site's public products."""
    return (
        isinstance(target, Product)
        and target.site_id == site.pk
        and target.is_public
    )

В default существующего COMMENTAIL:

python
COMMENTAIL = {
    "default": {
        "OBJECT_ADAPTERS": {"shop.Product": "shop.comments.ProductAdapter"},
        "CAN_SEND_GUEST_EMAIL": "shop.comments.can_send_product_email",
    },
}

Этот callback разрешает гостевые письма только для публичных товаров нужного Site. Если в проекте несколько типов объектов или закрытые Page, расширьте единую функцию с отдельными проверками, а не заменяйте правило безусловным True. Для обычных аккаунтов обработчик проверяет адаптер с учётной записью получателя и пустой сессией; браузерные подтверждения в нём недоступны.

Чтобы пример работал и на учебном сайте без существующего base.html, создайте templates/course_base.html:

django
{% load i18n commentail_tags %}
{% get_current_language as current_language %}
<!doctype html>
<html lang="{{ current_language }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ product.title }}</title>
  {% commentail_css %}
  {% commentail_js %}
</head>
<body>
  <main>{% block content %}{% endblock %}</main>
</body>
</html>

В templates/shop/product_detail.html:

django
{% extends "course_base.html" %}
{% load commentail_tags %}
{% block content %}
  <h1>{{ product.title }}</h1>
  {% commentail product %}
{% endblock %}

На своём сайте вместо course_base можно наследовать существующий базовый шаблон с ресурсами Commentail и его блоком content. Создайте таблицы python manage.py makemigrations shop, затем python manage.py migrate и python manage.py check.

Создадим учебный товар

Откройте python manage.py shell. Вставьте блок ниже, заменив YOUR_TEST_HOST и порт точными значениями из настроек Wagtail Site. Для неизменённого учебного примера это 127.0.0.1 и 8000.

python
from wagtail.models import Site
from shop.models import Product

site = Site.objects.get(hostname="YOUR_TEST_HOST", port=8000)
product = Product.objects.create(site=site, title="Practice product", is_public=True)
print(product.get_absolute_url())

Откройте напечатанный адрес на том же сайте. DoesNotExist при поиске Site означает, что hostname/port не совпали; исправьте их, не создавайте случайный новый Site ради устранения ошибки.

После первого открытия появится запись в Comments → Object discussions; там задаются исключения настроек объекта. Проверьте публичный товар гостем, закрытый товар гостем и редактором, другой Site и полный почтовый путь. Поддерживаются конкретные непрокси-модели с целым, UUID или строковым ключом до 255 символов.

Удаление объекта через обычный Django ORM удаляет связанные обсуждения и служебные данные. Сырой SQL и исторические модели data migration этот runtime-механизм обходят: их очистка требует отдельного кода. До удаления рабочего товара проверьте требуемую политику сохранения данных.

Другие уроки по этой задаче

Документация по теме

Была ли статья полезна?

Комментарии 0

Имя: до 40 символов.

После отправки мы пришлём письмо со ссылкой для подтверждения email.

Не более 5000 символов.

Опубликованных комментариев пока нет.