diff --git a/src/openforms/analytics_tools/migrations/0003_cspsetting_identifier.py b/src/openforms/analytics_tools/migrations/0003_cspsetting_identifier.py index abd6a92e59..382f8f0ce1 100644 --- a/src/openforms/analytics_tools/migrations/0003_cspsetting_identifier.py +++ b/src/openforms/analytics_tools/migrations/0003_cspsetting_identifier.py @@ -1,78 +1,16 @@ # Generated by Django 3.2.21 on 2023-11-22 17:27 from django.db import migrations -from django.db.migrations.state import StateApps from django.db.backends.base.schema import BaseDatabaseSchemaEditor - -from openforms.analytics_tools.constants import AnalyticsTools - -SITEIMPROVE_VALUES = [ - "https://siteimproveanalytics.com", - "https://siteimproveanalytics.com", - "https://*.siteimproveanalytics.io", -] -GA_VALUES = ["https://www.googleanalytics.com", "https://www.googletagmanager.com"] - -FIELD_TO_IDENTIFIER = { - "matomo_url": AnalyticsTools.matomo, - "piwik_pro_url": AnalyticsTools.piwik_pro, - "piwik_url": AnalyticsTools.piwik, -} - - -def set_identifier(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> None: - """Set the corresponding analytic tool as the ``CSPSetting.identifier`` field. - - Depending on the analytic tool used, the ``CSPSetting.value`` field can be fixed - (e.g. with GA or Siteimprove) or configured by the user. The latter requires more work, - as we need to iterate over the ``AnalyticsToolsConfiguration`` fields to find the right match. - """ - - AnalyticsToolsConfiguration = apps.get_model( - "analytics_tools", "AnalyticsToolsConfiguration" - ) - try: - analytics_conf = AnalyticsToolsConfiguration.objects.get() - except AnalyticsToolsConfiguration.DoesNotExist: - return - - ContentType = apps.get_model("contenttypes", "ContentType") - analytics_content_type = ContentType.objects.get_for_model( - AnalyticsToolsConfiguration - ) - - CSPSetting = apps.get_model("config", "CSPSetting") - - set_content_type = False - for csp_setting in CSPSetting.objects.filter(identifier="").iterator(): - if csp_setting.value in SITEIMPROVE_VALUES: - csp_setting.identifier = AnalyticsTools.siteimprove - set_content_type = True - elif csp_setting.value in GA_VALUES: - csp_setting.identifier = AnalyticsTools.google_analytics - set_content_type = True - else: - for field, identifier in FIELD_TO_IDENTIFIER.items(): - if getattr(analytics_conf, field, None) == csp_setting.value: - csp_setting.identifier = identifier - set_content_type = True - - if set_content_type: - # `content_object` is not available in migrations, - # so we set `content_type` and `object_id` instead: - csp_setting.content_type = analytics_content_type - csp_setting.object_id = analytics_conf.id - - csp_setting.save() +from django.db.migrations.state import StateApps class Migration(migrations.Migration): dependencies = [ ("analytics_tools", "0002_auto_20230119_1500"), - ("config", "0063_auto_20231122_1816"), + ("config", "0053_v230_to_v250"), ] - operations = [ - migrations.RunPython(set_identifier, migrations.RunPython.noop), - ] + # RunPython operation is removed, it was executed as part of the 2.5.0 upgrade. + operations = [] diff --git a/src/openforms/analytics_tools/tests/test_migrations.py b/src/openforms/analytics_tools/tests/test_migrations.py deleted file mode 100644 index 60522505d1..0000000000 --- a/src/openforms/analytics_tools/tests/test_migrations.py +++ /dev/null @@ -1,75 +0,0 @@ -from django.db.migrations.state import StateApps - -from openforms.analytics_tools.constants import AnalyticsTools -from openforms.config.constants import CSPDirective -from openforms.utils.tests.test_migrations import TestMigrations - - -class CSPSettingIdentifierMigrationTests(TestMigrations): - app = "analytics_tools" - migrate_from = "0002_auto_20230119_1500" - migrate_to = "0003_cspsetting_identifier" - - def setUpBeforeMigration(self, apps: StateApps): - CSPSetting = apps.get_model("config", "CSPSetting") - AnalyticsToolsConfiguration = apps.get_model( - "analytics_tools", "AnalyticsToolsConfiguration" - ) - - AnalyticsToolsConfiguration.objects.create( - matomo_url="https://matomo.example.com", - piwik_url="https://piwik.example.com", - piwik_pro_url="https://your-instance-name.piwik.pro", - ) - - CSPSetting.objects.create( - directive=CSPDirective.DEFAULT_SRC, value="https://matomo.example.com" - ) - CSPSetting.objects.create( - directive=CSPDirective.DEFAULT_SRC, value="https://piwik.example.com" - ) - CSPSetting.objects.create( - directive=CSPDirective.DEFAULT_SRC, - value="https://your-instance-name.piwik.pro", - ) - CSPSetting.objects.create( - directive=CSPDirective.DEFAULT_SRC, value="https://siteimproveanalytics.com" - ) - CSPSetting.objects.create( - directive=CSPDirective.DEFAULT_SRC, value="https://www.googleanalytics.com" - ) - - def test_migration_sets_identifier_and_gfk(self): - CSPSetting = self.apps.get_model("config", "CSPSetting") - AnalyticsToolsConfiguration = self.apps.get_model( - "analytics_tools", "AnalyticsToolsConfiguration" - ) - analytics_conf = AnalyticsToolsConfiguration.objects.get() - - value_to_identifier = { - "https://matomo.example.com": AnalyticsTools.matomo, - "https://piwik.example.com": AnalyticsTools.piwik, - "https://your-instance-name.piwik.pro": AnalyticsTools.piwik_pro, - "https://siteimproveanalytics.com": AnalyticsTools.siteimprove, - "https://www.googleanalytics.com": AnalyticsTools.google_analytics, - } - - self.assertFalse(CSPSetting.objects.filter(identifier="").exists()) - - # We avoid using django.contrib.admin.options.get_content_type_for_model - # as it uses the "real" `ContentType` model. See: - # https://stackoverflow.com/q/51670468/#comment110467392_54357872 - content_type = self.apps.get_model( - "contenttypes", "ContentType" - ).objects.get_for_model(analytics_conf) - - for value, identifier in value_to_identifier.items(): - self.assertEqual( - CSPSetting.objects.filter( - value=value, - identifier=identifier, - content_type=content_type, - object_id=str(analytics_conf.pk), - ).count(), - 1, - ) diff --git a/src/openforms/config/management/__init__.py b/src/openforms/config/management/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/openforms/config/management/commands/__init__.py b/src/openforms/config/management/commands/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/openforms/config/management/commands/create_csp_form_action_directives_from_config.py b/src/openforms/config/management/commands/create_csp_form_action_directives_from_config.py deleted file mode 100644 index ebc71ebc47..0000000000 --- a/src/openforms/config/management/commands/create_csp_form_action_directives_from_config.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.core.management import BaseCommand - -from digid_eherkenning.models import DigidConfiguration, EherkenningConfiguration - -from openforms.payments.contrib.ogone.models import OgoneMerchant - - -class Command(BaseCommand): - help = ( - "Introspect the runtime configuration and generate the relevant CSP " - "form-action directives" - ) - - def handle(self, **options): - ogone_merchants = OgoneMerchant.objects.exclude( - endpoint_preset="", - endpoint_custom="", - ) - for ogone_merchant in ogone_merchants: - ogone_merchant.save() # triggers CSPSetting record creation - - for model_cls in (DigidConfiguration, EherkenningConfiguration): - configured_instance = model_cls.objects.exclude( - idp_metadata_file="" - ).first() - if configured_instance is None: - continue - - configured_instance.save() # triggers CSPSetting record creation diff --git a/src/openforms/config/migrations/0001_initial_to_v250.py b/src/openforms/config/migrations/0001_initial_to_v250.py new file mode 100644 index 0000000000..fb1bc302a3 --- /dev/null +++ b/src/openforms/config/migrations/0001_initial_to_v250.py @@ -0,0 +1,1468 @@ +# Generated by Django 4.2.10 on 2024-02-22 18:09 + +import colorsys +import functools +import re +import uuid +from io import StringIO + +import django.core.validators +import django.db.migrations.operations.special +import django.db.models.deletion +from django.core.management import call_command +from django.db import migrations, models + +import colorfield.fields +import django_jsonform.models.fields +import tinymce.models + +import openforms.config.models.config +import openforms.emails.validators +import openforms.payments.validators +import openforms.template.validators +import openforms.utils.fields +import openforms.utils.translations + + +def load_cookiegroups(*args): + call_command("loaddata", "cookie_consent", stdout=StringIO()) + + +# default colors from CKEditor source code (in CSS HSL format) +# via https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-font/src/fontcolor/fontcolorediting.js +default_cke_values = [ + {"color": "hsl(0, 0%, 0%)", "label": "Black"}, + {"color": "hsl(0, 0%, 30%)", "label": "Dim grey"}, + {"color": "hsl(0, 0%, 60%)", "label": "Grey"}, + {"color": "hsl(0, 0%, 90%)", "label": "Light grey"}, + { + "color": "hsl(0, 0%, 100%)", + "label": "White", + }, + {"color": "hsl(0, 75%, 60%)", "label": "Red"}, + {"color": "hsl(30, 75%, 60%)", "label": "Orange"}, + {"color": "hsl(60, 75%, 60%)", "label": "Yellow"}, + {"color": "hsl(90, 75%, 60%)", "label": "Light green"}, + {"color": "hsl(120, 75%, 60%)", "label": "Green"}, + {"color": "hsl(150, 75%, 60%)", "label": "Aquamarine"}, + {"color": "hsl(180, 75%, 60%)", "label": "Turquoise"}, + {"color": "hsl(210, 75%, 60%)", "label": "Light blue"}, + {"color": "hsl(240, 75%, 60%)", "label": "Blue"}, + {"color": "hsl(270, 75%, 60%)", "label": "Purple"}, +] + + +def hsl_to_rgbhex(hsl_css_color): + exp = "^hsl\((\d+), (\d+)%, (\d+)%\)$" + m = re.match(exp, hsl_css_color) + if m: + h = int(m.group(1)) + s = int(m.group(2)) + l = int(m.group(3)) + + # conversion algorithm via https://stackoverflow.com/questions/41403936/converting-hsl-to-hex-in-python3 + rgb = colorsys.hls_to_rgb(h / 360, l / 100, s / 100) + hex = "#%02x%02x%02x" % ( + round(rgb[0] * 255), + round(rgb[1] * 255), + round(rgb[2] * 255), + ) + return hex + + +def add_colors(apps, schema_editor): + RichTextColor = apps.get_model("config", "RichTextColor") + + for elem in default_cke_values: + hex_color = hsl_to_rgbhex(elem["color"]) + if not hex_color: + continue + RichTextColor.objects.create(label=elem["label"], color=hex_color) + + +def remove_colors(apps, schema_editor): + RichTextColor = apps.get_model("config", "RichTextColor") + RichTextColor.objects.all().delete() + + +class Migration(migrations.Migration): + + replaces = [ + ("config", "0001_initial_squashed_0022_merge_20210903_1228"), + ("config", "0002_squashed_to_of_v230"), + ("config", "0053_v230_to_v250"), + ] + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("digid_eherkenning", "0006_digidconfiguration_metadata_file_source_and_more"), + ("payments_ogone", "0002_auto_20210902_2120"), + ("cookie_consent", "0002_auto__add_logitem"), + ] + + operations = [ + migrations.RunPython( + code=load_cookiegroups, + reverse_code=django.db.migrations.operations.special.RunPython.noop, + ), + migrations.CreateModel( + name="RichTextColor", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "color", + colorfield.fields.ColorField( + default="#FFFFFF", + help_text="Color in RGB hex format (#RRGGBB)", + image_field=None, + max_length=18, + samples=None, + verbose_name="color", + ), + ), + ( + "label", + models.CharField( + help_text="Human readable label for reference", + max_length=64, + verbose_name="label", + ), + ), + ], + options={ + "verbose_name": "text editor color preset", + "verbose_name_plural": "text editor color presets", + "ordering": ("label",), + }, + ), + migrations.RunPython(code=add_colors, reverse_code=remove_colors), + migrations.CreateModel( + name="CSPSetting", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "directive", + models.CharField( + choices=[ + ("default-src", "default-src"), + ("script-src", "script-src"), + ("script-src-attr", "script-src-attr"), + ("script-src-elem", "script-src-elem"), + ("img-src", "img-src"), + ("object-src", "object-src"), + ("prefetch-src", "prefetch-src"), + ("media-src", "media-src"), + ("frame-src", "frame-src"), + ("font-src", "font-src"), + ("connect-src", "connect-src"), + ("style-src", "style-src"), + ("style-src-attr", "style-src-attr"), + ("style-src-elem", "style-src-elem"), + ("base-uri", "base-uri"), + ("child-src", "child-src"), + ("frame-ancestors", "frame-ancestors"), + ("navigate-to", "navigate-to"), + ("form-action", "form-action"), + ("sandbox", "sandbox"), + ("report-uri", "report-uri"), + ("report-to", "report-to"), + ("manifest-src", "manifest-src"), + ("worker-src", "worker-src"), + ("plugin-types", "plugin-types"), + ("require-sri-for", "require-sri-for"), + ], + help_text="CSP header directive.", + max_length=64, + verbose_name="directive", + ), + ), + ( + "value", + models.CharField( + help_text="CSP header value.", + max_length=255, + verbose_name="value", + ), + ), + ( + "content_type", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="contenttypes.contenttype", + verbose_name="content type", + ), + ), + ( + "object_id", + models.TextField( + blank=True, db_index=True, verbose_name="object id" + ), + ), + ( + "identifier", + models.CharField( + blank=True, + help_text="An extra tag for this CSP entry, to identify the exact source.", + max_length=64, + verbose_name="identifier", + ), + ), + ], + options={ + "ordering": ("directive", "value"), + }, + ), + migrations.CreateModel( + name="Theme", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + help_text="An easily recognizable name for the theme, used to identify it.", + max_length=100, + verbose_name="name", + ), + ), + ( + "logo", + openforms.utils.fields.SVGOrImageField( + blank=True, + help_text="Upload the theme/orgnization logo, visible to users filling out forms. We advise dimensions around 150px by 75px. SVG's are permitted.", + upload_to="logo/", + verbose_name="theme logo", + ), + ), + ( + "classname", + models.SlugField( + blank=True, + help_text="If provided, this class name will be set on the element.", + verbose_name="theme CSS class name", + ), + ), + ( + "stylesheet", + models.URLField( + blank=True, + help_text="The URL stylesheet with theme-specific rules for your organization. This will be included as final stylesheet, overriding previously defined styles. Note that you also have to include the host to the `style-src` CSP directive. Example value: https://unpkg.com/@utrecht/design-tokens@1.0.0-alpha.20/dist/index.css.", + max_length=1000, + validators=[ + django.core.validators.RegexValidator( + message="The URL must point to a CSS resource (.css extension).", + regex="\\.css$", + ) + ], + verbose_name="theme stylesheet URL", + ), + ), + ( + "stylesheet_file", + models.FileField( + blank=True, + help_text="A stylesheet with theme-specific rules for your organization. This will be included as final stylesheet, overriding previously defined styles. If both a URL to a stylesheet and a stylesheet file have been configured, the uploaded file is included after the stylesheet URL.", + upload_to="config/themes/", + validators=[ + django.core.validators.FileExtensionValidator( + allowed_extensions=("css",) + ) + ], + verbose_name="theme stylesheet", + ), + ), + ( + "design_token_values", + models.JSONField( + blank=True, + default=dict, + help_text="Values of various style parameters, such as border radii, background colors... Note that this is advanced usage. Any available but un-specified values will use fallback default values. See https://open-forms.readthedocs.io/en/latest/installation/form_hosting.html#run-time-configuration for documentation.", + verbose_name="design token values", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, + editable=False, + unique=True, + verbose_name="UUID", + ), + ), + ], + options={ + "verbose_name": "theme", + "verbose_name_plural": "themes", + }, + ), + migrations.CreateModel( + name="GlobalConfiguration", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "email_template_netloc_allowlist", + django_jsonform.models.fields.ArrayField( + base_field=models.CharField(max_length=1000), + blank=True, + default=list, + help_text="Provide a list of allowed domains (without 'https://www').Hyperlinks in a (confirmation) email are removed, unless the domain is provided here.", + size=None, + verbose_name="allowed email domain names", + ), + ), + ( + "display_sdk_information", + models.BooleanField( + default=False, + help_text="When enabled, information about the used SDK is displayed.", + verbose_name="display SDK information", + ), + ), + ( + "submission_confirmation_template", + tinymce.models.HTMLField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Thank you for submitting this form.",), + **{} + ), + help_text="The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data.", + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="submission confirmation template", + ), + ), + ( + "allow_empty_initiator", + models.BooleanField( + default=False, + help_text="When enabled and the submitter is not authenticated, a case is created without any initiator. Otherwise, a fake initiator is added with BSN 111222333.", + verbose_name="allow empty initiator", + ), + ), + ( + "form_begin_text", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Begin form",), + **{} + ), + help_text="The text that will be displayed at the start of the form to indicate the user can begin to fill in the form", + max_length=50, + verbose_name="begin text", + ), + ), + ( + "form_change_text", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, *("Change",), **{} + ), + help_text="The text that will be displayed in the overview page to change a certain step", + max_length=50, + verbose_name="change text", + ), + ), + ( + "form_confirm_text", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Confirm",), + **{} + ), + help_text="The text that will be displayed in the overview page to confirm the form is filled in correctly", + max_length=50, + verbose_name="confirm text", + ), + ), + ( + "form_previous_text", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Previous page",), + **{} + ), + help_text="The text that will be displayed in the overview page to go to the previous step", + max_length=50, + verbose_name="back to form text", + ), + ), + ( + "form_step_next_text", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, *("Next",), **{} + ), + help_text="The text that will be displayed in the form step to go to the next step", + max_length=50, + verbose_name="step next text", + ), + ), + ( + "form_step_previous_text", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Previous page",), + **{} + ), + help_text="The text that will be displayed in the form step to go to the previous step", + max_length=50, + verbose_name="previous step text", + ), + ), + ( + "form_step_save_text", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Save current information",), + **{} + ), + help_text="The text that will be displayed in the form step to save the current information", + max_length=50, + verbose_name="step save text", + ), + ), + ( + "admin_session_timeout", + models.PositiveIntegerField( + default=60, + help_text="Amount of time in minutes the admin can be inactive for before being logged out", + validators=[django.core.validators.MinValueValidator(5)], + verbose_name="admin session timeout", + ), + ), + ( + "form_session_timeout", + models.PositiveIntegerField( + default=15, + help_text="Amount of time in minutes a user filling in a form can be inactive for before being logged out", + validators=[ + django.core.validators.MinValueValidator(5), + django.core.validators.MaxValueValidator( + 15, + message="Due to DigiD requirements this value has to be less than or equal to %(limit_value)s minutes.", + ), + ], + verbose_name="form session timeout", + ), + ), + ( + "main_website", + models.URLField( + blank=True, + help_text="URL to the main website. Used for the 'back to municipality website' link.", + verbose_name="main website link", + ), + ), + ( + "enable_demo_plugins", + models.BooleanField( + default=False, + help_text="If enabled, the admin allows selection of demo backend plugins.", + verbose_name="enable demo plugins", + ), + ), + ( + "all_submissions_removal_limit", + models.PositiveIntegerField( + default=90, + help_text="Amount of days when all submissions will be permanently deleted", + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="all submissions removal limit", + ), + ), + ( + "errored_submissions_removal_limit", + models.PositiveIntegerField( + default=30, + help_text="Amount of days errored submissions will remain before being removed", + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="errored submission removal limit", + ), + ), + ( + "errored_submissions_removal_method", + models.CharField( + choices=[ + ("delete_permanently", "Submissions will be deleted"), + ( + "make_anonymous", + "Sensitive data within the submissions will be deleted", + ), + ], + default="delete_permanently", + help_text="How errored submissions will be removed after the", + max_length=50, + verbose_name="errored submissions removal method", + ), + ), + ( + "incomplete_submissions_removal_limit", + models.PositiveIntegerField( + default=7, + help_text="Amount of days incomplete submissions will remain before being removed", + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="incomplete submission removal limit", + ), + ), + ( + "incomplete_submissions_removal_method", + models.CharField( + choices=[ + ("delete_permanently", "Submissions will be deleted"), + ( + "make_anonymous", + "Sensitive data within the submissions will be deleted", + ), + ], + default="delete_permanently", + help_text="How incomplete submissions will be removed after the limit", + max_length=50, + verbose_name="incomplete submissions removal method", + ), + ), + ( + "successful_submissions_removal_limit", + models.PositiveIntegerField( + default=7, + help_text="Amount of days successful submissions will remain before being removed", + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="successful submission removal limit", + ), + ), + ( + "successful_submissions_removal_method", + models.CharField( + choices=[ + ("delete_permanently", "Submissions will be deleted"), + ( + "make_anonymous", + "Sensitive data within the submissions will be deleted", + ), + ], + default="delete_permanently", + help_text="How successful submissions will be removed after the limit", + max_length=50, + verbose_name="successful submissions removal method", + ), + ), + ( + "ask_privacy_consent", + models.BooleanField( + default=True, + help_text="If enabled, the user will have to agree to the privacy policy before submitting a form.", + verbose_name="ask privacy consent", + ), + ), + ( + "privacy_policy_url", + models.URLField( + blank=True, + help_text="URL to the privacy policy", + verbose_name="privacy policy URL", + ), + ), + ( + "payment_order_id_prefix", + models.CharField( + blank=True, + default="{year}", + help_text="Prefix to apply to generated numerical order IDs. Alpha-numerical only, supports placeholder {year}.", + max_length=16, + validators=[ + openforms.payments.validators.validate_payment_order_id_prefix + ], + verbose_name="Payment Order ID prefix", + ), + ), + ( + "confirmation_email_subject", + models.CharField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/confirmation/subject.txt",), + **{} + ), + help_text="Subject of the confirmation email message. Can be overridden on the form level", + max_length=1000, + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="subject", + ), + ), + ( + "plugin_configuration", + models.JSONField( + blank=True, + default=dict, + help_text="Configuration of plugins for authentication, payments, prefill, registrations and validation", + verbose_name="plugin configuration", + ), + ), + ( + "form_display_required_with_asterisk", + models.BooleanField( + default=True, + help_text="If checked, required fields are marked with an asterisk and optional fields are unmarked. If unchecked, optional fields will be marked with '(optional)' and required fields are unmarked.", + verbose_name="Mark required fields with asterisks", + ), + ), + ( + "form_fields_required_default", + models.BooleanField( + default=False, + help_text="Whether the checkbox 'required' on form fields should be checked by default.", + verbose_name="Mark form fields 'required' by default", + ), + ), + ( + "registration_attempt_limit", + models.PositiveIntegerField( + default=5, + help_text="How often we attempt to register the submission at the registration backend before giving up", + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="default registration backend attempt limit", + ), + ), + ( + "save_form_email_subject", + models.CharField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/save_form/subject.txt",), + **{} + ), + help_text="Subject of the save form email message.", + max_length=1000, + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="subject", + ), + ), + ( + "allow_indexing_form_detail", + models.BooleanField( + default=True, + help_text="Whether form detail pages may be indexed and displayed in search engine result lists. Disable this to prevent listing.", + verbose_name="Allow form page indexing", + ), + ), + ( + "confirmation_email_subject_en", + models.CharField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/confirmation/subject.txt",), + **{} + ), + help_text="Subject of the confirmation email message. Can be overridden on the form level", + max_length=1000, + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="subject", + ), + ), + ( + "confirmation_email_subject_nl", + models.CharField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/confirmation/subject.txt",), + **{} + ), + help_text="Subject of the confirmation email message. Can be overridden on the form level", + max_length=1000, + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="subject", + ), + ), + ( + "form_begin_text_en", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Begin form",), + **{} + ), + help_text="The text that will be displayed at the start of the form to indicate the user can begin to fill in the form", + max_length=50, + null=True, + verbose_name="begin text", + ), + ), + ( + "form_begin_text_nl", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Begin form",), + **{} + ), + help_text="The text that will be displayed at the start of the form to indicate the user can begin to fill in the form", + max_length=50, + null=True, + verbose_name="begin text", + ), + ), + ( + "form_change_text_en", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, *("Change",), **{} + ), + help_text="The text that will be displayed in the overview page to change a certain step", + max_length=50, + null=True, + verbose_name="change text", + ), + ), + ( + "form_change_text_nl", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, *("Change",), **{} + ), + help_text="The text that will be displayed in the overview page to change a certain step", + max_length=50, + null=True, + verbose_name="change text", + ), + ), + ( + "form_confirm_text_en", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Confirm",), + **{} + ), + help_text="The text that will be displayed in the overview page to confirm the form is filled in correctly", + max_length=50, + null=True, + verbose_name="confirm text", + ), + ), + ( + "form_confirm_text_nl", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Confirm",), + **{} + ), + help_text="The text that will be displayed in the overview page to confirm the form is filled in correctly", + max_length=50, + null=True, + verbose_name="confirm text", + ), + ), + ( + "form_step_next_text_en", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, *("Next",), **{} + ), + help_text="The text that will be displayed in the form step to go to the next step", + max_length=50, + null=True, + verbose_name="step next text", + ), + ), + ( + "form_step_next_text_nl", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, *("Next",), **{} + ), + help_text="The text that will be displayed in the form step to go to the next step", + max_length=50, + null=True, + verbose_name="step next text", + ), + ), + ( + "form_step_save_text_en", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Save current information",), + **{} + ), + help_text="The text that will be displayed in the form step to save the current information", + max_length=50, + null=True, + verbose_name="step save text", + ), + ), + ( + "form_step_save_text_nl", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Save current information",), + **{} + ), + help_text="The text that will be displayed in the form step to save the current information", + max_length=50, + null=True, + verbose_name="step save text", + ), + ), + ( + "save_form_email_subject_en", + models.CharField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/save_form/subject.txt",), + **{} + ), + help_text="Subject of the save form email message.", + max_length=1000, + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="subject", + ), + ), + ( + "save_form_email_subject_nl", + models.CharField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/save_form/subject.txt",), + **{} + ), + help_text="Subject of the save form email message.", + max_length=1000, + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="subject", + ), + ), + ( + "submission_confirmation_template_en", + tinymce.models.HTMLField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Thank you for submitting this form.",), + **{} + ), + help_text="The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data.", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="submission confirmation template", + ), + ), + ( + "submission_confirmation_template_nl", + tinymce.models.HTMLField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Thank you for submitting this form.",), + **{} + ), + help_text="The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data.", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator() + ], + verbose_name="submission confirmation template", + ), + ), + ( + "form_previous_text_en", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Previous page",), + **{} + ), + help_text="The text that will be displayed in the overview page to go to the previous step", + max_length=50, + null=True, + verbose_name="back to form text", + ), + ), + ( + "form_previous_text_nl", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Previous page",), + **{} + ), + help_text="The text that will be displayed in the overview page to go to the previous step", + max_length=50, + null=True, + verbose_name="back to form text", + ), + ), + ( + "form_step_previous_text_en", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Previous page",), + **{} + ), + help_text="The text that will be displayed in the form step to go to the previous step", + max_length=50, + null=True, + verbose_name="previous step text", + ), + ), + ( + "form_step_previous_text_nl", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Previous page",), + **{} + ), + help_text="The text that will be displayed in the form step to go to the previous step", + max_length=50, + null=True, + verbose_name="previous step text", + ), + ), + ( + "organization_name", + models.CharField( + blank=True, + help_text="The name of your organization that will be used as label for elements like the logo.", + max_length=100, + verbose_name="organization name", + ), + ), + ( + "save_form_email_content", + tinymce.models.HTMLField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/save_form/content.html",), + **{} + ), + help_text="Content of the save form email message.", + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend" + ), + openforms.emails.validators.URLSanitationValidator(), + ], + verbose_name="content", + ), + ), + ( + "save_form_email_content_en", + tinymce.models.HTMLField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/save_form/content.html",), + **{} + ), + help_text="Content of the save form email message.", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend" + ), + openforms.emails.validators.URLSanitationValidator(), + ], + verbose_name="content", + ), + ), + ( + "save_form_email_content_nl", + tinymce.models.HTMLField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/save_form/content.html",), + **{} + ), + help_text="Content of the save form email message.", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend" + ), + openforms.emails.validators.URLSanitationValidator(), + ], + verbose_name="content", + ), + ), + ( + "form_upload_default_file_types", + django_jsonform.models.fields.ArrayField( + base_field=models.CharField( + choices=[ + ("*", "any filetype"), + ("image/heic", ".heic"), + ("image/png", ".png"), + ("image/jpeg", ".jpg"), + ("application/pdf", ".pdf"), + ("application/vnd.ms-excel", ".xls"), + ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ".xlsx", + ), + ("text/csv", ".csv"), + ("text/plain", ".txt"), + ("application/msword", ".doc"), + ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ".docx", + ), + ( + "application/vnd.oasis.opendocument.*,application/vnd.stardivision.*,application/vnd.sun.xml.*", + "Open Office", + ), + ("application/zip", ".zip"), + ("application/vnd.rar", ".rar"), + ("application/x-tar", ".tar"), + ("application/vnd.ms-outlook", ".msg"), + ( + "application/acad.dwg,application/autocad_dwg.dwg,application/dwg.dwg,application/x-acad.dwg,application/x-autocad.dwg,application/x-dwg.dwg,drawing/dwg.dwg,image/vnd.dwg,image/x-dwg.dwg", + ".dwg", + ), + ], + max_length=256, + ), + blank=True, + default=list, + help_text="Provide a list of default allowed file upload types. If empty, all extensions are allowed.", + size=None, + verbose_name="Default allowed file upload types", + ), + ), + ( + "privacy_policy_label", + tinymce.models.HTMLField( + blank=True, + default=functools.partial( + openforms.utils.translations.get_default, + *( + "Yes, I have read the {% privacy_policy %} and explicitly agree to the processing of my submitted information.", + ), + **{} + ), + help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend", + required_template_tags=["privacy_policy"], + ) + ], + verbose_name="privacy policy label", + ), + ), + ( + "privacy_policy_label_en", + tinymce.models.HTMLField( + blank=True, + default=functools.partial( + openforms.utils.translations.get_default, + *( + "Yes, I have read the {% privacy_policy %} and explicitly agree to the processing of my submitted information.", + ), + **{} + ), + help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend", + required_template_tags=["privacy_policy"], + ) + ], + verbose_name="privacy policy label", + ), + ), + ( + "privacy_policy_label_nl", + tinymce.models.HTMLField( + blank=True, + default=functools.partial( + openforms.utils.translations.get_default, + *( + "Yes, I have read the {% privacy_policy %} and explicitly agree to the processing of my submitted information.", + ), + **{} + ), + help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend", + required_template_tags=["privacy_policy"], + ) + ], + verbose_name="privacy policy label", + ), + ), + ( + "favicon", + openforms.utils.fields.SVGOrImageField( + blank=True, + help_text="Allow the uploading of a favicon, .png .jpg .svg and .ico are compatible.", + upload_to="logo/", + verbose_name="favicon", + ), + ), + ( + "clamav_host", + models.CharField( + blank=True, + help_text="Hostname or IP address where ClamAV is running.", + max_length=1000, + verbose_name="ClamAV server hostname", + ), + ), + ( + "clamav_port", + models.PositiveIntegerField( + blank=True, + default=3310, + help_text="The TCP port on which ClamAV is listening.", + null=True, + validators=[django.core.validators.MaxValueValidator(65535)], + verbose_name="ClamAV port number", + ), + ), + ( + "clamav_timeout", + models.PositiveSmallIntegerField( + blank=True, + help_text="ClamAV socket timeout expressed in seconds (optional).", + null=True, + validators=[django.core.validators.MaxValueValidator(60)], + verbose_name="ClamAV socket timeout", + ), + ), + ( + "enable_virus_scan", + models.BooleanField( + default=False, + help_text="Whether the files uploaded by the users should be scanned by ClamAV virus scanner.In case a file is found to be infected, the file is deleted.", + verbose_name="Enable virus scan", + ), + ), + ( + "hide_non_applicable_steps", + models.BooleanField( + default=False, + help_text="If checked, form steps that become non-applicable as a result of user input are hidden from the progress indicator display (by default, they are displayed but marked as non-applicable.)", + verbose_name="Hide non-applicable form steps", + ), + ), + ( + "enable_react_formio_builder", + models.BooleanField( + default=True, + help_text="Use the new Form.io component builder implementation.", + verbose_name="enable new formio builder", + ), + ), + ( + "submission_report_download_link_title", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Download PDF",), + **{} + ), + help_text="The title of the link to download the report of a submission.", + max_length=128, + verbose_name="submission report download link title", + ), + ), + ( + "submission_report_download_link_title_en", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Download PDF",), + **{} + ), + help_text="The title of the link to download the report of a submission.", + max_length=128, + null=True, + verbose_name="submission report download link title", + ), + ), + ( + "submission_report_download_link_title_nl", + models.CharField( + default=functools.partial( + openforms.utils.translations.get_default, + *("Download PDF",), + **{} + ), + help_text="The title of the link to download the report of a submission.", + max_length=128, + null=True, + verbose_name="submission report download link title", + ), + ), + ( + "confirmation_email_content", + tinymce.models.HTMLField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/confirmation/content.html",), + **{} + ), + help_text="Content of the confirmation email message. Can be overridden on the form level", + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend", + required_template_tags=[ + "appointment_information", + "payment_information", + "cosign_information", + ], + ), + openforms.emails.validators.URLSanitationValidator(), + ], + verbose_name="content", + ), + ), + ( + "confirmation_email_content_en", + tinymce.models.HTMLField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/confirmation/content.html",), + **{} + ), + help_text="Content of the confirmation email message. Can be overridden on the form level", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend", + required_template_tags=[ + "appointment_information", + "payment_information", + "cosign_information", + ], + ), + openforms.emails.validators.URLSanitationValidator(), + ], + verbose_name="content", + ), + ), + ( + "confirmation_email_content_nl", + tinymce.models.HTMLField( + default=functools.partial( + openforms.config.models.config._render, + *("emails/confirmation/content.html",), + **{} + ), + help_text="Content of the confirmation email message. Can be overridden on the form level", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend", + required_template_tags=[ + "appointment_information", + "payment_information", + "cosign_information", + ], + ), + openforms.emails.validators.URLSanitationValidator(), + ], + verbose_name="content", + ), + ), + ( + "form_map_default_latitude", + models.FloatField( + default=52.1326332, + validators=[ + django.core.validators.MinValueValidator(-180.0), + django.core.validators.MaxValueValidator(180.0), + ], + verbose_name="The default latitude for the leaflet map.", + ), + ), + ( + "form_map_default_longitude", + models.FloatField( + default=5.291266, + validators=[ + django.core.validators.MinValueValidator(-90.0), + django.core.validators.MaxValueValidator(90.0), + ], + verbose_name="The default longitude for the leaflet map.", + ), + ), + ( + "form_map_default_zoom_level", + models.IntegerField( + default=13, + validators=[ + django.core.validators.MinValueValidator(0), + django.core.validators.MaxValueValidator(13), + ], + verbose_name="The default zoom level for the leaflet map.", + ), + ), + ( + "ask_statement_of_truth", + models.BooleanField( + default=False, + help_text="If enabled, the user will have to agree that they filled out the form truthfully before submitting it.", + verbose_name="ask statement of truth", + ), + ), + ( + "statement_of_truth_label", + tinymce.models.HTMLField( + blank=True, + default=functools.partial( + openforms.utils.translations.get_default, + *( + "I declare that I have filled out the form truthfully and have not omitted any information.", + ), + **{} + ), + help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", + verbose_name="statement of truth label", + ), + ), + ( + "statement_of_truth_label_en", + tinymce.models.HTMLField( + blank=True, + default=functools.partial( + openforms.utils.translations.get_default, + *( + "I declare that I have filled out the form truthfully and have not omitted any information.", + ), + **{} + ), + help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", + null=True, + verbose_name="statement of truth label", + ), + ), + ( + "statement_of_truth_label_nl", + tinymce.models.HTMLField( + blank=True, + default=functools.partial( + openforms.utils.translations.get_default, + *( + "I declare that I have filled out the form truthfully and have not omitted any information.", + ), + **{} + ), + help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", + null=True, + verbose_name="statement of truth label", + ), + ), + ( + "show_form_link_in_cosign_email", + models.BooleanField( + default=True, + help_text="When enabled, a link to the form is shown in the co-sign email.", + verbose_name="show form link in co-sign email", + ), + ), + ( + "recipients_email_digest", + django_jsonform.models.fields.ArrayField( + base_field=models.EmailField(max_length=254), + blank=True, + default=list, + help_text="The email addresses that should receive a daily report of items requiring attention.", + size=None, + verbose_name="recipients email digest", + ), + ), + ( + "default_theme", + models.OneToOneField( + blank=True, + help_text="If no explicit theme is configured, the configured default theme will be used as a fallback.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="config.theme", + verbose_name="default theme", + ), + ), + ( + "wait_for_payment_to_register", + models.BooleanField( + default=False, + help_text="Should a submission be processed (sent to the registration backend) only after payment has been received?", + verbose_name="wait for payment to register", + ), + ), + ( + "organization_oin", + models.CharField( + blank=True, + help_text="The OIN of the organization.", + max_length=20, + verbose_name="organization OIN", + ), + ), + ], + options={ + "verbose_name": "General configuration", + }, + ), + ] diff --git a/src/openforms/config/migrations/0053_auto_20230830_1432.py b/src/openforms/config/migrations/0053_auto_20230830_1432.py deleted file mode 100644 index 55108e2889..0000000000 --- a/src/openforms/config/migrations/0053_auto_20230830_1432.py +++ /dev/null @@ -1,138 +0,0 @@ -# Generated by Django 3.2.20 on 2023-08-30 12:32 - -import functools - -from django.db import migrations - -import tinymce.models - -import openforms.template.validators -import openforms.utils.translations - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0002_squashed_to_of_v230"), - ] - - operations = [ - migrations.AlterField( - model_name="globalconfiguration", - name="privacy_policy_label", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Ja, ik heb kennis genomen van het {% privacy_policy %} en geef uitdrukkelijk toestemming voor het verwerken van de door mij opgegeven gegevens.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", - validators=[ - openforms.template.validators.DjangoTemplateValidator( - backend="openforms.template.openforms_backend", - required_template_tags=["privacy_policy"], - ) - ], - verbose_name="privacy policy label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="privacy_policy_label_en", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Ja, ik heb kennis genomen van het {% privacy_policy %} en geef uitdrukkelijk toestemming voor het verwerken van de door mij opgegeven gegevens.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", - null=True, - validators=[ - openforms.template.validators.DjangoTemplateValidator( - backend="openforms.template.openforms_backend", - required_template_tags=["privacy_policy"], - ) - ], - verbose_name="privacy policy label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="privacy_policy_label_nl", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Ja, ik heb kennis genomen van het {% privacy_policy %} en geef uitdrukkelijk toestemming voor het verwerken van de door mij opgegeven gegevens.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", - null=True, - validators=[ - openforms.template.validators.DjangoTemplateValidator( - backend="openforms.template.openforms_backend", - required_template_tags=["privacy_policy"], - ) - ], - verbose_name="privacy policy label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="statement_of_truth_label", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Ik verklaar dat ik deze aanvraag naar waarheid heb ingevuld en geen informatie heb verzwegen.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", - verbose_name="statement of truth label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="statement_of_truth_label_en", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Ik verklaar dat ik deze aanvraag naar waarheid heb ingevuld en geen informatie heb verzwegen.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", - null=True, - verbose_name="statement of truth label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="statement_of_truth_label_nl", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Ik verklaar dat ik deze aanvraag naar waarheid heb ingevuld en geen informatie heb verzwegen.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", - null=True, - verbose_name="statement of truth label", - ), - ), - ] diff --git a/src/openforms/config/migrations/0053_v230_to_v250.py b/src/openforms/config/migrations/0053_v230_to_v250.py index 656dd5ca23..8f367c62e3 100644 --- a/src/openforms/config/migrations/0053_v230_to_v250.py +++ b/src/openforms/config/migrations/0053_v230_to_v250.py @@ -8,7 +8,6 @@ import django.db.models.deletion from django.core.management import call_command from django.db import migrations, models -from django.utils.module_loading import import_string import django_jsonform.models.fields import tinymce.models @@ -17,53 +16,9 @@ import openforms.utils.fields import openforms.utils.translations -from ._design_tokens import ( - update_button_design_token_values, - update_layout_design_token_values, -) - -global_theme_config_to_dedicated_model = import_string( - "openforms.config.migrations.0064_auto_20231206_0921." - "global_theme_config_to_dedicated_model" -) -dedicated_model_to_global_configuration = import_string( - "openforms.config.migrations.0064_auto_20231206_0921." - "dedicated_model_to_global_configuration" -) - - -def create_csp_form_action_configs(apps, schema_editor): - call_command("create_csp_form_action_directives_from_config") - - -def set_theme_uuid(apps, _): - Theme = apps.get_model("config", "Theme") - for theme in Theme.objects.all(): - theme.uuid = uuid.uuid4() - theme.save() - class Migration(migrations.Migration): - replaces = [ - ("config", "0053_auto_20230830_1432"), - ("config", "0054_auto_20230908_1046"), - ("config", "0055_auto_20230911_2131"), - ("config", "0056_globalconfiguration_show_form_link_in_cosign_email"), - ("config", "0057_globalconfiguration_recipients_email_digest"), - ("config", "0058_auto_20231026_1525"), - ("config", "0059_convert_button_design_tokens"), - ("config", "0060_create_csp_form_action_configs"), - ("config", "0061_convert_container_layout_design_tokens"), - ("config", "0062_cspsetting_identifier"), - ("config", "0063_auto_20231122_1816"), - ("config", "0064_auto_20231206_0921"), - ("config", "0065_theme_uuid"), - ("config", "0066_globalconfiguration_wait_for_payment_to_register"), - ("config", "0067_alter_globalconfiguration_enable_react_formio_builder"), - ("config", "0068_globalconfiguration_organization_oin"), - ] - dependencies = [ ("payments_ogone", "0002_auto_20210902_2120"), ("config", "0002_squashed_to_of_v230"), @@ -360,18 +315,7 @@ class Migration(migrations.Migration): help_text="CSP header value", max_length=255, verbose_name="value" ), ), - migrations.RunPython( - code=update_button_design_token_values, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), - migrations.RunPython( - code=create_csp_form_action_configs, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), - migrations.RunPython( - code=update_layout_design_token_values, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), + # RunPython operations are removed, they were executed as part of the 2.5.0 upgrade. migrations.AlterField( model_name="cspsetting", name="directive", @@ -519,10 +463,8 @@ class Migration(migrations.Migration): verbose_name="default theme", ), ), - migrations.RunPython( - code=global_theme_config_to_dedicated_model, - reverse_code=dedicated_model_to_global_configuration, - ), + # Data migration removed - users must upgrade to 2.5 first, which guarantees + # this one to be executed. migrations.RemoveField( model_name="globalconfiguration", name="design_token_values", @@ -550,10 +492,7 @@ class Migration(migrations.Migration): default=uuid.uuid4, editable=False, verbose_name="UUID" ), ), - migrations.RunPython( - code=set_theme_uuid, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), + # RunPython operations are removed, they were executed as part of the 2.5.0 upgrade. migrations.AlterField( model_name="theme", name="uuid", diff --git a/src/openforms/config/migrations/0054_auto_20230908_1046.py b/src/openforms/config/migrations/0054_auto_20230908_1046.py deleted file mode 100644 index 3239bd1a52..0000000000 --- a/src/openforms/config/migrations/0054_auto_20230908_1046.py +++ /dev/null @@ -1,138 +0,0 @@ -# Generated by Django 3.2.21 on 2023-09-08 08:46 - -import functools - -from django.db import migrations - -import tinymce.models - -import openforms.template.validators -import openforms.utils.translations - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0053_auto_20230830_1432"), - ] - - operations = [ - migrations.AlterField( - model_name="globalconfiguration", - name="privacy_policy_label", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Yes, I have read the {% privacy_policy %} and explicitly agree to the processing of my submitted information.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", - validators=[ - openforms.template.validators.DjangoTemplateValidator( - backend="openforms.template.openforms_backend", - required_template_tags=["privacy_policy"], - ) - ], - verbose_name="privacy policy label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="privacy_policy_label_en", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Yes, I have read the {% privacy_policy %} and explicitly agree to the processing of my submitted information.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", - null=True, - validators=[ - openforms.template.validators.DjangoTemplateValidator( - backend="openforms.template.openforms_backend", - required_template_tags=["privacy_policy"], - ) - ], - verbose_name="privacy policy label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="privacy_policy_label_nl", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "Yes, I have read the {% privacy_policy %} and explicitly agree to the processing of my submitted information.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree to the privacy policy.", - null=True, - validators=[ - openforms.template.validators.DjangoTemplateValidator( - backend="openforms.template.openforms_backend", - required_template_tags=["privacy_policy"], - ) - ], - verbose_name="privacy policy label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="statement_of_truth_label", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "I declare that I have filled out the form truthfully and have not omitted any information.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", - verbose_name="statement of truth label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="statement_of_truth_label_en", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "I declare that I have filled out the form truthfully and have not omitted any information.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", - null=True, - verbose_name="statement of truth label", - ), - ), - migrations.AlterField( - model_name="globalconfiguration", - name="statement_of_truth_label_nl", - field=tinymce.models.HTMLField( - blank=True, - default=functools.partial( - openforms.utils.translations.get_default, - *( - "I declare that I have filled out the form truthfully and have not omitted any information.", - ), - **{} - ), - help_text="The label of the checkbox that prompts the user to agree that they filled out the form truthfully. Note that this field does not have templating support.", - null=True, - verbose_name="statement of truth label", - ), - ), - ] diff --git a/src/openforms/config/migrations/0055_auto_20230911_2131.py b/src/openforms/config/migrations/0055_auto_20230911_2131.py deleted file mode 100644 index 97a8f0a291..0000000000 --- a/src/openforms/config/migrations/0055_auto_20230911_2131.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.2.20 on 2023-09-11 19:31 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0054_auto_20230908_1046"), - ] - - operations = [ - migrations.RemoveField( - model_name="globalconfiguration", - name="default_test_bsn", - ), - migrations.RemoveField( - model_name="globalconfiguration", - name="default_test_kvk", - ), - ] diff --git a/src/openforms/config/migrations/0056_globalconfiguration_show_form_link_in_cosign_email.py b/src/openforms/config/migrations/0056_globalconfiguration_show_form_link_in_cosign_email.py deleted file mode 100644 index 85670dc053..0000000000 --- a/src/openforms/config/migrations/0056_globalconfiguration_show_form_link_in_cosign_email.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.2.20 on 2023-09-18 09:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("config", "0055_auto_20230911_2131"), - ] - - operations = [ - migrations.AddField( - model_name="globalconfiguration", - name="show_form_link_in_cosign_email", - field=models.BooleanField( - default=True, - help_text="When enabled, a link to the form is shown in the co-sign email.", - verbose_name="show form link in co-sign email", - ), - ), - ] diff --git a/src/openforms/config/migrations/0057_globalconfiguration_recipients_email_digest.py b/src/openforms/config/migrations/0057_globalconfiguration_recipients_email_digest.py deleted file mode 100644 index b960bc0b2a..0000000000 --- a/src/openforms/config/migrations/0057_globalconfiguration_recipients_email_digest.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.2.21 on 2023-09-29 08:55 - -from django.db import migrations, models - -import django_jsonform.models.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0056_globalconfiguration_show_form_link_in_cosign_email"), - ] - - operations = [ - migrations.AddField( - model_name="globalconfiguration", - name="recipients_email_digest", - field=django_jsonform.models.fields.ArrayField( - base_field=models.EmailField(max_length=254), - blank=True, - default=list, - help_text="The email addresses that should receive a daily report of items requiring attention.", - size=None, - verbose_name="recipients email digest", - ), - ), - ] diff --git a/src/openforms/config/migrations/0058_auto_20231026_1525.py b/src/openforms/config/migrations/0058_auto_20231026_1525.py deleted file mode 100644 index 22fb1191bb..0000000000 --- a/src/openforms/config/migrations/0058_auto_20231026_1525.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 3.2.21 on 2023-10-26 13:25 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ("contenttypes", "0002_remove_content_type_name"), - ("config", "0057_globalconfiguration_recipients_email_digest"), - ] - - operations = [ - migrations.AddField( - model_name="cspsetting", - name="content_type", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="contenttypes.contenttype", - verbose_name="content type", - ), - ), - migrations.AddField( - model_name="cspsetting", - name="object_id", - field=models.TextField(blank=True, db_index=True, verbose_name="object id"), - ), - migrations.AlterField( - model_name="cspsetting", - name="value", - field=models.CharField( - help_text="CSP header value", max_length=255, verbose_name="value" - ), - ), - ] diff --git a/src/openforms/config/migrations/0059_convert_button_design_tokens.py b/src/openforms/config/migrations/0059_convert_button_design_tokens.py deleted file mode 100644 index cb3e20833b..0000000000 --- a/src/openforms/config/migrations/0059_convert_button_design_tokens.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.2.21 on 2023-10-31 14:23 - -from django.db import migrations - -from ._design_tokens import update_button_design_token_values - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0058_auto_20231026_1525"), - ] - - operations = [ - # reversing this is not possible - make sure you have backups! - migrations.RunPython( - update_button_design_token_values, migrations.RunPython.noop - ), - ] diff --git a/src/openforms/config/migrations/0060_create_csp_form_action_configs.py b/src/openforms/config/migrations/0060_create_csp_form_action_configs.py deleted file mode 100644 index 1b65d92d60..0000000000 --- a/src/openforms/config/migrations/0060_create_csp_form_action_configs.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.2.21 on 2023-11-09 08:58 - -from django.core.management import call_command -from django.db import migrations - - -def call_management_command(apps, schema_editor): - call_command("create_csp_form_action_directives_from_config") - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0059_convert_button_design_tokens"), - ("payments_ogone", "0002_auto_20210902_2120"), - ("digid_eherkenning", "0006_digidconfiguration_metadata_file_source_and_more"), - ] - - operations = [ - migrations.RunPython(call_management_command, migrations.RunPython.noop), - ] diff --git a/src/openforms/config/migrations/0061_convert_container_layout_design_tokens.py b/src/openforms/config/migrations/0061_convert_container_layout_design_tokens.py deleted file mode 100644 index 705f5d333b..0000000000 --- a/src/openforms/config/migrations/0061_convert_container_layout_design_tokens.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.21 on 2023-11-09 16:11 -from django.db import migrations - -from ._design_tokens import update_layout_design_token_values - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0060_create_csp_form_action_configs"), - ] - - operations = [ - # reversing this is not possible - make sure you have backups! - migrations.RunPython( - update_layout_design_token_values, migrations.RunPython.noop - ), - ] diff --git a/src/openforms/config/migrations/0062_cspsetting_identifier.py b/src/openforms/config/migrations/0062_cspsetting_identifier.py deleted file mode 100644 index 08c3f74368..0000000000 --- a/src/openforms/config/migrations/0062_cspsetting_identifier.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.21 on 2023-11-17 15:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0061_convert_container_layout_design_tokens"), - ] - - operations = [ - migrations.AddField( - model_name="cspsetting", - name="identifier", - field=models.CharField( - blank=True, - help_text="An extra tag for this CSP entry, to identify the exact source", - max_length=64, - verbose_name="identifier", - ), - ), - ] diff --git a/src/openforms/config/migrations/0063_auto_20231122_1816.py b/src/openforms/config/migrations/0063_auto_20231122_1816.py deleted file mode 100644 index 406130a32a..0000000000 --- a/src/openforms/config/migrations/0063_auto_20231122_1816.py +++ /dev/null @@ -1,67 +0,0 @@ -# Generated by Django 3.2.21 on 2023-11-22 17:16 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0062_cspsetting_identifier"), - ] - - operations = [ - migrations.AlterField( - model_name="cspsetting", - name="directive", - field=models.CharField( - choices=[ - ("default-src", "default-src"), - ("script-src", "script-src"), - ("script-src-attr", "script-src-attr"), - ("script-src-elem", "script-src-elem"), - ("img-src", "img-src"), - ("object-src", "object-src"), - ("prefetch-src", "prefetch-src"), - ("media-src", "media-src"), - ("frame-src", "frame-src"), - ("font-src", "font-src"), - ("connect-src", "connect-src"), - ("style-src", "style-src"), - ("style-src-attr", "style-src-attr"), - ("style-src-elem", "style-src-elem"), - ("base-uri", "base-uri"), - ("child-src", "child-src"), - ("frame-ancestors", "frame-ancestors"), - ("navigate-to", "navigate-to"), - ("form-action", "form-action"), - ("sandbox", "sandbox"), - ("report-uri", "report-uri"), - ("report-to", "report-to"), - ("manifest-src", "manifest-src"), - ("worker-src", "worker-src"), - ("plugin-types", "plugin-types"), - ("require-sri-for", "require-sri-for"), - ], - help_text="CSP header directive.", - max_length=64, - verbose_name="directive", - ), - ), - migrations.AlterField( - model_name="cspsetting", - name="identifier", - field=models.CharField( - blank=True, - help_text="An extra tag for this CSP entry, to identify the exact source.", - max_length=64, - verbose_name="identifier", - ), - ), - migrations.AlterField( - model_name="cspsetting", - name="value", - field=models.CharField( - help_text="CSP header value.", max_length=255, verbose_name="value" - ), - ), - ] diff --git a/src/openforms/config/migrations/0064_auto_20231206_0921.py b/src/openforms/config/migrations/0064_auto_20231206_0921.py deleted file mode 100644 index e90ca5c0c1..0000000000 --- a/src/openforms/config/migrations/0064_auto_20231206_0921.py +++ /dev/null @@ -1,182 +0,0 @@ -# Generated by Django 3.2.23 on 2023-12-06 08:21 - -import django.core.validators -import django.db.models.deletion -from django.db import migrations, models - -import openforms.utils.fields - - -def global_theme_config_to_dedicated_model(apps, _): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - Theme = apps.get_model("config", "Theme") - - config = GlobalConfiguration.objects.first() - if not config: - return - - # if no styling things are configured, there's no point in creating a theme record - if not any( - [ - config.logo, - config.theme_classname, - config.theme_stylesheet, - config.theme_stylesheet_file, - config.design_token_values, - ] - ): - return - - # create a theme record with the equivalent configuration and set it as default - theme = Theme.objects.create( - name="Standaard", - logo=config.logo, - classname=config.theme_classname, - stylesheet=config.theme_stylesheet, - stylesheet_file=config.theme_stylesheet_file, - design_token_values=config.design_token_values, - ) - config.default_theme = theme - config.save() - - -def dedicated_model_to_global_configuration(apps, _): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - - config = GlobalConfiguration.objects.first() - if not config or not (theme := config.default_theme): - return - - config.logo = theme.logo - config.theme_classname = theme.classname - config.theme_stylesheet = theme.stylesheet - config.theme_stylesheet_file = theme.stylesheet_file - config.design_token_values = theme.design_token_values - config.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0063_auto_20231122_1816"), - ] - - operations = [ - migrations.CreateModel( - name="Theme", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField( - help_text="An easily recognizable name for the theme, used to identify it.", - max_length=100, - verbose_name="name", - ), - ), - ( - "logo", - openforms.utils.fields.SVGOrImageField( - blank=True, - help_text="Upload the theme/orgnization logo, visible to users filling out forms. We advise dimensions around 150px by 75px. SVG's are permitted.", - upload_to="logo/", - verbose_name="theme logo", - ), - ), - ( - "classname", - models.SlugField( - blank=True, - help_text="If provided, this class name will be set on the element.", - verbose_name="theme CSS class name", - ), - ), - ( - "stylesheet", - models.URLField( - blank=True, - help_text="The URL stylesheet with theme-specific rules for your organization. This will be included as final stylesheet, overriding previously defined styles. Note that you also have to include the host to the `style-src` CSP directive. Example value: https://unpkg.com/@utrecht/design-tokens@1.0.0-alpha.20/dist/index.css.", - max_length=1000, - validators=[ - django.core.validators.RegexValidator( - message="The URL must point to a CSS resource (.css extension).", - regex="\\.css$", - ) - ], - verbose_name="theme stylesheet URL", - ), - ), - ( - "stylesheet_file", - models.FileField( - blank=True, - help_text="A stylesheet with theme-specific rules for your organization. This will be included as final stylesheet, overriding previously defined styles. If both a URL to a stylesheet and a stylesheet file have been configured, the uploaded file is included after the stylesheet URL.", - upload_to="config/themes/", - validators=[ - django.core.validators.FileExtensionValidator( - allowed_extensions=("css",) - ) - ], - verbose_name="theme stylesheet", - ), - ), - ( - "design_token_values", - models.JSONField( - blank=True, - default=dict, - help_text="Values of various style parameters, such as border radii, background colors... Note that this is advanced usage. Any available but un-specified values will use fallback default values. See https://open-forms.readthedocs.io/en/latest/installation/form_hosting.html#run-time-configuration for documentation.", - verbose_name="design token values", - ), - ), - ], - options={ - "verbose_name": "theme", - "verbose_name_plural": "themes", - }, - ), - migrations.AddField( - model_name="globalconfiguration", - name="default_theme", - field=models.OneToOneField( - blank=True, - help_text="If no explicit theme is configured, the configured default theme will be used as a fallback.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="config.theme", - verbose_name="default theme", - ), - ), - migrations.RunPython( - code=global_theme_config_to_dedicated_model, - reverse_code=dedicated_model_to_global_configuration, - ), - migrations.RemoveField( - model_name="globalconfiguration", - name="design_token_values", - ), - migrations.RemoveField( - model_name="globalconfiguration", - name="logo", - ), - migrations.RemoveField( - model_name="globalconfiguration", - name="theme_classname", - ), - migrations.RemoveField( - model_name="globalconfiguration", - name="theme_stylesheet", - ), - migrations.RemoveField( - model_name="globalconfiguration", - name="theme_stylesheet_file", - ), - ] diff --git a/src/openforms/config/migrations/0065_theme_uuid.py b/src/openforms/config/migrations/0065_theme_uuid.py deleted file mode 100644 index 0640a07d79..0000000000 --- a/src/openforms/config/migrations/0065_theme_uuid.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 3.2.23 on 2023-12-07 14:11 - -import uuid - -from django.db import migrations, models - - -def set_uuid(apps, _): - Theme = apps.get_model("config", "Theme") - for theme in Theme.objects.all(): - theme.uuid = uuid.uuid4() - theme.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0064_auto_20231206_0921"), - ] - - operations = [ - migrations.AddField( - model_name="theme", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, verbose_name="UUID" - ), - ), - migrations.RunPython(set_uuid, migrations.RunPython.noop), - migrations.AlterField( - model_name="theme", - name="uuid", - field=models.UUIDField( - default=uuid.uuid4, editable=False, unique=True, verbose_name="UUID" - ), - ), - ] diff --git a/src/openforms/config/migrations/0066_globalconfiguration_wait_for_payment_to_register.py b/src/openforms/config/migrations/0066_globalconfiguration_wait_for_payment_to_register.py deleted file mode 100644 index 9b320008ce..0000000000 --- a/src/openforms/config/migrations/0066_globalconfiguration_wait_for_payment_to_register.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.23 on 2023-12-12 08:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0065_theme_uuid"), - ] - - operations = [ - migrations.AddField( - model_name="globalconfiguration", - name="wait_for_payment_to_register", - field=models.BooleanField( - default=False, - help_text="Should a submission be processed (sent to the registration backend) only after payment has been received?", - verbose_name="wait for payment to register", - ), - ), - ] diff --git a/src/openforms/config/migrations/0067_alter_globalconfiguration_enable_react_formio_builder.py b/src/openforms/config/migrations/0067_alter_globalconfiguration_enable_react_formio_builder.py deleted file mode 100644 index 6d9724c044..0000000000 --- a/src/openforms/config/migrations/0067_alter_globalconfiguration_enable_react_formio_builder.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.23 on 2024-01-10 14:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0066_globalconfiguration_wait_for_payment_to_register"), - ] - - operations = [ - migrations.AlterField( - model_name="globalconfiguration", - name="enable_react_formio_builder", - field=models.BooleanField( - default=True, - help_text="Use the new Form.io component builder implementation.", - verbose_name="enable new formio builder", - ), - ), - ] diff --git a/src/openforms/config/migrations/0068_globalconfiguration_organization_oin.py b/src/openforms/config/migrations/0068_globalconfiguration_organization_oin.py deleted file mode 100644 index 6e3f62e9f2..0000000000 --- a/src/openforms/config/migrations/0068_globalconfiguration_organization_oin.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.2.23 on 2024-01-15 14:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0067_alter_globalconfiguration_enable_react_formio_builder"), - ] - - operations = [ - migrations.AddField( - model_name="globalconfiguration", - name="organization_oin", - field=models.CharField( - blank=True, - help_text="The OIN of the organization.", - max_length=20, - verbose_name="organization OIN", - ), - ), - ] diff --git a/src/openforms/config/migrations/_design_tokens.py b/src/openforms/config/migrations/_design_tokens.py deleted file mode 100644 index 250105c662..0000000000 --- a/src/openforms/config/migrations/_design_tokens.py +++ /dev/null @@ -1,182 +0,0 @@ -from copy import deepcopy -from typing import Callable, TypeAlias, TypedDict - -from glom import assign, delete, glom - -# mapping from new, NL DS token to the source (old custom token) -BUTTON_MAPPING = { - "utrecht.button.background-color": "of.button.bg", - "utrecht.button.border-color": "of.button.color-border", - "utrecht.button.color": "of.button.fg", - "utrecht.button.font-family": "of.typography.sans-serif.font-family", - "utrecht.button.hover.background-color": "of.button.hover.bg", - "utrecht.button.hover.border-color": "of.button.hover.color-border", - "utrecht.button.active.background-color": "of.button.active.bg", - "utrecht.button.active.border-color": "of.button.active.color-border", - "utrecht.button.active.color": "of.button.active.fg", - "utrecht.button.focus.border-color": "of.button.focus.color-border", - "utrecht.button.primary-action.background-color": "of.button.primary.bg", - "utrecht.button.primary-action.border-color": "of.button.primary.color-border", - "utrecht.button.primary-action.color": "of.button.primary.fg", - "utrecht.button.primary-action.hover.background-color": "of.button.primary.hover.bg", - "utrecht.button.primary-action.hover.border-color": "of.button.primary.hover.color-border", - "utrecht.button.primary-action.active.background-color": "of.button.primary.active.bg", - "utrecht.button.primary-action.active.border-color": "of.button.primary.active.color-border", - "utrecht.button.primary-action.active.color": "of.button.primary.active.fg", - "utrecht.button.primary-action.focus.border-color": "of.button.primary.focus.color-border", - "utrecht.button.primary-action.danger.background-color": "of.button.danger.bg", - "utrecht.button.primary-action.danger.border-color": "of.button.danger.color-border", - "utrecht.button.primary-action.danger.color": "of.button.danger.fg", - "utrecht.button.primary-action.danger.hover.background-color": "of.button.danger.hover.bg", - "utrecht.button.primary-action.danger.hover.border-color": "of.button.danger.hover.color-border ", - "utrecht.button.primary-action.danger.active.background-color": "of.button.danger.active.bg", - "utrecht.button.primary-action.danger.active.border-color": "of.button.danger.active.color-border ", - "utrecht.button.primary-action.danger.active.color": "of.button.danger.active.fg", - "utrecht.button.primary-action.danger.focus.border-color": "of.button.danger.focus.color-border ", - "utrecht.button.secondary-action.background-color": "of.color.bg", - "utrecht.button.secondary-action.border-color": "of.color.border", - "utrecht.button.secondary-action.color": "of.color.fg", - "utrecht.button.secondary-action.hover.background-color": "of.button.hover.bg", - "utrecht.button.secondary-action.hover.border-color": "of.button.hover.color-border", - "utrecht.button.secondary-action.active.background-color": "of.button.active.bg", - "utrecht.button.secondary-action.active.border-color": "of.button.active.color-border", - "utrecht.button.secondary-action.active.color": "of.button.active.fg", - "utrecht.button.secondary-action.focus.border-color": "of.color.focus-border", - "utrecht.button.secondary-action.danger.background-color": "of.button.danger.bg", - "utrecht.button.secondary-action.danger.border-color": "of.button.danger.color-border", - "utrecht.button.secondary-action.danger.color": "of.button.danger.fg", - "utrecht.button.secondary-action.danger.hover.background-color": "of.button.danger.hover.bg", - "utrecht.button.secondary-action.danger.hover.border-color": "of.button.danger.hover.color-border", - "utrecht.button.secondary-action.danger.active.background-color": "of.button.danger.active.bg ", - "utrecht.button.secondary-action.danger.active.border-color": "of.button.danger.active.color-border ", - "utrecht.button.secondary-action.danger.active.color": "of.button.danger.active.fg", - "utrecht.button.secondary-action.danger.focus.border-color": "of.button.danger.focus.color-border ", - "utrecht.button.subtle.danger.background-color": "of.button.danger.bg", - "utrecht.button.subtle.danger.border-color": "of.button.danger.color-border", - "utrecht.button.subtle.danger.color": "of.color.danger", - "utrecht.button.subtle.danger.active.background-color": "of.color.danger", - "utrecht.button.subtle.danger.active.color": "of.color.bg", -} - - -# mapping from new, NL DS token to the source (old custom token) -LAYOUT_MAPPING = { - # footer - "utrecht.page-footer.background-color": "of.page-footer.bg", - "utrecht.page-footer.color": "of.page-footer.fg", - # header - "utrecht.page-header.background-color": "of.page-header.bg", - "utrecht.page-header.color": "of.page-header.fg", - # use logical properties instead of absolute positions - "of.page-header.logo-return-url.min-block-size": "of.page-header.logo-return-url.min-height", - "of.page-header.logo-return-url.min-inline-size": "of.page-header.logo-return-url.min-width", - "of.page-header.logo-return-url.mobile.min-block-size": "of.page-header.logo-return-url.mobile.min-height", - "of.page-header.logo-return-url.mobile.min-inline-size": "of.page-header.logo-return-url.mobile.min-width", -} - -OBSOLETE_PREFIXES = ( - "of.page-footer.", - "of.page-header.", -) - - -class TokenValue(TypedDict): - value: str - - -TokenContainer: TypeAlias = dict[str, "TokenValue | TokenContainer"] - - -unset = object() - - -def apply_button_mapping(design_tokens: TokenContainer) -> TokenContainer: - result = deepcopy(design_tokens) - - tokens_to_unset = set() - - for new, old in BUTTON_MAPPING.items(): - old_value = glom(design_tokens, old, default=unset) - if old_value is unset: - continue - - existing_value = glom(result, new, default=unset) - if existing_value is not unset: - tokens_to_unset.add(old) - continue - - assign(result, new, old_value, missing=dict) - tokens_to_unset.add(old) - - for token in tokens_to_unset: - # don't delete utility tokens! - if not token.startswith("of.button."): - continue - delete(result, token) - - return remove_empty_design_tokens(result) - - -def apply_layout_mapping(design_tokens: TokenContainer) -> TokenContainer: - result = deepcopy(design_tokens) - - tokens_to_unset = set() - - for new, old in LAYOUT_MAPPING.items(): - old_value = glom(design_tokens, old, default=unset) - if old_value is unset: - continue - - existing_value = glom(result, new, default=unset) - if existing_value is not unset: - tokens_to_unset.add(old) - continue - - assign(result, new, old_value, missing=dict) - tokens_to_unset.add(old) - - # TODO: re-enable this when #3593 is properly resolved. - # for token in tokens_to_unset: - # # don't delete utility tokens! - # if not any(token.startswith(prefix) for prefix in OBSOLETE_PREFIXES): - # continue - # delete(result, token) - - return remove_empty_design_tokens(result) - - -def remove_empty_design_tokens(obj: dict) -> dict: - if "value" in obj: - return obj - - result = {} - for key, value in obj.items(): - if not isinstance(value, dict): - continue - updated_value = remove_empty_design_tokens(value) - # empty object -> remove it by not including it anymore - if not updated_value: - continue - - result[key] = updated_value - - return result - - -def _update_factory(mapper: Callable[[TokenContainer], TokenContainer]): - def inner(apps, _): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - config = GlobalConfiguration.objects.first() - if config is None: - return - - updated = mapper(config.design_token_values) - if updated != config.design_token_values: - config.design_token_values = updated - config.save(update_fields=["design_token_values"]) - - return inner - - -update_button_design_token_values = _update_factory(apply_button_mapping) -update_layout_design_token_values = _update_factory(apply_layout_mapping) diff --git a/src/openforms/config/tests/test_csp_update.py b/src/openforms/config/tests/test_csp_update.py index 0cf71d594d..3cae2e2763 100644 --- a/src/openforms/config/tests/test_csp_update.py +++ b/src/openforms/config/tests/test_csp_update.py @@ -1,19 +1,5 @@ -from io import StringIO - -from django.core.files.base import ContentFile -from django.core.management import call_command from django.test import TestCase, override_settings -from digid_eherkenning.models import DigidConfiguration, EherkenningConfiguration - -from openforms.contrib.digid_eherkenning.tests.test_csp_update import ( - DIGID_METADATA_POST, - EHERKENNING_METADATA_POST, -) -from openforms.payments.contrib.ogone.models import OgoneMerchant -from openforms.payments.contrib.ogone.tests.factories import OgoneMerchantFactory -from openforms.utils.tests.cache import clear_caches - from ..models import CSPSetting @@ -41,95 +27,3 @@ def test_middleware_applies_cspsetting_models(self): self.assertIn("http://foo.bar", csp_policy["img-src"]) self.assertIn("http://bazz.bar", csp_policy["img-src"]) self.assertIn("http://buzz.bazz", csp_policy["default-src"]) - - -class CreateCSPFormActionFromConfigTests(TestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - # looks like there may be left over (migration) data from non-transaction testcases - DigidConfiguration.objects.all().delete() - EherkenningConfiguration.objects.all().delete() - - def setUp(self): - super().setUp() - - self.addCleanup(clear_caches) - - def test_no_ogone_nor_digid_eherkenning_config(self): - assert not OgoneMerchant.objects.exists() - assert not DigidConfiguration.objects.exists() - assert not EherkenningConfiguration.objects.exists() - - call_command( - "create_csp_form_action_directives_from_config", - stdout=StringIO(), - stderr=StringIO(), - ) - - self.assertFalse(CSPSetting.objects.exists()) - - def test_config_records_exist_but_are_incomplete(self): - OgoneMerchant.objects.create( - endpoint_preset="", # not realistic, but the DB allows it - endpoint_custom="", - ) - digid_configuration = DigidConfiguration.objects.create() - assert not digid_configuration.idp_metadata_file - eherkenning_configuration = EherkenningConfiguration.objects.create() - assert not eherkenning_configuration.idp_metadata_file - CSPSetting.objects.all().delete() # delete creates from test data setup - - call_command( - "create_csp_form_action_directives_from_config", - stdout=StringIO(), - stderr=StringIO(), - ) - - self.assertFalse(CSPSetting.objects.exists()) - - def test_config_created_when_ogone_merchants_with_url_exist(self): - ogone_merchant = OgoneMerchantFactory.create() - CSPSetting.objects.all().delete() # delete creates from test data setup - - call_command( - "create_csp_form_action_directives_from_config", - stdout=StringIO(), - stderr=StringIO(), - ) - - csp_setting = CSPSetting.objects.get() - self.assertEqual(csp_setting.content_object, ogone_merchant) - - def test_digid_eherkenning_config_creates_cspsetting_records(self): - digid_metadata = ContentFile( - DIGID_METADATA_POST.read_bytes(), name="digid_metadata.xml" - ) - digid_configuration = DigidConfiguration.objects.create( - idp_metadata_file=digid_metadata - ) - assert digid_configuration.idp_metadata_file - eherkenning_metadata = ContentFile( - EHERKENNING_METADATA_POST.read_bytes(), name="eh_metadata.xml" - ) - eherkenning_configuration = EherkenningConfiguration.objects.create( - idp_metadata_file=eherkenning_metadata - ) - assert eherkenning_configuration.idp_metadata_file - CSPSetting.objects.all().delete() # delete creates from test data setup - - call_command( - "create_csp_form_action_directives_from_config", - stdout=StringIO(), - stderr=StringIO(), - ) - - csp_settings = CSPSetting.objects.all() - self.assertEqual(len(csp_settings), 2) - - by_content_object = { - csp_setting.content_object: csp_setting for csp_setting in csp_settings - } - self.assertIn(digid_configuration, by_content_object) - self.assertIn(eherkenning_configuration, by_content_object) diff --git a/src/openforms/config/tests/test_migrations.py b/src/openforms/config/tests/test_migrations.py index 3706b715ba..15b97726ca 100644 --- a/src/openforms/config/tests/test_migrations.py +++ b/src/openforms/config/tests/test_migrations.py @@ -1,286 +1,9 @@ -from django.test import SimpleTestCase - from openforms.utils.tests.test_migrations import TestMigrations -from ..migrations._design_tokens import apply_button_mapping, apply_layout_mapping - - -class ButtonMapperTests(SimpleTestCase): - def test_empty_tokens(self): - updated = apply_button_mapping({}) - - self.assertEqual(updated, {}) - - def test_unrelated_tokens(self): - source = {"utrecht": {"form-field": {"font-size": {"value": "18px"}}}} - - updated = apply_button_mapping(source) - - self.assertIsNot(updated, source) - self.assertEqual(updated, source) - - def test_map_old_token_to_new(self): - source = {"of": {"button": {"bg": {"value": "blue"}}}} - - updated = apply_button_mapping(source) - - self.assertEqual( - updated, {"utrecht": {"button": {"background-color": {"value": "blue"}}}} - ) - - def test_map_and_merge_old_token_to_new(self): - source = { - "of": {"button": {"bg": {"value": "blue"}}}, - "utrecht": {"button": {"color": {"value": "pink"}}}, - } - - updated = apply_button_mapping(source) - - self.assertEqual( - updated, - { - "utrecht": { - "button": { - "background-color": {"value": "blue"}, - "color": {"value": "pink"}, - } - } - }, - ) - - def test_do_not_overwrite_existing_token(self): - source = { - "of": {"button": {"bg": {"value": "red"}}}, - "utrecht": {"button": {"background-color": {"value": "blue"}}}, - } - - updated = apply_button_mapping(source) - - self.assertEqual( - updated, {"utrecht": {"button": {"background-color": {"value": "blue"}}}} - ) - - -class LayoutMapperTests(SimpleTestCase): - def test_empty_tokens(self): - updated = apply_layout_mapping({}) - - self.assertEqual(updated, {}) - - def test_unrelated_tokens(self): - source = {"utrecht": {"form-field": {"font-size": {"value": "18px"}}}} - - updated = apply_layout_mapping(source) - - self.assertIsNot(updated, source) - self.assertEqual(updated, source) - - def test_map_old_token_to_new(self): - source = {"of": {"page-footer": {"bg": {"value": "blue"}}}} - - updated = apply_layout_mapping(source) - - self.assertEqual( - updated, - { - "of": {"page-footer": {"bg": {"value": "blue"}}}, - "utrecht": {"page-footer": {"background-color": {"value": "blue"}}}, - }, - ) - - def test_map_and_merge_old_token_to_new(self): - source = { - "of": {"page-header": {"bg": {"value": "blue"}}}, - "utrecht": {"page-header": {"color": {"value": "pink"}}}, - } - - updated = apply_layout_mapping(source) - - self.assertEqual( - updated, - { - "of": {"page-header": {"bg": {"value": "blue"}}}, - "utrecht": { - "page-header": { - "background-color": {"value": "blue"}, - "color": {"value": "pink"}, - } - }, - }, - ) - - def test_do_not_overwrite_existing_token(self): - source = { - "of": {"page-header": {"bg": {"value": "red"}}}, - "utrecht": {"page-header": {"background-color": {"value": "blue"}}}, - } - - updated = apply_layout_mapping(source) - - self.assertEqual( - updated, - { - "of": {"page-header": {"bg": {"value": "red"}}}, - "utrecht": {"page-header": {"background-color": {"value": "blue"}}}, - }, - ) - - -class ButtonDesignTokensMigrationTests(TestMigrations): - - app = "config" - migrate_from = "0002_squashed_to_of_v230" - migrate_to = "0053_v230_to_v250" - - def setUpBeforeMigration(self, apps): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - # (Partial) tokens configuration taken from a test instance - GlobalConfiguration.objects.create( - design_token_values={ - "of": { - "text": {"font-size": {"value": "1rem"}}, - "color": { - "bg": {"value": "#fff"}, - "fg": {"value": "#000000"}, - "info": {"value": "#007bc7"}, - "border": {"value": "#b3b3b3"}, - "danger": {"value": "#d52b1e"}, - "primary": {"value": "#07838f"}, - "success": {"value": "green"}, - "warning": {"value": "#e17000"}, - }, - "button": { - "bg": {"value": "#07838f"}, - "fg": {"value": "#FFFFFF"}, - "hover": {"bg": {"value": "rgba(0,116,126,255)"}}, - "primary": { - "active": { - "bg": { - "value": "green", - } - } - }, - }, - }, - "utrecht": { - "button": { - "primary-action": { - "active": { - "background-color": {"value": "#9d2f66"}, - }, - } - }, - }, - } - ) - - def test_design_tokens_updated_correctly(self): - self.maxDiff = None - - Theme = self.apps.get_model("config", "Theme") - theme = Theme.objects.get() - - expected = { - "of": { - "text": {"font-size": {"value": "1rem"}}, - "color": { - "bg": {"value": "#fff"}, - "fg": {"value": "#000000"}, - "info": {"value": "#007bc7"}, - "border": {"value": "#b3b3b3"}, - "danger": {"value": "#d52b1e"}, - "primary": {"value": "#07838f"}, - "success": {"value": "green"}, - "warning": {"value": "#e17000"}, - }, - }, - "utrecht": { - "button": { - "background-color": {"value": "#07838f"}, - "color": {"value": "#FFFFFF"}, - "hover": {"background-color": {"value": "rgba(0,116,126,255)"}}, - "primary-action": { - "active": { - "background-color": {"value": "#9d2f66"}, - }, - }, - "secondary-action": { - "background-color": {"value": "#fff"}, - "border-color": {"value": "#b3b3b3"}, - "color": {"value": "#000000"}, - "hover": {"background-color": {"value": "rgba(0,116,126,255)"}}, - }, - "subtle": { - "danger": { - "color": {"value": "#d52b1e"}, - "active": { - "background-color": {"value": "#d52b1e"}, - "color": {"value": "#fff"}, - }, - }, - }, - }, - }, - } - self.assertEqual(theme.design_token_values, expected) - - -class LayoutDesignTokensMigrationTests(TestMigrations): - - app = "config" - migrate_from = "0002_squashed_to_of_v230" - migrate_to = "0053_v230_to_v250" - - def setUpBeforeMigration(self, apps): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - # (Partial) tokens configuration taken from a test instance - GlobalConfiguration.objects.create( - design_token_values={ - "of": { - "text": {"font-size": {"value": "1rem"}}, - "page-header": { - "bg": {"value": "#07838f"}, - "fg": {"value": "#FFFFFF"}, - }, - }, - "utrecht": { - "page-footer": { - "background-color": {"value": "green"}, - }, - }, - } - ) - - def test_design_tokens_updated_correctly(self): - self.maxDiff = None - - Theme = self.apps.get_model("config", "Theme") - theme = Theme.objects.get() - - expected = { - "of": { - "text": {"font-size": {"value": "1rem"}}, - "page-header": { - "bg": {"value": "#07838f"}, - "fg": {"value": "#FFFFFF"}, - }, - }, - "utrecht": { - "page-footer": { - "background-color": {"value": "green"}, - }, - "page-header": { - "background-color": {"value": "#07838f"}, - "color": {"value": "#FFFFFF"}, - }, - }, - } - self.assertEqual(theme.design_token_values, expected) - class EnableNewBuilderMigrationTests(TestMigrations): app = "config" - migrate_from = "0053_v230_to_v250" + migrate_from = "0001_initial_to_v250" migrate_to = "0054_enable_new_builder" def setUpBeforeMigration(self, apps): diff --git a/src/openforms/config/tests/test_theme_migrations.py b/src/openforms/config/tests/test_theme_migrations.py deleted file mode 100644 index 108bc16a3f..0000000000 --- a/src/openforms/config/tests/test_theme_migrations.py +++ /dev/null @@ -1,119 +0,0 @@ -from openforms.utils.tests.test_migrations import TestMigrations - - -class ForwardMigration(TestMigrations): - app = "config" - migrate_from = "0002_squashed_to_of_v230" - migrate_to = "0053_v230_to_v250" - - -class TestFreshInstanceMigration(ForwardMigration): - """ - Test themes data migration when deploying a fresh instance. - """ - - def test_no_config_or_themes_are_created(self): - GlobalConfiguration = self.apps.get_model("config", "GlobalConfiguration") - Theme = self.apps.get_model("config", "Theme") - - self.assertFalse(GlobalConfiguration.objects.exists()) - self.assertFalse(Theme.objects.exists()) - - -class TestPristineConfigurationMigration(ForwardMigration): - """ - Test themes data migration when no styling options have been configured. - """ - - def setUpBeforeMigration(self, apps): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - assert not GlobalConfiguration.objects.exists() - # create a record as if it would have been created by calling get_solo() - GlobalConfiguration.objects.create() - - def test_no_config_or_themes_are_created(self): - GlobalConfiguration = self.apps.get_model("config", "GlobalConfiguration") - Theme = self.apps.get_model("config", "Theme") - - self.assertTrue(GlobalConfiguration.objects.exists()) - self.assertFalse(Theme.objects.exists()) - - -class TestExistingConfigIsMigrated(ForwardMigration): - """ - Test themes data migration when some styling is configured. - """ - - def setUpBeforeMigration(self, apps): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - assert not GlobalConfiguration.objects.exists() - # create a record as if it would have been created by calling get_solo() - GlobalConfiguration.objects.create( - theme_stylesheet="https://example.com/styles/foo.css" - ) - - def tests_theme_record_created_and_set_as_default(self): - GlobalConfiguration = self.apps.get_model("config", "GlobalConfiguration") - Theme = self.apps.get_model("config", "Theme") - - self.assertEqual(Theme.objects.count(), 1) - config = GlobalConfiguration.objects.get() - self.assertIsNotNone(config.default_theme) - theme = config.default_theme - self.assertEqual(theme.stylesheet, "https://example.com/styles/foo.css") - self.assertNotEqual(theme.name, "") - - -class ReverseMigration(TestMigrations): - app = "config" - migrate_from = "0053_v230_to_v250" - migrate_to = "0002_squashed_to_of_v230" - - -class TestNoDefaultThemeReverseMigration(ReverseMigration): - def setUpBeforeMigration(self, apps): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - Theme = apps.get_model("config", "Theme") - assert not GlobalConfiguration.objects.exists() - # create a record as if it would have been created by calling get_solo() - GlobalConfiguration.objects.create() - assert not Theme.objects.exists() - - def test_style_fields_have_empty_defaults(self): - GlobalConfiguration = self.apps.get_model("config", "GlobalConfiguration") - config = GlobalConfiguration.objects.get() - - self.assertEqual(config.logo, "") - self.assertEqual(config.theme_classname, "") - self.assertEqual(config.theme_stylesheet, "") - self.assertEqual(config.theme_stylesheet_file, "") - self.assertEqual(config.design_token_values, {}) - - -class DefaultThemeConvertedBackTests(ReverseMigration): - def setUpBeforeMigration(self, apps): - GlobalConfiguration = apps.get_model("config", "GlobalConfiguration") - Theme = apps.get_model("config", "Theme") - Theme.objects.create( - name="not default", stylesheet="https://example.com/first.css" - ) - theme2 = Theme.objects.create( - name="default", - classname="foo", - stylesheet="https://example.com/second.css", - design_token_values={"utrecht": {"link": {"color": {"value": "red"}}}}, - ) - GlobalConfiguration.objects.create(default_theme=theme2) - - def test_style_fields_have_empty_defaults(self): - GlobalConfiguration = self.apps.get_model("config", "GlobalConfiguration") - config = GlobalConfiguration.objects.get() - - self.assertEqual(config.logo, "") - self.assertEqual(config.theme_classname, "foo") - self.assertEqual(config.theme_stylesheet, "https://example.com/second.css") - self.assertEqual(config.theme_stylesheet_file, "") - self.assertEqual( - config.design_token_values, - {"utrecht": {"link": {"color": {"value": "red"}}}}, - ) diff --git a/src/openforms/contrib/haal_centraal/migrations/0003_auto_20240115_1528.py b/src/openforms/contrib/haal_centraal/migrations/0003_auto_20240115_1528.py index 48583c44ee..a399a56589 100644 --- a/src/openforms/contrib/haal_centraal/migrations/0003_auto_20240115_1528.py +++ b/src/openforms/contrib/haal_centraal/migrations/0003_auto_20240115_1528.py @@ -1,14 +1,15 @@ # Generated by Django 3.2.23 on 2024-01-15 14:28 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models + import openforms.contrib.haal_centraal.validators class Migration(migrations.Migration): dependencies = [ - ("forms", "0103_fix_component_problems"), + ("forms", "0091_v230_to_v250"), ("haalcentraal", "0002_copy_config_from_prefill"), ] diff --git a/src/openforms/forms/migrations/0001_initial_to_v250.py b/src/openforms/forms/migrations/0001_initial_to_v250.py new file mode 100644 index 0000000000..d636991a6a --- /dev/null +++ b/src/openforms/forms/migrations/0001_initial_to_v250.py @@ -0,0 +1,1395 @@ +# Generated by Django 4.2.10 on 2024-02-22 18:15 + +import re +import uuid + +import django.core.validators +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + +import autoslug.fields +import privates.fields +import privates.storages +import tinymce.models + +import csp_post_processor.fields +import openforms.authentication.fields +import openforms.forms.migration_operations +import openforms.forms.models.form_step +import openforms.forms.models.form_variable +import openforms.forms.models.form_version +import openforms.forms.validators +import openforms.payments.fields +import openforms.registrations.fields +import openforms.template.validators +import openforms.utils.files + + +class Migration(migrations.Migration): + + replaces = [ + ("forms", "0001_initial_pre_openforms_v230"), + ("forms", "0046_squashed_to_openforms_v230"), + ("forms", "0091_v230_to_v250"), + ] + + dependencies = [ + ("products", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("variables", "0001_initial_to_openforms_v230"), + ("config", "0053_v230_to_v250"), + ] + + operations = [ + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("path", models.CharField(max_length=255, unique=True)), + ("depth", models.PositiveIntegerField()), + ("numchild", models.PositiveIntegerField(default=0)), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, unique=True, verbose_name="UUID" + ), + ), + ( + "name", + models.CharField( + help_text="Human readable name", + max_length=64, + verbose_name="name", + ), + ), + ], + options={ + "verbose_name": "category", + "verbose_name_plural": "categories", + }, + ), + migrations.CreateModel( + name="FormDefinition", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=50, verbose_name="name")), + ( + "name_en", + models.CharField(max_length=50, null=True, verbose_name="name"), + ), + ( + "name_nl", + models.CharField(max_length=50, null=True, verbose_name="name"), + ), + ( + "slug", + autoslug.fields.AutoSlugField( + editable=True, + max_length=100, + populate_from="name", + verbose_name="slug", + ), + ), + ( + "configuration", + models.JSONField( + help_text="The form definition as Form.io JSON schema", + validators=[ + openforms.forms.validators.validate_template_expressions + ], + verbose_name="Form.io configuration", + ), + ), + ( + "login_required", + models.BooleanField( + default=False, + help_text="DigID Login required for form step", + verbose_name="login required", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, unique=True, verbose_name="UUID" + ), + ), + ( + "is_reusable", + models.BooleanField( + default=False, + help_text="Allow this definition to be re-used in multiple forms", + verbose_name="is reusable", + ), + ), + ( + "internal_name", + models.CharField( + blank=True, + help_text="internal name for management purposes", + max_length=50, + verbose_name="internal name", + ), + ), + ( + "_num_components", + models.PositiveIntegerField( + default=0, + help_text="The total number of Formio components used in the configuration", + verbose_name="number of Formio components", + ), + ), + ( + "component_translations", + models.JSONField( + blank=True, + default=dict, + help_text="Translations for literals used in components", + verbose_name="Component translations", + ), + ), + ], + options={ + "verbose_name": "Form definition", + "verbose_name_plural": "Form definitions", + }, + ), + migrations.CreateModel( + name="Form", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=150, verbose_name="name")), + ( + "slug", + autoslug.fields.AutoSlugField( + editable=True, + max_length=100, + populate_from="name", + unique=True, + verbose_name="slug", + ), + ), + ("active", models.BooleanField(default=False, verbose_name="active")), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, unique=True, verbose_name="UUID" + ), + ), + ( + "product", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="products.product", + ), + ), + ("_is_deleted", models.BooleanField(default=False)), + ( + "maintenance_mode", + models.BooleanField( + default=False, + help_text="Users will not be able to start the form if it is in maintenance mode.", + verbose_name="maintenance mode", + ), + ), + ( + "authentication_backends", + openforms.authentication.fields.AuthenticationBackendMultiSelectField( + base_field=openforms.authentication.fields.BackendChoiceField( + max_length=100 + ), + blank=True, + default=list, + size=None, + ), + ), + ( + "submission_confirmation_template", + tinymce.models.HTMLField( + blank=True, + help_text="The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data. If not specified, the global template will be used.", + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend" + ) + ], + verbose_name="submission confirmation template", + ), + ), + ( + "show_progress_indicator", + models.BooleanField( + default=True, + help_text="Whether the step progression should be displayed in the UI or not.", + verbose_name="show progress indicator", + ), + ), + ( + "begin_text", + models.CharField( + blank=True, + help_text="The text that will be displayed at the start of the form to indicate the user can begin to fill in the form. Leave blank to get value from global configuration.", + max_length=50, + verbose_name="begin text", + ), + ), + ( + "change_text", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to change a certain step. Leave blank to get value from global configuration.", + max_length=50, + verbose_name="change text", + ), + ), + ( + "confirm_text", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to confirm the form is filled in correctly. Leave blank to get value from global configuration.", + max_length=50, + verbose_name="confirm text", + ), + ), + ( + "previous_text", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to go to the previous step. Leave blank to get value from global configuration.", + max_length=50, + verbose_name="previous text", + ), + ), + ( + "payment_backend", + openforms.payments.fields.PaymentBackendChoiceField( + blank=True, max_length=100, verbose_name="payment backend" + ), + ), + ( + "payment_backend_options", + models.JSONField( + blank=True, + default=dict, + null=True, + verbose_name="payment backend options", + ), + ), + ( + "all_submissions_removal_limit", + models.PositiveIntegerField( + blank=True, + help_text="Amount of days when all submissions of this form will be permanently deleted. Leave blank to use value in General Configuration.", + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="all submissions removal limit", + ), + ), + ( + "errored_submissions_removal_limit", + models.PositiveIntegerField( + blank=True, + help_text="Amount of days errored submissions of this form will remain before being removed. Leave blank to use value in General Configuration.", + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="errored submission removal limit", + ), + ), + ( + "errored_submissions_removal_method", + models.CharField( + blank=True, + choices=[ + ("delete_permanently", "Submissions will be deleted"), + ( + "make_anonymous", + "Sensitive data within the submissions will be deleted", + ), + ], + help_text="How errored submissions of this form will be removed after the limit. Leave blank to use value in General Configuration.", + max_length=50, + verbose_name="errored submission removal limit", + ), + ), + ( + "incomplete_submissions_removal_limit", + models.PositiveIntegerField( + blank=True, + help_text="Amount of days incomplete submissions of this form will remain before being removed. Leave blank to use value in General Configuration.", + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="incomplete submission removal limit", + ), + ), + ( + "incomplete_submissions_removal_method", + models.CharField( + blank=True, + choices=[ + ("delete_permanently", "Submissions will be deleted"), + ( + "make_anonymous", + "Sensitive data within the submissions will be deleted", + ), + ], + help_text="How incomplete submissions of this form will be removed after the limit. Leave blank to use value in General Configuration.", + max_length=50, + verbose_name="incomplete submissions removal method", + ), + ), + ( + "successful_submissions_removal_limit", + models.PositiveIntegerField( + blank=True, + help_text="Amount of days successful submissions of this form will remain before being removed. Leave blank to use value in General Configuration.", + null=True, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="successful submission removal limit", + ), + ), + ( + "successful_submissions_removal_method", + models.CharField( + blank=True, + choices=[ + ("delete_permanently", "Submissions will be deleted"), + ( + "make_anonymous", + "Sensitive data within the submissions will be deleted", + ), + ], + help_text="How successful submissions of this form will be removed after the limit. Leave blank to use value in General Configuration.", + max_length=50, + verbose_name="successful submissions removal method", + ), + ), + ( + "internal_name", + models.CharField( + blank=True, + help_text="internal name for management purposes", + max_length=150, + verbose_name="internal name", + ), + ), + ( + "explanation_template", + csp_post_processor.fields.CSPPostProcessedWYSIWYGField( + base_field=tinymce.models.HTMLField( + blank=True, + help_text="Content that will be shown on the start page of the form, below the title and above the log in text.", + verbose_name="explanation template", + ), + blank=True, + help_text="Content that will be shown on the start page of the form, below the title and above the log in text.", + verbose_name="explanation template", + ), + ), + ( + "submission_allowed", + models.CharField( + choices=[ + ("yes", "Yes"), + ("no_with_overview", "No (with overview page)"), + ("no_without_overview", "No (without overview page)"), + ], + default="yes", + help_text="Whether the user is allowed to submit this form or not, and whether the overview page should be shown if they are not.", + max_length=100, + verbose_name="submission allowed", + ), + ), + ( + "display_main_website_link", + models.BooleanField( + default=True, + help_text="Display the link to the main website on the submission confirmation page.", + verbose_name="display main website link", + ), + ), + ( + "auto_login_authentication_backend", + models.CharField( + blank=True, max_length=100, verbose_name="automatic login" + ), + ), + ( + "category", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="forms.category", + ), + ), + ( + "begin_text_en", + models.CharField( + blank=True, + help_text="The text that will be displayed at the start of the form to indicate the user can begin to fill in the form. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="begin text", + ), + ), + ( + "begin_text_nl", + models.CharField( + blank=True, + help_text="The text that will be displayed at the start of the form to indicate the user can begin to fill in the form. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="begin text", + ), + ), + ( + "change_text_en", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to change a certain step. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="change text", + ), + ), + ( + "change_text_nl", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to change a certain step. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="change text", + ), + ), + ( + "confirm_text_en", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to confirm the form is filled in correctly. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="confirm text", + ), + ), + ( + "confirm_text_nl", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to confirm the form is filled in correctly. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="confirm text", + ), + ), + ( + "explanation_template_en", + csp_post_processor.fields.CSPPostProcessedWYSIWYGField( + base_field=tinymce.models.HTMLField( + blank=True, + help_text="Content that will be shown on the start page of the form, below the title and above the log in text.", + verbose_name="explanation template", + ), + blank=True, + help_text="Content that will be shown on the start page of the form, below the title and above the log in text.", + null=True, + verbose_name="explanation template", + ), + ), + ( + "explanation_template_nl", + csp_post_processor.fields.CSPPostProcessedWYSIWYGField( + base_field=tinymce.models.HTMLField( + blank=True, + help_text="Content that will be shown on the start page of the form, below the title and above the log in text.", + verbose_name="explanation template", + ), + blank=True, + help_text="Content that will be shown on the start page of the form, below the title and above the log in text.", + null=True, + verbose_name="explanation template", + ), + ), + ( + "name_en", + models.CharField(max_length=150, null=True, verbose_name="name"), + ), + ( + "name_nl", + models.CharField(max_length=150, null=True, verbose_name="name"), + ), + ( + "previous_text_en", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to go to the previous step. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="previous text", + ), + ), + ( + "previous_text_nl", + models.CharField( + blank=True, + help_text="The text that will be displayed in the overview page to go to the previous step. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="previous text", + ), + ), + ( + "translation_enabled", + models.BooleanField( + default=False, verbose_name="translation enabled" + ), + ), + ( + "include_confirmation_page_content_in_pdf", + models.BooleanField( + default=True, + help_text="Display the instruction from the confirmation page in the PDF.", + verbose_name="include confirmation page content in PDF", + ), + ), + ( + "submission_confirmation_template_en", + tinymce.models.HTMLField( + blank=True, + help_text="The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data. If not specified, the global template will be used.", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend" + ) + ], + verbose_name="submission confirmation template", + ), + ), + ( + "submission_confirmation_template_nl", + tinymce.models.HTMLField( + blank=True, + help_text="The content of the submission confirmation page. It can contain variables that will be templated from the submitted form data. If not specified, the global template will be used.", + null=True, + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend" + ) + ], + verbose_name="submission confirmation template", + ), + ), + ( + "is_appointment", + models.BooleanField( + default=False, + help_text="Mark the form as an appointment form. Appointment forms do not support form designer steps.", + verbose_name="appointment enabled", + ), + ), + ( + "send_confirmation_email", + models.BooleanField( + default=True, + help_text="Whether a confirmation email should be sent to the end user filling in the form.", + verbose_name="send confirmation email", + ), + ), + ( + "suspension_allowed", + models.BooleanField( + default=True, + help_text="Whether the user is allowed to suspend this form or not.", + verbose_name="suspension allowed", + ), + ), + ( + "authentication_backend_options", + models.JSONField( + blank=True, + default=dict, + verbose_name="per form authentication backend config", + ), + ), + ( + "ask_privacy_consent", + models.CharField( + choices=[ + ("global_setting", "Global setting"), + ("required", "Required"), + ("disabled", "Disabled"), + ], + default="global_setting", + help_text="If enabled, the user will have to agree to the privacy policy before submitting a form.", + max_length=50, + verbose_name="ask privacy consent", + ), + ), + ( + "ask_statement_of_truth", + models.CharField( + choices=[ + ("global_setting", "Global setting"), + ("required", "Required"), + ("disabled", "Disabled"), + ], + default="global_setting", + help_text="If enabled, the user will have to agree that they filled out the form truthfully before submitting it.", + max_length=50, + verbose_name="ask statement of truth", + ), + ), + ( + "activate_on", + models.DateTimeField( + blank=True, + help_text="Date and time on which the form should be activated.", + null=True, + verbose_name="activate on", + ), + ), + ( + "deactivate_on", + models.DateTimeField( + blank=True, + help_text="Date and time on which the form should be deactivated.", + null=True, + verbose_name="deactivate on", + ), + ), + ( + "theme", + models.ForeignKey( + blank=True, + help_text="Apply a specific appearance configuration to the form. If left blank, then the globally configured default is applied.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="config.theme", + verbose_name="form theme", + ), + ), + ], + options={ + "verbose_name": "form", + "verbose_name_plural": "forms", + }, + ), + migrations.CreateModel( + name="FormStep", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "order", + models.PositiveIntegerField( + db_index=True, editable=False, verbose_name="order" + ), + ), + ( + "form", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="forms.form" + ), + ), + ( + "form_definition", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="forms.formdefinition", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, unique=True, verbose_name="UUID" + ), + ), + ( + "slug", + autoslug.fields.AutoSlugField( + editable=True, + max_length=100, + null=True, + populate_from=openforms.forms.models.form_step.populate_from_form_definition_name, + unique_with=("form",), + verbose_name="slug", + ), + ), + ( + "next_text", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to go to the next step. Leave blank to get value from global configuration.", + max_length=50, + verbose_name="step next text", + ), + ), + ( + "previous_text", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to go to the previous step. Leave blank to get value from global configuration.", + max_length=50, + verbose_name="step previous text", + ), + ), + ( + "save_text", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to save the current information. Leave blank to get value from global configuration.", + max_length=50, + verbose_name="step save text", + ), + ), + ( + "next_text_en", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to go to the next step. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="step next text", + ), + ), + ( + "next_text_nl", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to go to the next step. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="step next text", + ), + ), + ( + "previous_text_en", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to go to the previous step. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="step previous text", + ), + ), + ( + "previous_text_nl", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to go to the previous step. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="step previous text", + ), + ), + ( + "save_text_en", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to save the current information. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="step save text", + ), + ), + ( + "save_text_nl", + models.CharField( + blank=True, + help_text="The text that will be displayed in the form step to save the current information. Leave blank to get value from global configuration.", + max_length=50, + null=True, + verbose_name="step save text", + ), + ), + ( + "is_applicable", + models.BooleanField( + default=True, + help_text="Whether the step is applicable by default.", + verbose_name="is applicable", + ), + ), + ], + options={ + "verbose_name": "form step", + "verbose_name_plural": "form steps", + "ordering": ("order",), + }, + ), + migrations.AddConstraint( + model_name="formstep", + constraint=models.UniqueConstraint( + fields=("form", "slug"), name="form_slug_unique_together" + ), + ), + migrations.AddConstraint( + model_name="formstep", + constraint=models.UniqueConstraint( + fields=("form", "form_definition"), + name="form_form_definition_unique_together", + ), + ), + migrations.CreateModel( + name="FormLogic", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "order", + models.PositiveIntegerField( + db_index=True, editable=False, verbose_name="order" + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, unique=True, verbose_name="UUID" + ), + ), + ( + "description", + models.CharField( + blank=True, + help_text="Logic rule description in natural language.", + max_length=100, + verbose_name="description", + ), + ), + ( + "json_logic_trigger", + models.JSONField( + help_text="JSON logic associated with a step in a form.", + verbose_name="JSON logic", + ), + ), + ( + "actions", + models.JSONField( + help_text="Which action(s) to perform if the JSON logic evaluates to true.", + verbose_name="actions", + ), + ), + ( + "form", + models.ForeignKey( + help_text="Form to which the JSON logic applies.", + on_delete=django.db.models.deletion.CASCADE, + to="forms.form", + ), + ), + ( + "is_advanced", + models.BooleanField( + default=False, + help_text="Is this an advanced rule (the admin user manually wrote the trigger as JSON)?", + verbose_name="is advanced", + ), + ), + ( + "trigger_from_step", + models.ForeignKey( + blank=True, + help_text="When set, the trigger will only be checked once the specified step is reached. This means the rule will never trigger for steps before the specified trigger step. If unset, the trigger will always be checked.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="forms.formstep", + verbose_name="trigger from step", + ), + ), + ], + options={ + "verbose_name": "form logic", + "verbose_name_plural": "form logic rules", + "ordering": ("order",), + }, + ), + migrations.CreateModel( + name="FormPriceLogic", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, unique=True, verbose_name="UUID" + ), + ), + ( + "json_logic_trigger", + models.JSONField( + help_text='JSON logic expression that must evaluate to "true" for the price to apply.', + verbose_name="JSON logic", + ), + ), + ( + "price", + models.DecimalField( + decimal_places=2, max_digits=10, verbose_name="price" + ), + ), + ( + "form", + models.ForeignKey( + help_text="Form to which the pricing JSON logic applies.", + on_delete=django.db.models.deletion.CASCADE, + to="forms.form", + ), + ), + ], + options={ + "verbose_name": "form price rule", + "verbose_name_plural": "form price rules", + }, + ), + migrations.CreateModel( + name="FormVersion", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, unique=True, verbose_name="UUID" + ), + ), + ( + "created", + models.DateTimeField( + auto_now_add=True, + help_text="Date and time of creation of the form version.", + verbose_name="created", + ), + ), + ( + "export_blob", + models.JSONField( + help_text="The form, form definitions and form steps that make up this version, saved as JSON data." + ), + ), + ( + "form", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="forms.form", + verbose_name="form", + ), + ), + ( + "description", + models.TextField( + blank=True, + help_text="Description/context about this particular version.", + verbose_name="version description", + ), + ), + ( + "user", + models.ForeignKey( + help_text="User who authored this version.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), + ( + "app_git_sha", + models.CharField( + blank=True, + default=openforms.forms.models.form_version.get_app_git_sha, + editable=False, + help_text="Application commit hash at the time this version was created.", + max_length=50, + verbose_name="application commit hash", + ), + ), + ( + "app_release", + models.CharField( + blank=True, + default=openforms.forms.models.form_version.get_app_release, + editable=False, + help_text="App release/version at the time this version was created.", + max_length=50, + verbose_name="application version", + ), + ), + ], + options={ + "verbose_name": "form version", + "verbose_name_plural": "form versions", + }, + ), + migrations.CreateModel( + name="FormsExport", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, unique=True, verbose_name="UUID" + ), + ), + ( + "export_content", + privates.fields.PrivateMediaFileField( + help_text="Zip file containing all the exported forms.", + storage=privates.storages.PrivateMediaFileSystemStorage(), + upload_to="exports/%Y/%m/%d", + verbose_name="export content", + ), + ), + ( + "datetime_requested", + models.DateTimeField( + auto_now_add=True, + help_text="The date and time on which the bulk export was requested.", + verbose_name="date time requested", + ), + ), + ( + "user", + models.ForeignKey( + help_text="The user that requested the download.", + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), + ], + options={ + "verbose_name": "forms export", + "verbose_name_plural": "forms exports", + }, + bases=(openforms.utils.files.DeleteFileFieldFilesMixin, models.Model), + ), + migrations.CreateModel( + name="FormVariable", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.TextField( + help_text="Name of the variable", verbose_name="name" + ), + ), + ( + "key", + models.TextField( + help_text="Key of the variable, should be unique with the form.", + validators=[ + django.core.validators.RegexValidator( + message="Invalid variable key. It must only contain alphanumeric characters, underscores, dots and dashes and should not be ended by dash or dot.", + regex=re.compile("^(\\w|\\w[\\w.\\-]*\\w)$"), + ) + ], + verbose_name="key", + ), + ), + ( + "source", + models.CharField( + choices=[ + ("component", "Component"), + ("user_defined", "User defined"), + ], + help_text="Where will the data that will be associated with this variable come from", + max_length=50, + verbose_name="source", + ), + ), + ( + "prefill_plugin", + models.CharField( + blank=True, + help_text="Which, if any, prefill plugin should be used", + max_length=50, + verbose_name="prefill plugin", + ), + ), + ( + "prefill_attribute", + models.CharField( + blank=True, + help_text="Which attribute from the prefill response should be used to fill this variable", + max_length=200, + verbose_name="prefill attribute", + ), + ), + ( + "prefill_identifier_role", + models.CharField( + choices=[ + ("main", "Main"), + ("authorised_person", "Authorised person"), + ], + default="main", + help_text="In case that multiple identifiers are returned (in the case of eHerkenning bewindvoering and DigiD Machtigen), should the prefill data related to the main identifier be used, or that related to the authorised person?", + max_length=100, + verbose_name="prefill identifier role", + ), + ), + ( + "data_type", + models.CharField( + choices=[ + ("string", "String"), + ("boolean", "Boolean"), + ("object", "Object"), + ("array", "Array"), + ("int", "Integer"), + ("float", "Float"), + ("datetime", "Datetime"), + ("time", "Time"), + ("date", "Date"), + ], + help_text="The type of the value that will be associated with this variable", + max_length=50, + verbose_name="data type", + ), + ), + ( + "data_format", + models.CharField( + blank=True, + help_text="The format of the value that will be associated with this variable", + max_length=250, + verbose_name="data format", + ), + ), + ( + "is_sensitive_data", + models.BooleanField( + default=False, + help_text="Will this variable be associated with sensitive data?", + verbose_name="is sensitive data", + ), + ), + ( + "initial_value", + models.JSONField( + blank=True, + help_text="The initial value for this field", + null=True, + verbose_name="initial value", + ), + ), + ( + "form", + models.ForeignKey( + help_text="Form to which this variable is related", + on_delete=django.db.models.deletion.CASCADE, + to="forms.form", + verbose_name="form", + ), + ), + ( + "form_definition", + models.ForeignKey( + blank=True, + help_text="Form definition to which this variable is related. This is kept as metadata", + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="forms.formdefinition", + verbose_name="form definition", + ), + ), + ( + "service_fetch_configuration", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="variables.servicefetchconfiguration", + verbose_name="service fetch configuration", + ), + ), + ], + options={ + "verbose_name": "Form variable", + "verbose_name_plural": "Form variables", + "unique_together": {("form", "key")}, + }, + ), + migrations.AlterModelManagers( + name="formvariable", + managers=[ + ("objects", openforms.forms.models.form_variable.FormVariableManager()), + ], + ), + migrations.AddConstraint( + model_name="formvariable", + constraint=models.CheckConstraint( + check=models.Q( + models.Q( + models.Q(("prefill_plugin", ""), ("prefill_attribute", "")), + models.Q( + models.Q(("prefill_plugin", ""), _negated=True), + models.Q(("prefill_attribute", ""), _negated=True), + ), + _connector="OR", + ) + ), + name="prefill_config_empty_or_complete", + ), + ), + migrations.AddConstraint( + model_name="formvariable", + constraint=models.CheckConstraint( + check=models.Q( + models.Q( + models.Q( + ("form_definition__isnull", True), + models.Q(("source", "component"), _negated=True), + ), + ("form_definition__isnull", False), + _connector="OR", + ) + ), + name="form_definition_not_null_for_component_vars", + ), + ), + migrations.AddConstraint( + model_name="formvariable", + constraint=models.CheckConstraint( + check=models.Q( + models.Q( + models.Q(("prefill_plugin", ""), _negated=True), + ("service_fetch_configuration__isnull", False), + ), + _negated=True, + ), + name="prefill_config_xor_service_fetch_config", + ), + ), + migrations.CreateModel( + name="FormRegistrationBackend", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "key", + models.CharField( + help_text="The key to use to refer to this configuration in form logic.", + max_length=50, + verbose_name="key", + ), + ), + ( + "name", + models.CharField( + help_text="A recognisable name for this backend configuration.", + max_length=255, + verbose_name="name", + ), + ), + ( + "backend", + openforms.registrations.fields.RegistrationBackendChoiceField( + max_length=100, verbose_name="registration backend" + ), + ), + ( + "options", + models.JSONField( + blank=True, + default=dict, + verbose_name="registration backend options", + ), + ), + ( + "form", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="registration_backends", + to="forms.form", + ), + ), + ], + options={ + "unique_together": {("form", "key")}, + "verbose_name": "registration backend", + "verbose_name_plural": "registration backends", + }, + ), + ] diff --git a/src/openforms/forms/migrations/0091_auto_20230831_1152.py b/src/openforms/forms/migrations/0091_auto_20230831_1152.py deleted file mode 100644 index 3b781c0f6a..0000000000 --- a/src/openforms/forms/migrations/0091_auto_20230831_1152.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 3.2.20 on 2023-08-31 09:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("forms", "0046_squashed_to_openforms_v230"), - ] - - operations = [ - migrations.AddField( - model_name="form", - name="activate_on", - field=models.DateTimeField( - blank=True, - help_text="Date and time on which the form should be activated.", - null=True, - verbose_name="activate on", - ), - ), - migrations.AddField( - model_name="form", - name="deactivate_on", - field=models.DateTimeField( - blank=True, - help_text="Date and time on which the form should be deactivated.", - null=True, - verbose_name="deactivate on", - ), - ), - ] diff --git a/src/openforms/forms/migrations/0091_v230_to_v250.py b/src/openforms/forms/migrations/0091_v230_to_v250.py index e23daddcd6..230ebd1fdd 100644 --- a/src/openforms/forms/migrations/0091_v230_to_v250.py +++ b/src/openforms/forms/migrations/0091_v230_to_v250.py @@ -7,49 +7,12 @@ import openforms.forms.migration_operations -add_more_time_custom_errors = import_string( - "openforms.forms.migrations.0092_more_time_custom_errors." - "add_more_time_custom_errors" -) -add_date_component_settings = import_string( - "openforms.forms.migrations.0093_date_component_settings." - "add_date_component_settings" -) -add_configuration_to_family_component = import_string( - "openforms.forms.migrations.0094_update_config_family." - "add_configuration_to_family_component" -) -update_component_action = import_string( - "openforms.forms.migrations.0101_update_action_property." "update_component_action" -) -migrate_fd_translations = import_string( - "openforms.forms.migrations.0102_convert_formio_translations." - "migrate_fd_translations" -) - class Migration(migrations.Migration): - replaces = [ - ("forms", "0091_auto_20230831_1152"), - ("forms", "0092_more_time_custom_errors"), - ("forms", "0093_date_component_settings"), - ("forms", "0094_update_config_family"), - ("forms", "0095_formstep_form_form_definition_unique_together"), - ("forms", "0096_move_time_component_validators"), - ("forms", "0097_formstep_is_applicable"), - ("forms", "0098_update_default_value_components_prefill"), - ("forms", "0099_form_theme"), - ("forms", "0100_ensure_datasrc_property"), - ("forms", "0101_update_action_property"), - ("forms", "0102_convert_formio_translations"), - ("forms", "0103_fix_component_problems"), - ("forms", "0104_allow_invalid_input_datetime"), - ] - dependencies = [ ("forms", "0046_squashed_to_openforms_v230"), - ("config", "0064_auto_20231206_0921"), + ("config", "0053_v230_to_v250"), ] operations = [ @@ -73,18 +36,7 @@ class Migration(migrations.Migration): verbose_name="deactivate on", ), ), - migrations.RunPython( - code=add_more_time_custom_errors, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), - migrations.RunPython( - code=add_date_component_settings, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), - migrations.RunPython( - code=add_configuration_to_family_component, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), + # RunPython operations are removed, they were executed as part of the 2.5.0 upgrade. migrations.AddConstraint( model_name="formstep", constraint=models.UniqueConstraint( @@ -140,14 +92,7 @@ class Migration(migrations.Migration): openforms.forms.migration_operations.ConvertComponentsOperation( "selectboxes", "set_openforms_datasrc" ), - migrations.RunPython( - code=update_component_action, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), - migrations.RunPython( - code=migrate_fd_translations, - reverse_code=django.db.migrations.operations.special.RunPython.noop, - ), + # RunPython operations are removed, they were executed as part of the 2.5.0 upgrade. openforms.forms.migration_operations.ConvertComponentsOperation( "columns", "fix_column_sizes" ), diff --git a/src/openforms/forms/migrations/0092_more_time_custom_errors.py b/src/openforms/forms/migrations/0092_more_time_custom_errors.py deleted file mode 100644 index e8f8606256..0000000000 --- a/src/openforms/forms/migrations/0092_more_time_custom_errors.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 3.2.20 on 2023-09-04 13:50 - -from django.db import migrations -from openforms.formio.utils import iter_components - - -def add_more_time_custom_errors(apps, schema_editor): - FormDefinition = apps.get_model("forms", "FormDefinition") - - form_definitions = FormDefinition.objects.all() - - form_definitions_to_update = [] - for form_definition in form_definitions: - updated_form_definition = False - for comp in iter_components(configuration=form_definition.configuration): - if comp["type"] == "time" and ( - translated_errors := comp.get("translatedErrors") - ): - updated_form_definition = True - for language_code, custom_error_messages in translated_errors.items(): - translated_errors[language_code]["minTime"] = "" - translated_errors[language_code]["maxTime"] = "" - - if updated_form_definition: - form_definitions_to_update.append(form_definition) - - if form_definitions_to_update: - FormDefinition.objects.bulk_update( - form_definitions_to_update, fields=["configuration"] - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0091_auto_20230831_1152"), - ] - - operations = [ - migrations.RunPython(add_more_time_custom_errors, migrations.RunPython.noop) - ] diff --git a/src/openforms/forms/migrations/0093_date_component_settings.py b/src/openforms/forms/migrations/0093_date_component_settings.py deleted file mode 100644 index a9196d183a..0000000000 --- a/src/openforms/forms/migrations/0093_date_component_settings.py +++ /dev/null @@ -1,43 +0,0 @@ -# Generated by Django 3.2.21 on 2023-09-13 08:24 - -from django.db import migrations -from openforms.formio.utils import iter_components - - -def add_date_component_settings(apps, schema_editor): - FormDefinition = apps.get_model("forms", "FormDefinition") - - form_definitions = FormDefinition.objects.all() - - form_definitions_to_update = [] - for form_definition in form_definitions: - for comp in iter_components(configuration=form_definition.configuration): - if comp["type"] == "date": - if translated_errors := comp.get("translatedErrors"): - for ( - language_code, - custom_error_messages, - ) in translated_errors.items(): - translated_errors[language_code]["minDate"] = "" - translated_errors[language_code]["maxDate"] = "" - - comp.setdefault("customOptions", {}) - comp["customOptions"]["allowInvalidPreload"] = True - - form_definitions_to_update.append(form_definition) - - if form_definitions_to_update: - FormDefinition.objects.bulk_update( - form_definitions_to_update, fields=["configuration"] - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0092_more_time_custom_errors"), - ] - - operations = [ - migrations.RunPython(add_date_component_settings, migrations.RunPython.noop) - ] diff --git a/src/openforms/forms/migrations/0094_update_config_family.py b/src/openforms/forms/migrations/0094_update_config_family.py deleted file mode 100644 index 78dca45c61..0000000000 --- a/src/openforms/forms/migrations/0094_update_config_family.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 3.2.21 on 2023-10-04 08:48 - -from django.db import migrations - -from openforms.formio.utils import iter_components - - -def add_configuration_to_family_component(apps, schema_editor): - FormDefinition = apps.get_model("forms", "FormDefinition") - - form_definitions = FormDefinition.objects.all() - - form_definitions_to_update = [] - for form_definition in form_definitions: - updated_form_definition = False - - for comp in iter_components(configuration=form_definition.configuration): - if comp["type"] != "npFamilyMembers": - continue - - comp["includePartners"] = False - comp["includeChildren"] = True - - updated_form_definition = True - - if updated_form_definition: - form_definitions_to_update.append(form_definition) - - if form_definitions_to_update: - FormDefinition.objects.bulk_update( - form_definitions_to_update, fields=["configuration"] - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0093_date_component_settings"), - ] - - operations = [ - migrations.RunPython( - add_configuration_to_family_component, migrations.RunPython.noop - ) - ] diff --git a/src/openforms/forms/migrations/0095_formstep_form_form_definition_unique_together.py b/src/openforms/forms/migrations/0095_formstep_form_form_definition_unique_together.py deleted file mode 100644 index 427634809e..0000000000 --- a/src/openforms/forms/migrations/0095_formstep_form_form_definition_unique_together.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 3.2.21 on 2023-10-09 10:23 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0094_update_config_family"), - ] - - operations = [ - migrations.AddConstraint( - model_name="formstep", - constraint=models.UniqueConstraint( - fields=("form", "form_definition"), - name="form_form_definition_unique_together", - ), - ), - ] diff --git a/src/openforms/forms/migrations/0096_move_time_component_validators.py b/src/openforms/forms/migrations/0096_move_time_component_validators.py deleted file mode 100644 index f390150e8b..0000000000 --- a/src/openforms/forms/migrations/0096_move_time_component_validators.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.21 on 2023-10-10 13:51 - -from django.db import migrations - -from openforms.forms.migration_operations import ConvertComponentsOperation - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0095_formstep_form_form_definition_unique_together"), - ] - - operations = [ - ConvertComponentsOperation("time", "move_time_validators"), - ] diff --git a/src/openforms/forms/migrations/0097_formstep_is_applicable.py b/src/openforms/forms/migrations/0097_formstep_is_applicable.py deleted file mode 100644 index db553d958a..0000000000 --- a/src/openforms/forms/migrations/0097_formstep_is_applicable.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 3.2.21 on 2023-10-23 12:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0096_move_time_component_validators"), - ] - - operations = [ - migrations.AddField( - model_name="formstep", - name="is_applicable", - field=models.BooleanField( - default=True, - help_text="Whether the step is applicable by default.", - verbose_name="is applicable", - ), - ), - ] diff --git a/src/openforms/forms/migrations/0098_update_default_value_components_prefill.py b/src/openforms/forms/migrations/0098_update_default_value_components_prefill.py deleted file mode 100644 index b1e98c2b2a..0000000000 --- a/src/openforms/forms/migrations/0098_update_default_value_components_prefill.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 3.2.21 on 2023-10-31 15:33 - - -from django.db import migrations - -from openforms.forms.migration_operations import ConvertComponentsOperation - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0097_formstep_is_applicable"), - ] - - operations = [ - ConvertComponentsOperation("textfield", "alter_prefill_default_values"), - ConvertComponentsOperation("date", "alter_prefill_default_values"), - ConvertComponentsOperation("datetime", "alter_prefill_default_values"), - ConvertComponentsOperation("postcode", "alter_prefill_default_values"), - ConvertComponentsOperation("bsn", "alter_prefill_default_values"), - ] diff --git a/src/openforms/forms/migrations/0099_form_theme.py b/src/openforms/forms/migrations/0099_form_theme.py deleted file mode 100644 index 3e2a2bafcd..0000000000 --- a/src/openforms/forms/migrations/0099_form_theme.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.2.23 on 2023-12-06 09:53 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("config", "0064_auto_20231206_0921"), - ("forms", "0098_update_default_value_components_prefill"), - ] - - operations = [ - migrations.AddField( - model_name="form", - name="theme", - field=models.ForeignKey( - blank=True, - help_text="Apply a specific appearance configuration to the form. If left blank, then the globally configured default is applied.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="config.theme", - verbose_name="form theme", - ), - ), - ] diff --git a/src/openforms/forms/migrations/0100_ensure_datasrc_property.py b/src/openforms/forms/migrations/0100_ensure_datasrc_property.py deleted file mode 100644 index 758f0fd3d2..0000000000 --- a/src/openforms/forms/migrations/0100_ensure_datasrc_property.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.23 on 2023-12-27 13:01 - -from django.db import migrations - -from openforms.forms.migration_operations import ConvertComponentsOperation - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0099_form_theme"), - ] - - operations = [ - ConvertComponentsOperation("select", "set_openforms_datasrc"), - ConvertComponentsOperation("radio", "set_openforms_datasrc"), - ConvertComponentsOperation("selectboxes", "set_openforms_datasrc"), - ] diff --git a/src/openforms/forms/migrations/0101_update_action_property.py b/src/openforms/forms/migrations/0101_update_action_property.py deleted file mode 100644 index cec3a6a1e2..0000000000 --- a/src/openforms/forms/migrations/0101_update_action_property.py +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by Django 3.2.23 on 2024-01-08 15:05 - -from django.db import migrations -from django.db.models import Q -from glom import glom, assign - - -def update_component_action(apps, schema_editor): - FormLogic = apps.get_model("forms", "FormLogic") - - logic_rules = FormLogic.objects.filter(~Q(actions=[])) - - logic_rules_to_update = [] - for logic_rule in logic_rules: - updated_rule = False - for action in logic_rule.actions: - if ( - glom(action, "action.type", default=None) != "property" - or glom(action, "action.property.value", default=None) != "validate" - ): - continue - - value = glom(action, "action.state.required", default=None) - if value is None: - continue - - assign(action, "action.property.value", "validate.required") - assign(action, "action.property.type", "bool") - assign(action, "action.state", value) - updated_rule = True - - if updated_rule: - logic_rules_to_update.append(logic_rule) - - FormLogic.objects.bulk_update(logic_rules_to_update, fields=["actions"]) - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0100_ensure_datasrc_property"), - ] - - operations = [ - migrations.RunPython(update_component_action, migrations.RunPython.noop) - ] diff --git a/src/openforms/forms/migrations/0102_convert_formio_translations.py b/src/openforms/forms/migrations/0102_convert_formio_translations.py deleted file mode 100644 index a20dc25ea0..0000000000 --- a/src/openforms/forms/migrations/0102_convert_formio_translations.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 3.2.23 on 2023-12-21 16:03 - -from django.db import migrations - -from openforms.forms.fd_translations_converter import process_component_tree - - -def migrate_fd_translations(apps, _): - FormDefinition = apps.get_model("forms", "FormDefinition") - - for form_definition in FormDefinition.objects.iterator(): - has_translations = any( - bool(translations) - for translations in form_definition.component_translations.values() - ) - if not has_translations: - continue - - process_component_tree( - components=form_definition.configuration["components"], - translations_store=form_definition.component_translations, - ) - form_definition.save(update_fields=["configuration"]) - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0101_update_action_property"), - ] - - operations = [ - migrations.RunPython(migrate_fd_translations, migrations.RunPython.noop), - ] diff --git a/src/openforms/forms/migrations/0103_fix_component_problems.py b/src/openforms/forms/migrations/0103_fix_component_problems.py deleted file mode 100644 index 94ba6421f2..0000000000 --- a/src/openforms/forms/migrations/0103_fix_component_problems.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.2.23 on 2024-01-09 11:30 - -from django.db import migrations - -from openforms.forms.migration_operations import ConvertComponentsOperation - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0102_convert_formio_translations"), - ] - - operations = [ - ConvertComponentsOperation("columns", "fix_column_sizes"), - ConvertComponentsOperation("file", "fix_default_value"), - ConvertComponentsOperation("licenseplate", "ensure_validate_pattern"), - ConvertComponentsOperation("postcode", "ensure_validate_pattern"), - ] diff --git a/src/openforms/forms/migrations/0104_allow_invalid_input_datetime.py b/src/openforms/forms/migrations/0104_allow_invalid_input_datetime.py deleted file mode 100644 index 8dd50f3178..0000000000 --- a/src/openforms/forms/migrations/0104_allow_invalid_input_datetime.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.23 on 2024-01-18 14:09 - -from django.db import migrations - -from openforms.forms.migration_operations import ConvertComponentsOperation - - -class Migration(migrations.Migration): - - dependencies = [ - ("forms", "0103_fix_component_problems"), - ] - - operations = [ - ConvertComponentsOperation( - "datetime", "prevent_datetime_components_from_emptying_invalid_values" - ), - ] diff --git a/src/openforms/forms/tests/test_migrations.py b/src/openforms/forms/tests/test_migrations.py deleted file mode 100644 index 9cca521150..0000000000 --- a/src/openforms/forms/tests/test_migrations.py +++ /dev/null @@ -1,946 +0,0 @@ -from openforms.utils.tests.test_migrations import TestMigrations - - -class TestAddMoreCustomErrorMessagesTimeComponent(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - FormStep = apps.get_model("forms", "FormStep") - Form = apps.get_model("forms", "Form") - - self.form_def = FormDefinition.objects.create( - name="Time", - slug="time", - configuration={ - "components": [ - { - "key": "timeWithCustomError", - "type": "time", - "label": "Tijd with custom error", - "maxTime": "13:00:00", - "minTime": "12:00:00", - "translatedErrors": { - "en": {"required": "", "invalid_time": "Tralala"}, - "nl": {"required": "", "invalid_time": "Tralala"}, - }, - } - ] - }, - ) - form = Form.objects.create(name="Form time") - - FormStep.objects.create(form=form, form_definition=self.form_def, order=0) - - def test_the_custom_error_is_added(self): - self.form_def.refresh_from_db() - - self.assertEqual( - self.form_def.configuration["components"][0]["translatedErrors"], - { - "en": { - "required": "", - "invalid_time": "Tralala", - "minTime": "", - "maxTime": "", - }, - "nl": { - "required": "", - "invalid_time": "Tralala", - "minTime": "", - "maxTime": "", - }, - }, - ) - - -class TestAddDateComponentSettings(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - FormStep = apps.get_model("forms", "FormStep") - Form = apps.get_model("forms", "Form") - - self.form_def = FormDefinition.objects.create( - name="Date", - slug="date", - configuration={ - "components": [ - { - "key": "dateComponent", - "type": "date", - "label": "Date component", - "customOptions": {}, - "translatedErrors": { - "en": {"required": ""}, - "nl": {"required": ""}, - }, - }, - { - "key": "anotherDateComponent", - "type": "date", - "label": "Another Date component", - "translatedErrors": { - "en": {"required": ""}, - "nl": {"required": ""}, - }, - }, - ] - }, - ) - form = Form.objects.create(name="Form date") - - FormStep.objects.create(form=form, form_definition=self.form_def, order=0) - - def test_new_settings(self): - self.form_def.refresh_from_db() - - self.assertEqual( - self.form_def.configuration["components"][0]["translatedErrors"], - { - "en": { - "required": "", - "minDate": "", - "maxDate": "", - }, - "nl": { - "required": "", - "minDate": "", - "maxDate": "", - }, - }, - ) - - self.assertTrue( - self.form_def.configuration["components"][0]["customOptions"][ - "allowInvalidPreload" - ] - ) - self.assertTrue( - self.form_def.configuration["components"][1]["customOptions"][ - "allowInvalidPreload" - ] - ) - - -class TestChangeFamilyMembersComponent(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - FormStep = apps.get_model("forms", "FormStep") - Form = apps.get_model("forms", "Form") - - self.form_def = FormDefinition.objects.create( - name="Date", - slug="date", - configuration={ - "components": [ - { - "key": "npFamilyMembers", - "type": "npFamilyMembers", - "label": "Family members", - }, - ] - }, - ) - form = Form.objects.create(name="Form with family members") - - FormStep.objects.create(form=form, form_definition=self.form_def, order=0) - - def test_migration(self): - self.form_def.refresh_from_db() - - self.assertFalse( - self.form_def.configuration["components"][0]["includePartners"] - ) - self.assertTrue(self.form_def.configuration["components"][0]["includeChildren"]) - - -class TestTimeComponentValidatorMigration(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - - FormDefinition.objects.create( - name="Date", - slug="date", - configuration={ - "components": [ - { - "key": "time1", - "type": "time", - "label": "Time 1", - }, - { - "key": "time2", - "type": "time", - "label": "Time 2", - "minTime": "10:00", - "maxTime": "20:00", - }, - { - "key": "time3", - "type": "time", - "label": "Time 3", - "minTime": None, - "validate": { - "required": True, - }, - }, - { - "key": "time4", - "type": "time", - "label": "Time 4", - "maxTime": "20:00", - }, - ] - }, - ) - - def test_migration(self): - FormDefinition = self.apps.get_model("forms", "FormDefinition") - - form_def = FormDefinition.objects.get() - time1, time2, time3, time4 = form_def.configuration["components"] - - self.assertNotIn("minTime", time1) - self.assertNotIn("maxTime", time1) - self.assertNotIn("validate", time1) - - self.assertNotIn("minTime", time2) - self.assertNotIn("maxTime", time2) - self.assertIn("validate", time2) - self.assertEqual(time2["validate"]["minTime"], "10:00") - self.assertEqual(time2["validate"]["maxTime"], "20:00") - - self.assertNotIn("minTime", time3) - self.assertNotIn("maxTime", time3) - self.assertIn("validate", time3) - self.assertIsNone(time3["validate"]["minTime"]) - self.assertNotIn("maxTime", time3["validate"]) - - self.assertNotIn("minTime", time4) - self.assertNotIn("maxTime", time4) - self.assertIn("validate", time4) - self.assertNotIn("minTime", time4["validate"]) - self.assertEqual(time4["validate"]["maxTime"], "20:00") - - -class TestPrefillUpdateDefaultValuesMigration(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - - FormDefinition.objects.create( - name="Date", - slug="date", - configuration={ - "components": [ - { - # No prefill: - "key": "textfield", - "type": "textfield", - "label": "Textfield 1", - }, - { - # Prefill with values: - "key": "date", - "type": "date", - "label": "Date 1", - "prefill": { - "plugin": "my_plugin", - "attribute": "my_attr", - "identifierRole": "main", - }, - }, - { - # Prefill with OK default values: - "key": "datetime", - "type": "datetime", - "label": "Datetime 1", - "prefill": { - "plugin": "", - "attribute": "", - "identifierRole": "main", - }, - }, - { - # Prefill with not OK default values: - "key": "postcode", - "type": "postcode", - "label": "Postcode 1", - "prefill": { - "plugin": None, - "attribute": None, - "identifierRole": "main", - }, - }, - { - # Prefill without the keys: - "key": "bsn", - "type": "bsn", - "label": "BSN 1", - "prefill": { - "identifierRole": "main", - }, - }, - { - # Prefill as None: - "key": "bsn2", - "type": "bsn", - "label": "BSN 2", - "prefill": None, - }, - ] - }, - ) - - def test_migration(self): - FormDefinition = self.apps.get_model("forms", "FormDefinition") - - form_def = FormDefinition.objects.get() - textfield, date, datetime, postcode, bsn1, bsn2 = form_def.configuration[ - "components" - ] - - self.assertNotIn("prefill", textfield) - - self.assertEqual( - date["prefill"], - { - "plugin": "my_plugin", - "attribute": "my_attr", - "identifierRole": "main", - }, - ) - - self.assertEqual( - datetime["prefill"], - { - "plugin": "", - "attribute": "", - "identifierRole": "main", - }, - ) - - self.assertEqual( - postcode["prefill"], - { - "plugin": "", - "attribute": "", - "identifierRole": "main", - }, - ) - - self.assertEqual( - bsn1["prefill"], - { - "identifierRole": "main", - }, - ) - - self.assertIsNone(bsn2["prefill"]) - - -class TestSetDataSrcMigration(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - - FormDefinition.objects.create( - name="DataSrc", - slug="datasrc", - configuration={ - "components": [ - # Select variants - { - # dataSrc already set - "key": "select1", - "type": "select", - "label": "Select 1", - "openForms": { - "dataSrc": "variable", - "itemsExpression": "foo", - }, - }, - { - # dataSrc not set - "key": "select2", - "type": "select", - "label": "Select 2", - "openForms": {}, - "data": {"values": [{"value": "option1", "label": "Option 1"}]}, - }, - { - # dataSrc not set (bis) - "key": "select3", - "type": "select", - "label": "Select 3", - "data": {"values": [{"value": "option1", "label": "Option 1"}]}, - }, - { - # dataSrc already set (bis) - "key": "select4", - "type": "select", - "label": "Select 4", - "openForms": { - "dataSrc": "manual", - }, - "data": {"values": [{"value": "option1", "label": "Option 1"}]}, - }, - # Radio variants - { - # dataSrc already set - "key": "radio1", - "type": "radio", - "label": "radio 1", - "openForms": { - "dataSrc": "variable", - "itemsExpression": "foo", - }, - }, - { - # dataSrc not set - "key": "radio2", - "type": "radio", - "label": "radio 2", - "openForms": {}, - "values": [{"value": "option1", "label": "Option 1"}], - }, - { - # dataSrc not set (bis) - "key": "radio3", - "type": "radio", - "label": "radio 3", - "values": [{"value": "option1", "label": "Option 1"}], - }, - { - # dataSrc already set (bis) - "key": "radio4", - "type": "radio", - "label": "radio 4", - "openForms": { - "dataSrc": "manual", - }, - "values": [{"value": "option1", "label": "Option 1"}], - }, - # Selectboxes variants - { - # dataSrc already set - "key": "selectboxes1", - "type": "selectboxes", - "label": "selectboxes 1", - "openForms": { - "dataSrc": "variable", - "itemsExpression": "foo", - }, - }, - { - # dataSrc not set - "key": "selectboxes2", - "type": "selectboxes", - "label": "selectboxes 2", - "openForms": {}, - "values": [{"value": "option1", "label": "Option 1"}], - }, - { - # dataSrc not set (bis) - "key": "selectboxes3", - "type": "selectboxes", - "label": "selectboxes 3", - "values": [{"value": "option1", "label": "Option 1"}], - }, - { - # dataSrc already set (bis) - "key": "selectboxes4", - "type": "selectboxes", - "label": "selectboxes 4", - "openForms": { - "dataSrc": "manual", - }, - "values": [{"value": "option1", "label": "Option 1"}], - }, - ] - }, - ) - - def test_migration(self): - FormDefinition = self.apps.get_model("forms", "FormDefinition") - - form_def = FormDefinition.objects.get() - ( - select1, - select2, - select3, - select4, - radio1, - radio2, - radio3, - radio4, - selectboxes1, - selectboxes2, - selectboxes3, - selectboxes4, - ) = form_def.configuration["components"] - - with self.subTest("select"): - self.assertEqual(select1["openForms"]["dataSrc"], "variable") - self.assertEqual(select2["openForms"]["dataSrc"], "manual") - self.assertEqual(select3["openForms"]["dataSrc"], "manual") - self.assertEqual(select4["openForms"]["dataSrc"], "manual") - - with self.subTest("radio"): - self.assertEqual(radio1["openForms"]["dataSrc"], "variable") - self.assertEqual(radio2["openForms"]["dataSrc"], "manual") - self.assertEqual(radio3["openForms"]["dataSrc"], "manual") - self.assertEqual(radio4["openForms"]["dataSrc"], "manual") - - with self.subTest("selectboxes"): - self.assertEqual(selectboxes1["openForms"]["dataSrc"], "variable") - self.assertEqual(selectboxes2["openForms"]["dataSrc"], "manual") - self.assertEqual(selectboxes3["openForms"]["dataSrc"], "manual") - self.assertEqual(selectboxes4["openForms"]["dataSrc"], "manual") - - -class TestUpdateActionProperty(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormLogic = apps.get_model("forms", "FormLogic") - Form = apps.get_model("forms", "Form") - - form = Form.objects.create(name="Form time") - FormLogic.objects.create( - order=0, - form=form, - json_logic_trigger=True, - actions=[ - { - "component": "nicePostcode", - "action": { - "name": "Make not required", - "type": "property", - "property": { - "type": "object", - "value": "validate", - }, - "state": {"required": True}, - }, - } - ], - ) - FormLogic.objects.create( - order=1, - form=form, - json_logic_trigger=True, - actions=[ - { - "component": "nicePostcode", - "action": { - "type": "property", - "property": {"type": "bool", "value": "hidden"}, - "state": True, - "value": "", - }, - } - ], - ) - FormLogic.objects.create( - order=2, - form=form, - json_logic_trigger=True, - actions=[], - ) - - def test_migration(self): - FormLogic = self.apps.get_model("forms", "FormLogic") - - self.assertEqual( - FormLogic.objects.get(order=0).actions[0], - { - "component": "nicePostcode", - "action": { - "name": "Make not required", - "type": "property", - "property": { - "type": "bool", - "value": "validate.required", - }, - "state": True, - }, - }, - ) - self.assertEqual( - FormLogic.objects.get(order=1).actions[0], - { - "component": "nicePostcode", - "action": { - "type": "property", - "property": {"type": "bool", "value": "hidden"}, - "state": True, - "value": "", - }, - }, - ) - - -class TestFormioTranslationsMigration(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - - FormDefinition.objects.create( - name="Translations", - slug="translations", - configuration={ - "components": [ - { - "type": "textfield", - "key": "textfield", - "label": "Reused label", - "description": "Textfield description", - "tooltip": "", - }, - { - "type": "fieldset", - "key": "fieldset", - "label": "Reused label", - "components": [ - { - "type": "content", - "key": "content", - "html": "
Some content
", - }, - { - "type": "radio", - "key": "radio", - "label": "Radio label", - "values": [ - { - "value": "option-1", - "label": "Option 1", - }, - ], - }, - { - "type": "select", - "key": "select", - "label": "Reused label", - "data": { - "values": [ - { - "value": "option-1", - "label": "Option 1", - }, - { - "value": "option-2", - "label": "Option 2", - }, - ], - }, - }, - ], - }, - ] - }, - component_translations={ - "nl": { - "Reused label": "Herbruikt label", - "Textfield description": "Omschrijving van tekstveld", - "Some content
": "Inhoud
", - # "Radio label": "DELIBERATELY OMITTED", - "Option 1": "Optie 1", - }, - }, - ) - - def test_translations_moved_to_components(self): - FormDefinition = self.apps.get_model("forms", "FormDefinition") - fd = FormDefinition.objects.get() - - textfield, fieldset = fd.configuration["components"] - content, radio, select = fieldset["components"] - - with self.subTest("textfield"): - self.assertIn("openForms", textfield) - self.assertIn("translations", textfield["openForms"]) - self.assertIn("nl", textfield["openForms"]["translations"]) - textfield_nl_translations = textfield["openForms"]["translations"]["nl"] - self.assertEqual( - textfield_nl_translations, - { - "label": "Herbruikt label", - "description": "Omschrijving van tekstveld", - }, - ) - - with self.subTest("fieldset"): - self.assertIn("openForms", fieldset) - self.assertIn("translations", fieldset["openForms"]) - self.assertIn("nl", fieldset["openForms"]["translations"]) - fieldset_nl_translations = fieldset["openForms"]["translations"]["nl"] - self.assertEqual(fieldset_nl_translations, {"label": "Herbruikt label"}) - - with self.subTest("content"): - self.assertIn("openForms", content) - self.assertIn("translations", content["openForms"]) - self.assertIn("nl", content["openForms"]["translations"]) - content_nl_translations = content["openForms"]["translations"]["nl"] - self.assertEqual(content_nl_translations, {"html": "Inhoud
"}) - - with self.subTest("radio"): - self.assertIn("openForms", radio) - self.assertNotIn("translations", radio["openForms"]) - - radio_option_1 = radio["values"][0] - self.assertIn("openForms", radio_option_1) - self.assertIn("translations", radio_option_1["openForms"]) - self.assertIn("nl", radio_option_1["openForms"]["translations"]) - radio_option_1_nl_translations = radio_option_1["openForms"][ - "translations" - ]["nl"] - self.assertEqual( - radio_option_1_nl_translations, - { - "label": "Optie 1", - }, - ) - - with self.subTest("select"): - self.assertIn("openForms", select) - self.assertIn("translations", select["openForms"]) - self.assertIn("nl", select["openForms"]["translations"]) - select_nl_translations = select["openForms"]["translations"]["nl"] - self.assertEqual(select_nl_translations, {"label": "Herbruikt label"}) - - select_option_1, select_option_2 = select["data"]["values"] - - self.assertIn("openForms", select_option_1) - self.assertIn("translations", select_option_1["openForms"]) - self.assertIn("nl", select_option_1["openForms"]["translations"]) - select_option_1_nl_translations = select_option_1["openForms"][ - "translations" - ]["nl"] - self.assertEqual( - select_option_1_nl_translations, - { - "label": "Optie 1", - }, - ) - - self.assertNotIn("openForms", select_option_2) - - -class TestComponentFixesMigration(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - - FormDefinition.objects.create( - name="Translations", - slug="translations", - configuration={ - "components": [ - { - "type": "columns", - "key": "columns", - "columns": [ - { - "size": "5", - "sizeMobile": "4", - "components": [], - }, - { - "size": 6, - "sizeMobile": "4", - "components": [], - }, - # malformed - should not crash - { - "components": [], - }, - # nothing to do - { - "size": 4, - "components": [], - }, - # invalid configurations - { - "size": "xl", - "components": [], - }, - { - "size": "3.14", - "sizeMobile": "xs", - "components": [], - }, - { - "size": None, - "components": [], - }, - ], - }, - { - "type": "file", - "key": "file1", - "defaultValue": None, - }, - { - "type": "file", - "key": "file2", - "defaultValue": [None], - }, - { - "type": "licenseplate", - "key": "licenseplate1", - }, - { - "type": "licenseplate", - "key": "licenseplate2", - "validate": { - "required": True, - }, - }, - { - "type": "postcode", - "key": "postcode", - }, - ] - }, - ) - - def test_column_sizes_converted_to_int(self): - FormDefinition = self.apps.get_model("forms", "FormDefinition") - fd = FormDefinition.objects.get() - - col1, col2, col3, col4, col5, col6, col7 = fd.configuration["components"][0][ - "columns" - ] - - with self.subTest("Column 1"): - self.assertEqual(col1["size"], 5) - self.assertEqual(col1["sizeMobile"], 4) - - with self.subTest("Column 2"): - self.assertEqual(col2["size"], 6) - self.assertEqual(col2["sizeMobile"], 4) - - with self.subTest("Column 3"): - self.assertEqual(col3["size"], 6) - self.assertNotIn("sizeMobile", col3) - - with self.subTest("Column 4"): - self.assertEqual(col4["size"], 4) - self.assertNotIn("sizeMobile", col4) - - with self.subTest("Column 5"): - self.assertEqual(col5["size"], 6) - self.assertNotIn("sizeMobile", col5) - - with self.subTest("Column 6"): - self.assertEqual(col6["size"], 6) - self.assertEqual(col6["sizeMobile"], 4) - - with self.subTest("Column 7"): - self.assertEqual(col7["size"], 6) - self.assertNotIn("sizeMobile", col7) - - def test_file_default_value_fixed(self): - FormDefinition = self.apps.get_model("forms", "FormDefinition") - fd = FormDefinition.objects.get() - file1, file2 = fd.configuration["components"][1:3] - - with self.subTest("File 1 (ok)"): - self.assertIsNone(file1["defaultValue"]) - - with self.subTest("File 2 (broken)"): - self.assertIsNone(file2["defaultValue"]) - - def test_licenseplate_validate_added(self): - FormDefinition = self.apps.get_model("forms", "FormDefinition") - fd = FormDefinition.objects.get() - plate1, plate2 = fd.configuration["components"][3:5] - - with self.subTest("License plate 1"): - self.assertIn("validate", plate1) - self.assertEqual( - plate1["validate"]["pattern"], - r"^[a-zA-Z0-9]{1,3}\-[a-zA-Z0-9]{1,3}\-[a-zA-Z0-9]{1,3}$", - ) - self.assertNotIn("required", plate1["validate"]) - - with self.subTest("License plate 2"): - self.assertIn("validate", plate2) - self.assertEqual( - plate2["validate"]["pattern"], - r"^[a-zA-Z0-9]{1,3}\-[a-zA-Z0-9]{1,3}\-[a-zA-Z0-9]{1,3}$", - ) - self.assertTrue(plate2["validate"]["required"]) - - def test_postcode_validate_added(self): - FormDefinition = self.apps.get_model("forms", "FormDefinition") - fd = FormDefinition.objects.get() - postcode = fd.configuration["components"][5] - - self.assertIn("validate", postcode) - self.assertEqual( - postcode["validate"]["pattern"], - r"^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$", - ) - - -class DatetimeAllowInvalidInputTests(TestMigrations): - app = "forms" - migrate_from = "0046_squashed_to_openforms_v230" - migrate_to = "0091_v230_to_v250" - - def setUpBeforeMigration(self, apps): - FormDefinition = apps.get_model("forms", "FormDefinition") - - self.form_definition = FormDefinition.objects.create( - name="Datetime", - slug="datetime", - configuration={ - "components": [ - {"key": "datetime", "type": "datetime"}, - {"key": "time", "type": "time"}, - ] - }, - ) - - def test_datetime_component_modified(self): - self.form_definition.refresh_from_db() - - self.assertTrue( - self.form_definition.configuration["components"][0]["customOptions"][ - "allowInvalidPreload" - ] - ) - self.assertNotIn( - "customOptions", self.form_definition.configuration["components"][1] - ) diff --git a/src/openforms/registrations/contrib/objects_api/migrations/0011_create_objecttypesypes_service_from_url.py b/src/openforms/registrations/contrib/objects_api/migrations/0011_create_objecttypesypes_service_from_url.py index 3dd9b726b1..e7f259820e 100644 --- a/src/openforms/registrations/contrib/objects_api/migrations/0011_create_objecttypesypes_service_from_url.py +++ b/src/openforms/registrations/contrib/objects_api/migrations/0011_create_objecttypesypes_service_from_url.py @@ -3,15 +3,15 @@ import re from django.db import migrations -from django.db.migrations.state import StateApps from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps + +from zgw_consumers.constants import APITypes, AuthTypes from openforms.registrations.contrib.objects_api.plugin import ( PLUGIN_IDENTIFIER as OBJECTS_API_PLUGIN_IDENTIFIER, ) -from zgw_consumers.constants import APITypes, AuthTypes - logger = logging.getLogger(__name__) @@ -88,7 +88,7 @@ class Migration(migrations.Migration): dependencies = [ ("registrations_objects_api", "0010_objectsapiconfig_objecttypes_service"), - ("forms", "0104_allow_invalid_input_datetime"), + ("forms", "0091_v230_to_v250"), ] operations = [ diff --git a/src/openforms/registrations/contrib/objects_api/migrations/0012_fill_required_fields.py b/src/openforms/registrations/contrib/objects_api/migrations/0012_fill_required_fields.py index d1060a41cf..2410ec9b2d 100644 --- a/src/openforms/registrations/contrib/objects_api/migrations/0012_fill_required_fields.py +++ b/src/openforms/registrations/contrib/objects_api/migrations/0012_fill_required_fields.py @@ -1,8 +1,8 @@ # Generated by Django 3.2.24 on 2024-02-12 16:47 from django.db import migrations -from django.db.migrations.state import StateApps from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps from openforms.registrations.contrib.objects_api.plugin import ( PLUGIN_IDENTIFIER as OBJECTS_API_PLUGIN_IDENTIFIER, @@ -43,7 +43,7 @@ class Migration(migrations.Migration): dependencies = [ ("registrations_objects_api", "0011_create_objecttypesypes_service_from_url"), - ("forms", "0104_allow_invalid_input_datetime"), + ("forms", "0091_v230_to_v250"), ] operations = [ diff --git a/src/openforms/upgrades/upgrade_paths.py b/src/openforms/upgrades/upgrade_paths.py index b25eb59895..76ce5997b7 100644 --- a/src/openforms/upgrades/upgrade_paths.py +++ b/src/openforms/upgrades/upgrade_paths.py @@ -76,6 +76,12 @@ def run_checks(self) -> bool: # If your current version falls outside of a supported range, you need to do another # upgrade path (first) or there is no upgrade path at all. UPGRADE_PATHS = { + "2.6": UpgradeConstraint( + valid_ranges={ + # more migration squashing and removing squashed sources + VersionRange(minimum="2.5.2"), + }, + ), "2.5": UpgradeConstraint( valid_ranges={ VersionRange(minimum="2.4.0"), # squashed migration sources are removed