Login

Dynamic Form Class

Author:
dballanc
Posted:
March 31, 2007
Language:
Python
Version:
.96
Tags:
dynamic forms subclass
Score:
3 (after 3 ratings)

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

  1. Custom admin widgets by field type by dgouldin 6 years, 4 months ago
  2. clean validation for dynamic form fields by leveillej 5 years, 2 months ago
  3. Multiple User subclasses custom Auth backend by ungenio41 3 years, 10 months ago
  4. Changing the look of newforms as_table with a custom BaseForm by bikeshedder 8 years, 1 month ago
  5. form_for_model / instance customized save and model aware validation by fivethreeo 8 years, 2 months ago

Comments

Please login first before commenting.