From b6554fbf8369ac85807f1ce554429507da44b61c Mon Sep 17 00:00:00 2001 From: Konstantin Kostov Date: Fri, 1 Mar 2024 19:34:34 +0100 Subject: [PATCH] feat: add contact form --- base/migrations/0003_formpage_formfield.py | 52 +++++++++++++++++++ base/models.py | 39 ++++++++++++-- base/templates/base/form_page.html | 15 ++++++ base/templates/base/form_page_landing.html | 9 ++++ .../static/css/iamkonstantin_web.css | 18 +++++++ 5 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 base/migrations/0003_formpage_formfield.py create mode 100644 base/templates/base/form_page.html create mode 100644 base/templates/base/form_page_landing.html diff --git a/base/migrations/0003_formpage_formfield.py b/base/migrations/0003_formpage_formfield.py new file mode 100644 index 0000000..67a9922 --- /dev/null +++ b/base/migrations/0003_formpage_formfield.py @@ -0,0 +1,52 @@ +# Generated by Django 5.0.2 on 2024-03-01 18:28 + +import django.db.models.deletion +import modelcluster.fields +import wagtail.contrib.forms.models +import wagtail.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0002_footertext'), + ('wagtailcore', '0091_remove_revision_submitted_for_moderation'), + ] + + operations = [ + migrations.CreateModel( + name='FormPage', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), + ('to_address', models.CharField(blank=True, help_text='Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.', max_length=255, validators=[wagtail.contrib.forms.models.validate_to_address], verbose_name='to address')), + ('from_address', models.EmailField(blank=True, max_length=255, verbose_name='from address')), + ('subject', models.CharField(blank=True, max_length=255, verbose_name='subject')), + ('intro', wagtail.fields.RichTextField(blank=True)), + ('thank_you_text', wagtail.fields.RichTextField(blank=True)), + ], + options={ + 'abstract': False, + }, + bases=(wagtail.contrib.forms.models.FormMixin, 'wagtailcore.page', models.Model), + ), + migrations.CreateModel( + name='FormField', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), + ('clean_name', models.CharField(blank=True, default='', help_text='Safe name of the form field, the label converted to ascii_snake_case', max_length=255, verbose_name='name')), + ('label', models.CharField(help_text='The label of the form field', max_length=255, verbose_name='label')), + ('field_type', models.CharField(choices=[('singleline', 'Single line text'), ('multiline', 'Multi-line text'), ('email', 'Email'), ('number', 'Number'), ('url', 'URL'), ('checkbox', 'Checkbox'), ('checkboxes', 'Checkboxes'), ('dropdown', 'Drop down'), ('multiselect', 'Multiple select'), ('radio', 'Radio buttons'), ('date', 'Date'), ('datetime', 'Date/time'), ('hidden', 'Hidden field')], max_length=16, verbose_name='field type')), + ('required', models.BooleanField(default=True, verbose_name='required')), + ('choices', models.TextField(blank=True, help_text='Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.', verbose_name='choices')), + ('default_value', models.TextField(blank=True, help_text='Default value. Comma or new line separated values supported for checkboxes.', verbose_name='default value')), + ('help_text', models.CharField(blank=True, max_length=255, verbose_name='help text')), + ('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_fields', to='base.formpage')), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + ), + ] diff --git a/base/models.py b/base/models.py index 460f541..e95cd14 100644 --- a/base/models.py +++ b/base/models.py @@ -1,9 +1,12 @@ from django.db import models -from modelcluster.models import ClusterableModel +from modelcluster.fields import ParentalKey + from wagtail.admin.panels import ( FieldPanel, MultiFieldPanel, - PublishingPanel + PublishingPanel, + FieldRowPanel, + InlinePanel ) from wagtail.fields import RichTextField from wagtail.models import ( @@ -12,13 +15,18 @@ from wagtail.models import ( RevisionMixin, TranslatableMixin, ) + +from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField + from wagtail.snippets.models import register_snippet +from wagtail.contrib.forms.panels import FormSubmissionsPanel from wagtail.contrib.settings.models import ( BaseGenericSetting, register_setting, ) + @register_setting class NavigationSettings(BaseGenericSetting): linkedin_url = models.URLField(verbose_name="LinkedIn URL", blank=True) @@ -36,6 +44,7 @@ class NavigationSettings(BaseGenericSetting): ) ] + @register_snippet class FooterText( DraftStateMixin, @@ -44,7 +53,6 @@ class FooterText( TranslatableMixin, models.Model, ): - body = RichTextField() panels = [ @@ -62,4 +70,27 @@ class FooterText( return {"footer_text": self.body} class Meta(TranslatableMixin.Meta): - verbose_name_plural = "Footer Text" \ No newline at end of file + verbose_name_plural = "Footer Text" + + +class FormField(AbstractFormField): + page = ParentalKey('FormPage', on_delete=models.CASCADE, related_name='form_fields') + + +class FormPage(AbstractEmailForm): + intro = RichTextField(blank=True) + thank_you_text = RichTextField(blank=True) + + content_panels = AbstractEmailForm.content_panels + [ + FormSubmissionsPanel(), + FieldPanel('intro'), + InlinePanel('form_fields', label="Form fields"), + FieldPanel('thank_you_text'), + MultiFieldPanel([ + FieldRowPanel([ + FieldPanel('from_address'), + FieldPanel('to_address'), + ]), + FieldPanel('subject'), + ], "Email"), + ] diff --git a/base/templates/base/form_page.html b/base/templates/base/form_page.html new file mode 100644 index 0000000..ce0036f --- /dev/null +++ b/base/templates/base/form_page.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% load wagtailcore_tags %} + +{% block body_class %}template-formpage{% endblock %} + +{% block content %} +

{{ page.title }}

+
{{ page.intro|richtext }}
+ +
+ {% csrf_token %} + {{ form.as_div }} + +
+{% endblock content %} \ No newline at end of file diff --git a/base/templates/base/form_page_landing.html b/base/templates/base/form_page_landing.html new file mode 100644 index 0000000..f64cb9d --- /dev/null +++ b/base/templates/base/form_page_landing.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% load wagtailcore_tags %} + +{% block body_class %}template-formpage{% endblock %} + +{% block content %} +

{{ page.title }}

+
{{ page.thank_you_text|richtext }}
+{% endblock content %} \ No newline at end of file diff --git a/iamkonstantin_web/static/css/iamkonstantin_web.css b/iamkonstantin_web/static/css/iamkonstantin_web.css index ee65db9..2a1d3de 100644 --- a/iamkonstantin_web/static/css/iamkonstantin_web.css +++ b/iamkonstantin_web/static/css/iamkonstantin_web.css @@ -42,4 +42,22 @@ header { .skip-link:focus-visible { top: 5px; +} + +.page-form label { + display: block; + margin-top: 10px; + margin-bottom: 5px; +} + +.page-form :is(textarea, input, select) { + width: 100%; + max-width: 500px; + min-height: 40px; + margin-top: 5px; + margin-bottom: 10px; +} + +.page-form .helptext { + font-style: italic; } \ No newline at end of file