From 3b02100f75b316614d3eb4b647227b6cef776a1e Mon Sep 17 00:00:00 2001 From: Mandel Olaiya Date: Sat, 9 May 2026 11:05:20 +0200 Subject: [PATCH] Store contact form submissions in Wagtail admin --- contact_form/__init__.py | 7 ++ contact_form/views.py | 104 ++++++++++++++++++ .../migrations/0004_contact_messages.py | 43 ++++++++ mandelstudio/models.py | 45 ++++++++ mandelstudio/wagtail_hooks.py | 19 ++++ 5 files changed, 218 insertions(+) create mode 100644 contact_form/__init__.py create mode 100644 contact_form/views.py create mode 100644 mandelstudio/migrations/0004_contact_messages.py diff --git a/contact_form/__init__.py b/contact_form/__init__.py new file mode 100644 index 0000000..6c2afae --- /dev/null +++ b/contact_form/__init__.py @@ -0,0 +1,7 @@ +"""Project-level overrides for the Ocyan contact_form plugin. + +Ocyan loads contact form handlers via module labels like `contact_form.views`. +By shipping this package in the project repository we can extend behavior +without forking the upstream plugin. +""" + diff --git a/contact_form/views.py b/contact_form/views.py new file mode 100644 index 0000000..f941c64 --- /dev/null +++ b/contact_form/views.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +import logging + +from django.conf import settings +from django.contrib import messages +from django.core.mail import EmailMultiAlternatives +from django.shortcuts import redirect +from django.template.loader import render_to_string +from django.urls import reverse +from django.utils.translation import gettext as _ +from django.views.decorators.http import require_http_methods +from django.views.generic import TemplateView + +from oscar.core.utils import redirect_to_referrer +from wagtail.models import Locale, Site + +from ocyan.core.fender import config +from ocyan.plugin.contact_form.forms import ContactForm +from ocyan.plugin.contact_form.utils import get_from_email, get_to_email + +from mandelstudio.models import ContactMessage + +logger = logging.getLogger(__name__) + + +def _client_ip(request) -> str | None: + forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") + if forwarded_for: + return forwarded_for.split(",")[0].strip() or None + return request.META.get("REMOTE_ADDR") + +def _active_locale(request) -> Locale: + language_code = (getattr(request, "LANGUAGE_CODE", "") or "").split("-")[0] + if language_code: + locale = Locale.objects.filter(language_code=language_code).first() + if locale is not None: + return locale + return Locale.get_default() + + +@require_http_methods(["POST"]) +def post_contact_form(request): + form = ContactForm(request.POST, request=request) + + if form.is_valid(): + cleaned = form.cleaned_data + site = Site.find_for_request(request) or Site.objects.order_by("id").first() + locale = _active_locale(request) + + message_obj = ContactMessage.objects.create( + site=site, + locale=locale, + user=request.user if getattr(request.user, "is_authenticated", False) else None, + ip_address=_client_ip(request), + path=request.path or "", + name=str(cleaned.get("name", "")), + email=str(cleaned.get("email_from", "")), + phone_number=str(cleaned.get("phonenumber") or ""), + message=str(cleaned.get("message", "")), + ) + logger.info("Saved ContactMessage id=%s email=%s", message_obj.id, message_obj.email) + + context = { + "website_url": request.build_absolute_uri(), + "form_data": cleaned, + } + + html_message = render_to_string("contact_form/contact_email.html", context) + text_message = render_to_string("contact_form/contact_email.txt", context) + + site_name = getattr(site, "site_name", "") or config.get("django", "name") + subject = _("Contact form email from %s") % site_name + msg = EmailMultiAlternatives( + subject, + text_message, + from_email=get_from_email(request, form), + to=get_to_email(request, form), + reply_to=[cleaned["email_from"]], + ) + msg.attach_alternative(html_message, "text/html") + msg.send() + + request.session["contact_form_submitted"] = True + messages.add_message(request, messages.SUCCESS, _("Message sent")) + return redirect(reverse("contact_form:contact-form-thank-you")) + + request.session["contact_form_post_data"] = request.POST + messages.add_message( + request, + messages.ERROR, + _("An error occured in the contact form: %s") % form.errors.as_text(), + ) + return redirect_to_referrer(request, "contact_form:contact-form-handler") + + +class ContactFormThankYou(TemplateView): + template_name = "contact_form/thank_you.html" + + def get(self, request, *args, **kwargs): + contact_form_submitted = request.session.pop("contact_form_submitted", False) + if contact_form_submitted: + return super().get(request, *args, **kwargs) + return redirect(getattr(settings, "CONTACT_REDIRECT_URL", "/")) diff --git a/mandelstudio/migrations/0004_contact_messages.py b/mandelstudio/migrations/0004_contact_messages.py new file mode 100644 index 0000000..e848cf5 --- /dev/null +++ b/mandelstudio/migrations/0004_contact_messages.py @@ -0,0 +1,43 @@ +# Generated by Django 5.2.13 on 2026-05-08 23:34 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mandelstudio', '0003_locale_audit_models'), + ('wagtailcore', '0097_contact_messages'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='localizedfootercontent', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.CreateModel( + name='ContactMessage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), + ('ip_address', models.GenericIPAddressField(blank=True, null=True)), + ('path', models.CharField(blank=True, max_length=255)), + ('name', models.CharField(max_length=200)), + ('email', models.EmailField(max_length=254)), + ('phone_number', models.CharField(blank=True, max_length=64)), + ('message', models.TextField()), + ('locale', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='contact_messages', to='wagtailcore.locale')), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='contact_messages', to='wagtailcore.site')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contact_messages', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Contact message', + 'verbose_name_plural': 'Contact messages', + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/mandelstudio/models.py b/mandelstudio/models.py index 6106d0d..78e6f05 100644 --- a/mandelstudio/models.py +++ b/mandelstudio/models.py @@ -1,5 +1,6 @@ import uuid +from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ @@ -102,3 +103,47 @@ class LocaleAuditIssue(models.Model): class Meta: ordering = ["locale_code", "url", "field_path"] + + +class ContactMessage(models.Model): + created_at = models.DateTimeField(auto_now_add=True, db_index=True) + site = models.ForeignKey( + Site, on_delete=models.PROTECT, related_name="contact_messages" + ) + locale = models.ForeignKey( + Locale, on_delete=models.PROTECT, related_name="contact_messages" + ) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="contact_messages", + ) + ip_address = models.GenericIPAddressField(null=True, blank=True) + path = models.CharField(max_length=255, blank=True) + + name = models.CharField(max_length=200) + email = models.EmailField() + phone_number = models.CharField(max_length=64, blank=True) + message = models.TextField() + + panels = [ + FieldPanel("site"), + FieldPanel("locale"), + FieldPanel("user"), + FieldPanel("ip_address"), + FieldPanel("path"), + FieldPanel("name"), + FieldPanel("email"), + FieldPanel("phone_number"), + FieldPanel("message"), + ] + + class Meta: + ordering = ["-created_at"] + verbose_name = _("Contact message") + verbose_name_plural = _("Contact messages") + + def __str__(self): + return f"{self.created_at:%Y-%m-%d %H:%M} - {self.name}" diff --git a/mandelstudio/wagtail_hooks.py b/mandelstudio/wagtail_hooks.py index 6a677cb..97fdfda 100644 --- a/mandelstudio/wagtail_hooks.py +++ b/mandelstudio/wagtail_hooks.py @@ -1 +1,20 @@ from mandelblog_content_guard.hooks import * # noqa: F401,F403 + +from wagtail.snippets.views.snippets import SnippetViewSet +from wagtail.snippets.models import register_snippet + +from mandelstudio.models import ContactMessage + + +@register_snippet +class ContactMessageViewSet(SnippetViewSet): + model = ContactMessage + icon = "mail" + menu_label = "Contact messages" + menu_order = 220 + add_to_admin_menu = True + + list_display = ("created_at", "name", "email", "locale", "site") + list_filter = ("locale", "site") + search_fields = ("name", "email", "message", "phone_number") + ordering = ("-created_at",)