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.

Attachment: signature.asc
Description: OpenPGP digital signature

  • P... 'Moritz Sichert' via Django developers (Contributions to Django itself)
    • ... is_null
    • ... Curtis Maloney
      • ... 'Moritz Sichert' via Django developers (Contributions to Django itself)

Reply via email to