Skip to content

Commit

Permalink
[#9] Provide Mixin to retrieve field choices dynamically
Browse files Browse the repository at this point in the history
  • Loading branch information
pi-sigma committed Apr 29, 2024
1 parent 2896d94 commit a9351a6
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 100 deletions.
100 changes: 0 additions & 100 deletions openformsclient/forms.py

This file was deleted.

16 changes: 16 additions & 0 deletions openformsclient/mixins.py
Original file line number Diff line number Diff line change
@@ -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)
111 changes: 111 additions & 0 deletions openformsclient/models.py
Original file line number Diff line number Diff line change
@@ -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__)

Expand Down Expand Up @@ -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,
)

0 comments on commit a9351a6

Please sign in to comment.