Hi, thanks for your fast feedback. I was really looking forward to your input,
I haven't reached you in IRC before.

2011/4/1 Carl Meyer <c...@oddbird.net>:
> Hi Gregor,
>
> As you've probably seen in past threads, this is an area where I'm quite
> motivated to see some improvement. I think you've got quite a strong
> proposal here in general, and you've clearly done your homework, so my
> comments below dive directly into the details:

(I've shorten my proposal a bit in the quotes to make the whole email more
readable)

> On 04/01/2011 11:57 AM, Gregor Müllegger wrote:
>> I suggest reading this proposal online: https://gist.github.com/898375
>> It's exactly the same as below but formated nicely.
>>
>>
>> GSoC 2011 Proposal - Revised form rendering
>> ============================================
>>
>> ...
>>
>> Motiviation
>> -----------
>>
>> ...
>>
>>    I want to tell the designer how he can do this without my help in the
>>    template.
>
> I agree with your Motivations section - in particular this final line,
> which sums up the core motivation as I see it.
>
>> Goals I want to accomplish
>> --------------------------
>>
>> ...
>>
>> **1. No HTML in python source**
>>
>> ...
>>
>
> This all looks quite good to me.
>
>> **2. Reordering/skipping fields**
>>
>> ...
>>
>> The goal will be to make these modifiers defineable. Which means that you
>> can create your own modifiers to support some of your extravagant form
>> rendering needs. To support this we will need to have a rendering modifier
>> *registry* or something similiar, where you can ideally load new ones with 
>> the
>> existing ``{% load %}`` tag. The same will apply for custom *chrome*,
>> described below.
>
> This last bit worries me; it has the potential to add unneeded
> complexity. Do you have any actual use cases for the "custom rendering
> modifier" that can't be handled adequately by your proposal otherwise?
> The concept of what a "rendering modifier" actually _is_ and what it can
> do both seem poorly defined here. I'd rather see this level of
> flexibility left out (and potentially added later) than done without a
> clear driving concept and motivation.
>

I understand your worries and I clearly don't want to invent a YAGNI case. But
I think it fits nicely into the proposal (of it's current state). The reasons
are chrome. Chrome are just a specialized versions of "rendering modifiers"
(btw: I don't like the name but it's the best I came up with yet). And if you
can make it possible to register your own chrome, then it might be only a very
small overhead (if any) to do the same for rendering modifiers.


I want to make clear what a rendering modifier would be on the python level. I
thought about a FormRenderer class that handles the rendering of the form
based on the some predefined rules like what default layout is used. How to
render widgets and so on.

Rendering modifiers are then decorated around the FormRenderer. An example:
In the template::

    {% form myform using layout "p" and widget "CalendarInput" for
myform.birthday %}

The FormRenderer iterates over the fields in myform and wants to render them
with layout "default" and with the widgets defined in the form itself. It
asks then the form modifiers "layout" and "widget" that were used in the form
tag how they want to modify the rendering of a specific field. The "layout"
modifier would always return "use layout p", the "widget" modifier always
returns "use the default widget" except when FormRenderer reaches the birthday
field, then "widget" would return "use CalendarInput widget".

(Sorry for the very easy language, maybe my english is to limited to express
it in a not so verbose way.)


I haven't thought in detail about additional usecases for the rendering
modifiers that might life in the app ecosystem. But while writing this
response, there came some of the possibilities into my mind. Maybe there is
the potential for a doctype modifier::

   {% form myform using doctype "html4" %}

This will add a new template directory to the template path while rendering
the form. So that it renders HTML4 compatible widgets if available in the new
template directory. And if not it falls back to the default templates used by
the new template based widgets.

Something more exotic (and complex to implement but still possible) is a
"validation" rendering modifier::

   {% form myform using validation %}

This will walk through the form and outputs at the end some javascript that
validates the form on the client side based on the used form fields.
This will need a port of python validators into javascript, but that isn't
that complex (I already did a proof of concept for that kind of behaviour in a
template tag).
And thats something that wouldn't get into django core but could life nicely
in **django-clientsidevalidation** :-)


This is why I think that the registry for rendering modifiers is useful. We
would need a registry (i.e. a dict that holds the "name to modifier" mapping)
anyway. So why shouldn't we make it open to other developers? The power of
rendering modifiers would be huge. You could modify nearly any behaviour of
the form rendering.

>> **3. Chrome**
>>
>> ...
>>
>
> Chromes is the part of this proposal that I am least excited about. It
> seems to me that we already _have_ a perfectly workable abstraction for
> "a front-end UI widget" - that's a Widget. Once we have widgets
> rendering via template's (Bruno's floppyforms integration work) I don't
> see the value in having "widget" and "chrome" as separate abstractions.
>
> If you need a custom calendar control, define a widget for it. If you
> don't need to modify any of the Python behavior of the default Date
> widget, then we just need a way in the template API to say "render the
> widget for this field with this alternative widget template." With that,
> plus a syntax in your template tag for passing arbitrary attrs, like
> placeholder, to the widget for a field (and the widget template author
> can make use of those attrs however they want, they don't all have to
> become HTML attributes on an <input> tag), I think you can accomplish
> everything done here with no need to introduce a new abstraction.
>
> If we introduce chromes as a new concept, I'm concerned that in practice
> they will usually be heavily tied to a particular widget anyway, and not
> very composable. I.e. why would you ever use a "calendar" chrome with
> anything other than a Date widget? And when would you ever use an
> "autocomplete" chrome with, say, a textarea? Or a select box? IMO the
> calendar chrome should simply be a calendar widget, and the autocomplete
> chrome an autocomplete widget.

Fair point. The motivation behind chrome were modifiers that could work on
more than a single type of widgets and template based chrome that add HTML
before, after the widget -- totally controlled by the designer.

However by changing the widget template with something like ``{% form myform
using widget template "widgets/calendar.html" for myform.birthday %}`` this
will work in a similiar way as with chrome templates.

The more generic case, a chrome that can modify any widget, would be a
attribute changer, the chrome that adds a class, one that changes the input
type (e.g. from "text" to "email") and so on.

In an IRC discussion I had with Bruno yesterday, he saw the chrome as
possibility to change the widget rendering in the chrome template. Example::

   in forms/chromes/mychrome.html

   <html that is inserted before the widget>
   {% widgetrender bound_field
       using add_to_context "email" as input_type
       and template "mywidget.html" %}
   <html that is inserted after the widget>

But this isn't included in my proposal because that would duplicate logic from
the widget rendering itself.

And there is still another valid case for chrome. They can be stacked!

An autocomplete widget could operate on text inputs, on email inputs, on url
inputs, on search inputs ... (in the case you use HTML5 inputs). Additionally
another chrome could suppress the help_text defined in the field and the third
chrome might wrap the field with a <div> (or some other fancy decoration)::

    {% form sendemailform.receiver
        using autocomplete "/addressbook/emails"
        and nohelptext                           <--- since "enter a valid
                                                      email address" is not
                                                      needed if you use the
                                                      autocomplete
        and wrap_into_div %}

But, yes all of them can be done in the {% form %} tag as well through
simple rendering modifiers, like already said chromes are just like rendering
modifiers, they only have a simpler syntax to define. An analogy: Template tags
are difficult to write by subclassing template.Node. They get pretty easy for
simple cases if you use template.Library.simpletag to register a function.

>> **4. Keeping your form templates DRY**
>>
>> ...
>>
>> **5. Backwards compatibility**
>>
>> Backwards compatibility is a serious thing but straight forward in the case 
>> of
>> this proposal. We can fall back to use the internals of the ``{% form %}`` 
>> tag
>> while rendering the form via ``{{ myform }}`` or ``{{ myform.as_ul }}``. A 
>> bit
>> trickier is the use of ``{{ myform.field.label_tag }}`` and
>> ``{{ myform.field.errors }}``. The proposal above doesn't include these 
>> cases.
>>
>> But this is also possible to solve. Goal 1. suggests to refactor all HTML out
>> of the python source. This must include lables and errors as well. For this
>> case we would create some new templates::
>>
>>     forms/layouts/default/label.html
>>     forms/layouts/default/errors.html
>>
>> They get the bound field that is used passed in and can render there output 
>> like
>> the ``label_tag`` method and the ``errors`` attribute. In the template we 
>> would
>> use::
>>
>>     {% form myform.birthday display errors %}
>>     instead of {{ myform.birthday.errors %}
>>
>>     {% form myform.birthday display label %}
>>     instead of {{ myform.birthday.label_tag %}
>
> I agree with putting the error and label HTML in overridable templates,
> but why is this template syntax change needed? It doesn't seem like a
> clear improvement in syntax (it's more verbose, and less explicit), and
> the existing "errors" and "label_tag" methods can render a template.

Yeah, it will still work. Maybe with or without deprecation. But why should we
stop there with refactoring the form to know nothing about its HTML
representation? IMO it's better to remove these methods as well to stay
consistent.

>> Storing these templates in the layouts directory has also some nice side
>> effects. We can for example use some alternative styling of the labels and
>> errors based on the current layout::
>>
>>     {% formblock using layout "plain" %}
>>         {% form myform.birthday display label field %}
>>            ^--- uses the "plain" layout to render the label and the
>> field definition
>>         {% form myform.birthday display errors using layout "ul" %}
>>            ^--- the specified "ul" layout overwrites the "p" layout from the
>>                 formblock and displays a list of errors instead of errors
>>                 seperated by <br> that might be used in the "p" layout.
>>         {% form myform.birthday display helptext %}
>>     {% endfor %}
>
> Of course, if you wanted to do this sort of customization, you'd need to
> use the new {% form %} based syntax.

Exactly :-) thats the most important reason why I would like to encourage the
new syntax. You could also simply drop some new templates into the layout
directory like "highlight_row.html" and use::

   {% form registrationform using fields "username" "password" %}
   {% form registrationform.accept_tos display highlight_row %}

This way you could highlight the accept terms checkbox row to make sure the
user doesn't forget to tick the box.

Yes this could be done with a new layout as well (having a
forms/layouts/highlight/row.html file)::

   {% form registrationform using fields "username" "password" %}
   {% form registrationform.accept_tos using layout "highlight_row" %}

But I think the "display" version is more readable and propably easier to
understand (I have no evidence or user research on this).

>> **6. Admin integration**
>>
>> ...
>>
>
> With the exception of s/chrome/widget/g, this section looks pretty
> strong to me. I think converting the admin is a valuable minimum barrier
> to prove the usefulness and flexibility of the new API; and should
> result in more flexible and overridable admin templates as well.

Any way we go, with chrome or widgets only, converting the admin widgets to
template based ones is still necessary.

>> **7. Documentation**
>>
>> ..
>>
>> Media aka. JS/CSS
>> -----------------
>>
>> One of the other mainpoints in the discussions I reviewed for this proposal 
>> was
>> the use of JS and CSS files that must be maintained somehow to display them
>> how we already do through the media framework (e.g. ``{{ form.media }}``).
>>
>> The problem with this is that with the new template tag we can change some
>> of the widgets in the template and introducing new dependencies. Thats why I
>> would like to have an alternative for the ``using`` argument in the
>> ``{% form %}`` tag.
>>
>> If ``using`` is replaced with ``configure``, the ``{% form %}`` tag will 
>> _not_
>> output the HTML in the current place. However it will record and remember the
>> usage of widgets and fields to determine which media files are required. An
>> example template would look like::
>>
>>     {% block extrahead %}
>>         {% form myform configure widget "CalendarInput" for myform.birthday 
>> %}
>>                        ^--- The new widget for the field birthday will be
>>                             recorded, but the form will not be rendered.
>>         {% formmedia css for myform %}
>>            ^--- Outputting all necessary css files.
>>     {% endblock %}
>>
>>     {% block content %}
>>         {% form myform %}
>>            ^--- The form will be rendered as usual but with the
>>                 "CalendarInput" widget that was specified in the other tag.
>>     {% endblock %}
>>
>>     {% block extrajs %}
>>         {% formmedia js for myform %}
>>            ^--- Outputting all necessary js files at the end of the document.
>>     {% endblock %}
>>
>> A shortform for the ``{% formmedia %}`` tag is available if one likes to load
>> the css and javascript next to each other: ``{% formmedia for myform %}``
>
> This is a tricky piece, and I haven't seen any better proposal for
> solving it than what you're proposing here.
>
>> I don't know if backwards compatibility is also necessary here. If you use 
>> the
>> new ``{% form %}`` tag you will also need to update your ``{{ form.media }}``
>> statements.
>> However we could still provide backwards compatibility by overwriting the
>> ``media`` attribute of the form instance that we have modified with the
>> ``{% form ... configure .. %}`` tag.
>
> It shouldn't even be necessary to "overwrite" anything - as long as you
> update the widget for that field on the form instance, I think {{
> form.media }} should pick it up from the widget's Media automatically.
>
> In fact that might mean the {% formmedia %} tag isn't needed at all, and
> people can continue to use {{ form.media }} (and {{ form.media.css }}
> and {{ form.media.js }}) as we do now.

My intention was that the {% form ... configure ... %} tag only saves the
options that were made but doesn't apply them to the form, like changing a
widget. In that case we would need the {% formmedia %} tag that understands
the modifications that will be applied to the form rendering.

But thats all in all an implementation detail. Your version is cleaner and
less bug prone and I will go for that one if no other problem will come to my
mind.

>> Estimates
>> ---------
>>
>> That's it so far with the proposal. In the following I will go a bit into the
>> timeline that I have in mind for the implementation.
>>
>> 1st week: Examing what unittests are available for the current form rendering
>> and making sure they are stable for testing backwards compatibility
>> during the project.
>>
>> 2nd week: Converting the current layouts into template based renderers, 
>> ensuring
>> backwards compatibility.
>>
>> Goal: no HTML should be left now in the python source.
>
> Yay!

Party!

>> 3rd week: I will attend DjangoCon EU
>>
>> 4th week: Starting to write tests and implementing the {% form %} tag
>> to be able to
>> emulate all the rendering that is currently possible.
>>
>> 5th week: Implementing the necessary rendering modifiers like "fields"
>> (limiting the
>> form to some selected fields) and the API for chrome.
>>
>> 6th week: Building the registry that is necessary to register your own 
>> rendering
>> modifiers and chrome.
>
> Not sure this registry is actually needed...

See my response to it above.

>> 7th week: Taking care of the media handling.
>>
>> Goal: Project should be feature complete.
>>
>> 8th week: Converting the admin to use the new form rendering and
>> providing hooks for
>> applying chrome to some fields.
>
> I think a week for this might be ambitious.

Propably yes. I will rethink this and update my estimates. Thanks for the
hint.

>> 9th week: Integrating lessons learned from the admin especially in the sense 
>> of
>> making it easy to package chrome with a reusable app.
>>
>> Goal: Code should be ready to be used in sample projects
>>
>> 10th & 11th week: Documentation and bugfixes
>
> Allow me to channel Jacob briefly: Documentation is like tests, and
> should be written while (or before) writing code.
>
> I'd love to see a proposal where the _first_ week or two is writing
> documentation - I usually write my documentation first, and I think
> documentation-driven development is a pretty good way to make sure your
> API is something someone would actually want to use before you build it.
> "If the implementation is hard to explain, it's a bad idea."

Yep, that's a really good idea. I will take this into account when rethinking
the time estimates.

>> 12th week: Finalizing
>>
>> ...
>>
>> My first thought was to include template based widgets also in this proposal.
>> But Bruno has already made a great effort with developing
>> **django-floppyforms** that might also get merged into django. He is already
>> working on a patch [3].
>
> Yes, I think template-based widgets are a high priority, and my review
> of this proposal is assuming that they are already being done anyway.
>
> Good proposal - I'm excited about the potential here.
>
> Carl

Thanks a lot for the in details thoughts. I hope I can make it bulletproof
enough until the deadline next week.

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com.
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en.

Reply via email to