diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml index 852c7feb..3403d0f1 100644 --- a/.github/workflows/nightly-tests.yml +++ b/.github/workflows/nightly-tests.yml @@ -25,10 +25,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python 3.12 uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.12 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index e4f19036..55e81c04 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 + - name: Set up Python 3.12 uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.12 - name: Install build requirements run: python -m pip install wheel - name: Build package diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 304d4e69..3952e6fe 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -1,4 +1,3 @@ ---- name: Python Tests on: [push] @@ -7,13 +6,15 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v1 + - uses: actions/checkout@v3 + - name: Set up Python 3.12 + uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: "3.12" - name: Install dependencies - run: pip install tox + run: | + python -m pip install --upgrade pip + pip install tox - name: Validate formatting run: tox -e format @@ -22,58 +23,18 @@ jobs: strategy: max-parallel: 4 matrix: + python-version: ["3.12"] tox_env: - - py311-dj41-wt41 - - py311-dj41-wt50 - - py311-dj41-wt51 - - py311-dj41-wt52 - include: - - python-version: "3.11" - tox_env: py311-dj41-wt41 - - python-version: "3.11" - tox_env: py311-dj41-wt50 - - python-version: "3.11" - tox_env: py311-dj41-wt51 - - python-version: "3.11" - tox_env: py311-dj41-wt52 - + - py312-dj51-wt63 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox tox-gh-actions - - name: Test with tox + - name: Run tests with tox run: tox -e ${{ matrix.tox_env }} -- index-url=https://pypi.python.org/simple/ - - name: Prepare artifacts - run: mkdir -p .coverage-data && mv .coverage.* .coverage-data/ - - uses: actions/upload-artifact@master - with: - name: coverage-data - path: .coverage-data/ - - coverage: - runs-on: ubuntu-latest - needs: [test] - steps: - - uses: actions/checkout@v2 - - uses: actions/download-artifact@master - with: - name: coverage-data - path: . - - name: Set up Python 3.8 - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox - - name: Prepare Coverage report - run: tox -e coverage-report - - name: Upload to codecov - uses: codecov/codecov-action@v1.0.6 diff --git a/docker-compose.yml b/docker-compose.yml index 5b919e96..3410c4ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: ports: - 8000:8000 db: - image: postgres:9.6 + image: postgres:11 environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password diff --git a/example/migrations/0001_initial.py b/example/migrations/0001_initial.py index f623c26a..ed939d89 100644 --- a/example/migrations/0001_initial.py +++ b/example/migrations/0001_initial.py @@ -2,8 +2,8 @@ from django.db import migrations, models import django.db.models.deletion -import wagtail.core.blocks -import wagtail.core.fields +import wagtail.blocks +import wagtail.fields import wagtailstreamforms.blocks @@ -20,7 +20,7 @@ class Migration(migrations.Migration): name='BasicPage', 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')), - ('body', wagtail.core.fields.StreamField((('rich_text', wagtail.core.blocks.RichTextBlock()), ('form', wagtail.core.blocks.StructBlock((('form', wagtailstreamforms.blocks.FormChooserBlock()), ('form_action', wagtail.core.blocks.CharBlock(help_text='The form post action. "" or "." for the current page or a url', required=False)), ('form_reference', wagtailstreamforms.blocks.InfoBlock(help_text='This form will be given a unique reference once saved', required=False)))))))), + ('body', wagtail.fields.StreamField((('rich_text', wagtail.blocks.RichTextBlock()), ('form', wagtail.blocks.StructBlock((('form', wagtailstreamforms.blocks.FormChooserBlock()), ('form_action', wagtail.blocks.CharBlock(help_text='The form post action. "" or "." for the current page or a url', required=False)), ('form_reference', wagtailstreamforms.blocks.InfoBlock(help_text='This form will be given a unique reference once saved', required=False)))))))), ], options={ 'abstract': False, diff --git a/example/models.py b/example/models.py index 9bc38212..f69b28cb 100644 --- a/example/models.py +++ b/example/models.py @@ -1,7 +1,7 @@ from django.db import models -from wagtail.admin.panels import StreamFieldPanel from wagtail import blocks +from wagtail.admin.panels import FieldPanel from wagtail.fields import StreamField from wagtail.models import Page from wagtailstreamforms.blocks import WagtailFormBlock @@ -23,5 +23,5 @@ class BasicPage(Page): show_in_menus_default = True content_panels = Page.content_panels + [ - StreamFieldPanel('body'), + FieldPanel('body'), ] diff --git a/example/settings.py b/example/settings.py index 56241b04..b69b27d6 100644 --- a/example/settings.py +++ b/example/settings.py @@ -8,7 +8,7 @@ # Security -SECRET_KEY = environ.get('SECRET_KEY', '') +SECRET_KEY = environ.get('SECRET_KEY', 's3cr3t') DEBUG = True @@ -37,12 +37,11 @@ 'wagtail.search', 'wagtail.contrib.redirects', 'wagtail.sites', - 'wagtail.contrib.modeladmin', - 'wagtail.contrib.postgres_search', + 'wagtail_modeladmin', 'wagtail.contrib.settings', 'wagtail.contrib.search_promotions', - 'captcha', + 'django_recaptcha', 'taggit', # app specific @@ -90,14 +89,14 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'HOST': environ.get('RDS_HOSTNAME'), - 'PORT': environ.get('RDS_PORT'), - 'NAME': environ.get('RDS_DB_NAME'), - 'USER': environ.get('RDS_USERNAME'), - 'PASSWORD': environ.get('RDS_PASSWORD'), + 'HOST': environ.get('DB_HOSTNAME'), + 'PORT': environ.get('DB_PORT'), + 'NAME': environ.get('DB_NAME'), + 'USER': environ.get('DB_USERNAME'), + 'PASSWORD': environ.get('DB_PASSWORD'), } } - +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' # Email @@ -143,7 +142,7 @@ # Wagtail WAGTAIL_SITE_NAME = 'example.com' - +WAGTAILADMIN_BASE_URL = "/" # Forms @@ -156,4 +155,5 @@ RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe' NOCAPTCHA = True -SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error'] +SILENCED_SYSTEM_CHECKS = ['django_recaptcha.recaptcha_test_key_error'] + diff --git a/example/wagtailstreamforms_fields.py b/example/wagtailstreamforms_fields.py index 73d5c5b5..d3f8900a 100644 --- a/example/wagtailstreamforms_fields.py +++ b/example/wagtailstreamforms_fields.py @@ -1,7 +1,7 @@ from django import forms from django.contrib.auth.models import User +from django_recaptcha.fields import ReCaptchaField -from captcha.fields import ReCaptchaField from wagtail import blocks from wagtailstreamforms.fields import BaseField, register @@ -19,11 +19,26 @@ def get_options(self, block_value): }) return options + def get_form_block_class(self): + return blocks.StructBlock + + def get_local_blocks(self): + return [ + ("label", blocks.CharBlock()), + ("help_text", blocks.CharBlock(required=False)), + ] + + def get_form_block_kwargs(self): + return { + "icon": self.icon, + "label": self.label, + } + def get_form_block(self): - return blocks.StructBlock([ - ('label', blocks.CharBlock()), - ('help_text', blocks.CharBlock(required=False)), - ], icon=self.icon, label=self.label) + return self.get_form_block_class()( + self.get_local_blocks(), + **self.get_form_block_kwargs(), + ) @register('regex_validated') @@ -45,15 +60,21 @@ def get_regex_choices(self): ('^[a-zA-Z0-9]+$', 'Letters and numbers only'), ) - def get_form_block(self): - return blocks.StructBlock([ + def get_local_blocks(self): + return [ ('label', blocks.CharBlock()), ('help_text', blocks.CharBlock(required=False)), ('required', blocks.BooleanBlock(required=False)), ('regex', blocks.ChoiceBlock(choices=self.get_regex_choices())), ('error_message', blocks.CharBlock()), ('default_value', blocks.CharBlock(required=False)), - ], icon=self.icon, label=self.label) + ] + + def get_form_block(self): + return self.get_form_block_class()( + self.get_local_blocks(), + **self.get_form_block_kwargs() + ) @register('user') @@ -71,9 +92,15 @@ def get_options(self, block_value): options.update({'queryset': self.get_queryset()}) return options - def get_form_block(self): - return blocks.StructBlock([ + def get_local_blocks(self): + return [ ('label', blocks.CharBlock()), ('help_text', blocks.CharBlock(required=False)), ('required', blocks.BooleanBlock(required=False)), - ], icon=self.icon, label=self.label) + ] + + def get_form_block(self): + return self.get_form_block_class()( + self.get_local_blocks(), + **self.get_form_block_kwargs() + ) diff --git a/pyproject.toml b/pyproject.toml index c8c642a5..cb8deba6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,77 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "wagtailstreamforms" +dynamic = ["version"] +description = "Wagtail forms in a streamfield" +readme = "README.rst" +license = "MIT" +authors = [ + { name = "Stuart George", email = "stuart@accentdesign.co.uk" }, +] +keywords = [ + "accent", + "design", + "forms", + "streamfield", + "wagtail", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Framework :: Django", + "Framework :: Django :: 5.0", + "Framework :: Wagtail", + "Framework :: Wagtail :: 5", + "Framework :: Wagtail :: 6", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Internet :: WWW/HTTP :: Site Management", +] +dependencies = [ + "Unidecode>=0.04.14,<2.0", + "wagtail-modeladmin", + "wagtail>6.0,<=6.3", +] + +[project.optional-dependencies] +docs = [ + "karma_sphinx_theme>=0.0.6", + "sphinx-autobuild>=0.6.0", + "Sphinx>=1.5.2", + "sphinxcontrib-spelling>=2.3.0", +] +test = [ + "flake8-blind-except==0.1.1", + "flake8-debugger==3.1.0", + "flake8==3.7.9", + "isort[pyproject]==4.3.21", + "mock==4.0.2", + "pytest-django==4.7.0", + "pytest==7.3.2", +] + +[project.urls] +Download = "https://pypi.python.org/pypi/wagtailstreamforms" +Homepage = "https://github.com/AccentDesign/wagtailstreamforms/" + +[tool.hatch.version] +path = "wagtailstreamforms/__init__.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/wagtailstreamforms", +] + [tool.black] line-length=99 -target-version=["py39"] +target-version=["py312"] exclude = ''' /( \.git diff --git a/readthedocs.yml b/readthedocs.yml index 06897971..2264ed84 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -1,5 +1,5 @@ --- python: - version: 3.5 + version: 3.12 pip_install: true requirements_file: docs/requirements.txt diff --git a/requirements.txt b/requirements.txt index 1f7ad04c..722a9c9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ # The app itself -e . -Django>=3.2 +Django>=5 mock psycopg2-binary # example site django-recaptcha +wagtail-modeladmin \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 87ec759e..00000000 --- a/setup.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python - -from codecs import open -from os import path - -from setuptools import setup - -from wagtailstreamforms import __version__ - -tests_require = [ - "mock==4.0.2", - "pytest-django==4.7.0", - "pytest==7.3.2", - # Linting - "isort[pyproject]==4.3.21", - "flake8==3.7.9", - "flake8-blind-except==0.1.1", - "flake8-debugger==3.1.0", -] - - -install_requires = [ - "wagtail>=4.1,<5.3", - "Unidecode>=0.04.14,<2.0", - "wagtail-generic-chooser>=0.5.0,<0.7", -] - -documentation_extras = [ - "sphinxcontrib-spelling>=2.3.0", - "Sphinx>=1.5.2", - "sphinx-autobuild>=0.6.0", - "karma_sphinx_theme>=0.0.6", -] - -here = path.abspath(path.dirname(__file__)) - -with open(path.join(here, "README.rst"), encoding="utf-8") as f: - long_description = f.read() - -setup( - name="wagtailstreamforms", - version=__version__, - description="Wagtail forms in a streamfield", - long_description=long_description, - author="Stuart George", - author_email="stuart@accentdesign.co.uk", - url="https://github.com/AccentDesign/wagtailstreamforms/", - download_url="https://pypi.python.org/pypi/wagtailstreamforms", - license="MIT", - packages=["wagtailstreamforms"], - install_requires=install_requires, - extras_require={ - "docs": documentation_extras, - "test": tests_require, - }, - include_package_data=True, - keywords=["wagtail", "streamfield", "forms", "accent", "design"], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Framework :: Django", - "Framework :: Django :: 3.2", - "Framework :: Django :: 4", - "Framework :: Django :: 4.0", - "Framework :: Django :: 4.1", - "Framework :: Django :: 4.2", - "Framework :: Wagtail", - "Framework :: Wagtail :: 4", - "Topic :: Internet :: WWW/HTTP :: Site Management", - ], -) diff --git a/tests/blocks/test_form_block.py b/tests/blocks/test_form_block.py index c1b72d7f..addef28f 100644 --- a/tests/blocks/test_form_block.py +++ b/tests/blocks/test_form_block.py @@ -1,5 +1,3 @@ -from django.conf import settings - from wagtailstreamforms.blocks import WagtailFormBlock from wagtailstreamforms.models import Form @@ -25,98 +23,87 @@ def test_render(self): expected_html = "\n".join( [ "

Basic Form

", - '
', - '', - '' % self.form.pk, - '', - '
' - '' - '' - '

Help

' + '', + f'' + '', + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '", + '

Help

', + "
", + '
', + "", + '
', + '
', + '
', + '
', + "
", + '

Help

', + "
", + '
', + "", + '
', + '
', + '
', + '
', + "
", + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', "
", - '
' - '' - '" - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - "" - '
' - '
" - '
" - '
" - '

Help

' - "
" - '
' - "" - '
' - '
" - '
" - '
" - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '' + '', "", ] ) diff --git a/tests/fields/test_hook_select_field.py b/tests/fields/test_hook_select_field.py index bb40fe08..5902af41 100644 --- a/tests/fields/test_hook_select_field.py +++ b/tests/fields/test_hook_select_field.py @@ -1,6 +1,5 @@ from django.core import serializers from django.core.exceptions import ValidationError -from django.db import models from django.forms import CheckboxSelectMultiple from wagtailstreamforms.fields import HookMultiSelectFormField, HookSelectField diff --git a/tests/settings.py b/tests/settings.py index ca1423f5..ae61af2c 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -25,7 +25,7 @@ "wagtail.contrib.redirects", "wagtail.contrib.forms", "wagtail.sites", - "wagtail.contrib.modeladmin", + "wagtail_modeladmin", "wagtail.contrib.settings", "taggit", "wagtailstreamforms", diff --git a/tests/templatetags/test_form.py b/tests/templatetags/test_form.py index 97b3bde5..9f0d332a 100644 --- a/tests/templatetags/test_form.py +++ b/tests/templatetags/test_form.py @@ -1,5 +1,3 @@ -from django.conf import settings - from wagtailstreamforms.models import Form from ..test_case import AppTestCase @@ -23,98 +21,87 @@ def test_render(self): expected_html = "\n".join( [ "

Basic Form

", - '
', - '', - '' % self.form.pk, - '', - '
' - '' - '' - '

Help

' + '', + f'' + '', + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '", + '

Help

', + "
", + '
', + "", + '
', + '
', + '
', + '
', + "
", + '

Help

', + "
", + '
', + "", + '
', + '
', + '
', + '
', + "
", + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', + "
", + '
', + '', + '', + '

Help

', "
", - '
' - '' - '" - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - "" - '
' - '
" - '
" - '
" - '

Help

' - "
" - '
' - "" - '
' - '
" - '
" - '
" - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '
' - '' - '' - '

Help

' - "
" - '' + '', "", ] ) diff --git a/tests/urls.py b/tests/urls.py index 10877f70..225f73f6 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.urls import include, re_path -from wagtail.admin import urls as wagtailadmin_urls from wagtail import urls as wagtail_urls +from wagtail.admin import urls as wagtailadmin_urls urlpatterns = [ re_path(r"^admin/", admin.site.urls), diff --git a/tox.ini b/tox.ini index 44273018..d87f2fdd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,28 +1,24 @@ [tox] envlist = flake8 - py{311,312}-dj{41}-wt{41,50,51,52} + py{312}-dj{51}-wt{60,63} [gh-actions] python = - "3.11": py311 "3.12": py312 [testenv] deps = coverage mock - dj41: Django>=4.1,<4.2 - wt41: wagtail>=4.1,<4.2 - wt50: wagtail>=5.0,<5.1 - wt51: wagtail>=5.1,<5.2 - wt52: wagtail>=5.2,<5.3 + dj51: Django>=5.1,<5.2 + wt60: wagtail>=6.0,<6.1 + wt63: wagtail>=6.3,<6.4 commands = - coverage run manage.py test + coverage run --parallel-mode manage.py test basepython = - py311: python3.11 py312: python3.12 setenv = @@ -33,19 +29,19 @@ setenv = passenv = TOX_* [testenv:wagtaildev] -basepython = python3.11 +basepython = python3.12 install_command = pip install -e ".[test]" -U {opts} {packages} deps = git+https://github.com/wagtail/wagtail.git@master coverage mock - django>=3.2 + django>=5.1 commands = coverage run manage.py test ignore_errors = True [testenv:flake8] -basepython = python3.11 +basepython = python3.12 deps = flake8 commands = flake8 wagtailstreamforms @@ -63,7 +59,7 @@ exclude = max-line-length = 120 [testenv:coverage-report] -basepython = python3.11 +basepython = python3.12 deps = coverage[toml] skip_install = true commands = @@ -72,7 +68,7 @@ commands = coverage report [testenv:format] -basepython = python3.11 +basepython = python3.12 deps = isort black diff --git a/wagtailstreamforms/blocks.py b/wagtailstreamforms/blocks.py index b29ca153..00046eb0 100644 --- a/wagtailstreamforms/blocks.py +++ b/wagtailstreamforms/blocks.py @@ -42,7 +42,7 @@ class WagtailFormBlock(blocks.StructBlock): ) class Meta: - icon = "icon icon-form" + icon = "form" template = None def render(self, value, context=None): diff --git a/wagtailstreamforms/conf.py b/wagtailstreamforms/conf.py index 245bd5b5..551dd212 100644 --- a/wagtailstreamforms/conf.py +++ b/wagtailstreamforms/conf.py @@ -24,7 +24,7 @@ "singlefile", "multifile", ), - "FORM_TEMPLATES": (("streamforms/form_block.html", "Default Form Template"),), + "FORM_TEMPLATES": [("streamforms/form_block.html", "Default Form Template")], } diff --git a/wagtailstreamforms/fields.py b/wagtailstreamforms/fields.py index 2d682013..5e8bcb43 100644 --- a/wagtailstreamforms/fields.py +++ b/wagtailstreamforms/fields.py @@ -25,7 +25,6 @@ class SingleLineTextField(BaseField): """ if cls is None: - def decorator(cls): register(field_name, cls) return cls @@ -147,24 +146,60 @@ def get_options(self, block_value): "initial": self.get_formfield_initial(block_value), } + def get_form_block_class(self): + """ + The StreamField block class to be created for this field. This is + almost always a StructBlock, but conceptually it could be any structural block. + + Override this method and return a subclass of a structural block for further + control over the block class, such as overriding the clean() method to provide + custom validation. + :return: The ``wagtail.blocks.StructBlock`` to be used in the StreamField + """ + return blocks.StructBlock + + def get_form_block_kwargs(self): + """The kwargs to be passed into the StreamField block class. + + Override this to provide additional kwargs to the StreamField block class. + + :return: The kwargs to be passed into the StreamField block class + """ + return { + "icon": self.icon, + "label": self.label, + } + def get_form_block(self): - """The StreamField StructBlock. + """The StreamField block class. Override this to provide additional fields in the StreamField. - :return: The ``wagtail.core.blocks.StructBlock`` to be used in the StreamField - """ - return blocks.StructBlock( - [ - ("label", blocks.CharBlock()), - ("help_text", blocks.CharBlock(required=False)), - ("required", blocks.BooleanBlock(required=False)), - ("default_value", blocks.CharBlock(required=False)), - ], - icon=self.icon, - label=self.label, + :return: The resuld of calling get_form_block_class() is to be used in the + StreamField + """ + return self.get_form_block_class()( + self.get_local_blocks(), + **self.get_form_block_kwargs(), ) + def get_local_blocks(self): + """The blocks that should be added to the StructBlock for this field. + + Override this to add blocks to, or remove blocks from, the StructBlock + before it is instantiated. This is useful because adding blocks to the + StructBlock after instantiation requires mucking with the StructBlock's + internal, undocumented API. + + :return: A list of tuples containing the block name and block instance. + """ + return [ + ("label", blocks.CharBlock()), + ("help_text", blocks.CharBlock(required=False)), + ("required", blocks.BooleanBlock(required=False)), + ("default_value", blocks.CharBlock(required=False)), + ] + class HookMultiSelectFormField(forms.MultipleChoiceField): widget = forms.CheckboxSelectMultiple diff --git a/wagtailstreamforms/forms.py b/wagtailstreamforms/forms.py index 08805315..754d1831 100644 --- a/wagtailstreamforms/forms.py +++ b/wagtailstreamforms/forms.py @@ -26,20 +26,8 @@ def formfields(self): formfields = OrderedDict() - registered_fields = get_fields() - for field in self.fields: - field_type = field.get("type") - field_value = field.get("value") - - # check we have the field - if field_type not in registered_fields: - raise AttributeError("Could not find a registered field of type %s" % field_type) - - # get the field - registered_cls = registered_fields[field_type]() - field_name = registered_cls.get_formfield_name(field_value) - field_cls = registered_cls.get_formfield(field_value) + field_name, field_cls = self.create_field_class(field) formfields[field_name] = field_cls # add fields to uniquely identify the form @@ -48,6 +36,45 @@ def formfields(self): return formfields + def create_field_class(self, field): + """ + Encapsulates the field_cls creation such that there is a method to override + when the field_cls needs to be modified. + + :param field: StreamBlock representing a form field; an item in + fields.stream_data + :return: a tuple of field_name - the name to use in the html form for this + field, and field_cls - in instantiated field class that may be added to a form + """ + registered_fields = get_fields() + + field_type = field.get("type") + field_value = field.get("value") + # check we have the field + if field_type not in registered_fields: + raise AttributeError( + "Could not find a registered field of type %s" % field_type + ) + + # get the field + registered_cls = registered_fields[field_type]() + field_cls = registered_cls.get_formfield(field_value) + field_name = self.create_field_name(registered_cls, field) + return field_name, field_cls + + def create_field_name(self, registered_cls, field): + """ + Encapsulates the field_name creation such that there is a method to override + when the field_name needs to be modified. + + :param field: StreamBlock representing a form field; an item in + fields.stream_data + :param registered_cls: The subclass of wagtailstreamforms.fields.BaseField + that defined this form field + :return: a name to use in the html form for this field + """ + return registered_cls.get_formfield_name(field.get("value")) + def get_form_class(self): return type(str("StreamformsForm"), (BaseForm,), self.formfields) diff --git a/wagtailstreamforms/migrations/0001_initial.py b/wagtailstreamforms/migrations/0001_initial.py index 01eba326..90151c1e 100644 --- a/wagtailstreamforms/migrations/0001_initial.py +++ b/wagtailstreamforms/migrations/0001_initial.py @@ -1,9 +1,8 @@ # Generated by Django 2.0.5 on 2018-05-30 23:03 import django.db.models.deletion -from wagtail import blocks - from django.db import migrations, models +from wagtail import blocks import wagtailstreamforms.conf import wagtailstreamforms.fields diff --git a/wagtailstreamforms/migrations/0003_alter_form_fields.py b/wagtailstreamforms/migrations/0003_alter_form_fields.py index cb9e2318..a486033c 100644 --- a/wagtailstreamforms/migrations/0003_alter_form_fields.py +++ b/wagtailstreamforms/migrations/0003_alter_form_fields.py @@ -1,7 +1,8 @@ # Generated by Django 4.1.8 on 2023-04-14 05:21 -from django.db import migrations import wagtail.blocks +from django.db import migrations + import wagtailstreamforms.streamfield diff --git a/wagtailstreamforms/models/form.py b/wagtailstreamforms/models/form.py index fa7358e9..2409e074 100644 --- a/wagtailstreamforms/models/form.py +++ b/wagtailstreamforms/models/form.py @@ -43,7 +43,11 @@ class AbstractForm(models.Model): template_name = models.CharField( _("Template"), max_length=255, choices=get_setting("FORM_TEMPLATES") ) - fields = FormFieldsStreamField([], use_json_field=True, verbose_name=_("Fields")) + fields = FormFieldsStreamField( + [], + verbose_name=_("Fields"), + use_json_field=True, + ) submit_button_text = models.CharField( _("Submit button text"), max_length=100, default="Submit" ) diff --git a/wagtailstreamforms/streamfield.py b/wagtailstreamforms/streamfield.py index 2269527a..a511aaa8 100644 --- a/wagtailstreamforms/streamfield.py +++ b/wagtailstreamforms/streamfield.py @@ -26,13 +26,21 @@ def __init__(self, local_blocks=None, **kwargs): "'%s' must be a subclass of '%s'" % (field_class, BaseField) ) - # assign the block - block = field_class().get_form_block() - block.set_name(name) - self._child_blocks[name] = block + # assign the block if instantiation returns non None + if block := self.instantiate_block(field_class, name): + self._child_blocks[name] = block self._dependencies = self._child_blocks.values() + def instantiate_block(self, field_class, name): + """ + Provides an extension point for changing attributes of blocks, like the + meta.group + """ + block = field_class().get_form_block() + block.set_name(name) + return block + @property def child_blocks(self): return self._child_blocks diff --git a/wagtailstreamforms/templates/streamforms/advanced_settings.html b/wagtailstreamforms/templates/streamforms/advanced_settings.html index a90254c5..e3f81f28 100644 --- a/wagtailstreamforms/templates/streamforms/advanced_settings.html +++ b/wagtailstreamforms/templates/streamforms/advanced_settings.html @@ -13,7 +13,7 @@
    {% block visible_fields %} {% for field in form.visible_fields %} - {% include "wagtailadmin/shared/field_as_li.html" %} + {% include "wagtailadmin/shared/field.html" %} {% endfor %} {% endblock %}
  • diff --git a/wagtailstreamforms/templates/streamforms/confirm_copy.html b/wagtailstreamforms/templates/streamforms/confirm_copy.html index 4d9a7ce6..dca0d89c 100644 --- a/wagtailstreamforms/templates/streamforms/confirm_copy.html +++ b/wagtailstreamforms/templates/streamforms/confirm_copy.html @@ -14,7 +14,7 @@
      {% block visible_fields %} {% for field in form.visible_fields %} - {% include "wagtailadmin/shared/field_as_li.html" %} + {% include "wagtailadmin/shared/field.html" %} {% endfor %} {% endblock %}
    • diff --git a/wagtailstreamforms/templates/streamforms/index_submissions.html b/wagtailstreamforms/templates/streamforms/index_submissions.html index 8a6f50da..054706f6 100644 --- a/wagtailstreamforms/templates/streamforms/index_submissions.html +++ b/wagtailstreamforms/templates/streamforms/index_submissions.html @@ -77,7 +77,7 @@