Django База [2023]: Добавление формы обратной связи #28
Django

Django База [2023]: Добавление формы обратной связи #28

Razilator

Данная статья подробно описывает процесс создания модели и формы для возможности пользователей и гостей отправлять сообщения администрации сайта используя форму обратной связи в Django.

Чтобы отправить письмо вам необходимо настроить SMTP-Сервер, в одном из уроков всё подробно расписано.

Создание модели обратной связи

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

В нашем приложении system, в файле models.py добавим следующую модель:

system/modesl.py
from django.db import models
from django.contrib.auth.models import User


class Feedback(models.Model):
    """
    Модель обратной связи
    """
    subject = models.CharField(max_length=255, verbose_name='Тема письма')
    email = models.EmailField(max_length=255, verbose_name='Электронный адрес (email)')
    content = models.TextField(verbose_name='Содержимое письма')
    time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата отправки')
    ip_address = models.GenericIPAddressField(verbose_name='IP отправителя',  blank=True, null=True)
    user = models.ForeignKey(User, verbose_name='Пользователь', on_delete=models.CASCADE, null=True, blank=True)

    class Meta:
        verbose_name = 'Обратная связь'
        verbose_name_plural = 'Обратная связь'
        ordering = ['-time_create']
        db_table = 'app_feedback'

    def __str__(self):
        return f'Вам письмо от {self.email}'

После этого нам необходимо провести миграции в базу данных с помощью команды python manage.py makemigrations и python manage.py migrate

Результат миграций:

Терминал
(venv) PS C:\Users\Razilator\Desktop\Base\backend> py manage.py makemigrations
Migrations for 'system':
  modules\system\migrations\0004_feedback.py
    - Create model Feedback
(venv) PS C:\Users\Razilator\Desktop\Base\backend> py manage.py migrate       
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions, sites, system
Running migrations:
  Applying system.0004_feedback... OK

И переходим к созданию формы.

Создание формы для обратной связи

В файле forms.py нашего приложения system мы создадим форму на основе нашей модели.

system/forms.py
class FeedbackCreateForm(forms.ModelForm):
    """
    Форма отправки обратной связи
    """

    class Meta:
        model = Feedback
        fields = ('subject', 'email', 'content')

    def __init__(self, *args, **kwargs):
        """
        Обновление стилей формы
        """
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs.update({'class': 'form-control', 'autocomplete': 'off'})

В примере выше мы создали форму на основе нашей модели Feedback, задав к показу 3 поля - тема, email адрес и содержимое. Остальные поля заполним во вьюхе.

Создание функции получения IP-Адреса и отправки письма

В одном из уроков мы создавали папку services, а в ней файл utils.py, в нём мы и создадим необходимый функционал:

services/utils.py
def get_client_ip(request):
    """
    Get user's IP
    """
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    ip = x_forwarded_for.split(',')[0] if x_forwarded_for else request.META.get('REMOTE_ADDR')
    return ip

Функция get_client_ip() получает request и из него извлекает данные об IP адресе.

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

services/email.py
from django.core.mail import EmailMessage
from django.conf import settings
from django.template.loader import render_to_string
from django.contrib.auth.models import User


def send_contact_email_message(subject, email, content, ip, user_id):
    """
    Function to send contact form email
    """
    user = User.objects.get(id=user_id) if user_id else None
    message = render_to_string('system/email/feedback_email_send.html', {
        'email': email,
        'content': content,
        'ip': ip,
        'user': user,
    })
    email = EmailMessage(subject, message, settings.EMAIL_SERVER, settings.EMAIL_ADMIN)
    email.send(fail_silently=False)

Этот код представляет функцию send_contact_email_message() для отправки электронного письма из формы обратной связи сайта. Функция принимает 5 аргументов: тему письма, адрес электронной почты, содержание письма, IP-адрес пользователя и идентификатор пользователя.

В функции сначала используется метод get для получения пользователя с указанным идентификатором из модели User, если идентификатор пользователя присутствует.

Затем используется шаблон письма system/email/feedback_email_send.html для генерации сообщения письма. В шаблон передаются данные электронной почты, содержания письма, IP-адреса пользователя и пользователя.

Далее создается объект EmailMessage с указанным темой, сообщением, адресом электронной почты сервера и адресом администратора, указанным в settings.

Создание представления для обратной связи

В файл views.py нашего приложения system добавим следующее представление CreateView:

system/views.py
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.views.generic import CreateView

from .forms import FeedbackCreateForm
from .models import Feedback
from ..services.email import send_contact_email_message
from ..services.utils import get_client_ip


class FeedbackCreateView(SuccessMessageMixin, CreateView):
    model = Feedback
    form_class = FeedbackCreateForm
    success_message = 'Ваше письмо успешно отправлено администрации сайта'
    template_name = 'system/feedback.html'
    extra_context = {'title': 'Контактная форма'}
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        if form.is_valid():
            feedback = form.save(commit=False)
            feedback.ip_address = get_client_ip(self.request)
            if self.request.user.is_authenticated:
                feedback.user = self.request.user
            send_contact_email_message(feedback.subject, feedback.email, feedback.content, feedback.ip_address, feedback.user_id)
        return super().form_valid(form)

В примере выше мы используем представление на основе класса (CreateView), а тажке миксин (SuccessMessageMixin) для уведомления об успешной отправке письма.

В этом классе указаны модель (Feedback), форма (FeedbackCreateForm), шаблон (system/feedback.html), контекст для заголовка страницы (extra_context) адрес для успешной отправки (home) и сообщение об успехе.

Метод form_valid() переопределяет метод родительского класса и вызывается после успешной валидации формы. В нем создается объект feedback, заполняется его атрибут ip_address с помощью функции get_client_ip(), а если пользователь аутентифицирован, то заполняется его атрибут user. Затем вызывается функция send_contact_email_message(), которая отправляет электронное письмо.

Обработка в urls.py

Данное представление нужно обработать, поэтому добавляем следующую строку в наш urls.py нашего приложения system:

system/urls.py
from django.urls import path

from .views import ProfileUpdateView, ProfileDetailView, UserRegisterView, UserLoginView, UserPasswordChangeView, \
    UserForgotPasswordView, UserPasswordResetConfirmView, UserConfirmEmailView, EmailConfirmationSentView, \
    EmailConfirmedView, EmailConfirmationFailedView, UserLogoutView, FeedbackCreateView
    
urlpatterns = [
    path('user/edit/', ProfileUpdateView.as_view(), name='profile_edit'),
    path('user/<str:slug>/', ProfileDetailView.as_view(), name='profile_detail'),
    path('login/', UserLoginView.as_view(), name='login'),
    path('logout/', UserLogoutView.as_view(), name="logout"),
    path('password-change/', UserPasswordChangeView.as_view(), name='password_change'),
    path('password-reset/', UserForgotPasswordView.as_view(), name='password_reset'),
    path('set-new-password/<uidb64>/<token>/', UserPasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('register/', UserRegisterView.as_view(), name='register'),
    path('email-confirmation-sent/', EmailConfirmationSentView.as_view(), name='email_confirmation_sent'),
    path('confirm-email/<str:uidb64>/<str:token>/', UserConfirmEmailView.as_view(), name='confirm_email'),
    path('email-confirmed/', EmailConfirmedView.as_view(), name='email_confirmed'),
    path('confirm-email-failed/', EmailConfirmationFailedView.as_view(), name='email_confirmation_failed'),
    path('feedback/', FeedbackCreateView.as_view(), name='feedback'),
]

Добавим отображение обратной связи в админ-панеле

В файл admin.py нашего приложения system добавим следующий фрагмент кода:

system/admin.py
from django.contrib import admin

from .models import Feedback


@admin.register(Feedback)
class FeedbackAdmin(admin.ModelAdmin):
    """
    Админ-панель модели профиля
    """
    list_display = ('email', 'ip_address', 'user')
    list_display_links = ('email', 'ip_address')

Добавление шаблона для письма и формы

В папке templates/system/ создадим файл feedback.html, а внутри папки templates/system/email/ создадим файл шаблона письма feedback_email_send.html

Заполняем HTML файл feedback.html следующей разметкой:

templates/system/feedback.html
{% extends 'main.html' %}
{% load static %}
{% block content %}
    <div class="card mb-3 border-0 nth-shadow">
        <div class="card-body">
            <div class="card-title nth-card-title">
                <h4>Контактная форма</h4>
            </div>
            <form method="post" action="{% url 'feedback' %}">
                {% csrf_token %}
                {{ form.as_p }}
                <div class="d-grid gap-2 d-md-block mt-2">
                <button type="submit" class="btn btn-dark">Отправить письмо</button>
             </div>
            </form>
        </div>
    </div>
{% endblock %}

А файл feedback_email_send.html следующей разметкой:

templates/system/email/feedback_email_send.html
{% autoescape off %}

Здравствуйте, Администратор,

С контактной формы поступило новое сообщение со следующим содержимым:

    {{ content }}

    IP Адрес отправителя: {{ ip }}
    Email отправителя: {{ email }}
    {% if user %}Пользователь: {{ user.username }}{% endif %}

Команда сайта Django Base Project

{% endautoescape %}

Отлично. Перейдем к проверке.

Проверка работы обратной связи

Как авторизованный пользователь:

Перейдем на страницу: http://127.0.0.1:8000/feedback/

Заполняем форму
Заполняем форму
Нажав отправить получаем уведомление об успешной отправке письма
Нажав отправить получаем уведомление об успешной отправке письма
Заходим на почту администратора, получаем письмо
Заходим на почту администратора, получаем письмо
Проверяем в админ-панеле
Проверяем в админ-панеле

Как неавторизованный пользователь:

Заполняем форму
Заполняем форму
Отправляем письмо
Отправляем письмо
Проверяем письмо, как видите, пользователь уже не указан
Проверяем письмо, как видите, пользователь уже не указан
Проверяем в админ-панеле
Проверяем в админ-панеле

Отлично. Надеюсь у вас все получилось!

;