I'd like to propose a few extensions to Django's form library for 1.3.
I'm still working on some fine details, but before I get too far, I'd
like to field opinions so that I can:
* Discover any edge cases I've missed in my analysis
* Field any criticisms from people with more design/frontend
experience than myself
* Determine any related problems that we have the opportunity to
solve at the same time
* Find out if there is anyone in the community who is interested in
helping out.
Apologies in advance for the length, but there's a lot of detail to cover.
With this proposal, I'd like to address three problems:
1. The layout problem. Django's forms can be rendered "as_ul",
"as_table" or "as_p", and that's it. These layout schemes can be
overridden and customized if you know what you're doing, but it's not
easy to do so. Furthermore, visual layout concerns aren't separated
from data processing concerns. You need to write (and install) a form
subclass to implement your own form layout. Although it's good
app-writing practice to ensure that forms can be easily substituted,
it's not an enforced or universal practice.
2. The widget problem. This is a variant on the previous point. A
designer that wants to use a specialized calendar widget for a date
field needs to modify form code. This is a complexity that shouldn't
exist; a designer should be able to specify the widget library that
needs to be used (with all it's required rendering requirements,
javascript triggers etc) without modifying views and form processing
code.
3. The DOCTYPE problem. Most importantly, there is the closing slash
problem, but the introduction of HTML5 also means that there are
richer input types like <input type="email"> that aren't available in
HTML4 or XHTML1. Django currently outputs XHTML1 unconditionally, and
has no support for the new HTML5 input types.
To solve these three problems, I'd like to propose that we add (and
promote) the use of a new approach to form rendering, based around the
use of a new {% form %} template tag. This proposal has some
similarities to a proposal made by in the 1.2 feature phase [1] -- but
that proposal was only aiming to solve the doctype issue.
[1] http://groups.google.com/group/django-developers/browse_thread/thread/bbf75f0eeaf9fa64
So: What I'm proposing is that we introduce a new template tag: {% form %}.
How does this solve the three problems?
Layout
------
The simplest approach for rendering a form would become:
{% form myform %}
This would effectively implement the as_table rendering strategy, just
as {{ myform }} does right now.
If we want a different rendering, we exploit the fact that {% load
%}ing a template library will override any template tags that are
redefined. {% form %} would be defined as part of the default template
tag library, implementing the 'as_table' strategy. However, if we load
a library that also defines the {% form %} tag, that definition will
override the base definition. If we want to use a custom rendering
style, we can get that by simply loading a different renderer that
implements that style:
{% load custom_renderer %}
{% form myform %}
Django would ship with {% form %} implementations of the 'as_p' and
'as_ul' strategies, so getting 'as_p' rendering would mean:
{% load xhtml_p_forms %}
{% form myform %}
{% form %} is just a template tag, but the default implementation
would be designed in such a way that it could be easily subclassed to
alter the rendering strategy for the form. I'm still tinkering with
details here, but broadly, the intention is to expose a similar
interface to that used by Form.as_*() -- that is, returning format
strings that specify like '<td>%(errors)s%(field)s%(help_text)s</td>'
to define the rendering strategy; this would be implemented as a
function so that forms could decide on a per field basis what output
format is appropriate. However, unlike the existing as_*() approach,
you don't need to have access to the form in order to use the
different renderer, which means you can define and apply your
rendering strategy independent of the view and form code.
Since the form renderer exposes the logic for rendering individual
form rows, we can also expose the ability to render individual form
fields, plus the non-field errors and hidden fields:
{% form myform errors %} -- All the non-field form errors, plus
hidden field errors
{% form myform field birthdate %} - output a full row for the
birthdate field (wrappers, label, errors, help etc)
{% form myform hidden %} -- output all the hidden fields
This just exposes the internal mechanics that makes the full-form
rendering of {% form myform %} possible.
Widgets
-------
The second problem is support for widgets and other rendering
customization. This can be addressed using extra arguments to the {%
form %} tag when rendering individual fields:
{% form myform field birthdate using calendar %}
This instructs the rendering of the birthday DateField to use the
'calendar' chrome.
What is chrome? Chrome is an attempt to overcome the practical
limitations of Django's Widgets.
When we introduced newforms, the intention was that widgets would be
the point at which form rendering would be customized. If a developer
wanted to use a rich Javascript rendering for the calendar, they would
define a custom Date widget, override the render() method to introduce
the appropriate Javascript and CSS hooks, and then define a form with
fields that specify the use of that widget. The way admin uses widgets
is probably the best example of how this was intended to work.
However, in practice, widgets aren't used like this. Widgets aren't
trivial to define, and they aren't easy to deploy, either. The
convoluted mechanics that ModelAdmin goes through in order to install
a custom calendar widget is probably the best demonstration of why
this idea hasn't taken off -- it's just too complex.
It's also not simple to "just use a different widget", either. The
interplay between form and widget is sufficiently complex that the
only time at which you can define the widget that is to be used is
when you define the field itself. This means that widget choice is
closely bound to form design, which makes it nigh impossible define a
scheme for deploying widgets as part of the template rendering layer.
So -- the idea behind chrome is to separate the raw HTML input
mechanism from the bits that make the input look good on the rendered
page.
Widgets remain as they are, but we de-emphasize their use as a
rendering customization tool. They become little more than the
decision over which HTML <input> (or <select>) will be used. It's
still important that widgets are distinct from fields. After all,
there are times when there are multiple input options for particular
field data -- consider the case of a location field, that will require
a custom lat/long split widget.
It's the job of the chrome to make the <input> provided by the Widget
look pretty. This might mean adding extra classes to the rendering of
the <input>; it might mean putting placeholder <div>s or other
elements around the input; or it might mean registering that a
javascript block is required to support that input.
Chrome can also be layered. Different pieces of chrome could perform
different roles, and could be applied one after he other. For example
in the following:
{% form myform field birthdate using calendar important %}
The "calendar" chrome might add the javascript and classes to enable
the rich widget, where the "important" chrome might just add CSS
classes that enables a particular field to be rendered in a particular
style.
Chrome can also be parameterized; for example:
{% form myform field name using autocomplete:"name_autocomplete" %}
might define a chrome that implements an Ajax autocomplete widget
using the named URL "name_autocomplete" as a data source. This has to
potential to start giving an answer to the "Why doesn't Django do
AJAX" monkey; Django won't provide an AJAX solution out of the box,
but packaging a chrome that implements AJAX should be a lot easier.
Chrome could also be programatically assigned as part of the form.
Using a formfield_callback() style setup, it should be possible to
automatically assign certain chromes to certain field types (e.g.,
always apply the calendar chrome to DateFields).
I also hope to be able to provide a solution for the problem of
putting all the javascript for a form at the bottom of the page. At
present, widget rendering allows you to insert a <script> tag
immediately before or after a form element, but best practice says it
should be contained in a single block at the end of your page.
By rendering a widget using a template tag, we get the ability to set
variables in the context. When a form field is rendered using a
particular piece of chrome, that chrome can register that it requires
a specific javascript trigger; a {% form triggers %} call can then be
used to retrieve all the triggers that have been registered, and embed
them in a <script> call at the end of the page.
Again, I'm still working on details here, but the intention is to make
chrome easy to define -- hopefully just a class with some fairly
simple entry points.
Doctypes
--------
The third problem is doctype support. To solve this, I propose two changes.
1) We introduce a 'doctype' argument to the render() method on
widgets. Widgets would then be expected to generate HTML output that
is compatible with the supplied doctype. For backwards compatibility,
internal calls to widget.render() would need to introspect for the
existence of the 'doctype' kwarg on the specific widget, and raise
warnings if the kwargs isn't available.
2) We introduce a number of new default widgets to support the new
HTML5 input types. At present, EmailField uses a TextInput by default.
Under the new scheme, we would introduce an EmailInput, and EmailField
would use EmailInput as the default widget. When rendered using the
HTML4/XHTML1 doctype, this would output <input type="text" ...; under
HTML5, this would output <input type="email".... Again, the doctype
argument to render() would be used to determine which input type is
used.
Once these two changes are in place, we use the form template tag
specify the doctype that is passed to the widget render call. A
'html5_p_forms' library will pass 'html5' as the doctype when
rendering fields, and get HTML5-compliant form fields; the
'xhml1_p_forms' library will pass 'xhtml1', and get XHMTL1-compliant
form fields.
Summary
-------
In summary, I'm proposing:
- A new {% form %} template tag with a fairly simple interface for overriding
- A couple of default implementations of the {% form %} template tag
- Adding a chrome layer
- Propagating the 'doctype' kwarg through the existing
form/field/widget infrastructure
It's important to note that all of this is backwards compatible. The
old {{ form }} and {{ form.as_p }} approaches to rendering will
continue to work, as will the longhand {{ field.errors}} {{
field.label }} {{ field }} approach. What I'm proposing is a layer on
top of the primitives Django already provides. Hopefully, this new
layer will be sufficient to make the common use cases for form
customization slightly more automated, while providing hooks that
allow designers to customize the way forms are rendered without
needing to delve into view/form internals.
I appreciate that some of this is hard to judge without specifics --
in particular, the specifics of how end users will customize the Form
template tag rendering and specify chrome. I really wanted to propose
this with a sample implementation, but:
1) I accidentally let the cat out of the bag on Twitter,
2) I wasn't sure I was going to have a implementation ready for
comment before we draw the curtain on the 1.3 feature list,
3) I wanted to get some feedback on the broad design goals before I
went too far down a rabbit hole
4) I wanted to work out if there was any interest in the community in
helping out to achieve these goals
Comments? Criticisms? Queries?
Yours,
Russ Magee %-)
Le 11 juil. 2010 à 17:36, Russell Keith-Magee a écrit :
> I'd like to propose a few extensions to Django's form library for 1.3.
First of all, thanks or your proposal, the current form rendering is the worst part of Django to me and I'd like to help to improve that in 1.3.
> Layout
> ------
> style, we can get that by simply loading a different renderer that
> implements that style:
>
> {% load custom_renderer %}
> {% form myform %}
>
> Django would ship with {% form %} implementations of the 'as_p' and
> 'as_ul' strategies, so getting 'as_p' rendering would mean:
>
> {% load xhtml_p_forms %}
> {% form myform %}
Just a personal feedback, to me the rendering strategy is related to a whole project and should be defined in settings, it's too easy to forget a loading in a template. I know that you can use the django.template.add_to_builtins function but it in this case it should be documented.
>
> Widgets
> -------
>
> Chrome can also be parameterized; for example:
>
> {% form myform field name using autocomplete:"name_autocomplete" %}
>
> might define a chrome that implements an Ajax autocomplete widget
> using the named URL "name_autocomplete" as a data source. This has to
> potential to start giving an answer to the "Why doesn't Django do
> AJAX" monkey; Django won't provide an AJAX solution out of the box,
> but packaging a chrome that implements AJAX should be a lot easier.
If it requires an extra {% form %} arg it will not be that easier if you need to override all third-party apps' templates. Note that I haven't got any solution, that's more to bring the discussion on that topic :-).
>
> Doctypes
> --------
>
> Once these two changes are in place, we use the form template tag
> specify the doctype that is passed to the widget render call. A
> 'html5_p_forms' library will pass 'html5' as the doctype when
> rendering fields, and get HTML5-compliant form fields; the
> 'xhml1_p_forms' library will pass 'xhtml1', and get XHMTL1-compliant
> form fields.
Again, why not directly in settings in order to be project's specific? Is there anybody mixing doctypes on the same website? (backward compatibility maybe?)
Regards,
David
Sure. The admin is XHTML and plenty of the frontends I work with are HTML[45].
Alex
> Regards,
> David
>
> --
> You received this message because you are subscribed to the Google Groups "Django developers" group.
> To post to this group, send email to django-d...@googlegroups.com.
> To unsubscribe from this group, send email to django-develop...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/django-developers?hl=en.
>
>
--
"I disapprove of what you say, but I will defend to the death your
right to say it." -- Voltaire
"The people's good is the highest law." -- Cicero
"Code can always be simpler than you think, but never as simple as you
want" -- Me
i'd say the other way around; it's not hard to imagine a single page
with two or three forms rendered with different styles: a 'main' one
rendered as_table(), and a footer field rendered as_p(). in that
case, {%load%}'ing the style is too coarse granularity. i'd prefer an
optional parameter to {%form%} to choose the renderer.
something like:
class myformrenderer (forms.as_table):
name = 'left_table'
......
and in the template you could say {% form myform:"left_table" %}
>> Doctypes
>> --------
>>
>> Once these two changes are in place, we use the form template tag
>> specify the doctype that is passed to the widget render call. A
>> 'html5_p_forms' library will pass 'html5' as the doctype when
>> rendering fields, and get HTML5-compliant form fields; the
>> 'xhml1_p_forms' library will pass 'xhtml1', and get XHMTL1-compliant
>> form fields.
> Again, why not directly in settings in order to be project's specific? Is there anybody mixing doctypes on the same website? (backward compatibility maybe?)
here i fully agree, doctype should be project-specific.
--
Javier
On 07/11/2010 10:36 AM, Russell Keith-Magee wrote:
> {% form myform %}
For what my vote may be worth, I'm -0 on this...
> {% form myform field birthdate using calendar %}
and -1 on this
> {% form myform field birthdate using calendar important %}
> {% form myform field name using autocomplete:"name_autocomplete" %}
and REALLY -1.0e1 on this syntax-soup. Does this result in
something that looks like the following monstrosity?
{% form myform field name using
autocomplete:"name_autocomplete" field birthdate using calendar
field other_field using some_other_chrome_or_widget%}
Putting in a subtle plug for one of my wish-list items, I've
always been curious why form-rendering wasn't implemented as
a filter:
{{ myform|as_ul }}
> These layout schemes can be overridden and customized if you
> know what you're doing, but it's not easy to do so.
I've occasionally wished for an as_dl (which would render
labels as <dt> and controls as <dd> elements), and having a
base/ancestor as_* filter object to base it off of would
make this a lot easier. This would take care of your
issue#1 (more easily extensible renderings).
Under such a scheme, I'd imagine your above examples might
look something like
{{ myform|as_ul }}
{{ myform|use_widget:"birthdate=calendar"|as_ul }}
where the use_widget (deep?)copies the input form object,
adjusting its field-widgets per the requested use_widget
modifiers. It could even be neatly stacked something like
(line-broken for clarity)
{{ myform
|use_widget:"birthdate=calendar"
|use_widget:"name=name_autocomplete"
|use_widget:"other_field=some_other_chrome_or_widget"
|as_ul
}}
For backwards compatibility, the implementations of
Form.as_*() should be able to just look something like
def as_table(self, ...):
return filters.as_table(self, ...)
I'd suspect this could take care of your issue#2.
However that leaves me with no good answer to
1) your "parameters to chrome" wish (though that might be
possibly incorporated in the use_widget parameters). I'm
not entirely clear on your chrome ideas, but I like some of
what I read in your novella. :)
2) your "put all JS at the bottom of the page" wish
3) your issue#3 about DOCTYPES, but unless filters were given
access to a rendering context in which some global
DOCTYPE-preference was specified, I haven't liked any of
the solutions I've seen bandied about on this matter
(including a settings.py directive, since I can easily
imagine a single site producing a mix of html4, xhtml
and/or html5).
However I think all 3 of my outliers are somewhat
orthogonal to the rejiggering of form rendering.
> - Propagating the 'doctype' kwarg through the existing
> form/field/widget infrastructure
I'm +1 on this (or something like it such as request-context
or some other bag-of-attributes that could be useful in
rendering a widget) so that widgets can tweak their
display behavior.
Just my late-afternoon rambling thoughts...
-tkc
This may be the case on many websites. However, in my experience, it
isn't the case for all websites. I wouldn't consider it unusual to
have different rendering strategies per-page, or even multiple
strategies on a single page.
>> Widgets
>> -------
>>
>> Chrome can also be parameterized; for example:
>>
>> {% form myform field name using autocomplete:"name_autocomplete" %}
>>
>> might define a chrome that implements an Ajax autocomplete widget
>> using the named URL "name_autocomplete" as a data source. This has to
>> potential to start giving an answer to the "Why doesn't Django do
>> AJAX" monkey; Django won't provide an AJAX solution out of the box,
>> but packaging a chrome that implements AJAX should be a lot easier.
>
> If it requires an extra {% form %} arg it will not be that easier if you need to override all third-party apps' templates. Note that I haven't got any solution, that's more to bring the discussion on that topic :-).
I acknowledge that this is a weakness; however, I don't see it as a
major concern. I'm of a similar mind to James Bennett on this: I don't
generally consider templates to be reusable app elements, except when
you're dealing with entire applications like admin.
James doesn't ship templates with his reusable apps specifically
because template aren't ever really reusable. If you're building a
site, you can't just drop large chunks of someone else's markup into
your site. The only time I've found templates truly reusable is when
you are given the template for an entire page -- at which point, the
issue of customizing the rendering scheme for an indiviual form is a
non-issue, since you just use whatever the full page gives you.
> Again, why not directly in settings in order to be project's specific? Is there anybody mixing doctypes on the same website? (backward compatibility maybe?)
Although a single doctype across a site will be common, there's
certainly no guarantee that this will be the case.
Yours,
Russ Magee %-)
I'm in completely agreement on this. One of the reasons I posted this
in an incomplete form is to get feedback. Feedback from the designer
community on the utility of the interface is a big part of this, both
in terms of how the tag would be used, and how new tag implementations
would be defined.
I'm not 100% convinced that templates are the answer, but they're
certainly worth considering. I have two immediate concerns:
- the complexity of getting the templates picked up by the form
rendering template tag (getting all the template paths lined up so
that they are discovered and used correctly)
- the performance overhead of using templates to handle very small
template snippets. This is less of a concern now that we have cached
loaders, but
One possibility might be to include a template-based implementation of
form rendering as one of the default implementations. This way, the
template-based tag would be a gateway drug that designers could use to
prototype a new style; if a particular rendering needs to be optimized
or becomes common across multiple projects, it should be easy for a
developer to take a bunch of prototype templates and turn them into a
native implementation.
Yours,
Russ Magee %-)
I agree - form rendering scheme isn't something that is site-wide, and
it isn't necessarily page-wide either. I acknowledge that page-wide
rendering is a limitation of my proposal. If we can find an elegant
way to represent it, I agree that it is worth considering.
> in that
> case, {%load%}'ing the style is too coarse granularity. i'd prefer an
> optional parameter to {%form%} to choose the renderer.
>
> something like:
>
> class myformrenderer (forms.as_table):
> name = 'left_table'
> ......
>
> and in the template you could say {% form myform:"left_table" %}
I can see where you're going with this; I have two concerns:
* Duplication. The 'left_table' flag needs to be applied to every use
of the {% form %} tag on a page. If you're
manually rolling out every field on a form, this is a lot of code duplication.
* Composibility. If I understand your intention, a form library would
need to provide all the layout schemes that you want to have available
on a page. That means if you wanted "P" and "UL" forms on the same
page, you would need to define a combined "P & UL" form library. This
sort of composition doesn't strike me as a desirable goal.
One proposal that has been made elsewhere in this thread (by Iván
Raskovsky) is to introduce namespacing for tempatetags. This has been
mooted several times in the past, and there have been several proposed
syntaxes; here's yet another:
{% load xhtml_div_forms %} -- loads into the root namespace
{% load namespace foo xhtml_p_forms %} -- loads forms into the 'foo' namespace
{% load namespace bar xhtml_ul_forms otherlib %} -- loads forms and
otherlib into the bar namespace
{% bar:form firstform %} -- renders as ul
{% foo:form secondform %} -- renders as p
{% form thirdform %} -- renders as div
This way, you can have two different implementations of {% form %} on
the same page if you need them. Whatever the final agreed syntax, this
isn't a form-specific feature; it could be useful anywhere that you
need to disambiguate template tags/filters. This means the discussion
is orthogonal to this forms discussion; if we agree that namespacing
is a way to tackle this general problem, we can keep the discussion
about how to implement namespaces as a separate issue.
>> Again, why not directly in settings in order to be project's specific? Is there anybody mixing doctypes on the same website? (backward compatibility maybe?)
>
> here i fully agree, doctype should be project-specific.
As noted elsewhere, there's no guarantee that doctype will be
consistent across an entire project.
Yours,
Russ Magee %-)
Agreed -- namespacing could be very useful in this context. See my
response to Javier for some comments.
Yours,
Russ Magee %-)
Agreed.
> This looks like what I'll be sprinting on at DjangoCon. :)
That's the response I was looking for :-)
Russ %-)
i like this syntax; it's a lot more readable than a chain of
parameters of filters on a single tag.
> I see this as having several advantages:
>
> 1) It introduces a clearer way of laying out settings.
> 2) It leverages the template engine for specifying defaults in a
> simple but ingenious way:
> {% form myform %}
> {% include "form_settings.django" %}
> {% endform %}
> -- form_settings.django:
> {% doctype xhtml1 %}
> {% replace_widget datetime=calendar %}
personally, i don't think this is a nice solution. {%include%}'ing a
set of settings is not a default, it's a packaged setting.
i like better to define a python form of the same; IOW document a
mapping, so that your first example would be equivalent to:
myform.render ({
'birthday_widget':'calendar',
'renderer': "as_ul",
'autocomplete': "name_autocomplete",
'doctype': "xhtml1"
})
or:
myform.render (birthday_widget='calendar', renderer="as_ul",
autocomplete="name_autocomplete", doctype="xhtml1")
and allow the use of either of these syntaxes in settings.py, form
definition or context processors to set defaults.
--
Javier
absolutely. see my answer to André for an idea on this
> * Composibility. If I understand your intention, a form library would
> need to provide all the layout schemes that you want to have available
> on a page. That means if you wanted "P" and "UL" forms on the same
> page, you would need to define a combined "P & UL" form library. This
> sort of composition doesn't strike me as a desirable goal.
no, the P, UL (and my hypothetical left_table) would each one be a
class; you could import each one separately (or maybe several included
by default). in my example, left_table would inherit from as_table,
simplifying the implementation. the {%form%} syntax wouldn't be a
parameter to a single renderer, it's a selector to choose which
renderer to use.
--
Javier
On Mon, Jul 12, 2010 at 5:38 AM, Idan Gazit <id...@pixane.com> wrote:
> What a fantastic proposal. I have some concerns, but I'm not sure if
> any of them have to do with my misunderstanding.
>
> 1. The {% load %} mechanism can get ugly, fast. What if I am rendering
> multiple different forms on the same page? {% load %} {% form %} {%
> load %} {% form %} feels mildly unclean to me. The only alternative
> that comes to (my) mind is specifying an alternate renderer in the {%
> form %} tag, but that will add yet another argument to a tag that
> already has an unwieldy list of (possible) arguments.
I think templatetag namespacing may address this issue. See my
response to Javier for details.
> 2. The {% load %} mechanism is overloaded with two orthogonal
> functions: doctype selection and custom rendering. If I write a
> reusable widget that need some custom rendering fanciness, then the
> logic for rendering the widget goes in one place (the widget class,
> based on the doctype kwarg) but I have to provide several custom
> renderers, one for each doctype. Seems inconsistent to me. I like the
> idea of widgets being responsible for the widget and chrome being
> responsible for everything else, but feel like the two concerns might
> need to be represented individually. Doctype doesn't change throughout
> the document, but renderers might.
I will admit I don't really have a good answer to this.
One counterargument would be that from the perspective of form layout,
there isn't much that is doctype specific -- from my initial
experimentation, the UL/P/DIV/TABLE based layouts are pretty much
doctype-agnostic. This isn't true for chrome, however.
Another counterargument would be that you only have to provide
multiple form renderers (or chrome implementation) if you actually
need to support multiple doctypes; a form renderer/chrome that only
works under HTML4 would always be a simple option.
A related issue here is that mixing chromes from external sources
isn't trivial. For example, if Alice wants to ship my own custom
calendar chrome, and Bob want to ship a custom autocomplete chrome,
the only way for Chuck to use both chromes on the same form is to
define a form renderer that absorbs (possibly by mixin) both chrome
definitions. This isn't a solution that most designers are going to be
comfortable with.
Ideally, chrome would be completely independent of the form
definition, but the problem I hit here is working out how to
"register" chrome without either duplicating the template tag
registration services, or making a special case in the existing
registration services for chrome. I'm not a big fan of special cases,
but in this case I may have to make an exception. Of course, if
someone can make the case for a general facility for registering
capabilities as part of the template library, we can add this feature
and chrome need not be a special case.
> 3. Related to #2, what is the behavior of a Widget if I ask it for a
> doctype it doesn't support?
I'm not sure this is actually a problem. Django currently ships a
widget for every legal <input> type (plus <select>). In order to
support HTML5, we'll need to add a couple more (like EmailField), but
these new types all have documented regressions to type="text". In the
new vision of Widgets as "just the input mechanics", there isn't much
need for end users to define custom widgets. Django will ship with all
the possible basic widgets; if you need customization, you should be
defining chrome instead.
Of course, there is the issue of dealing with people in the wild that
have written custom widgets. Those will continue to work exactly as
they do now, but they won't be adaptive to doctypes (they won't even
necessarily accept the doctype argument, so there will need to be some
internal jiggery-pokery to support that backwards compatibility case).
I suppose you *could* continue to use widgets as the keeper of rich
rendering, but I'm not convinced this needs to be a focus of our
design discussions.
> I need to think about the renderer/chrome bits some more, will weigh
> in again in the morning with a clear head.
Much appreciated. As a designer (or someone with a design focus)
you're part of the target audience, so I want to make sure we
implement something that will actually be useful.
Yours,
Russ Magee %-)
As I commented to Tim; the {% form X field ... %} tag isn't trying to
render the entire form -- it's just rendering a single field. This
removes the need for "using" and "autocomplete" as a subtag. The only
way a full field is rendered is in the simple case of {% form X %},
which is the equivalent of the current {{ form }} -- i.e., just render
the entire form using defaults (where "default" == doctype, layout and
chrome).
Secondly, the "renderer" subtag misses the significance of the trick
with the template tag loading. The aim of the loading trick is that {%
form %} *is* an as_ul renderer; it doesn't need to be specified
separately.
Lastly, the doctype subtag isn't something that should be specified on
a per-form basis. It's a per-document attribute. My proposal gets
around this by tying the doctype to the form tag itself, which is a
constant through the rendered page.
> I see this as having several advantages:
>
> 1) It introduces a clearer way of laying out settings.
For some definitions of clever. It also has some potential problems:
* How do you put a <hr/> between the name and birthday fields?
* How do you render fields in a different order to the form specified
on the form?
* How do we handle content that is included inside the {% form %} block?
> 2) It leverages the template engine for specifying defaults in a
> simple but ingenious way:
> {% form myform %}
> {% include "form_settings.django" %}
> {% endform %}
> -- form_settings.django:
> {% doctype xhtml1 %}
> {% replace_widget datetime=calendar %}
Again, most of these default don't actually need to be specified if
you treat the {% form %} tag as encompassing the doctype and renderer
requirement. Arguing that template inclusions are a convenient way to
get around the fact that defining a form requires lots of subtags
doesn't seem like a net win to me.
Call me -0 on this; Given that there's only one tag that actually
needs to be a subtag (using), I don't see what making {% form %} a
full block tag gains us.
Yours,
Russ Magee %-)
I'm not sure I understand.
In my proposal, {% load custom_form %} loads a single form template
tag. That template tag implements a single specific rendering scheme.
You import library X, you get library X's layout. If you want a
different layout, you import a different library (namespaces being one
way to get around the 'multiple layouts in a single )
How are you getting your "as_p" renderer into your template? How is it
made available to the {% form %} tag as an argument? How does the
"selector" determine what is available for selection? What defines the
default behavior of {% form %} in the first place?
Yours,
Russ Magee %-)
Looking back over the thread, I'm the only Tim, but I don't seem
to see your response (neither in my email nor via gmane). If you
could disinter it from your sent-mail folder and resend, I'd
appreciate reading your thoughts in reply.
-tkc
It's not appropriate to set doctype on a per-form basis, but that's
not really what my proposal is doing -- it's being defined on a
template tag that has scope over the template. If you import form
rendering library X, that library provides a {% form %} implementation
that specifies a doctype. Everywhere you use that instance of {% form
%}, you get the same doctype without the need to specify doctype.
> What about adding a {% doctype %} template tag for use in base
> templates which will not only render the appropriate DOCTYPE, but
> store it in the context? Then templates that extend from the base
> template will have access to this context variable and know whether or
> not to include a trailing forward slash for self closing elements, or
> whether or not `email` fields should be rendered as `text` or `email`
> type?
This has been proposed in the past [1]. I can certainly see how this
is compatible with what I've proposed.
[1] http://github.com/simonw/django-html
However, I prefer keeping things local to the templatetag rather than
relying on a template context variable. If you use the context
variable approach, writing an 'as_ul' layout requires that you provide
an implementation that can support every possible doctype, or provide
error handling when the wrong doctype is present. It also requires
handling for the 'doctype not provided' case.
Using the 'doctype defined on the tag' approach, you only have to
support the doctype that your form defines. If you want to support a
different doctype, write a different tag; if there are commonalities,
you can use a common base class. To me, this ultimately results in a
cleaner interface for end-users to implement.
> Regarding the application of settings and substitution of widgets
> inside templates, I'd like to see an easy way for designers to create
> bundles of presentational logic (templates, stylesheets, images) which
> they can pass in or refer to as arguments to {% form %} or {%
> formfield %} template tags. This could take the form of {% form myform
> myformpresentationbundle %} or {% formblock %}{% formbundle
> myformpresentationbundle %}{% fieldbundle mydatefield mycalendarbundle
> %}{{ myform }}{% endformblock %} or {% formfield myform.mydatefield
> mycalendarbundle %}.
Agreed - templates are a natural way to do this, even if only as a
prototyping tool. And bundling/packaging of these templates is
probably the biggest examples for why this might not be the best
approach.
Yours,
Russ Magee %-)
Ok - we must have different measures for what constitutes a "long
parameter list". As I noted in my response to Tim, there's exactly 1
redundant word in the template tag I've proposed.
The only other way I can think of to shorten the argument list is to
introduce different tags for rendering a single field, rendering
top-of-form field errors, and rendering javascript triggers; so:
{% form myform %} -- would render an entire form
but rolling out the full form manually would require:
{% form_errors myform %}
{% form_field myform.name using autocomplete:"name_ajax" %}
{% form_field myform.email %}
{% form_field myform.birthdate using calendar %}
{% form_triggers %}
To me, this just means a bunch more underscores in templates, and 3
extra tags to register (along with finding a way to share the form row
rendering logic between the {% form %} tag and the other tags.
> Having written some
> complex form field rendering tags for extjs/sencha I can say that
> without some kind of built-in *args/**kwargs-like parameter handling
> for template tags, then having more than a handful of parameters gets
> long and messy quickly.
I'll concede args/kwargs handling in template arguments isn't pretty.
My counterargument would be that I don't see the use case for lots of
argument and kwargs to chrome. "Use this calendar", or "show this
decoration", not "here are 5 different arguments to configure how this
chrome will operate".
The one use case I can think of for chrome arguments is to provide the
named URL for AJAX callbacks. However, this may just be a case of me
not being imaginative enough. If you can provide a use case, I'll
happily reconsider my position.
> Also, it doesn't make your tag that user
> friendly either when they have to keep referring to the docs to figure
> out how to use it.
>
> Andre's idea is interesting and is certainly more readable.
I'm having difficulty reconciling these two positions. My template tag
is too complex because it requires you to remember the idiom FORM X
FIELD Y USING Z; but a nested tag structure with 4 different subtags
is more readable and won't require reference to documentation to
understand how and when to use each subtag?
Yours,
Russ Magee %-)
Hrm. Gmail says my response was sent, but it's not turning up in the
public archive -- I'll resend. Apologies if this results in a repost
for anyone.
Yours,
Russ Magee %-)
Only if you grant me the same liberty :-)
> On 07/11/2010 10:36 AM, Russell Keith-Magee wrote:
>> {% form myform field birthdate using calendar important %}
>> {% form myform field name using autocomplete:"name_autocomplete" %}
>
> and REALLY -1.0e1 on this syntax-soup. Does this result in
> something that looks like the following monstrosity?
>
> {% form myform field name using autocomplete:"name_autocomplete" field
> birthdate using calendar field other_field using
> some_other_chrome_or_widget%}
I think some wires have gotten crossed here - this example wouldn't be
legal syntax under what I'm proposing.
Under the scheme I'm proposing:
* {% form myform %} would render an entire form, and
* {% form myform field name ... %} would render a *single* field
using the strategy described by the form.
If you want to render multiple fields, then you make multiple calls to
{% form myform field ... %}. Your example would require the following
template:
{% form myform field name using autocomplete:"name_autocomplete" %}
{% form myform field birthdate using calendar %}
{% form myform field other_field using some_other_chrome_or_widget %}
The intention is to encompass the rules for a full line of a form in a
call to {% form X field Y %}, not to try and put then entire form and
all it's individual field customizations into a single tag. This also
ensures that the user has final control over form ordering, layout of
forms on the page, and any interstitial elements they want to put in
between form elements on the page.
> Putting in a subtle plug for one of my wish-list items, I've
> always been curious why form-rendering wasn't implemented as
> a filter:
>
> {{ myform|as_ul }}
If you dig into the archives, this was proposed (I'm fairly certain
*I* suggested it or supported it at one point); I can't say I can give
a concrete answer as to why the suggestion wasn't adopted. However, in
the context of this proposal, I can give some counterreasons -- more
in a bit.
>> These layout schemes can be overridden and customized if you
>> know what you're doing, but it's not easy to do so.
>
> I've occasionally wished for an as_dl (which would render
> labels as <dt> and controls as <dd> elements), and having a
> base/ancestor as_* filter object to base it off of would
> make this a lot easier. This would take care of your
> issue#1 (more easily extensible renderings).
>
> Under such a scheme, I'd imagine your above examples might
> look something like
>
> {{ myform|as_ul }}
> {{ myform|use_widget:"birthdate=calendar"|as_ul }}
I think this last example should probably be:
{{ myform.birthdate|chrome:"calendar"|as_ul }}
or even:
{{ myform.birthdate|calendar_chrome|as_ul }}
since this enables you to define chrome as a filter. This bit appeals to me.
However, beyond that advantage, I have three reasons for preferring a
template tag over a filter.
* The first is stylistic. {{ myform|as_ul }} is certainly elegant;
however, I don't see that {{ myform.birthdate|calendar_chrome|as_ul }}
is equally elegant. Filter syntax requires that you take out all your
whitespace, which just seems messy to me. In a template tag, arguments
are all distinct, ordering is obvious, and when it isn't, you can
introduce syntactic sugar (like "using") to clarify.
* Secondly, while I can certainly explain from a programmers
perspective as_ul formatting and calendar_chrome are applied as
filters in the order that they are being applied, I can't think of a
good conceptual explanation, suitable for non-programmers, for why
form rendering is a 'filtering' activity.
* Lastly, a template tag gives you access to the context, which means
tricks like collating javascript triggers becomes possible. For me,
this is one of the major goals of this refactoring, and it's something
you can't do with a template tag.
> where the use_widget (deep?)copies the input form object,
> adjusting its field-widgets per the requested use_widget
> modifiers. It could even be neatly stacked something like
> (line-broken for clarity)
... which, it's worth noting, isn't legal Django template syntax.
Django's template tags and filters are all limited to a single line
specifically to avoid the contents becoming syntax soup.
> {{ myform
> |use_widget:"birthdate=calendar"
> |use_widget:"name=name_autocomplete"
> |use_widget:"other_field=some_other_chrome_or_widget"
> |as_ul
> }}
Again, this shouldn't be a single template substitution -- it should
be three (one for each field).
> For backwards compatibility, the implementations of
> Form.as_*() should be able to just look something like
>
> def as_table(self, ...):
> return filters.as_table(self, ...)
>
> I'd suspect this could take care of your issue#2.
I suspect you're correct, although the implementation could be interesting.
> However that leaves me with no good answer to
>
> 1) your "parameters to chrome" wish (though that might be
> possibly incorporated in the use_widget parameters). I'm
> not entirely clear on your chrome ideas, but I like some of
> what I read in your novella. :)
If we make chrome fully fledged template tags, arguments to chrome
could be covered by normal templatetag argument handling:
{{ myform.name|autocomplete:"name_view"...
> 2) your "put all JS at the bottom of the page" wish
>
> 3) your issue#3 about DOCTYPES, but unless filters were given
> access to a rendering context in which some global
> DOCTYPE-preference was specified, I haven't liked any of
> the solutions I've seen bandied about on this matter
> (including a settings.py directive, since I can easily
> imagine a single site producing a mix of html4, xhtml
> and/or html5).
>
> However I think all 3 of my outliers are somewhat
> orthogonal to the rejiggering of form rendering.
I'm not sure I agree. Points 2 and 3 are major components of the the
raison d'être for this refactor.
>> {% form myform %}
>
> For what my vote may be worth, I'm -0 on this...
>
>> {% form myform field birthdate using calendar %}
>
> and -1 on this
You've said you're -0/-1, but you haven't really explained why. Is it
just the preference for using filter-style syntax (which, as I've
shown, has it's own problems), or is there some deeper objection? You
call it syntax soup, but the final syntax is no more than:
{% form A field B using C D ... %}
Of this syntax:
* "form" identifies the tag
* "field" specifies a particular rendering action; it's necessary
so that you can also render form errors and form javascript triggers
without introducing a name ambiguity (i.e., is it the form field named
"errors", or a request to display errors"
* "using" is syntactic sugar; strictly, it could be dropped, but
personally I think it adds to readability.
Everything else is specifically naming an operand, except that I let
you use whitespace, whereas a filter doesn't :-)
Yours,
Russ Magee %-)
Ah - on closer inspection, I sent the original response to
django...@tim.thechases.com. I'm not sure why that address came up
as the default reply-to.
I've reposted to the list for posterity. Apologies for the confusion.
Yours,
Russ Magee %-)
i didn't think too much about namespaces; maybe that's a cleaner
solution than my proposal.
> How are you getting your "as_p" renderer into your template? How is it
> made available to the {% form %} tag as an argument? How does the
> "selector" determine what is available for selection? What defines the
> default behavior of {% form %} in the first place?
what i think is: currently, the form system has a few renderers as
class methods. the new system would factorize them out as classes.
for this, it would expose a small API, so that the user could easily
write new renderers, ideally even supporting inheritance to make it
easier to just modify existing renderers.
a new renderer class would have to be registered with the form system,
just like a new template tag has to be registered with the template
system. this might be at app loading, or more likely, with {%load%}
tags on the template. you could {%load%} as many renderers you want,
since they don't overwrite any behavior, they don't define a new
{%form%} tag.
the {% form %} tag is provided by the form system, not by the
renderers. there are a few 'included' renderers, which probably you
don't have to load, (just like you don't have to load the standard
template tags). one of these would be the default (just like the
current form system has a default of as_table() )
a parameter to the {%form%} tag (or a subtag, on André's proposal)
would be the 'selector' to choose between the registered renderers.
again, the renderer doesn't override the {%form%} tag; it's the tag
the one that chooses the selected renderer.
--
Javier
Fair's only fair :)
>> and REALLY -1.0e1 on this syntax-soup. Does this result in
>> something that looks like the following monstrosity?
>>
>> {% form myform field name using autocomplete:"name_autocomplete" field
>> birthdate using calendar field other_field using
>> some_other_chrome_or_widget%}
>
> I think some wires have gotten crossed here - this example wouldn't be
> legal syntax under what I'm proposing.
>
> Under the scheme I'm proposing:
> * {% form myform %} would render an entire form, and
> * {% form myform field name ... %} would render a *single* field
> using the strategy described by the form.
Ah, that does clarify (and minimize my strenuous -1.0 objections
back to just a -0 "meh")
>> {{ myform|as_ul }}
>
> If you dig into the archives, this was proposed (I'm fairly certain
> *I* suggested it or supported it at one point); I can't say I can give
> a concrete answer as to why the suggestion wasn't adopted. However, in
> the context of this proposal, I can give some counterreasons -- more
> in a bit.
Okay -- A little improvement on my previously-decaffeinated
google-fu turned up at least one thread[1]...though as you say,
your participation in the thread was "Put me down as a +1"
advocating filters ;-)
>> {{ myform|as_ul }}
>> {{ myform|use_widget:"birthdate=calendar"|as_ul }}
>
> I think this last example should probably be:
>
> {{ myform.birthdate|chrome:"calendar"|as_ul }}
>
> or even:
>
> {{ myform.birthdate|calendar_chrome|as_ul }}
>
> since this enables you to define chrome as a filter. This bit appeals to me.
But I want a BLUE bikeshed ;-) Yeah, I'll give you the point
that your solution looks more elegant for individual field
rendering, while one of my envisioned use cases was "I want this
form exactly as it would normally render, only I want to override
the widget-choice for a particular field". It's the difference
between something like
{{ myform|use_widget:"birthdate=calendar"|as_ul }}
and
<li>{{ myform.name }}</li>
<li>{{ myform.favorite_cheese }}</li>
<li>{{ myform...8 more fields here }}</li>
<li>{{ myform.birthdate|calendar_chrome }}</li>
<li>{{ myform...7 more fields here }}</li>
or perhaps a (forgive the pseudo-code template) slightly less
verbose version:
{% for field in form.fields %}
<li>
{% if field.name == "birthdate" %}
{{ field|calendar_chrome }}
{% else %}
{{ field }}
{% endif %}
</li>
{% endfor %}
> * The first is stylistic. {{ myform|as_ul }} is certainly elegant;
> however, I don't see that {{ myform.birthdate|calendar_chrome|as_ul }}
> is equally elegant. Filter syntax requires that you take out all your
> whitespace, which just seems messy to me. In a template tag, arguments
> are all distinct, ordering is obvious, and when it isn't, you can
> introduce syntactic sugar (like "using") to clarify.
The whitespace issue is a grumble for another day (I can't count
the number of times I've reached to use whitespace for improved
readability in a filter-chain or template, only to be burned by
exploding template shrapnel).
However since you're only dealing with one field and controlling
the rendering yourself (wrapping in <li> tags), it would be
reduced to
<li>{{ myform.birthdate|calendar_chrome }}</li>
<li>{{ myform.name|auto_suggest:"some_view_name" }}</li>
which isn't quite so bad, IMHO. Unless the volume of parameters
to the filter balloons, in which case I can cede you the point.
For most of what I do, the 0-or-1 parameter case is by far the
most prevalent. Individual tweaks such as CSS styles can be done
by identifying a containing tag:
<style>
li#fabulous tr td { background-color: #f0f; }
</style>
...
<li class="spiffy" id="fabulous"
>{{ myform.birthdate|calendar_chrome }}</li>
so I've not really found myself reaching for attribute tweaks
inside rendered controls.
> * Secondly, while I can certainly explain from a programmers
> perspective as_ul formatting and calendar_chrome are applied as
> filters in the order that they are being applied, I can't think of a
> good conceptual explanation, suitable for non-programmers, for why
> form rendering is a 'filtering' activity.
Filters apply transformations of input to output -- the "lower"
filter takes the input (as text) and makes it lower-case; the
$FILTER takes the input and makes it ${FILTER}ed. The "as_ul"
filter takes the input form and makes it <ul>ified; the
calendar_chrome filter takes the input field and makes it
calendar'ified. I don't see this as a particularly big
conceptual jump for non-programmers.
> * Lastly, a template tag gives you access to the context, which means
> tricks like collating javascript triggers becomes possible. For me,
> this is one of the major goals of this refactoring, and it's something
> you can't do with a template tag.
I think this is your biggest win here -- while I'd still advocate
(from your original post)
>>> - Propagating the 'doctype' kwarg through the existing
>>> form/field/widget infrastructure
which would allow smarter rendering.
>> It could even be neatly stacked something like (line-broken
>> for clarity)
>
> ... which, it's worth noting, isn't legal Django template
> syntax. Django's template tags and filters are all limited to
> a single line specifically to avoid the contents becoming
> syntax soup.
Yeah, I know it's invalid syntax and have elided that rant for
another day. Suffice to say that decisions to purposefully make
things less readable (even if they should hopefully be fairly
easy tag/filter-parser tweaks for the most boring cases) in an
effort to prevent long filter-chains makes for a step backwards,
IMHO.
> You've said you're -0/-1, but you haven't really explained
> why. Is it just the preference for using filter-style syntax
> (which, as I've shown, has it's own problems), or is there
> some deeper objection?
>
>>> {% form myform %}
>>
>> For what my vote may be worth, I'm -0 on this...
In the base case above (whole form), I'm not yet seeing value
beyond the traditional
{{ myform }}
thus my -0.
>>> {% form myform field birthdate using calendar %}
>>
>> and -1 on this
>
> You call it syntax soup,
I reserved the "syntax soup" slur for what I misunderstood as the
modification of a *form* in that hideous fashion above (also
referenced as a "monstrosity"), not a *field*. It becomes much
less soupy (it congeals? get strained? like this metaphor?) if
you're only doing a single field. No soup for you!? ;-)
> but the final syntax is no more than:
>
> {% form A field B using C D ... %}
With your clarification that this is a *field* rendering, not a
*form* rendering, I'm more okay with this now. Waffling between
a -0 and a +0, but easily swayed either direction depending on
readability vs. power (yay, whitespace and args/kwargs). I still
begrudge the "form" keyword as misleading (and possibly
redundant) as you're talking about fields. I'd prefer something like
{% field myform.myfield using ... %}
{% field some_formless_field using ... ? ... %}
{% modify_field myform.myfield using ... %}
or some such variant that emphasizes the field-nature instead of
the form-nature. (I don't know if formless fields would ever
reasonably show up in a template, so take that
psychopathical/hypothetical example with a grain of salt)
Thanks for your detailed response and thoughtful arguments.
-tkc
I'd say there's a 3rd subset of folks (Gabriel Hurley and myself
have voiced advocacy for this camp) who would be interested in a
middle-road "mostly the default form, only override the behavior
for fields X & Y". Andre's proposed {% form myform %}...{%
endform %} syntax does this nicely, making it cleaner by removing
the redundancy of referencing the form in each field-modifier and
without the need to manually render each field.
-tkc
Ok - now I see where you're driving at. I wasn't trying to hit this use case.
One of the historical traps that form rendering discussions have
fallen into is the idea that somehow we can come up with the One True
Rendering Implementation; if we just add enough options and flags, we
will be able to programatically define all the fieldsets, field
alignments, and individual per-field layout options you could ever
want.
The core team has generally rejected these proposals, for the same
reason we have rejected ORM extensions that expose edge cases of SQL.
We have a language for describing form layouts -- it's called HTML.
Trying to come up with programatic abstractions of HTML just leads to
leaky abstractions.
I know that's not what you're proposing here, but I'm wary of getting
into territory where it looks like that might be what we're willing to
support. By tackling the form layout problem as a set of rules for
rendering individual fields, it leaves no doubt that you're *supposed*
to manually layout forms if you have rendering requirements that step
outside the simplest possible case.
I'd also like to think that it would be easy enough to write a chrome
package in a way that doesn't require "if field.name == birthdate"; I
would have thought that chrome could generally be applied on a
per-field-type, rather than per-field-name basis (ie. apply calendar
to all dates, rather than having to specifically name the date
fields). However, absent of an actual implementation, it's difficult
for me to actually assert this.
>> but the final syntax is no more than:
>>
>> {% form A field B using C D ... %}
>
> With your clarification that this is a *field* rendering, not a *form*
> rendering, I'm more okay with this now. Waffling between a -0 and a +0, but
> easily swayed either direction depending on readability vs. power (yay,
> whitespace and args/kwargs). I still begrudge the "form" keyword as
> misleading (and possibly redundant) as you're talking about fields. I'd
> prefer something like
>
> {% field myform.myfield using ... %}
> {% field some_formless_field using ... ? ... %}
> {% modify_field myform.myfield using ... %}
>
> or some such variant that emphasizes the field-nature instead of the
> form-nature. (I don't know if formless fields would ever reasonably show up
> in a template, so take that psychopathical/hypothetical example with a grain
> of salt)
I toyed with using multiple tags; I ended up proposing a single tag to
keep the internal implementation simple. Given that {% field %} needs
to use the per-row logic defined as part of {% form %}, using a single
tag with a qualifying argument was the easiest way I could see to
avoid duplicating logic. However, there are some other ways that this
could be achieved.
> Thanks for your detailed response and thoughtful arguments.
Likewise.
Yours,
Russ Magee %-)
It wasn't in the direction I was originally thinking, so there was
some confusion on my part. I have a better understanding of what you
(and Tim) proposing now, and I've responded with some of my reasoning
in my response to Tim.
Yours,
Russ Magee %-)
Under my proposal as I presented it -- yes, it's a chrome issue.
Chrome is essentially a set of operations that can be performed on the
raw widget output; adding attributes, classes, wrapping the widget
HTML in other HTML, or registering the need for trigger Javascript.
As a helper mechanism, we could ship a generic "attribute" chrome that
allows you to set or override a specific attribute:
{% form myform field name using attr:"autocorrect=off" %}
Adding classes could also be handled as chrome:
{% form myform field name using class="colorful large" %}
Since chrome can be stacked, you could have multiple attr, class, or
any other chrome.
Yours,
Russ Magee %-)
> And I don't think writing custom
> chrome is the place to add these types of attributes. They're
> functional, not presentational.
As noted in my reply to Preston, this should easily possibly using
chrome; I'm not sure I see why you disagree. The new options may be
funcitonal, but from my reading of the HTML5 spec, they're functional
in terms of UX, not in terms of data modification. The widget and
field are used to process and parse the content of any value that is
POSTed by a form; anything that the user sees about a form is a
presentational issue, AFAICT.
> 2. Ability to alter individual fields without writing out the entire
> form.
I missed the focus of Tim et al's proposals on this front; I can now
see what he's driving at. To that end, I can see the appeal of a {%
form %} syntax that allows for overriding individual field settings.
> 3. Syntax and Readability.
>
> There's been a lot of discussion on template tag(s) vs. filters, and
> while wearing my designer hat I'd say that calling a "form" tag to
> render individual fields doesn't make sense. Adding a {% field %} tag
> or something of the sort sits better with me.
Sure; I've admitted elsewhere in this thread that my use of a single
tag was mostly driven by a desire to keep the implementation simple.
This was especially important keeping in mind that my original intent
was that extension would be largely in code; this becomes less of an
issue if templates become a larger part of the common usage of this
new form rendering approach.
> 4. Layout renderers being loaded implicitly by form libraries.
>
> I'm a HUGE -1 on this. The choice of "as_ul", "as_p", etc. needs to be
> as explicit as possible, and the idea that you could load multiple tag
> libraries and accidentally override your form rendering sounds like a
> nightmare. So, whether these become filters, or arguments to the {%
> form %} tag, I really can't support them being implicit in the form
> tag that gets loaded. No magic here, please!
I think I understand the nature of your concern, but I'm not sure if I
agree with calling this magic. You import a library that exercises the
defaults you want to use. Library loads only survive for the duration
of a single template, so there isn't any risk of a load in one
template file accidentally changing behavior elsewhere.
That said, the block-based {% form %} rendering proposal potentially
gets around this by opening the possibility of making the layout
selection a subtag that is explicitly specified in the context of a
specific {% form %} instance.
Thanks for the feedback, Gabriel.
Yours,
Russ Magee %-)
Excellent. Sounds like we might have a posse of form wranglers forming.
> I'll first outline in brief what I think a fully template-oriented
> approach might look like, address some possible concerns with it, and
> follow that with some specific comments on your proposal.
>
> A fully template-based approach to form rendering
> =================================================
An interesting counterproposal Carl; thanks for the input.
> Possible objections
> -------------------
>
> - Russ mentions the "complexity" of "getting all the template paths
> lined up." I'm not sure what the issue is here; Django template
> overrides use a simple path-based scheme, and it's a proven system
> that works. Designers use it successfully all the time. I think
> introducing new complexity in the form of new Python abstractions is
> more likely to be a real problem.
To explain my concerns, I perhaps need to explain my motivation better.
When I introduced the Media framework 3 years ago, I had something
very specific in mind: I wanted to start a marketplace for reusable
widgets.
I have exactly 0 mad skillz as a front end guy, but I know that rich,
pretty ajax widgets et al are necessary for a good user experience. My
goal, as a backend guy with limited frontend skills, was to find a way
to get the people who *did* have frontend skills to be able to package
their widgets in such a way that I could reuse them.
Before the design community starts to think that I'm trying to
minimize the importance of what they do -- I know that there's a lot
more to the design process than this. I'm just looking at this from a
prototyping perspective -- I should be able to get a decent calendar
or AJAX autocomplete onto a page without much more effort than it
takes to put a simple text field on the page. The legacy of "How does
Django do AJAX" questions on django-users is the more public face of
this desire.
Now, the Media framework has been largely a flop in this regard
(although we may be able to use bits of it in a support role in this
new form work). However, my desire for a widget library is unquenched
:-)
My concern about doing this entirely in templates is that it makes the
process of sharing a widget library a little more difficult. If it's
just code, then it just needs to be in the PYTHONPATH. If there are
templates too, then the templates need to be somewhere that they can
be accessed.
There's also more to a widget in this context than just the template
-- for example, there's nominating the javascript triggers that the
widget requires.
I'm not saying that these problems can't be overcome -- just that it's
a complexity that I want to make sure is covered.
> - Concerns about filter syntax. The first two concerns Russ raised are
> only issues if many filters have to be strung together, which this
> proposal does not require. The third objection is predicated on the
> necessity of doing tricks with the context to load widget JS; again,
> a template-based solution doesn't need this.
The filter syntax doesn't hit the 'render the form completely standard
except for this one change on this one field' use case. However,
there's nothing about the 'template' aspects of your proposal that
need to be bound to filters; they could be equally accomodated in a
template tag.
> - "Too much work to override all these widget templates just to switch
> doctypes!" Again, I think the complexity of dedicated Python
> machinery is likely to be worse. Django's philosophy (and James
> Bennett's philosophy of reusable app templates, referenced above)
> assume that templates should be per-project: why should forms or
> widgets be different? Packaging up a reusable app with a set of
> generic "HTML5" or "XHTML1" widget templates is almost certainly
> simpler (and less bug prone) than writing the equivalent Python code
> for the customized {% form %} tag renderer.
My manifestation of this problem is slightly different -- it's the
issue of how to ship a widget library that can be used anywhere. I
suppose one argument here is that you just need to advertise your
chrome with doctype support notes.
Alternatively, since this is a template language, you could always
introduce an {% if %} block and render conditionally on doctype...
> - I'm skeptical that the "chrome" abstraction buys very much in
> exchange for the overhead of the new concept and the syntactical and
> registration problems. As mentioned above, in practice I don't think
> layering different chromes on top of each other will work very
> often; they'll frequently have to be manually integrated into a new
> unified chrome anyway.
This depends a little on the level at which chrome is implemented.
Very low level "modify attribute" and "add class" chrome could easily
be layered, and would address the common request of "how do I add
class X to my widget". More complex chrome (like the skeleton for a
calendar widget) would require more markup, and probably wouldn't be
mixed with other complex chromes, but it might still be mixed with a
simple chrome to add a class or an attribute (or a collection of
attributes and classes packaged into a single chrome).
> I'm also concerned that it's still insufficiently flexible; the core
> input tag markup will still be hardcoded in the widget, thus
> requiring special-cases for anything in it that you might want to
> tweak (doctype, extra classes or attributes). This is the big one
> for me, as it means our core problem with the current system (markup
> hardcoded in Python code) would not be fully fixed.
>
> In contrast, being able to directly edit widget templates for your
> specific cases is simpler, more intuitive for designers, and fully
> flexible.
Classes and attributes on the base widget are something that could be
modified with chrome. If you look at the API for widget.render() right
now, it's pretty flexible -- the issue is that there's no programatic
way to get at that API from a template. Chrome is the way into that
existing API, as well as providing a way to wrap decorating HTML and
Javascript around the base widget.
What exactly is your use case for something that designers want to
customize in the raw widget that widget.render() doesn't expose?
Yours,
Russ Magee %-)
> What exactly is your use case for something that designers want to
> customize in the raw widget that widget.render() doesn't expose?
That reminds me of one thing I'd very much like to see in any
refactoring of form/widget handling.
I'd like to be able to get at the "rendered" data (I say rendered, what
I suppose I really mean is "transformed" either due to rendering needs
or conversion from a graphical representation in the widget to something
that could be resubmitted), without any of the surrounding chrome. There
are various cases in which this can be useful, the most obvious of which
is to tell whether the data in the form has been edited before
submission. It's currently not possible to work this out from the
"before" and "after" data, as you don't know how any particular field's
data might have been munged by the widget (e.g. how is empty data
rendered, any forcing to unicode, Python boolean to textual
representation, date format transformation etc.).
This would make solving problems like those in #11303 simple. As another
example, changeset 10757 is heading down this path to fix #10288 in
adding the _format_value method. I'd suggest that this be generalised
and applied to all widgets (and the _format_value or whatever you want
to call it to lose the "_" and be documented as part of the widget API).
In one project here, I've had to write "duplicate" versions of all
widgets to add a "disable" method and enable them to disable themselves
in the UI while still submitting data (actually I'd like the standard
widgets to support this too, but... ;-) ); while doing so I took the
opportunity to refactor slightly more and ensure that all munging of
data took place in one method which could then be used for the purpose
described above - which worked just fine.
Cheers,
Nick
--
Nick Phillips / +64 3 479 4195 / nick.p...@otago.ac.nz
# these statements are my own, not those of the University of Otago
By "just code", I don't mean "code is easy", I meant "code, and
nothing else". Anything you can do in template you can also do in
code. The same is not true the other way around. I'll concede that the
intended audience may not see code as an easy option, but that doesn't
change the fact that code offers solutions that aren't otherwise
available.
> The app_directories template loader is installed by default. Getting
> templates "somewhere they can be accessed" means creating a directory
> on the PYTHONPATH with an empty models.py, an empty __init__.py, and a
> "templates/" subdirectory, then putting that in INSTALLED_APPS. That's
> it. How is that harder than following exactly the same steps for an
> app that includes Python code? From the perspective of a non-
> programmer, it's much easier.
Fair point.
> The key issue is familiarity and ease-of-use. For template authors,
> not programmers. Template authors know perfectly well how to add
> classes and attributes to HTML elements. Why should they have to use a
> special API to do the same thing for form input elements? Again: if
> the goal is for Django template authors to create an ecosystem of
> reusable widgets, the system needs to be geared around the tools they
> are comfortable with, not the ones programmers are comfortable with.
> That means templates. And not Python code.
Ok. I can certainly see that a template-based solution is the popular
solution, especially amongst designers. I still have some
reservations, but they're almost entirely about technical feasibility
and complexity of representation, not about desirability.
Part of the problem is that we've spent a lot of time talking about
the template tag/filter, but almost none on what the templates would
look like, and how the multiple layers of templates will knit
together.
Here's my first cut at a list of the use cases we need to support:
We need to be able to define templates for:
a) The layout for a single widget (e.g., a DateWidget)
b) The layout for a single row of a form
c) The layout for an entire form (top errors, fields, hidden fields)
in as_* style.
In each of these templates, (b) will need to render instances of (a),
and (c) will need to render multiple instances of (b) -- and not
necessarily the same instances, either.
Each of templates could be applied:
1) As a background system default - what you get if you don't override anything
2) As a specified default for the entire rendered page
3) As a specified default for a single form on a page
4) As a specified override for a field on a form (a/b) or a form as a whole (c)
There's also the issue of doctypes; If I have a document that is html4
document, I need to ensure I only get html4 snippets; ideally without
having to specify html4 every time I use a snippet.
If anybody has any other use cases or points worth considering, please speak up.
I'd like to spend a couple of days digesting what's been said in this
thread; I also want to look at some existing template-based projects
(like django-form-utils), and play around with a couple of ideas that
are lurking in the back of my brain. Hopefully, I'll be back in a week
or so with a revised proposal.
Yours,
Russ Magee %-)
Sounds like a reasonable request; and one that may almost be a
necessity if we're planning to make widgets rendered by template,
since we're going to need to expose most of the internals of a widget
to make them available for any possible rendering scheme.
Yours,
Russ Magee %-)
I'm not wildly enthusiastic about this. You've posted this snippet (or
something like it) a couple of times, and every time my immediate
reaction has been "You're in a twisty maze of endtemplatedir tags, all
alike" :-)
I can see what you're trying to do in terms of setting default
directories; but the syntax you are proposing:
* makes the representation of setting a default over a wide range quite elegant
* makes the representation for overriding a default on a single
field/form quite verbose.
To me, this seems like priorities the wrong way around. Overriding a
single default should be the simple case, not the verbose case.
Consider the template where you want to override the default widget on
every field on a form - your entire form will potentially be a nest of
{% templatedir %}{% endtemplatedir %} tags.
Yours,
Russ Magee %-)