Skip to content

Commit 3c34dd7

Browse files
authored
Allow subclassing of form builder (#15)
By following wagtail convention of defining `form_builder` at the class level instead of hardcoding in in `init`, it allows the end-user to provide his own subclass.
1 parent 7b8c73e commit 3c34dd7

7 files changed

Lines changed: 154 additions & 10 deletions

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Unreleased
55
------------------
66
#. Add Wagtail 2.0 compatibility.
77
#. Ensure captcha field is properly removed from submission data. (issue #11)
8+
#. Allow subclassing of the form builder. (issue #7)
89

910
0.2 (2016-10-16)
1011
------------------

README.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,23 @@ Example
7171
7272
The captcha field can't be added from the admin UI but will appear in your frontend as the last of the form fields.
7373

74-
For a more thorough example, `Made with Wagtail <http://madewithwagtail.org/>`_ (`github.com/springload/madewithwagtail <https://github.com/springload/madewithwagtail>`_) is an example of an open-source site using this module.
74+
If you need to customise the behaviour of the form builder, make sure to inherit from ``wagtailcaptcha.forms.WagtailCaptchaFormBuilder`` instead of Wagtail's default form builder, then declare it as usual on the page model.
75+
76+
.. code-block:: python
77+
78+
from wagtailcaptcha.forms import WagtailCaptchaFormBuilder
79+
from wagtailcaptcha.models import WagtailCaptchaForm
80+
81+
82+
class CustomFormBuilder(WagtailCaptchaFormBuilder):
83+
# Some custom behaviour...
84+
85+
86+
class FormPage(WagtailCaptchaForm):
87+
form_builder = CustomFormBuilder
88+
# The rest of the page definition as usual...
89+
90+
For a more thorough example, `Made with Wagtail <http://madewithwagtail.org/>`_ (`github.com/springload/madewithwagtail <https://github.com/springload/madewithwagtail>`_) is an example of an open-source site using this module.
7591

7692
Development
7793
-----------

tests/test_models.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
from django.test import TestCase
66

7-
from home.models import TestCaptchaEmailFormPage, TestCaptchaFormPage
7+
from home.forms import CustomCaptchaFormBuilder
8+
from home.models import (
9+
TestCaptchaEmailFormPage,
10+
TestCaptchaFormPage,
11+
TestCustomFormBuilderCaptchaEmailFormPage,
12+
TestCustomFormBuilderCaptchaFormPage,
13+
)
814
from wagtailcaptcha.forms import WagtailCaptchaFormBuilder
915

1016
try:
@@ -26,11 +32,16 @@ def setUp(self):
2632
class TestCaptchaEmailFormPageTestCase(CaptchaTestingModeMixin, TestCase):
2733
fixtures = ['test_data.json']
2834

29-
def test_captcha_form_builder_is_set(self):
35+
def test_default_form_builder_is_set(self):
3036
page = TestCaptchaEmailFormPage()
3137

3238
self.assertIs(page.form_builder, WagtailCaptchaFormBuilder)
3339

40+
def test_form_builder_can_be_replaced(self):
41+
page = TestCustomFormBuilderCaptchaEmailFormPage()
42+
43+
self.assertIs(page.form_builder, CustomCaptchaFormBuilder)
44+
3445
def test_captcha_field_is_removed_from_submission_data(self):
3546
page = TestCaptchaEmailFormPage.objects.get(slug='email-form')
3647
form_data = dict(self.captcha_form_data, name='Robert')
@@ -48,11 +59,16 @@ def test_captcha_field_is_removed_from_submission_data(self):
4859
class TestCaptchaFormPageTestCase(CaptchaTestingModeMixin, TestCase):
4960
fixtures = ['test_data.json']
5061

51-
def test_captcha_form_builder_is_set(self):
62+
def test_default_form_builder_is_set(self):
5263
page = TestCaptchaFormPage()
5364

5465
self.assertIs(page.form_builder, WagtailCaptchaFormBuilder)
5566

67+
def test_form_builder_can_be_replaced(self):
68+
page = TestCustomFormBuilderCaptchaFormPage()
69+
70+
self.assertIs(page.form_builder, CustomCaptchaFormBuilder)
71+
5672
def test_captcha_field_is_removed_from_submission_data(self):
5773
page = TestCaptchaFormPage.objects.get(slug='form')
5874
form_data = dict(self.captcha_form_data, name='Robert')

tests/testapp/home/forms.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from __future__ import absolute_import, unicode_literals
2+
3+
from wagtailcaptcha.forms import WagtailCaptchaFormBuilder
4+
5+
6+
class CustomCaptchaFormBuilder(WagtailCaptchaFormBuilder):
7+
pass # This doesn't implement any new functionality but is needed to test that subclassing is possible.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.10 on 2018-02-09 01:04
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
import django.db.models.deletion
7+
import modelcluster.fields
8+
9+
10+
class Migration(migrations.Migration):
11+
12+
dependencies = [
13+
('wagtailcore', '0040_page_draft_title'),
14+
('home', '0001_initial'),
15+
]
16+
17+
operations = [
18+
migrations.CreateModel(
19+
name='TestCustomFormBuilderCaptchaEmailFormField',
20+
fields=[
21+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22+
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
23+
('label', models.CharField(help_text='The label of the form field', max_length=255, verbose_name='label')),
24+
('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')], max_length=16, verbose_name='field type')),
25+
('required', models.BooleanField(default=True, verbose_name='required')),
26+
('choices', models.TextField(blank=True, help_text='Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.', verbose_name='choices')),
27+
('default_value', models.CharField(blank=True, help_text='Default value. Comma separated values supported for checkboxes.', max_length=255, verbose_name='default value')),
28+
('help_text', models.CharField(blank=True, max_length=255, verbose_name='help text')),
29+
],
30+
options={
31+
'ordering': ['sort_order'],
32+
'abstract': False,
33+
},
34+
),
35+
migrations.CreateModel(
36+
name='TestCustomFormBuilderCaptchaEmailFormPage',
37+
fields=[
38+
('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')),
39+
('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, verbose_name='to address')),
40+
('from_address', models.CharField(blank=True, max_length=255, verbose_name='from address')),
41+
('subject', models.CharField(blank=True, max_length=255, verbose_name='subject')),
42+
],
43+
options={
44+
'abstract': False,
45+
},
46+
bases=('wagtailcore.page',),
47+
),
48+
migrations.CreateModel(
49+
name='TestCustomFormBuilderCaptchaFormField',
50+
fields=[
51+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
52+
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
53+
('label', models.CharField(help_text='The label of the form field', max_length=255, verbose_name='label')),
54+
('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')], max_length=16, verbose_name='field type')),
55+
('required', models.BooleanField(default=True, verbose_name='required')),
56+
('choices', models.TextField(blank=True, help_text='Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.', verbose_name='choices')),
57+
('default_value', models.CharField(blank=True, help_text='Default value. Comma separated values supported for checkboxes.', max_length=255, verbose_name='default value')),
58+
('help_text', models.CharField(blank=True, max_length=255, verbose_name='help text')),
59+
],
60+
options={
61+
'ordering': ['sort_order'],
62+
'abstract': False,
63+
},
64+
),
65+
migrations.CreateModel(
66+
name='TestCustomFormBuilderCaptchaFormPage',
67+
fields=[
68+
('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')),
69+
],
70+
options={
71+
'abstract': False,
72+
},
73+
bases=('wagtailcore.page',),
74+
),
75+
migrations.AddField(
76+
model_name='testcustomformbuildercaptchaformfield',
77+
name='page',
78+
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_fields', to='home.TestCustomFormBuilderCaptchaFormPage'),
79+
),
80+
migrations.AddField(
81+
model_name='testcustomformbuildercaptchaemailformfield',
82+
name='page',
83+
field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_fields', to='home.TestCustomFormBuilderCaptchaEmailFormPage'),
84+
),
85+
]

tests/testapp/home/models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from modelcluster.fields import ParentalKey
66

77
from wagtailcaptcha.models import WagtailCaptchaEmailForm, WagtailCaptchaForm
8+
from .forms import CustomCaptchaFormBuilder
89

910
if wagtail.VERSION >= (2, 0):
1011
from wagtail.admin.edit_handlers import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel
@@ -39,3 +40,25 @@ class TestCaptchaFormPage(WagtailCaptchaForm):
3940
content_panels = WagtailCaptchaForm.content_panels + [
4041
InlinePanel('form_fields', label="Form fields"),
4142
]
43+
44+
45+
class TestCustomFormBuilderCaptchaEmailFormField(AbstractFormField):
46+
page = ParentalKey(
47+
'TestCustomFormBuilderCaptchaEmailFormPage',
48+
related_name='form_fields',
49+
on_delete=models.CASCADE,
50+
)
51+
52+
53+
class TestCustomFormBuilderCaptchaEmailFormPage(WagtailCaptchaEmailForm):
54+
form_builder = CustomCaptchaFormBuilder
55+
is_creatable = False # Don't show in the admin
56+
57+
58+
class TestCustomFormBuilderCaptchaFormField(AbstractFormField):
59+
page = ParentalKey('TestCustomFormBuilderCaptchaFormPage', related_name='form_fields', on_delete=models.CASCADE)
60+
61+
62+
class TestCustomFormBuilderCaptchaFormPage(WagtailCaptchaForm):
63+
form_builder = CustomCaptchaFormBuilder
64+
is_creatable = False # Don't show in the admin

wagtailcaptcha/models.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
class WagtailCaptchaEmailForm(AbstractEmailForm):
1313
"""Pages implementing a captcha form with email notification should inhert from this"""
1414

15-
def __init__(self, *args, **kwargs):
16-
super(WagtailCaptchaEmailForm, self).__init__(*args, **kwargs)
17-
self.form_builder = WagtailCaptchaFormBuilder
15+
form_builder = WagtailCaptchaFormBuilder
1816

1917
def process_form_submission(self, form):
2018
remove_captcha_field(form)
@@ -27,9 +25,7 @@ class Meta:
2725
class WagtailCaptchaForm(AbstractForm):
2826
"""Pages implementing a captcha form should inhert from this"""
2927

30-
def __init__(self, *args, **kwargs):
31-
super(WagtailCaptchaForm, self).__init__(*args, **kwargs)
32-
self.form_builder = WagtailCaptchaFormBuilder
28+
form_builder = WagtailCaptchaFormBuilder
3329

3430
def process_form_submission(self, form):
3531
remove_captcha_field(form)

0 commit comments

Comments
 (0)