I’m developing a website for use in education and it requires tracking a lot of different kinds of evaluation forms. It doesn’t make sense to define a Model
/Form
per evaluation type.
My plan is to create a subclass of BaseForm
that, in addition to its parameters, takes a schema which defines the form’s fields and their attributes. It would otherwise behave just like a regular Form
class. I would then save the contents of cleaned_data
in a JSON field.
This would be roughly what I’m planning to do:
from django import forms
class DynamicForm(forms.BaseForm):
def __init__(self, *args, **kwargs):
schema = kwargs.pop('schema')
# define `dynamic_fields` based on this schema
# ...
self.base_fields = dynamic_fields
super(DynamicForm, self).__init__(*args, **kwargs)
According to Django’s documentation, we should only use fields
to customize the fields of a form, not base_fields
. But from reading the source code, I believe that modifying base_fields
is the only way to go for my design. The caution against changing base_fields
seems to only apply when the form subclass is derived through a metaclass, which I’m planning to use in my design.
Am I on the right track or should I be reconsidering my design?
Any suggestions would be appreciated. I looked at a few existing options for “dynamic forms” but none of them seemed to match my requirements.
The caution against changing base_fields applies anywhere you’re creating a future instance of the form. The attribute base_fields
is stored as an attribute of the class and not the instance that you are working with. So if you create a new field, every instance of that form you create in the same process after this will have that new field.
Copied from the source code:
#The base_fields class attribute is the class-wide definition of
# fields. Because a particular instance of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
So if you want to add fields on a per-instance basis, you can add the fields to the fields
object in the __init__
method of your form - or you can add them in the view after the form has been created.
Thanks Ken!
The reason I asked is because base_fields
is only set as a class attribute in DeclarativeFieldsMetaclass
, which I don’t plan to use in my DynamicForm
. DynamicForm
is meant to be instantiated directly with the schema passed to it as an argument as opposed to subclassed with the field defined as attributes on the subclass.
The reason I wanted to use base_fields
instead of fields
is that fields
gets overwritten with base_fields
in BaseForm.__init__
.
I could call super
first and then overwrite fields
. However, this misses some components of the initialization, specifically the call to order_fields
which may modify fields. I guess I could just re-run that order_fields
call and I’ll need to get the field_order
argument passed into __init__
in the kwargs
. Of course this could break if BaseForm.__init__
changes in the future.
I was trying to think of the “cleanest” and most generic to implement the class so it could then be published as a small reusable package, but I’ll just go with a pragmatic solution for now.
As I think about this some more, I’m wondering what there is to be gained by even trying to do this.
It sounds to me like each of these forms are already defined - I’m not seeing where there’s any advantage to not just defining these as forms.
Yes, I understand you may not want individual models - that’s ok. But a form doesn’t need to be tied to a model. You can create these forms as forms, and store the responses as a JSON structure within a model.