I'm sorry to say that I missed the thread "Proposal: Forms and BoundForms" last month. I'm very interested in the topic, largely because, having run out of patience with manipulators, I had written code to handle form definition, validation, and rendering that is similar to the proposals described in that thread. In developing this library, I was focusing mainly on providing useful integration between forms and models (including "nested forms"), and customisability of forms. I'll describe this here, focusing mostly on the needs or decisions which haven't been discussed in that thread already. I hope that some of this will be of use for the manipulator replacement work that is in progress.
I was motivated in creating this to fulfill these needs that I had that manipulators weren't fully suitable for: A. To allow easy declarative form creation, as well as customisation of the default form for a model. B. To unify adding/changing model instances. Adding and changing are processed identically, with only the form defaults being different. C. To allow a form to be easily generated and handled for a model hierarchy -- i.e. a single model instance, and other instances related via ManyToManyFields and ForeignKeys in either direction; and supporting model validation. D. To make it easy to customise the rendering of a form or form field. E. To allow forms with validation to be used without being tied to a model class/instance -- i.e. to work with dicts and lists. For (A), I implemented the unbound/bound distinction with the natural class/instance distinction: a Form subclass defines the form structure and validation, and an instance of the class is that form, "bound" with data, and errors (if it has been validated). Similarly, a Widget subclass (a form field) defines the widget's attributes (name, size, choices list), and a Widget instance has data and errors. All customisation of forms and widgets is done through subclassing, with an alter() method providing a convenient helper for small changes. For example, assume a UserProfile model that has 'username', 'fullname', and 'email' TextFields, and a 'group' ForeignKey. The automatically generated default form for this model would have a TextWidget for the first three fields, and a SelectField for the group_id, with a list of the available groups. # Never let the username be edited, and use a custom widget for the # email field class EditProfileForm(Profile.default_form): username = None email = EmailWidget.alter(maxlength=20) # Only let admin users edit the group if not request.user.is_admin(): EditProfileForm = EditProfileForm.alter(group_id=None) For (B): handling the request involves creating a form instance with default data, updating that with POST data (if applicable), processing changes, and rendering the form if unsubmitted or errors. # If we were adding a profile here, we'd just use Profile() profile = Profile.objects.get(id=whatever) form = EditProfileForm(profile) if request.POST: form.update_data(convert_post_data(request.POST)) form.process_input_data() if not form.has_errors(): return HTTPResponseRedirect("/profile/") return render_to_response("profile.html", {'form': form}) Since the base Form class is ignorant of models (E), and only uses a dict to store its data, there is a ModelForm subclass that extracts data from a model instance (and related instances) to fill in the widgets declared in the form. A default_form descriptor is added to the models.Model class to generate a form automatically -- but for convenience, if a default_form class is declared in the model, it will be used instead (there were some technical hoops to jump through with this, but I'll gloss over that). (C), integration with models, is the trickiest part. as implemented, there are four distinct types of widget, each corresponding to a different data structure or relation type: A simple widget corresponds to a simple data type (int, string) or, for a model instance, a TextField, IntegerField, etc. -- or the fieldname_id attribute for a ForeignKey relation. A widget with its has_form attribute set corresponds to a dict, or the fieldname attribute for a ForeignKey relation to another model; the SubForm Widget subclass handles this with another Form class: class EditProfileForm(Profile.default_form): home_address = SubForm.alter(form=Address.default_form) # Now we can render form.address.street in a template, and so on A widget with its has_list attribute set corresponds to a list, or a ManyToMany or reverse ForeignKey relation to another model, when selecting from the existing instances, and not editing them inline. A multi-select list widget handles these. Finally, a widget with both has_list and has_form corresponds to a list of dicts, or a ManyToMany or reverse ForeignKey relation, edited inline. A SubFormList widget is used for this. As implemented, these could be nested as needed. In a particular case which was part of my motivation for this, I had something like the following for editing a product, its variants, and their attributes (which distinguish them from other variants) all nested in one form: class ProductVariantAdminForm(ProductVariant.default_form) attributes = SubFormList(form=Attribute.default_form) class ProductAdminForm(Product.default_form): product_variants = SubFormList(form=ProductVariantAdminForm) This provided two levels of nesting automatically. Another critical issue with model integration was validation. Validation must be handled both at the form level (before converting from bare strings to Python objects) and at the model level. If a field fails form validation, it should not try and validate it at the model level; and any errors in model validation should be saved into the form errors, so they can be displayed alongside the widgets they refer to. I didn't come up with a solution to this that was satisfactory; I had a two-step process whereby, if all form validation passed, then model validation was done; and if that also all passed, then the model was saved. This was hacky, and resulted in a poor UI experience. A better solution would do as much validation as possible in one step, so as to show the errors to the user together; this might be obtainable by implementing something like the FormEncode Schema, and combining the two levels by appropriately chaining the form's schema and the model's schema. Finally, (D) -- each Widget has a template attribute which was the default template for that widget (using XHTML) -- this was the bare widget only (e.g. "<input type='text'...>". Subclasses that only need to change the rendering could override this. For the common case of displaying a widget with a label and any errors, a template tag {% wrap_widget form.somewidget %} did the job neatly, with a similar tag providing a shortcut for rendering an entire form. My original plan was to propose this whole package as a candidate for inclusion in Django as a replacement for manipulators; however, due to other demands, I never got around to tidying it up or writing it up to my satisfaction for this. Andrew --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django developers" group. To post to this group, send email to django-developers@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/django-developers -~----------~----~----~----~------~----~------~--~---