Proposal: Forms and BoundForms

32 views
Skip to first unread message

Adrian Holovaty

unread,
Sep 12, 2006, 3:35:54 PM9/12/06
to django-d...@googlegroups.com
I've put together the beginnings of the manipulator framework
replacement, using ideas from the various proposals we've had, plus
inspiration from some prior art (the previous Django form/manipulator
system, plus some ideas from FormEncode).

For those of you arriving late, note that the three main tasks this
library deals with are: displaying blank HTML forms, validating input
and redisplaying forms with error messages.

Here's how it looks.

For example, let's take a contact form, such as the one here:

http://projects.washingtonpost.com/staff/email/bob+woodward/

Currently, we deal with Manipulators and FormFields and FormWrappers,
and there's a fair bit of boilerplate for the most common case. In the
current system, view code would look something like this (disregard
how ContactForm is defined, as I'll get to that later):

manipulator = ContactForm()
if request.method == 'POST':
new_data = request.POST.copy()
errors = manipulator.get_validation_errors(new_data)
if not errors:
send_email_and_redirect()
else:
errors = new_data = {}
form = forms.FormWrapper(manipulator, new_data, errors)
return render_to_response('email_form.html', {'form': form})

And the template looks like this:

<form action="/foo/" method="post">
{% if form.sendername.errors %}{{ form.sendername.html_error_list
}}{% endif %}
<p>Your name: {{ form.sendername }}</p>

{% if form.senderemail.errors %}{{
form.senderemail.html_error_list }}{% endif %}
<p>Your e-mail: {{ form.senderemail }}</p>

{% if form.subject.errors %}{{ form.subject.html_error_list }}{% endif %}
<p>Subject: {{ form.subject }}</p>

{% if form.message.errors %}{{ form.message.html_error_list }}{% endif %}
<p>Message: {{ form.message }}</p>

<input type="submit" value="Send e-mail">
</form>

A lot of the boilerplate is necessary because a manipulator does not
contain state. Hence the need for FormWrappers and collecting errors
in a temporary variable.

As an alternative, my proposal has two main classes: Form and
BoundForm. A Form is similar to an old Manipulator -- it's a
collection of validation fields and encapsulates the concepts of form
display, validation and redisplay. A BoundForm is simply a Form plus
data. Thus, a BoundForm knows how many validation errors it has, knows
how to display itself with the given data populated in the form, and
can return a single field's errors and display. This is similar to the
old FormWrapper class, but simpler and cleaner.

Here's a barebones structure (and partial implementation) of the new
classes, along with some additional classes that are used behind the
scenes:

#####################################################################

class Form(object):
fields = {} # This gets filled with field names and data types.
def bind(self, **kwargs):
"Returns a BoundForm instance for the given kwargs."
return BoundForm(self, kwargs)

def is_valid(self, **kwargs):
"""
Returns True if the given kwargs (field_name -> field_value) are valid.
Otherwise, returns False.
"""
raise NotImplementedError

def errors(self, **kwargs):
"Returns an ErrorCollection for the given field_name ->
field_value input."
raise NotImplementedError

class BoundForm(object):
"A form plus data"
def __init__(self, form, kwargs):
self._form = form
self._kwargs = kwargs

def _is_valid(self):
return self._form.is_valid(**self._kwargs)
is_valid = property(_is_valid)

def _errors(self):
return self._form.errors(*self._kwargs)
errors = property(_errors)

def __getattr__(self, name):
try:
self._form.fields[name]
except KeyError:
raise KeyError('Key %r not found in BoundForm' % name)
return BoundFormField(self, name)

class BoundFormField(object):
"A form field plus data"
def __init__(self, bound_form, name)
self._form = bound_form
self._name = name

def _errors(self):
"Returns a list of errors for this field. Returns an empty
list if none."
return self._form.errors.get(self.name, [])
errors = property(_errors)

def as_text(self, attrs=None):
"""
Returns one of the following (haven't decided yet):
* a string of HTML for representing this as an <input type="text">
* an instance of some TextField class, whose __str__() is the HTML
"""
raise NotImplementedError

def as_textarea(self, attrs=None):
"""
Returns one of the following (haven't decided yet):
* a string of HTML for representing this as a <textarea>
* an instance of some TextField class, whose __str__() is the HTML
"""
raise NotImplementedError

def as_select(self, attrs=None):
"""
Returns one of the following (haven't decided yet):
* a string of HTML for representing this as a <select>
* an instance of some TextField class, whose __str__() is the HTML
"""
raise NotImplementedError

class ErrorCollection(dict):
"A collection of errors that knows how to display itself in
various formats."
def as_ul(self):
return '<ul class="errorlist">%s</ul>' % \
''.join(['<li>%s</li>' % e for e in self.values()])

#####################################################################

Both Form and BoundForm have is_valid and errors attributes, which is
nice and symmetrical. The only difference is that, in BoundForm,
they're properties, while in Form they're methods that take the data
as keyword arguments.

Hence, with this new API, the above view code would be written like this:

form = ContactForm()
if request.method == 'POST' and form.is_valid(**request.POST):
send_email_and_redirect()
return render_to_response('email_form.html', {'form':
form.bind(**request.POST)})

Note that this is nice and terse but comes slightly at the expense of
efficiency, as both form.is_valid() and form.bind() perform
validation. A more efficient way of writing this (which would only
perform validation once) would be as so:

form = ContactForm()
if request.method == 'POST':
boundform = form.bind(**request.POST)
if boundform.errors:
return render_to_response('email_form.html', {'form': boundform})
else:
send_email_and_redirect()
else:
return render_to_response('email_form.html', {'form': form.bind()})

I don't think this inefficiency is worth worrying about for the common
case, particularly because form.is_valid() would be short-circuited to
return False as soon as it encounters its first validation error.
Still, we should point this out in the docs.

The template would look like this:

<form action="/foo/" method="post">
{% if form.sendername.errors %}{{ form.sendername.errors.as_ul }}{% endif %}
<p>Your name: {{ form.sendername.as_text }}</p>

{% if form.senderemail.errors %}{{ form.senderemail.errors_as_ul
}}{% endif %}
<p>Your e-mail: {{ form.senderemail.as_text }}</p>

{% if form.subject.errors %}{{ form.subject.errors.as_ul }}{% endif %}
<p>Subject: {{ form.subject.as_text }}</p>

{% if form.message.errors %}{{ form.message.errors.as_ul }}{% endif %}
<p>Message: {{ form.message.as_textarea }}</p>

<input type="submit" value="Send e-mail">
</form>

There are two differences here:

1. form.sendername.errors.as_url rather than
form.sendername.html_error_list. This gives us some flexibility to add
other error shortcut-display types, like JavaScript or something.

2. "form.sendername.as_text" rather than "form.sendername". I'm not
100% sold on this idea, but I've been toying with the idea of
controlling the decision of the HTML widget *in the template* like
this. The implication of this is that the field definitions (when you
define your Form class) are purely data-oriented, rather than
presentation-oriented. Maybe each form field type can have a default
HTML display, to save on typing "as_text". Also, there would need to
be a way of specifying custom HTML attributes for those widgets -- the
as_text() and as_textarea() methods take an "attrs" parameter, but
we'd also have to be able to specify the attributes in the template
somehow. Maybe we could provide a template tag that does this.

Now, how does the form class look? Currently, we have something like
this, a Manipulator class:

class ContactForm(forms.Manipulator):
def __init__(self):
self.fields = (
forms.TextField(field_name='sendername', is_required=True),
forms.EmailField(field_name='senderemail', is_required=True),
forms.TextField(field_name='subject', is_required=False,
maxlength=20),
forms.LargeTextField(field_name='message', is_required=True),
)

Let's change this to be similar to model classes, like so:

class ContactForm(Form):
sendername = TextField()
senderemail = EmailField()
subject = TextField(maxlength=20, blank=True)
message = TextField()

One big change, other than the syntax, is that 'is_required' is
dropped, in favor of 'blank', to match our model syntax. Also, as in
models, blank=False will be default. Currently in manipulators,
is_required=False is default.

Another big change is that each of the Fields in this ContactForm
would be responsible primarily for *validation* rather than specifying
the HTML form field. This decouples the concept of validation from the
HTML form type.

Another thing to note: In the current Manipulator framework, sometimes
it is convenient to be able to construct self.fields dynamically. We
should provide a way to do that with the new syntax.

I haven't yet come up with the protocol for Field classes, but
FormEncode has some good ideas in that department, like to_python()
and stuff.

Finally, how does this stuff tie into models? Well, it's completely
decoupled from models, but we should have some helper functions that
create a Form from a model. (This is equivalent to the automatic
AddManipulator and ChangeManipulator classes we already create for
each model.) But the added flexibility of this new system will let us
do things like specify a special-case Form that an admin page uses,
and make it easy to create a Form that is based *almost* entirely on a
model but, say, removes a few fields.

So that's the proposal -- thoughts/criticisms? I'd like to get this
out there as soon as possible, because we (or, rather, I) have dawdled
for quite some time!

Adrian

--
Adrian Holovaty
holovaty.com | djangoproject.com

Jacob Kaplan-Moss

unread,
Sep 12, 2006, 4:15:49 PM9/12/06
to django-d...@googlegroups.com
On Sep 12, 2006, at 2:35 PM, Adrian Holovaty wrote:
> I've put together the beginnings of the manipulator framework
> replacement, using ideas from the various proposals we've had, plus
> inspiration from some prior art (the previous Django form/manipulator
> system, plus some ideas from FormEncode).

Yay!

Generally, this rocks. My questions follow...

> As an alternative, my proposal has two main classes: Form and
> BoundForm. A Form is similar to an old Manipulator -- it's a
> collection of validation fields and encapsulates the concepts of form
> display, validation and redisplay. A BoundForm is simply a Form plus
> data. Thus, a BoundForm knows how many validation errors it has, knows
> how to display itself with the given data populated in the form, and
> can return a single field's errors and display. This is similar to the
> old FormWrapper class, but simpler and cleaner.

Out of curiosity, did you consider having a single ``Form`` object
that may be bound or unbound? It seems like most of ``BoundForm``
simply delegates down to the enclosed ``Form``, so perhaps ``Form.bind
()`` should store the data internally and just behave differently
once bound?

This would likely result in code within ``Form`` like::

if self.is_bound():
# whatever

I don't think I have a strong feeling either way... it seems it might
be slightly easier to only have to deal with a single object instead
of ``Form``/``BoundForm``, but perhaps you've already thought about
that.

> Hence, with this new API, the above view code would be written like
> this:
>
> form = ContactForm()
> if request.method == 'POST' and form.is_valid(**request.POST):
> send_email_and_redirect()
> return render_to_response('email_form.html', {'form':
> form.bind(**request.POST)})

Beautiful.

> The template would look like this:

I'd very much like to see ``{{ form }}`` simply spit out some (nice,
semantically-correct) default HTML so that the common case of using
forms is as easy as possible. Given good enough HTML, we should be
able to make form display a bit simpler.

This probably means giving form fields (optional) "human-readable"
names, but that's a good addition as far as I'm concerned.

> 1. form.sendername.errors.as_url rather than
> form.sendername.html_error_list. This gives us some flexibility to add
> other error shortcut-display types, like JavaScript or something.
>
> 2. "form.sendername.as_text" rather than "form.sendername". I'm not
> 100% sold on this idea, but I've been toying with the idea of
> controlling the decision of the HTML widget *in the template* like
> this. The implication of this is that the field definitions (when you
> define your Form class) are purely data-oriented, rather than
> presentation-oriented. Maybe each form field type can have a default
> HTML display, to save on typing "as_text". Also, there would need to
> be a way of specifying custom HTML attributes for those widgets -- the
> as_text() and as_textarea() methods take an "attrs" parameter, but
> we'd also have to be able to specify the attributes in the template
> somehow. Maybe we could provide a template tag that does this.

For simplicity, I'd *really* like these to be spelled ``
{{ form.sendername.errors }}`` and ``{{ form.sendername }}``.
Rendering a form/errors as some default HTML is the common case;
doing something more complex is less common.

I like the idea of having ``{{ form.sendername.as_textarea }}`` as an
option (along with ``{{ form.sendername.errors.as_javascript }}`` [or
whatever]), but I really think we should keep the common case as
simple as possible.

> class ContactForm(Form):
> sendername = TextField()
> senderemail = EmailField()
> subject = TextField(maxlength=20, blank=True)
> message = TextField()

Love it. I assume these fields live within ``django.forms``, yes?

> Another thing to note: In the current Manipulator framework, sometimes
> it is convenient to be able to construct self.fields dynamically. We
> should provide a way to do that with the new syntax.

Indeed; assuming ``self.fields`` is writable, it seems that
``__init__`` is the natural place to do this, still. So::

class ContactForm(Form):
sendername = TextField()
senderemail = EmailField()
subject = TextField(maxlength=20, blank=True)
message = TextField()

def __init__(self, use_second_message):
if use_second_message:
self.fields.append(TextField(name="second_message"))

?

> Finally, how does this stuff tie into models? Well, it's completely
> decoupled from models, but we should have some helper functions that
> create a Form from a model. (This is equivalent to the automatic
> AddManipulator and ChangeManipulator classes we already create for
> each model.) But the added flexibility of this new system will let us
> do things like specify a special-case Form that an admin page uses,
> and make it easy to create a Form that is based *almost* entirely on a
> model but, say, removes a few fields.

I'd say a ``Form`` class method is the right way to go::

form = Form.edit_form(Person.objects.get(pk=3))
form = Form.create_form(Person)

?

Again, rock on -- this is very cool!

Jacob


Ivan Sagalaev

unread,
Sep 12, 2006, 4:35:25 PM9/12/06
to django-d...@googlegroups.com
Jacob Kaplan-Moss wrote:
> Out of curiosity, did you consider having a single ``Form`` object
> that may be bound or unbound? It seems like most of ``BoundForm``
> simply delegates down to the enclosed ``Form``, so perhaps ``Form.bind
> ()`` should store the data internally and just behave differently
> once bound?

+1, because I BoundForm's behavior is not so much different than Form's:
the latter just has empty data and empty error dict but the main purpose
is always the same: display a form.

> This would likely result in code within ``Form`` like::
>
> if self.is_bound():
> # whatever

Looks like it can be useful for raising Exception when doing
form.is_valid for an unbound form.

Ivan Sagalaev

unread,
Sep 12, 2006, 4:39:14 PM9/12/06
to django-d...@googlegroups.com
Adrian Holovaty wrote:
> The template would look like this:
>
> <form action="/foo/" method="post">
> {% if form.sendername.errors %}{{ form.sendername.errors.as_ul }}{% endif %}
> <p>Your name: {{ form.sendername.as_text }}</p>

Does this 'as_text' mean that template will be the only place where an
actual form control is defined? If yes how one can define a <select>
with some custom option list or even some custom form control?

I think that the current way of defining form fields in a manipulator in
Python works rather good...

Ian Holsman

unread,
Sep 12, 2006, 6:01:24 PM9/12/06
to django-d...@googlegroups.com
it looks good Adrian.
the only thing I could complain about it is not having a way to
replace the HTML input fields with something in the form itself.

eg.. I might want to use dojo's textedit stuff (which has custom html
it needs to generate) instead of a regular textfield.

also.. it would be nice to have a form.init() so that you have a spot
to set up javascript in.

regards
Ian.

--
Ian Holsman
I...@Holsman.net
http://peopleintopoker.com/ -- where the poker people go


Joseph Kocherhans

unread,
Sep 12, 2006, 6:35:35 PM9/12/06
to django-d...@googlegroups.com
I've been working on the same thing, but my ideas differ slightly from
Adrian's. In a lot of way we're on the same page though, so that much
is encouraging :) I've liberally stolen ideas from both FormEncode and
a version of Zope 3's formlib.

There are four main classes the mostly correspond to Adrian's
proposal. I've spelled out some of the details below.

=Field (unboud field)=

Field classes are just validator/converters similar to FormEncode.
They have a from_python(python_data) and a to_python(raw_data) method,
and their state consists only of configuration (i.e. they are not
bound). Validation and conversion happens in one step any time
from_python or to_python are called. If validation fails, a
ValidationError is raised. Extra validation will probably be setup by
a validator_list argument in the Field constructor. validator_list is
just a list of validator functions like we already have. I think we
could create a validator that delegates to the appropriate
django.db.fields class to avoid duplication of logic with validation
aware models.


=Fields (unbound form)=

Rather than a Form class, I've come up with a Fields class (or maybe
FieldCollection, but that sounds javaish) that is similar to a
FormEncode Schema class. I think Fields or something similar is a lot
more clear than Form, since this is really just a collection of fields
that do validation and conversion. Form seems like something that gets
displayed, but there is *no* display logic here at all. It has
from_python and to_python methods as well so it can be nested like
FormEncode Schema classes can. from_python and to_python just take and
return a dict of name value pairs.


=Widget (bound field)=

Widgets actually hold the data that was submitted from a form, and
probably the errors (if any) as well. It has an associated unbound
Field instance. It can display itself as html (via render() or
__str__()) It also knows what POST var name it corresponds to, so it
can get the raw value from the post data, and pass it to it's field's
to_python method. (I'll talk about the importance of this later.) It
also knows what it's verbose name is.


=Form (bound form)=

This is the part I'm least clear about. It should be able to render
itself, or present attributes for use in a template to do totally
custom html. At any rate, we'd need some sort of widget setup
function/method that takes a model, a collection of fields, and the
POST data, and returns an ordered dict (or something similar) of
widgets. This class/method would be the only point where this whole
library would necessarily be coupled to Django, and it should be
possible to write a similar setup function for SQLAlchemy Mappers,
etc.

You should also be able to set a prefix on the form. It would be
prepended to every html variable name. That way we could support
multiple instances of the same type of form on one page. (Extending
this idea might also get us to generic edit_inline behavior.) This is
where widgets knowing their html var name comes in handy.


=Creating Forms=

Well it should be possible to do it declaratively or imperatively. I'm
thinking that field collections or widget collections should act kind
of like query sets in that we could do something like
fields.exclude('name') and get a field collection back that has the
'name' field excluded. This behavior could also be extended to work
with Forms declaratively.


=Errors/Exceptions=

I would also like to see some of the validation ideas that have been
floating around implemented. All this is kind of pie-in-the-sky, but I
want it anyhow :)

I see 3 types of validation exceptions: ValidationWarning,
ValidationError, and FatalValidationError. Keep in mind that I'm
talking about the default behavior when I talk about aggregating
catching and re-raising errors.

ValidationWarning would be used to trigger additional fields/messages
to appear in a BoundForm (or equivalent) and would not be raised if
certain criteria are met (most likely a specifically named boolean
value was present in the data we're converting/validating) Haven't
thought about how to actually configure the messages, etc. however.

ValidationError is pretty much what we have now. Validation continues
even if this is raised. They are aggregated into some sort or dict
like wrapper, and re-raised in each Field collection's to_python
method. The (bound) form can then catch and store it as an attribute.

FatalValidationError - this one should short-circuit all other
validation by default.


=What else?=

There should be a way to specify a custom widget class for a particular field.

There's also the question of validation that is not tied to a
particular field. This could be useful, but does it need to be
included?

Anyhow, I hate to make the discussion more convoluted, but I'd really
like to see most of this implemented. Sorry if some of it is a little
vague or poorly worded... the ideas are still a work in progress, but
seeing Adrian's proposal helped support some of my design decisions.

Joseph

Adrian Holovaty

unread,
Sep 12, 2006, 6:41:22 PM9/12/06
to django-d...@googlegroups.com
On 9/12/06, Jacob Kaplan-Moss <ja...@jacobian.org> wrote:
> Out of curiosity, did you consider having a single ``Form`` object
> that may be bound or unbound? It seems like most of ``BoundForm``
> simply delegates down to the enclosed ``Form``, so perhaps ``Form.bind
> ()`` should store the data internally and just behave differently
> once bound?
>
> This would likely result in code within ``Form`` like::
>
> if self.is_bound():
> # whatever
>
> I don't think I have a strong feeling either way... it seems it might
> be slightly easier to only have to deal with a single object instead
> of ``Form``/``BoundForm``, but perhaps you've already thought about
> that.

Hmmm, this is a good point. On the other hand, it feels cleaner (to
me) to separate this stuff into two classes. Are there any strong
arguments for either approach other than personal taste?

> I'd very much like to see ``{{ form }}`` simply spit out some (nice,
> semantically-correct) default HTML so that the common case of using
> forms is as easy as possible. Given good enough HTML, we should be
> able to make form display a bit simpler.
>
> This probably means giving form fields (optional) "human-readable"
> names, but that's a good addition as far as I'm concerned.

Yes, definitely!

> For simplicity, I'd *really* like these to be spelled ``
> {{ form.sendername.errors }}`` and ``{{ form.sendername }}``.
> Rendering a form/errors as some default HTML is the common case;
> doing something more complex is less common.
>
> I like the idea of having ``{{ form.sendername.as_textarea }}`` as an
> option (along with ``{{ form.sendername.errors.as_javascript }}`` [or
> whatever]), but I really think we should keep the common case as
> simple as possible.

Sounds good to me.

> Love it. I assume these fields live within ``django.forms``, yes?

Yes, django.forms would be the home for all of this stuff.

> Indeed; assuming ``self.fields`` is writable, it seems that
> ``__init__`` is the natural place to do this, still. So::
>
> class ContactForm(Form):
> sendername = TextField()
> senderemail = EmailField()
> subject = TextField(maxlength=20, blank=True)
> message = TextField()
>
> def __init__(self, use_second_message):
> if use_second_message:
> self.fields.append(TextField(name="second_message"))

There we go -- that solves the problem nicely.

> I'd say a ``Form`` class method is the right way to go::
>
> form = Form.edit_form(Person.objects.get(pk=3))
> form = Form.create_form(Person)

Sure, something like that would work. We'd need to specify some way of
adding/ignoring fields from the model.

limodou

unread,
Sep 12, 2006, 9:15:01 PM9/12/06
to django-d...@googlegroups.com
I agree with some opions of Joseph Kocherhans:

1) The form name should be more clearly, Joseph named it Fields, maybe
I named it DataSet
2) There should be some validator that is not tied to a particular
field, how to include them and how to show the errors
3) 3 types of validation exceptions(don't repeat them again, that's
good, I don't think them before)

--
I like python!
My Blog: http://www.donews.net/limodou
UliPad Site: http://wiki.woodpecker.org.cn/moin/UliPad
UliPad Maillist: http://groups.google.com/group/ulipad

JP

unread,
Sep 12, 2006, 11:07:47 PM9/12/06
to Django developers
> class Form(object):
> fields = {} # This gets filled with field names and data types.
> def bind(self, **kwargs):
> "Returns a BoundForm instance for the given kwargs."
> return BoundForm(self, kwargs)

What's the rationale for using **kwargs instead of a single data
parameter? I think there are two big advantages to a single parameter
vs. kwargs: first, there are legal form field names that aren't legal
python identifiers, and second, it lets you use anything dict-like as a
form data source -- with the natural next step of a ModelForm that
takes a model instance as its bind parameter.

Otherwise, I think this is excellent, and Joseph's extensions to the
proposal also make a lot of sense. And +1 to the suggestions that
there's no need for a separate BoundForm class.

JP

Adrian Holovaty

unread,
Sep 12, 2006, 11:44:05 PM9/12/06
to django-d...@googlegroups.com
On 9/12/06, JP <jpel...@gmail.com> wrote:
> > class Form(object):
> > fields = {} # This gets filled with field names and data types.
> > def bind(self, **kwargs):
> > "Returns a BoundForm instance for the given kwargs."
> > return BoundForm(self, kwargs)
>
> What's the rationale for using **kwargs instead of a single data
> parameter? I think there are two big advantages to a single parameter
> vs. kwargs: first, there are legal form field names that aren't legal
> python identifiers, and second, it lets you use anything dict-like as a
> form data source -- with the natural next step of a ModelForm that
> takes a model instance as its bind parameter.

Open-source software development is awesome. Thanks for making this
fantastic point!

Adrian Holovaty

unread,
Sep 13, 2006, 2:06:30 AM9/13/06
to django-d...@googlegroups.com
On 9/12/06, Jacob Kaplan-Moss <ja...@jacobian.org> wrote:
> Out of curiosity, did you consider having a single ``Form`` object
> that may be bound or unbound? It seems like most of ``BoundForm``
> simply delegates down to the enclosed ``Form``, so perhaps ``Form.bind
> ()`` should store the data internally and just behave differently
> once bound?

If we have a single Form object that may be bound or unbound, what is
the "more correct" behavior of bind()? Namely, should any data passed
to those functions *overwrite* any existing bound data, or should it
raise an exception?

Solution One:

class Form(object):
def __init__(self):
self._data = None

def bind(self, kwargs):
self._data = kwargs # Overwrite regardless of whether already bound.

Solution Two:

class Form(object):
def __init__(self):
self._data = None

def bind(self, kwargs):
if self._data is not None:
raise Exception('This form is already bound.')
self._data = kwargs

Solution Three:

class Form(object):
def __init__(self):
self._data = {}

def bind(self, kwargs):
self._data.update(kwargs)

I can see arguments for any of these, although I think my preference
is for Solution Two. (Actually I must admit my *true* preference is
for separate Form and BoundForm classes, so we avoid this issue
entirely.)

Similarly, how would is_valid() and errors() work? If is_valid() or
errors() get passed kwargs, does the form instance automatically
become a BoundForm? What if it's already been bound? Do the kwargs
replace any existing bound data?

Russell Keith-Magee

unread,
Sep 13, 2006, 3:24:37 AM9/13/06
to django-d...@googlegroups.com
Hi Adrian

On the whole, this looks like pretty good stuff to me. A few comments
along the way; some of these ideas need not be in the v1
implementation, but I mention them anyway so that we don't roll out a
system that can't accomodate them in the future:

On 9/13/06, Adrian Holovaty <holo...@gmail.com> wrote:
>
> As an alternative, my proposal has two main classes: Form and
> BoundForm.

I'm with Jacob on having a single Form class, with the bind method
used to associate data.
All the examples you have provided only use the Form instance as a
factory for producing the BoundForm instance. Is there any other
reason that you would need an unbound Form? If so, is there any reason
that the functionality on an unbound Form() would be different to
BoundForm? Isn't an unbound form the same as a form that is bound to
nothing? In which case, why not specify the binding dictionary at time
of construction (empty, if you don't want any binding)?

> Note that this is nice and terse but comes slightly at the expense of
> efficiency, as both form.is_valid() and form.bind() perform
> validation.

Wouldn't the following:

form = ContactForm().bind(**request.POST)
# or maybe form = ContactForm(**request.POST)
if request.method == 'POST' and not form.errors()
send_email_and_redirect()
return render_to_response('email_form.html', {'form':form})

be almost equivalent to your terse version, but avoid the inefficiency
of two calls to validate? It would require 1 call to validate in the
'empty form' case, but that could be immediately shortcut as 'if not
_kwargs: return false` (and it's implicitly made by the form.bind()
call on the last line of your example, anyway).

> There are two differences here:
>
> 1. form.sendername.errors.as_url rather than

> 2. "form.sendername.as_text" rather than "form.sendername". I'm not

+1 to providing the ability to select a widget at the template level,
but -1 to requiring .as_text for the default case. Similarly -1 for
errors.as_ul. Two reasons:

1) Minimizing the migration task for existing templates
2) I can't think of a data types that doesn't have a reasonable
default widget (as evidenced by the existing Forms setup). Allowing
easy access to a non-default widget would be great, but the default
widget suffices in all existing cases, and should be easy to specify
in the field definition itself.

On this note, is there an opportunity here to handle the 'this form
field requires foo.js' issue? The admin system currently has a
mechanism for gathering JS requirements; it would be nice if something
like {{ form.includes }} would output all the javascript includes
required by the fields on the form (although there are some
interesting intersections between allowing different field renderings
and knowing which JS includes are required).

> Let's change this to be similar to model classes, like so:

+1

Some other issues that come to mind:

- Handling of related objects; in particular, select widgets for
reverse m2o/m2m relations. I presume that you are looking at using an
`authors = form.SelectField()` type arrangement for handling m2m
relations? Does this approach allow for reverse m2o/m2m fields to be
in the default form for a model? (I'm fairly certain it does, but I
thought I'd check)

t- Handling of 'inline edited' objects. I, for one, would like to see
core/edit_inline removed from the model definition; for me, this is a
form rendering issue. How do you see inline forms being handled in
this setup? I seem to recall there was talk of 'recursive forms' at
one point, rather than the data-flattening approach currently used.

Yours,
Russ Magee %-)

Ivan Sagalaev

unread,
Sep 13, 2006, 4:26:14 AM9/13/06
to django-d...@googlegroups.com
Adrian Holovaty wrote:
> Similarly, how would is_valid() and errors() work? If is_valid() or
> errors() get passed kwargs, does the form instance automatically
> become a BoundForm? What if it's already been bound? Do the kwargs
> replace any existing bound data?

I think with just one Form class those methods shouldn't accept any
kwargs at all. The only way to set data to a form is to call bind with
kwargs, then is_valid() and errors() use the state of the Form.

JP

unread,
Sep 13, 2006, 10:21:37 AM9/13/06
to Django developers
Russell Keith-Magee wrote:

> +1 to providing the ability to select a widget at the template level,
> but -1 to requiring .as_text for the default case.

What if widgets could act as filters? I think using filter syntax for
setting the widget for a field in the template would be pretty
natural::

{{ form.field }}

draws the default widget for the field type, but if you want to use
SomeOtherWidget, you can do::

{{ form.field|SomeOtherWidget }}

The tough part would be handling things on the POST side when
SomeOtherWidget is a collection of html fields that need to be
processed into one value, or something like a tags field that renders
as comma-separated text but should be processed as a list. For that
case I think you'd have to set the widget at form definition time,
otherwise you'd have mo way to know what widget's to_python method to
call to convert the input.

JP

Joseph Kocherhans

unread,
Sep 13, 2006, 3:36:44 PM9/13/06
to django-d...@googlegroups.com
On 9/12/06, Adrian Holovaty <holo...@gmail.com> wrote:
>
> Hence, with this new API, the above view code would be written like this:
>
> form = ContactForm()
> if request.method == 'POST' and form.is_valid(**request.POST):
> send_email_and_redirect()
> return render_to_response('email_form.html', {'form':
> form.bind(**request.POST)})

Hmm... taking some ideas from Russ Magee, what about this:

form = ContactForm()
if request.method == POST and form.bind(request.POST):
send_email_and_redirect()
return render_to_response('email_form.html', {'form':form})

Assumptions: form.bind(data) does *not* return a BoundForm. bind does
the validation and probably populates form.errors or .errors() or
whatever. bind returns True or False depending on whether validation
succeeded or not. bind does not short circuit on the first error.

Validation happens only once, in the bind call. It's not entirely
obvious that a method called bind would return a boolean depending on
the success of validation, but examples and docs should clear that up
that I think. Maybe this is being too clever to save a couple of lines
or a few milliseconds though.

Just for good measure, here's what it would look like to use a change form:

address = Address.objects.get(address_id)
form = AddressChangeForm(address)
if request.method == POST and form.bind(request.POST):
# save the object and return a redirect


return render_to_response('email_form.html', {'form':form})

You pass the original object into the form constructor. The form
values are then initialized with values from the original object. When
you call bind, it overwrites the form values with the data passed to
bind.

Let me know if this doesn't make any sense. I may have forgotten to
write down some essential assumptions.

Joseph

Rob Hudson

unread,
Sep 13, 2006, 3:59:59 PM9/13/06
to django-d...@googlegroups.com
Joseph Kocherhans wrote:
>
> form = ContactForm()
> if request.method == POST and form.bind(request.POST):
> send_email_and_redirect()
> return render_to_response('email_form.html', {'form':form})
>
> Assumptions: form.bind(data) does *not* return a BoundForm. bind does
> the validation and probably populates form.errors or .errors() or
> whatever. bind returns True or False depending on whether validation
> succeeded or not. bind does not short circuit on the first error.
>
> Validation happens only once, in the bind call. It's not entirely
> obvious that a method called bind would return a boolean depending on
> the success of validation, but examples and docs should clear that up
> that I think. Maybe this is being too clever to save a couple of lines
> or a few milliseconds though.

(I'm not an official "dev" so I hope it's not considered inappropriate
of me to provide my feedback.)

Instead of the assumption that bind() validates, why not have an
is_valid() method that assumes binding of the Form. To me this is a
more appropriate assumption since you have to bind the form to validate
it. And it leaves some flexibility open so you can bind forms and not
yet validate -- say if you want to bind and do some other form actions
and then validate.

Example...

form = ShoppingForm()
if request.method == POST and form.is_valid(request.POST):
...

Or...

form = ShoppingForm()
if request.method == POST:
form.bind(request.POST)
if form.billing_same_as_shipping == True:
# Copy billing info to shipping
form.shipping = form.billing
if form.is_valid():
...

The access methods for "form.shipping" probably aren't appropriate, but
just making a use case for extra manipulation of form data before
validation.

-Rob

Gary Wilson

unread,
Sep 13, 2006, 4:01:40 PM9/13/06
to Django developers
JP wrote:
> What if widgets could act as filters? I think using filter syntax for
> setting the widget for a field in the template would be pretty
> natural::
>
> {{ form.field }}
>
> draws the default widget for the field type, but if you want to use
> SomeOtherWidget, you can do::
>
> {{ form.field|SomeOtherWidget }}

Or how about template tags for both the default form html and for form
fields (default html or other widget):

{% form form %}
{% formfield form.field %}
{% formfield form.field widget %}

Ivan Sagalaev

unread,
Sep 13, 2006, 4:01:00 PM9/13/06
to django-d...@googlegroups.com
Joseph Kocherhans wrote:
> Assumptions: form.bind(data) does *not* return a BoundForm. bind does
> the validation and probably populates form.errors or .errors() or
> whatever. bind returns True or False depending on whether validation
> succeeded or not. bind does not short circuit on the first error.
>
> Validation happens only once, in the bind call. It's not entirely
> obvious that a method called bind would return a boolean depending on
> the success of validation, but examples and docs should clear that up
> that I think.

It shouldn't be called 'bind' then. Binding makes sense for the original
Adrian's proposal with two kinds of forms. If we will agree on a single
form populated by data then 'bind' may be called 'process_data' or
something...

Joseph Kocherhans

unread,
Sep 13, 2006, 4:35:01 PM9/13/06
to django-d...@googlegroups.com
On 9/13/06, Ivan Sagalaev <Man...@softwaremaniacs.org> wrote:
>
> Joseph Kocherhans wrote:
> > Assumptions: form.bind(data) does *not* return a BoundForm. bind does
> > the validation and probably populates form.errors or .errors() or
> > whatever. bind returns True or False depending on whether validation
> > succeeded or not. bind does not short circuit on the first error.
> >
> > Validation happens only once, in the bind call. It's not entirely
> > obvious that a method called bind would return a boolean depending on
> > the success of validation, but examples and docs should clear that up
> > that I think.
>
> It shouldn't be called 'bind' then.

Agreed. Your suggestion of 'process_data' makes more sense.... in
fact, I like that, but I'd shorten it to just 'process'. It would do
both binding and validation.

> Binding makes sense for the original
> Adrian's proposal with two kinds of forms.

Bind still makes sense with only one kind of form, it just works
differently. Rob Husdon's recent post is a good example of why. It may
be something as simple as this though:

def bind(data):
self.bound_data = data

Joseph

Joseph Kocherhans

unread,
Sep 13, 2006, 4:49:07 PM9/13/06
to django-d...@googlegroups.com
On 9/13/06, Rob Hudson <trebor...@gmail.com> wrote:
>
> (I'm not an official "dev" so I hope it's not considered inappropriate
> of me to provide my feedback.)

It's not inappropriate at all. :)

> Instead of the assumption that bind() validates, why not have an
> is_valid() method that assumes binding of the Form. To me this is a
> more appropriate assumption since you have to bind the form to validate
> it. And it leaves some flexibility open so you can bind forms and not
> yet validate -- say if you want to bind and do some other form actions
> and then validate.

[snip]

> form = ShoppingForm()
> if request.method == POST:
> form.bind(request.POST)
> if form.billing_same_as_shipping == True:
> # Copy billing info to shipping
> form.shipping = form.billing
> if form.is_valid():
> ...

You make a good point. I was focusing on the simplest case in my
example, but we might as well spell out how more complex cases would
work. I think having separate mehods - 'is_valid', 'bind', and
probably 'process' as well (that does both, see my response to Ivan
Sagalaev) - would be a good idea.

Joseph

Brantley Harris

unread,
Sep 13, 2006, 6:21:26 PM9/13/06
to django-d...@googlegroups.com
On 9/12/06, Adrian Holovaty <holo...@gmail.com> wrote:
>
> Hence, with this new API, the above view code would be written like this:
>
> form = ContactForm()
> if request.method == 'POST' and form.is_valid(**request.POST):
> send_email_and_redirect()
> return render_to_response('email_form.html', {'form':
> form.bind(**request.POST)})
>

"send_email_and_redirect()"
I want that function. Surely, this is a basic example, but it hides some of
the other issues that need to be hammered out. How do you access the
data once it's validated? How do validation aware models interact
with this BoundForm? Does the user have to set up a try, except block
when they save an object or does the form know something about the
model that allows it to validate it with the validation aware model?
Finally, how does one set the default data?

> The template would look like this:
>

> <form action="/foo/" method="post"> ...

Personally I think it should be like this:


<form action="/foo/" method="post">

{% render-form form %}
</form>

That should give you almost exactly what the Admin does. If you want
more control, then you should be able to, but for the most simple
case, this should be possible.

> 1. form.sendername.errors.as_url rather than
> form.sendername.html_error_list. This gives us some flexibility to add
> other error shortcut-display types, like JavaScript or something.

Don't we have template tags for this sort of stuff? I favor a
"to_html" for just about everything in the common case, including
error lists. Otherwise, a filter or template tag should be used, or
the object's class could be subclassed do define other functionality.
(Come to think of it: the template rendering should check for a
"to_html" or ''__html__'' function before it tries str() when it comes
across "{{ object }}")

> Let's change this to be similar to model classes, like so:
>
> class ContactForm(Form):
> sendername = TextField()
> senderemail = EmailField()
> subject = TextField(maxlength=20, blank=True)
> message = TextField()
>

+1

I also think some extra default kwargs should be available:
attributes = <dictionary of attributes to add to (or overwrite) on
the rendererd tag>
label = <the label for the rendering mechanism>

For instance, using the given example:
message = TextField(label='Your message',
attributes={'class':'vRichTextField'})

> One big change, other than the syntax, is that 'is_required' is
> dropped, in favor of 'blank', to match our model syntax. Also, as in
> models, blank=False will be default. Currently in manipulators,
> is_required=False is default.

This is backwards to me. "is_required" is much more descriptive;
"blank" seems to say "this field is blank". I agree that it should
match up with the model, but I posit that the model syntax should
change. New users constantly ask about this in the chat-room; they
don't see a difference between "blank" and "null" in the model syntax.
I prefer "required", which is easier to understand, and is part of
the common web parlance: "required fields are marked in red". I don't
care whether it's default or not, although I would suggest polling
many real-world cases to find the most common option.

>
> Another big change is that each of the Fields in this ContactForm
> would be responsible primarily for *validation* rather than specifying
> the HTML form field. This decouples the concept of validation from the
> HTML form type.
>

I don't understand this, and I think might hold the key to a greater
understanding of your plans, could you explain how validation would
work exactly? Also, I agree with limodou that there should be a
general "validate" function that allows the writer of the Form to
provide some custom validation on the form as a whole. Likewise there
should be a "convert" function that does the same but for conversion
from HTML-POST data to Python data.

> Another thing to note: In the current Manipulator framework, sometimes
> it is convenient to be able to construct self.fields dynamically. We
> should provide a way to do that with the new syntax.

+1

> So that's the proposal -- thoughts/criticisms? I'd like to get this
> out there as soon as possible, because we (or, rather, I) have dawdled
> for quite some time!

Some things occur to me:

The whole BoundForm and Form thing is really crufty. When does the
Form NOT have data associated with it? And when would you really want
to have it validate data but not "bind" that data?

There needs to be a fundemental change in the model syntax as well,
which I'm sure you don't want to hear, but it's true. Half
(hyperbolishly) of the stuff that is in the model syntax is really
there to define the Form. You have this sort of siamease twin, but
I'm not sure if it should be ripped apart or reinforced to support the
whole DRY / in-one-place paradigm.

Defining a Form, should be like defining a Model-Manager, it should be
portable in the way that many applications can utilize it, including
the one the Model was written for AND the Admin. That is why I favor
a "Manipulator" approach which would not only define fields, but also
define what to do with the resulting data.

Idealy, it sems to me, every part of the Admin should be reusable in
our applications. It would be a great step in this direction if forms
could be rendered very easily as in: {% render-form form %}

Thanks

Matthew Flanagan

unread,
Sep 13, 2006, 7:32:33 PM9/13/06
to django-d...@googlegroups.com

+1 on this for me. I'd love to be able to do:

author = SelectField(attributes={'dojoType': 'Select'})

Gary Wilson

unread,
Sep 13, 2006, 10:31:28 PM9/13/06
to Django developers
Brantley Harris wrote:
> > Another thing to note: In the current Manipulator framework, sometimes
> > it is convenient to be able to construct self.fields dynamically. We
> > should provide a way to do that with the new syntax.
>
> +1

+1

Being able to contruct fields dynamically would be great. So if I had
a model that stored contacts, for example, it would be nice be able to
easily create a form that accepted 3 contacts, or a form that accepted
6 contacts, or a form that shows fields for 6 contacts but only
requires 3 to be filled in. This would be similar to the edit_inline
and num_in_admin stuff in the admin app.

Russell Keith-Magee

unread,
Sep 13, 2006, 11:26:07 PM9/13/06
to django-d...@googlegroups.com
On 9/14/06, Matthew Flanagan <mattim...@gmail.com> wrote:
>
> On 14/09/06, Brantley Harris <deadw...@gmail.com> wrote:

> > For instance, using the given example:
> > message = TextField(label='Your message',
> > attributes={'class':'vRichTextField'})
> >
>
> +1 on this for me. I'd love to be able to do:
>
> author = SelectField(attributes={'dojoType': 'Select'})

Agreed on the need, not on the implementation.

My understanding is that idea of the .as_text, etc, modifiers on
FormFields is to allow a FormField to be represented in different ways
on a page; to me, putting attributes in the field definition implies
that every renderer (as_text, as_select, etc), should use that
attribute.

IMHO, a better approach would be to allow rendering extensions to be
registered on the field itself; e.g.,

author = SelectField(extra_renderers={'dojo':dojo_renderer,
'brantley':brantley_renderer})

The goal being to allow:
- {{ form.author }} to render as a the default widget, without dojo or
class modifications
- {{ form.author.as_dojo }} to render as a 'dojo-ified' widget
- {{ form.author.as_brantley }} renders with a modified 'class' attribute

The onus is then on the underlying as_text/as_select etc
implementations to allow easy modification/extension of attributes;
e.g.,

class SelectField:
...
def as_select(self, **kwargs):
_class = kwargs.getdefault('class','vSelectMultiple')
return "<select class='%s'>...</select>' % _class

def brantley_renderer(self):
return self.as_select(class='vBrantleyClass')

Yours,
Russ Magee %-)

Marc D.M.

unread,
Sep 14, 2006, 7:25:55 PM9/14/06
to django-d...@googlegroups.com
On Tue, 2006-09-12 at 14:35 -0500, Adrian Holovaty wrote:
> The template would look like this:
>
> <form action="/foo/" method="post">
> {% if form.sendername.errors
> %}{{ form.sendername.errors.as_ul }}{% endif %}
> <p>Your name: {{ form.sendername.as_text }}</p>
>
> {% if form.senderemail.errors %}{{ form.senderemail.errors_as_ul
> }}{% endif %}
> <p>Your e-mail: {{ form.senderemail.as_text }}</p>
>
> {% if form.subject.errors %}{{ form.subject.errors.as_ul }}{%
> endif %}
> <p>Subject: {{ form.subject.as_text }}</p>
>
> {% if form.message.errors %}{{ form.message.errors.as_ul }}{%
> endif %}
> <p>Message: {{ form.message.as_textarea }}</p>
>
> <input type="submit" value="Send e-mail">
> </form>


Alas, it seems I'm the only one wanting to have the form framework
handle buttons as well.

Now I'm not asking for the Form to automatically have submit buttons,
what I'm asking for is to be able to have a ButtonField or
GroupOfButtonsField .

Example:
I just finished working on a multi-page form that uses three
<input type="submit" name="action"/> with values value="&lt;
prev" value="next &gt;" and value="cancel"

The value of request.POST["action"] always has the correct
value. [1] That is, the value for the button that was clicked.
So I'm able to use it in processing.

This value though, is the only one not handled by the
Manipulator.

Now I see in this new Form thing, you're ignoring buttons completely.

Am I wrong to assume that the value of the button clicked can be
important? Or is it not important enough?

/Marc DM


[1] I tested it in Firefox 1.5 and Epiphany on Gnome 2.14 and IE6 and
IE4.01 on Win2k. In ie4, I had to remove the styling from the buttons so
I could click them but they submit the correct value. Contrary to what I
heard in #django

Brantley Harris

unread,
Sep 14, 2006, 10:14:24 PM9/14/06
to django-d...@googlegroups.com
On 9/13/06, Russell Keith-Magee <freakb...@gmail.com> wrote:
>
> On 9/14/06, Matthew Flanagan <mattim...@gmail.com> wrote:
> >
> > On 14/09/06, Brantley Harris <deadw...@gmail.com> wrote:
>
> > > For instance, using the given example:
> > > message = TextField(label='Your message',
> > > attributes={'class':'vRichTextField'})
> > >
> >
> > +1 on this for me. I'd love to be able to do:
> >
> > author = SelectField(attributes={'dojoType': 'Select'})
>
> Agreed on the need, not on the implementation.
>
> [...]

>
> IMHO, a better approach would be to allow rendering extensions to be
> registered on the field itself; e.g.,
>
> author = SelectField(extra_renderers={'dojo':dojo_renderer,
> 'brantley':brantley_renderer})
>

I think this is needlessly complicated and impractical. Remember that
the renderer is the ultimate decider on what to do with the field's
properties, so if the renderer doesn't trifle with 'attributes' or
'label', it doesn't have to. Also, I support a default renderer (for
simplicity), but I don't support multiple renderers for a field class;
I think that is the realm of templatetags-- It is responsible for the
rendering of data after all, not the structure or logic of the data.

Scott Paul Robertson

unread,
Sep 15, 2006, 8:39:36 PM9/15/06
to django-d...@googlegroups.com
On Tue, Sep 12, 2006 at 02:35:54PM -0500, Adrian Holovaty wrote:
> Let's change this to be similar to model classes, like so:
>
> class ContactForm(Form):
> sendername = TextField()
> senderemail = EmailField()
> subject = TextField(maxlength=20, blank=True)
> message = TextField()
>

I've got two thoughts that I'd like to see.

Inter-field dependency. For example:

class AdminTicketForm(Form):
...
state = SelectField()
solution = TextField(blank=True)
related_to = SelectField(blank=True)
field = TextField(blank=True)
depends_on_field = TextField(blank=True)

Now in this case I actually want to say that is state equals 'resolved'
then solution needs to be filled and if state equals 'duplicate' then
related_to needs to be filled. That's an actual case I have in my app.
The field 'field' is just there as an example of the general case: if
field is not blank then neither should depends_on_field. Putting this in
the class seems natural, so maybe something like:

state = SelectField()
solution = TextField(blankunless='state == resolved')
related_to = SelectField(blankunless='state == duplicate')
field = TextField(blank=True)
depends_on_field = TextField(blankunless='field')

Is this something that is worthwhile? It would be really useful for me,
and I bet other people would like it too. The syntax isn't quite right I
think, but that can always be improved.

The other thing has to do with hidden fields in Forms. Typically if
people want to have a field passed through, but not editable, you set it
as hidden. Of course hidden values can be edited by the enterprising
user. Basically maintaining the 'follow' attribute of
ChangeManipulators, and making sure that I can put values back in easily
to the BoundForm (or whatever it's called). Not too worried about this
going away, but just want to make sure.

Otherwise I really like the proposal so far, and I'll chime in and give
a +1 to having just Form and not Form and BoundForm.

Thanks

--
Scott Paul Robertson
http://spr.mahonri5.net
GnuPG FingerPrint: 09ab 64b5 edc0 903e 93ce edb9 3bcc f8fb dc5d 7601

James Bennett

unread,
Sep 15, 2006, 10:25:37 PM9/15/06
to django-d...@googlegroups.com
On 9/15/06, Scott Paul Robertson <s...@mahonri5.net> wrote:
> Inter-field dependency. For example:

Most of this looks like it can be much more flexibly handled by the
already-available validator syntax (which, I assume, will be finishing
its migration into the model system).


--
"May the forces of evil become confused on the way to your house."
-- George Carlin

Scott Paul Robertson

unread,
Sep 15, 2006, 10:58:53 PM9/15/06
to django-d...@googlegroups.com
On Fri, Sep 15, 2006 at 09:25:37PM -0500, James Bennett wrote:
>
> On 9/15/06, Scott Paul Robertson <s...@mahonri5.net> wrote:
> > Inter-field dependency. For example:
>
> Most of this looks like it can be much more flexibly handled by the
> already-available validator syntax (which, I assume, will be finishing
> its migration into the model system).
>

Ah, I wasn't aware that the validator system would be moving into
models. Must have missed that. That's even better :)

Rob Hudson

unread,
Sep 18, 2006, 2:44:43 PM9/18/06
to Django developers
I'd like to see some flexibility to output HTML4 as opposed to XHTML
(or whatever flavor comes along next).

Currently in 0.95 it looks like all the render() calls on the various
"generic widgets" output XHTML form elements. I've been pretty much
convinced not to use XHTML by Ian Hickson (at least not yet):
http://www.hixie.ch/advocacy/xhtml

Of course, all this is based on my assumption that something like this:

<input type="text" />

is invalid as HTML4, but I haven't verified that HTML4 can't have self
closing tags.

-Rob

James Bennett

unread,
Sep 18, 2006, 3:05:13 PM9/18/06
to django-d...@googlegroups.com
On 9/18/06, Rob Hudson <trebor...@gmail.com> wrote:
> Of course, all this is based on my assumption that something like this:
>
> <input type="text" />
>
> is invalid as HTML4, but I haven't verified that HTML4 can't have self
> closing tags.

<pedantry>

It's valid, but it doesn't mean the same thing it does in XHTML; in
HTML4, that's a form of SHORTTAG minimization, and translates,
roughly, to an input element of type "text", followed by a literal
greater-than sign (SHORTTAG NET, the particular flavor of SHORTTAG in
this example, dictates that a single forward slash constitutes the
closing of the element, so anything which follows the slash is assumed
to be content).

For a full rundown of the various SGML minimization features,
including this one, see this excellent reference:

http://www.is-thought.co.uk/book/sgml-9.htm

</pedantry>

James Bennett

unread,
Sep 18, 2006, 3:06:30 PM9/18/06
to django-d...@googlegroups.com
On 9/18/06, James Bennett <ubern...@gmail.com> wrote:
> greater-than sign (SHORTTAG NET, the particular flavor of SHORTTAG in
> this example, dictates that a single forward slash constitutes the
> closing of the element

<uber-pedantry>

I mistyped that and sent too soon; the slash actually closes the tag,
not the element. A second slash further along will close the element.

</uber-pedantry>

Reply all
Reply to author
Forward
0 new messages