Hello, I want to propose a refactor of the Form class to make it easier to customize and subclass.
Motivation: Currently the Form class (combined with Field, Widget and BoundField) has many responsibilities and combines much functionality into a single class. It currently does: - data validation (using Field validators and clean* methods) - parsing HTTP form data and handling special cases like file upload (using Widget.value_from_datadict() and Field.to_python()) - displaying values as HTML input fields (using Widget.render(), BoundField.value(), Field.prepare_value() and even the Media class) Although those three functionalities are closely related one can easily think of situations where only one is needed. Following Django's principle of loose coupling it would also be desirable to be able to exchange components. For example, it's conceivable that the form data doesn't come from a HTTP request with x-www-form-urlencoded or multipart/form-data content but gets transferred as JSON. In this case it should be easy to replace the HTTP parsing component with a JSON parser. Also HTML forms may not be the only place where data validation is needed. An application may get an untrusted dict of Python objects that doesn't have to be parsed but should still be validated. In that case it would be nice to be able to just use the data validation component. Currently it is also pretty hard to subclass or customize the Form class without having to rewrite most of the class. An example is the handling of "initial" and "files" data. Those two arguments provide all the data of a form in conjunction with the "data" argument. Those three attributes are accessed explicitly all over the classes of django.forms (i.e. Form, BoundField, Field, Widget). So if one wanted to change the semantics of "initial" data it would be necessary to change all of those classes. In the spirit of loose coupling those classes should rely as little as possible on the existence of certain attributes like "initial" and "files". Therefore I propose splitting the Form class into multiple components that can be easily exchanged and used without another. Rationale: To give you a better idea about the proposal I already prepared a general architecture of how I think this could be implemented. This is of course in no way final and I'll be happy to receive feedback and suggestions! Components: Each of the aforementioned components is implemented in a class of its own: - FormValidator: Implements the data validation as the name suggests. It uses Field instances that can be declared declaratively as it is possible with the old Form class. Furthermore it provides all methods of the old Form class that dealt with data validation, i.e. clean*(), is_valid(), errors(), etc. - DataProvider: Implements the "parsing and providing data" component. It has only one method "get_value(name)" that must be overwritten by subclasses and returns the data for a given field name. HtmlFormProvider is a subclass of DataProvider that provides the parsing of HTTP form content by using Widget objects and their "value_from_datadict()" method in particular. The widgets of a HtmlFormProvider can be specified declaratively just like the fields of a FormValidator. - HtmlForm: Implements the "displaying values as HTML input fields" component. Same as HtmlFormProvider it uses Widget objects and their "render()" method in particular. It also features all methods of the old Form class that were used mainly in templates, like __iter__(), __getitem__(), __str__(), as_p(), etc. Furthermore it fetches its data from a given HtmlFormProvider instance and provides handling of error messages with a FormValidator instance. It is also possible to decouple HtmlForm and HtmlFormProvider such that HtmlForm works with any DataProvider subclass. Changes to existing classes: Obviously the Form class does not exist anymore, however the Field class was also changed slightly. As it isn't used for any display logic anymore but solely for validation it loses a few attributes that are better suited to the Widget class like "label", "help_text" and "show_hidden_inital" and it isn't possible to define a set a Field's widget. To compensate those attributes where moved to the Widget class. New classes: To demonstrate the new possibilities there are also two new classes called "SimpleDataProvider" and "JsonProvider". They take a Python dict and a JSON string respectively instead of a QueryDict from a HTTP request. The attached file shows a class diagram of the proposed classes. Additional thoughts: The long standing issue about replacing the Media class (#22298 and friends) will probably be easier to solve if this proposal is accepted. As a result of being able to subclass HtmlForm without having to rewrite large parts of code it will be easier for third party apps to define mixins or subclasses of HtmlForm that deal with HTML assets. This also means that Django doesn't have to specify a single method on how to deal with assets but leaves that decision to app authors. Backwards Compatibility: Since the Form class is used in basically every Django project it is important to provide total backwards compatibility. This is possible by creating a subclass of "HtmlForm" that becomes the backwards compatible Form class. It uses a meta class to extract the widgets from the declared fields and create a corresponding HtmlFormProvider subclass. This way a Form subclass that was written as specified in the (old) documentation will continue working. I realize there are a few things to think about to make this proposal complete. However I first wanted to hear your feedback and suggestions before continuing to work on it. Do you agree with the motivation and the goals of this proposal? Do you think the motivation or the rationale have significant flaws that make it impossible to realize? Does the rationale look like something you would like to have in Django or does it violate some basic principles? I'd really like you to give your opinion! Moritz -- You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at https://groups.google.com/group/django-developers. To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/56D897D9.4090904%40googlemail.com. For more options, visit https://groups.google.com/d/optout.
signature.asc
Description: OpenPGP digital signature
