Создание модели обратной связи:
Создадим нашу модель в модуле "system". Поэтому создаем файл feedback.py в папке models.

modules/system/models/feedback.py
from django.contrib.auth.models import User
from django.db import models
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='Содержимое')
created_at = 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 = ('-created_at',)
db_table = 'app_feedback'
def __str__(self):
return f'Вам письмо: {self.email}'
Пояснения:
- В этой моделе мы будем получать ip, email, пользователя (если есть)
Напоминаю, если вы используете структуру папок как и я, то добавим модель в __init__.py
modules/system/models/__init__.py
from modules.system.models.abstract import AbstractBaseMeta
from modules.system.models.feedback import Feedback
from modules.system.models.profiles import Profile
__all__ = (
'AbstractBaseMeta',
'Profile',
'Feedback',
)
Обязательно делаем миграции:
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend> python manage.py makemigrations
Migrations for 'system':
modules\system\migrations\0002_feedback.py
- Create model Feedback
(venv) PS C:\Users\Razilator\Desktop\Courses\App\backend> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions, sites, system
Running migrations:
Applying system.0002_feedback... OK
Далее создадим форму для обратной связи:
modules/system/forms/feedback.py
from captcha.fields import ReCaptchaField
from captcha.widgets import ReCaptchaV2Checkbox
from django import forms
from backend import settings
from modules.system.models import Feedback
class CreateFeedbackForm(forms.ModelForm):
"""
Форма отправки обратной связи
"""
class Meta:
model = Feedback
fields = ('subject', 'email', 'content')
recaptcha = ReCaptchaField(widget=ReCaptchaV2Checkbox, public_key=settings.RECAPTCHA_PUBLIC_KEY, private_key=settings.RECAPTCHA_PRIVATE_KEY, label='Капча')
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'
})
Пояснения:
- В форму выведем только тему, емейл и текст сообщения.
- Добавим ReCaptcha из прошлого урока.
Добавим в __init__.py
modules/system/forms/__init__.py
from modules.system.forms.authenticated import *
from modules.system.forms.profiles import *
from modules.system.forms.feedback import *
__all__ = '__all__'
Теперь создадим представление для обратной связи:
modules/system/views/feedback.py
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.views.generic import CreateView
from modules.system.forms.feedback import CreateFeedbackForm
from modules.system.models import Feedback
from modules.system.services.email import send_contact_email_message
from modules.system.services.utils import get_client_ip
class FeedbackCreateView(SuccessMessageMixin, CreateView):
model = Feedback
form_class = CreateFeedbackForm
success_message = 'Ваше письмо успешно отправлено администрации сайта'
template_name = 'modules/system/feedback/feedback.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Контактная форма'
return context
def form_valid(self, form):
current_user = self.request.user
if form.is_valid():
feedback = form.save(commit=False)
feedback.ip_address = get_client_ip(self.request)
if current_user.is_authenticated:
feedback.user = current_user
user_id = getattr(feedback, 'user_id', None)
send_contact_email_message(feedback.subject, feedback.email, feedback.content, feedback.ip_address, user_id)
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('home')
Пояснения:
- Я создаю обратную связь наследуясь от CreateView.
- В конце мы получаем ip адрес с помощью функции, а также отправляем сообщение с помощью функции, которую мы создадим далее.
Не забываем про __init__.py который я использую для декомпозиции:
modules/system/views/__init__.py
from modules.system.views.profiles import *
from modules.system.views.authenticated import *
from modules.system.views.feedback import *
__all__ = '__all__'
Функция получения IP адреса:
Файл utils.py мы уже создавали в папке services в одном из уроков, поэтому идем в этот файлик и добавляем туда нашу функцию:
modules/system/services/utils.py
def get_client_ip(request):
"""
Получение IP юзера
"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
А теперь добавим функцию отправки сообщения, также в одном из уроков мы создали файл email.py в папке services:
modules/system/services/email.py
from django.core.mail import EmailMessage
from backend import settings
def send_contact_email_message(subject, email, content, ip, user_id):
"""
Функция отправки контактной формы
"""
if user_id:
user = User.objects.get(id=user_id)
else:
user = None
message = render_to_string('modules/system/feedback/email/contact_mail.html', {
'email': email,
'content': content,
'ip': ip,
'user': user,
})
email = EmailMessage(subject, message, settings.EMAIL_SERVER, settings.EMAIL_ADMIN)
return email.send(fail_silently=False)
Пояснения:
- Если юзер есть, получаем его по id, передаем в контекст email сообщения.
- Используем SMTP из настроек.
Теперь осталось добавить шаблоны и urls.py
В templates/modules/system я создаю папку feedback, а в ней папку email и рядом с папкой email файл feedback.html
templates/modules/system/feedback/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-create-view' %}">
{% 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 %}
Также добавляем шаблон письма:
templates/modules/system/feedback/email/contact_mail.html
{% autoescape off %}
Здравствуйте, Администратор,
С контактной формы поступило новое сообщение со следующим содержимым:
{{ content }}
IP Адрес отправителя: {{ ip }}
Email отправителя: {{ email }}
{% if user %}Пользователь: {{ user.username }}{% endif %}
Команда сайта Django Project
{% endautoescape %}
И наконец добавим ссылку на контактную форму:
modules/system/urls.py
from django.urls import path
from modules.system.views import ProfileView, ProfileEditView, RegisterCreateView, UserLoginView, \
UserPasswordChangeView, UserForgotPasswordView, UserPasswordResetConfirmView, UserLogoutView, ActivateAccountView, \
FeedbackCreateView
urlpatterns = [
path('user/edit/', ProfileEditView.as_view(), name='profile-edit'),
path('user/<str:slug>/', ProfileView.as_view(), name='profile'),
path('register/', RegisterCreateView.as_view(), name='register'),
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('activate/<uidb64>/<token>/', ActivateAccountView.as_view(), name='activate'),
path('feedback/', FeedbackCreateView.as_view(), name='feedback-create-view'),
]
Давайте проверим нашу форму на сайте:



И само письмо:

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