Nutshell: Subclass this form with the required fields defined to automatically generate a form based on a model in a similar fashion to how form_for_model works, but in a way that tries to be a little easier if you want to customize the form. It handles updates and creations automatically as long as any database field requirements are met.
This is something I made while trying to understand newforms, and is my own attempt at something between the simplicity of a stock form_for_model form, and a full blown custom form. The proper way is to use a callback function to customize form_for_model, but that felt cumbersome so I did it my way :) It works for me, but I'm relatively new to both python and Django so please test yourself before trusting.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | class DynForm(forms.BaseForm):
"""
Given defined fields exclude,include,model, and widgets this class will attempt to build a form from a model definition
like form_for_model/instance. The only required field is model. exclude,include, and widgets can be left off in which
case all fields except the primary key field will be used with default widgets.
exclude =[List of model field names] Can be empty or undefined which will exclude only the primary key field
include =[List of model field names] Can be empty or undefined which will make it use all fields in model
widgets = {'field': (widgetmodel,{widget args}),}
formfields = {'field': (formfieldmodel,{formfield args}),} the widgets dict above will override any widgets specified
here.
If you specify an 'include' list it's pointless to specify and 'exclude' list because only the fields in include will be used.
'exclude' is really only useful if you want almost all the model fields except for one or two, which you'd put in the list
and leave include empty or undefined to get all the other fields automatically.
This not really a normal form, so some things may not work the same as you might expect.
To emmulate form_for_model you'd basically do the bare minimum
class MyForm(DynForm):
model = MyModel
Unbound form:
form = AgentEditForm()
Bound to a pk for the model defined, and will use that data to populate as initial if it exists.
form = AgentEditForm(None,pk=5) # Agent.objects.get(pk=5)
Bound to a pk, and processing POST
form = AgentEditForm(request.POST,pk=5)
optional keyword arguments
pk - primary key. Will fallback to None if pk not valid
widget_kwargs - Dictionary field keys, and a dict with keyword args
for the fields widget used instead of those in form definition (models_def)
form.save() -
If no primary key (pk) was given at form creation, or the pk was invalud a new object will
be created and the object will be returned. Any keyword arguments passed to save will be
used to instantiate a new new instance for the model, but will be overridden by form data if
present. This is to allow you to define values for required fields you excluded
in the form definition.
------------------------------------------------------------------------------------------------------------------------------------------------------
Example form:
class AgentReminderForm(DynForm):
model= AgentReminder
include_fields=['who','what','when','priority','notice','date']
exclude_fields=[]
widgets= { 'what': (forms.Textarea,{'attrs':{'rows':'5','cols':'10'},}),
'notice': (forms.Select,{'choices': [('15','15 Minutes'),('60','1 Hour')]}),
}
formfields ={ 'when': (forms.DateTimeField,{'input_formats': ["%b-%d-%Y %I:%M:%S %p"] })
}
------------------------------------------------------------------------------------------------------------------------------------------------------
In View:
def myview(request,id=None)
if request.POST:
form = AgentReminderForm(request.POST,pk=id)
if form.is_valid():
form.save(date=datetime.now())
else:
form=AgentReminderForm(pk=id)
...
"""
def __init__(self, *args, **kwargs):
try:
getattr(self,'include')
except:
self.include = []
try:
getattr(self,'exclude')
except:
self.exclude = []
try:
getattr(self,'widgets')
except:
self.widgets = {}
try:
getattr(self,'formfields')
except:
self.formfields = {}
newkwargs = kwargs
self.base_fields = {} # Since we are bypassing Form and using BaseForm
if 'widget_kwargs' in kwargs:
self.widget_kwargs = newkwargs.pop('widget_kwargs')
else:
self.widget_kwargs = {}
self.primary_key=None
if 'pk' in kwargs:
self.primary_key = newkwargs.pop('pk')
try:
mi = self.model.objects.get(pk=self.primary_key)
except:
self.primary_key = None
if self.primary_key:
newinitial = mi.__dict__
if 'initial' in newkwargs:
newinitial.update(newkwargs['initial'])
newkwargs['initial'] = newinitial
super(DynForm,self).__init__(*args,**newkwargs)
mm = self.model._meta # grab the model info
include = []
if self.include:
for field in mm.fields + mm.many_to_many:
if field.attname in self.include:
include.append(field)
else:
include = mm.fields + mm.many_to_many
self.exclude.append(mm.pk.attname)
for f in include:
if (f.attname not in self.exclude) and (f is not mm.pk.attname) :
if f.attname in self.formfields:
formfield = self.formfields[f.attname][0]
ffargs = self.formfields[f.attname][1]
else:
formfield = f.formfield
ffargs = {}
# Handle Widgets
if f.attname in self.widget_kwargs: #use what was given at form instantiation
ffargs['widget'] =self.widgets[f.attname][0](**self.widget_kwargs[f.attname][1])
elif f.attname in self.widgets: # use what was given at form definition
ffargs['widget'] =self.widgets[f.attname][0](**self.widgets[f.attname][1] )
self.fields[f.attname] = formfield(**ffargs)
self.base_fields=self.fields # don't actually need this, but just in case someone tries to treat as a normal form
def save(self, **kwargs):
if not self.is_valid():
return None
rel_wait = [] # start with no many to many relations to worry about
q_wait = [] # start with no fields to queue until after initial save
if self.primary_key is None: # treat as new
pm = self.model(**kwargs)
# since this is new, put any waiting fields in the rel_wait list
for mf in pm.__class__._meta.many_to_many:
rel_wait.append(mf.attname)
else:
pm = self.model.objects.get(pk=self.primary_key)
for f in self.fields:
if f not in rel_wait:
setattr(pm,f,self.clean_data[f])
else:
q_wait.append(f)
pm.save() # Must save here, so relations work
if len(q_wait) > 0: # something is queued, and can now be added since the insert is done and pm has a pk
for f in q_wait: # now that it's inserted we can take care of the waiting relations
setattr(pm,f,self.clean_data[f])
pm.save()
return pm
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Please login first before commenting.