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

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


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

Django База [2023]: Добавление формы обратной связи #28
Заполняем форму
Django База [2023]: Добавление формы обратной связи #28
Нажав отправить получаем уведомление об успешной отправке письма
Django База [2023]: Добавление формы обратной связи #28
Заходим на почту администратора, получаем письмо
Django База [2023]: Добавление формы обратной связи #28
Проверяем в админ-панеле

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

Django База [2023]: Добавление формы обратной связи #28
Заполняем форму
Django База [2023]: Добавление формы обратной связи #28
Отправляем письмо
Django База [2023]: Добавление формы обратной связи #28
Проверяем письмо, как видите, пользователь уже не указан
Django База [2023]: Добавление формы обратной связи #28
Проверяем в админ-панеле

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