Skip to content

Commit a9351a6

Browse files
committed
[#9] Provide Mixin to retrieve field choices dynamically
1 parent 2896d94 commit a9351a6

File tree

3 files changed

+127
-100
lines changed

3 files changed

+127
-100
lines changed

openformsclient/forms.py

-100
This file was deleted.

openformsclient/mixins.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from .models import OpenFormsSlugField, OpenFormsUUIDField
2+
3+
4+
class OpenFormsClientMixin:
5+
def __init__(self, *args, **kwargs):
6+
super().__init__(*args, **kwargs)
7+
8+
# retrieve choices for OpenForms fields when the form instance is created
9+
form_fields = self.instance._meta.fields
10+
openforms_fields = (
11+
field
12+
for field in form_fields
13+
if isinstance(field, (OpenFormsSlugField, OpenFormsUUIDField))
14+
)
15+
for field in openforms_fields:
16+
field.choices = field.get_choices(include_blank=True)

openformsclient/models.py

+111
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import logging
22

3+
from django.core.cache import cache
34
from django.db import models
5+
from django.db.models.fields import BLANK_CHOICE_DASH
6+
from django.forms.fields import TypedChoiceField
7+
from django.forms.widgets import Select
48
from django.utils.functional import cached_property
9+
from django.utils.text import capfirst
510
from django.utils.translation import gettext_lazy as _
611

712
from solo.models import SingletonModel
813

914
from .client import Client
15+
from .utils import get_form_choices
1016

1117
logger = logging.getLogger(__name__)
1218

@@ -73,3 +79,108 @@ def save(self, *args, **kwargs):
7379
@cached_property
7480
def client(self):
7581
return Client(self.api_root, self.api_token, self.client_timeout)
82+
83+
84+
class OpenFormsBaseField:
85+
"""
86+
Basic field for use in Django models to render a Select widget filled with
87+
the available forms (uuid, name) or (slug, name) in Open Forms.
88+
89+
This form records the form's UUID or slug, depending on what concrete model
90+
class is used.
91+
"""
92+
93+
description = _("Open Forms form")
94+
use_uuids = None
95+
96+
def deconstruct(self):
97+
name, path, args, kwargs = super().deconstruct()
98+
# We do not exclude max_length if it matches default as we want to change
99+
# the default in future.
100+
return name, path, args, kwargs
101+
102+
def formfield(self, **kwargs):
103+
defaults = {
104+
"required": not self.blank,
105+
"label": capfirst(self.verbose_name),
106+
"help_text": self.help_text,
107+
"widget": Select,
108+
}
109+
110+
if self.choices is None:
111+
defaults["choices"] = self.get_choices(include_blank=self.blank)
112+
defaults["coerce"] = self.to_python
113+
114+
return TypedChoiceField(**defaults)
115+
116+
def get_choices(
117+
self,
118+
include_blank=True,
119+
blank_choice=BLANK_CHOICE_DASH,
120+
limit_choices_to=None,
121+
ordering=(),
122+
):
123+
cache_key = f"openformsclient.models.OpenFormsFieldMixin.get_choices__use_uuids_{self.use_uuids}"
124+
125+
choices = cache.get(cache_key)
126+
if choices is None:
127+
try:
128+
choices = get_form_choices(use_uuids=self.use_uuids)
129+
except Exception as e:
130+
logger.exception(e)
131+
choices = []
132+
else:
133+
cache.set(cache_key, choices, timeout=60)
134+
135+
if choices:
136+
if include_blank:
137+
blank_defined = any(choice in ("", None) for choice, _ in choices)
138+
if not blank_defined:
139+
choices = blank_choice + choices
140+
141+
return choices
142+
143+
144+
class OpenFormsUUIDField(OpenFormsBaseField, models.UUIDField):
145+
"""
146+
Basic field for use in Django models to render a Select widget filled with
147+
the available forms (uuid, name) in Open Forms.
148+
149+
This field records the form's UUID. This makes the choice really specific.
150+
Note that to allow empty records, you will need to set ``null=True`` and
151+
``blank=True``.
152+
"""
153+
154+
use_uuids = True
155+
156+
def get_db_prep_value(self, value, connection, prepared=False):
157+
# A Select widget always returns a string. If an empty string is
158+
# returned, we need to force it to be None since an empty string is not
159+
# valid UUID nor is it empty.
160+
if not value:
161+
return None
162+
return super().get_db_prep_value(value, connection, prepared)
163+
164+
165+
class OpenFormsSlugField(OpenFormsBaseField, models.SlugField):
166+
"""
167+
Basic field for use in Django models to render a Select widget filled with
168+
the available forms (slug, name) in Open Forms.
169+
170+
This field records the form's slug. This allows an Open Forms user to
171+
gracefully change the form without the need to change the reference
172+
everywhere.
173+
"""
174+
175+
use_uuids = False
176+
177+
def __init__(
178+
self, *args, max_length=100, db_index=False, allow_unicode=False, **kwargs
179+
):
180+
super().__init__(
181+
*args,
182+
max_length=max_length,
183+
db_index=db_index,
184+
allow_unicode=allow_unicode,
185+
**kwargs,
186+
)

0 commit comments

Comments
 (0)