diff --git a/openformsclient/forms.py b/openformsclient/forms.py deleted file mode 100644 index b677ddc..0000000 --- a/openformsclient/forms.py +++ /dev/null @@ -1,100 +0,0 @@ -import logging - -from django import forms -from django.core.cache import cache -from django.db.models.fields import BLANK_CHOICE_DASH -from django.forms.fields import TypedChoiceField -from django.forms.widgets import Select -from django.utils.translation import gettext_lazy as _ - -from .utils import get_form_choices - -logger = logging.getLogger(__name__) - - -class OpenFormsBaseField(TypedChoiceField): - """ - Basic field for use in Django forms to render a Select widget filled with - the available forms (uuid, name) or (slug, name) in Open Forms. - - This form records the form's UUID or slug, depending on what concrete model - class is used. - """ - - description = _("Open Forms form") - use_uuids = None - widget = Select - include_blank = True - - def get_choices( - self, - blank_choice=BLANK_CHOICE_DASH, - limit_choices_to=None, - ordering=(), - ): - cache_key = f"openformsclient.models.OpenFormsFieldMixin.get_choices__use_uuids_{self.use_uuids}" - - choices = cache.get(cache_key) - if choices is None: - try: - choices = get_form_choices(use_uuids=self.use_uuids) - except Exception as e: - logger.exception(e) - choices = [] - else: - cache.set(cache_key, choices, timeout=60) - - if choices: - if self.include_blank: - blank_defined = any(choice in ("", None) for choice, _ in choices) - if not blank_defined: - choices = blank_choice + choices - - return choices - - -class OpenFormsUUIDField(OpenFormsBaseField, forms.UUIDField): - """ - Basic field for use in Django forms to render a Select widget filled with - the available forms (uuid, name) in Open Forms. - - This field records the form's UUID. This makes the choice really specific. - Note that to allow empty records, you will need to set ``null=True`` and - ``blank=True``. - """ - - use_uuids = True - - def get_db_prep_value(self, value, connection, prepared=False): - # A Select widget always returns a string. If an empty string is - # returned, we need to force it to be None since an empty string is not - # valid UUID nor is it empty. - if not value: - return None - return super().get_db_prep_value(value, connection, prepared) - - -class OpenFormsSlugField(OpenFormsBaseField, forms.SlugField): - """ - Basic field for use in Django forms to render a Select widget filled with - the available forms (slug, name) in Open Forms. - - This field records the form's slug. This allows an Open Forms user to - gracefully change the form without the need to change the reference - everywhere. - """ - - use_uuids = False - - def __init__( - self, *args, max_length=100, required=False, allow_unicode=False, include_blank=True, **kwargs - ): - super().__init__( - *args, - max_length=max_length, - required=False, - allow_unicode=allow_unicode, - **kwargs, - ) - self.include_blank = include_blank - self.coerce = self.to_python diff --git a/openformsclient/mixins.py b/openformsclient/mixins.py new file mode 100644 index 0000000..f419187 --- /dev/null +++ b/openformsclient/mixins.py @@ -0,0 +1,16 @@ +from .models import OpenFormsSlugField, OpenFormsUUIDField + + +class OpenFormsClientMixin: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # retrieve choices for OpenForms fields when the form instance is created + form_fields = self.instance._meta.fields + openforms_fields = ( + field + for field in form_fields + if isinstance(field, (OpenFormsSlugField, OpenFormsUUIDField)) + ) + for field in openforms_fields: + field.choices = field.get_choices(include_blank=True) diff --git a/openformsclient/models.py b/openformsclient/models.py index a124902..88bddd3 100644 --- a/openformsclient/models.py +++ b/openformsclient/models.py @@ -1,12 +1,18 @@ import logging +from django.core.cache import cache from django.db import models +from django.db.models.fields import BLANK_CHOICE_DASH +from django.forms.fields import TypedChoiceField +from django.forms.widgets import Select from django.utils.functional import cached_property +from django.utils.text import capfirst from django.utils.translation import gettext_lazy as _ from solo.models import SingletonModel from .client import Client +from .utils import get_form_choices logger = logging.getLogger(__name__) @@ -73,3 +79,108 @@ def save(self, *args, **kwargs): @cached_property def client(self): return Client(self.api_root, self.api_token, self.client_timeout) + + +class OpenFormsBaseField: + """ + Basic field for use in Django models to render a Select widget filled with + the available forms (uuid, name) or (slug, name) in Open Forms. + + This form records the form's UUID or slug, depending on what concrete model + class is used. + """ + + description = _("Open Forms form") + use_uuids = None + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + # We do not exclude max_length if it matches default as we want to change + # the default in future. + return name, path, args, kwargs + + def formfield(self, **kwargs): + defaults = { + "required": not self.blank, + "label": capfirst(self.verbose_name), + "help_text": self.help_text, + "widget": Select, + } + + if self.choices is None: + defaults["choices"] = self.get_choices(include_blank=self.blank) + defaults["coerce"] = self.to_python + + return TypedChoiceField(**defaults) + + def get_choices( + self, + include_blank=True, + blank_choice=BLANK_CHOICE_DASH, + limit_choices_to=None, + ordering=(), + ): + cache_key = f"openformsclient.models.OpenFormsFieldMixin.get_choices__use_uuids_{self.use_uuids}" + + choices = cache.get(cache_key) + if choices is None: + try: + choices = get_form_choices(use_uuids=self.use_uuids) + except Exception as e: + logger.exception(e) + choices = [] + else: + cache.set(cache_key, choices, timeout=60) + + if choices: + if include_blank: + blank_defined = any(choice in ("", None) for choice, _ in choices) + if not blank_defined: + choices = blank_choice + choices + + return choices + + +class OpenFormsUUIDField(OpenFormsBaseField, models.UUIDField): + """ + Basic field for use in Django models to render a Select widget filled with + the available forms (uuid, name) in Open Forms. + + This field records the form's UUID. This makes the choice really specific. + Note that to allow empty records, you will need to set ``null=True`` and + ``blank=True``. + """ + + use_uuids = True + + def get_db_prep_value(self, value, connection, prepared=False): + # A Select widget always returns a string. If an empty string is + # returned, we need to force it to be None since an empty string is not + # valid UUID nor is it empty. + if not value: + return None + return super().get_db_prep_value(value, connection, prepared) + + +class OpenFormsSlugField(OpenFormsBaseField, models.SlugField): + """ + Basic field for use in Django models to render a Select widget filled with + the available forms (slug, name) in Open Forms. + + This field records the form's slug. This allows an Open Forms user to + gracefully change the form without the need to change the reference + everywhere. + """ + + use_uuids = False + + def __init__( + self, *args, max_length=100, db_index=False, allow_unicode=False, **kwargs + ): + super().__init__( + *args, + max_length=max_length, + db_index=db_index, + allow_unicode=allow_unicode, + **kwargs, + )