Dynamic form requirements in Django
I am about to start a large Django project at work. And the key features are form handling. There is going to be a lot of forms that the users of the app is going to use. And one requirement is that it should be possible to edit the forms in the admin interface of the application.
That is, it is not a requirement to add/delete form fields. But it sho开发者_JAVA百科uld be possible to edit form field attributes. E.g change which fields which is required and such.
Anybody have any good solutions on how to do this. I'm thinking that I might have to subclass the fields I want to be dynamic and do something there. I guess that all the models fields have to have "blank=True/null=True" and some how add some meta information in a separate model(?) about the model which declares which fields are required. And then use that information when forms is displayed and validated.
Before I start off doing this I would really like some input in how to design such a solution, anybody got ideas on how it should be done?
After more research, I've learned that you could do a lot with factory functions that would return the form with the given attributes set. So it looks like the problem is not that hard do solve.
I would make a function that returns a form with the right attributes/fields set. But the part that I haven't found a good solution for is how to manage this in the admin interface. I am thinking that I would make a db.Model that stores information about another models fields. Where I can set which is required and such.
And then in the function that returns the form, go trough that model and return a form with the right attributes. But how to make that model (which should mirror another model's fields) in a good way?
You should use certain models for this. For every form that might be customised this way, a new database entry must be created. I guess, it should look like this:
class FormSettings(Model):
form = CharField(..)
class FormAttrib(Model):
form_settings = ForeignKey(FormSettings)
field = CharField(..)
attrib_name=CharField(..)
attrib_value=CharField(..)
In FormSettings.form you should store some address of form, like say . and when form is built (in init), it should look for an entry in db and use attribs, that were described for it.
You can make it create db entries easily, if you use own metaclass for your forms and make it register in the database (create proper FormSettings entry), when a class is created. It will be done once when the process is started, which shouldn't be that bad.
I hope it helps a little. If you have further questions, I'll be happy to help :-) I like those non standard appliances of django :-) This is where all the fun begins :-)
EDIT:
OK, let's say, that you want to you have app food and form FavouriteFoodForm with food_name field. You store in the database in formsettings table as food.FavouriteFoodForm and add one attribute setting: field='food_name', attrib_name='required', attribute_value='True' (it's not perfect, but still it's not that bad').
No in FavouriteFoddForm you look for the settings in database, so you do:
settings = FormSettings.objects.get(form=app_name + self.__class__.name)
and you iterate over settings
for setting in settings.formattrib_set():
and here you simple call exec and set proper attribute:
exec("getattr(self, settings.field)[attrib_name] = %s" % attrib_value)
This should set attributes to required=True.
This isn't an exact answer to your problem, but I did something similar which I thought might be of use to you. I created an entirely customizable form, so that the form can be customized by end users.
I ended up with 2 models, a BuiltForm
and a BuiltFormField
:
class BuiltForm(models.Model):
name = models.CharField(max_length=32)
def form(self, data=None, initial=None):
form = BuiltFormGenericForm(data, initial=initial)
form.addBuiltFormFields(BuiltFormField.objects.filter(builtform=self, disabled=0))
return form
class BuiltFormField(models.Model):
builtform = models.ForeignKey(BuiltForm)
type = models.CharField(max_length=32, choices=ALL_FIELD_TYPES)
label = models.CharField(max_length=32)
fieldname = models.CharField(max_length=32)
helptext = models.CharField(max_length=256, blank=True)
required = models.BooleanField(default=False)
sort_order = models.IntegerField(default=0)
disabled = models.BooleanField(default=False)
options = models.TextField(blank=True)
def field(self):
field = ALL_FIELD_MAPS.get(self.type)
## Build out the field, supplying choices, `required`, etc.
There are a couple of things that are abnormal. ALL_FIELD_TYPES
is a map of the types of fields allowed in the form. This is used in conjuction with a dict()
to discern what class (CharField
, EmailField
, ChoiceField
, etc) should be used for this field. options
is also a pickle
d list of options for later use in a ChoiceField
. This allowed me to create an arbitrary list of options without a separate call to the database.
The other major piece to this is a custom Form
class which allows itself to be populated with the fields from a BuiltForm
. Mine looks like this:
class BuiltFormGenericForm(forms.Form):
built_form_fields = {}
builtform = None
def addBuiltFormFields(self, fields):
for field in fields:
self.fields[field.label] = field.field()
self.built_form_fields[field.pk] = field
def is_valid(self):
# Do validation here. My code for this is pretty big because of custom fields
# and calculations that I have to squeeze in.
The BuiltFormField
objects aren't designed to be created through the admin interface, but rather through a custom one with a ton of JavaScript to make it user-friendly, but you can certainly expose parts of the BuiltFormField
model in the admin interface for them to be updated.
Hope this helps you work out a model for your forms.
精彩评论