A newforms widget currently defines the way to render a user interface
input element. However, in order to make interesting input elements,
you also need other media - i.e., Javascript and CSS. These
requirements can be defined on a per-widget basis (a Date field
requires a Calendar widget; the calendar widget requires
media/calendar.js and media/calendar.css).
At present, there is no easy way to:
1) identify what media is required to display a form
2) identify and eliminate any duplicate media requirements, e.g., a
base css file required by two different widgets on a form.
newforms-admin has some hand rolled functionality to determine
Javascript requirements for a page; I would like to propose a more
general solution:
- Add a media() method to Widget. This method would return a
dictionary, keyed against the type of media, values containing lists
of files that are required for that widget:
class CalendarWidget(Widget):
...
def media(self):
return {
'css' : ['base.css','calendar.css'],
'javascript': ['calendar.js']
}
By default, this would return the empty dictionary.
- Add an argument to the Form constructor to define form-level media
requirements. This argument would accept a dictionary analogous to
that returned by Widget.media()
- Add a method to Form that returns a the union of all media required
by the widgets used by the form, as well as the form-based media.
>>> f = MyForm()
>>> f.media()
{ 'css': ['base.css','calendar.css','otherwidget.css','myform.css'], ...
- Since there could be multiple forms on a page, and the page could
have other media requirements, there needs to be a way to aggregate
media requirements. To do this, introduce a 'combine' template tag:
{% combine form1.media form2.media other_media as mymedia %}
{% for file in mymedia.css %}
<link .... href='{{ file }}' type='text/css'>
{% endfor %}
i.e., combine does the same operation on form media dictionaries that
the form does on widget media dictionaries.
This approach could be used by newforms-admin; there would be some
lightweight admin widgets pointing at the admin media, but those
widgets could be easily reused by users in their own pages.
Comments?
Yours,
Russ Magee %-)
class MyWidget(Widget):
...
class Media:
js = ('/path/to/js',)
css = ('/path/to/css',)
> The javascript
> portion seems fine, however I would see media as a class, not as a
> method, ala a model's Meta class:
Yea, I tend to agree.
> This gets a little complicated: <link> tags are only allowed inside
> <head> tags, otherwise you have invalid (X)HTML.
I'm not sure how that complicates anything. Russ' example could have
easily been called within a `{% block extrahead %}` or the like.
However I'm not so keen on such an obscure template tag weasling its
way in to the default template tags.
The `css` and `js` elements could return some sort of `OrderedSet`
objects. Then you can just use the `|add` filter for media items to
it.
In addition, the `media()` function could return a `Media` object
which would have an `__add__()` method (to handle adding the `css` and
`js` element additions) and a helper `as_links()` method so you could
do the following:
{% with form1.media|add:form2.media|add:other_media as mymedia %}
{% mymedia.as_links %}
{% endwith %}
Also, regarding output to a template, the paths returned would have to
be absolute paths wouldn't they?
A <form> is only allowed in the <body> - but you can still write a
template that renders the form in the <head> block. We can't be
expected to prevent people from writing broken HTML templates.
Yours,
Russ Magee %-)
I hadn't considered that approach. +1.
> However I'm not so keen on such an obscure template tag weasling its
> way in to the default template tags.
> The `css` and `js` elements could return some sort of `OrderedSet`
> objects. Then you can just use the `|add` filter for media items to
> it.
<slaps forehead>
That's a much better idea. Scratch the combine templatetag suggestion.
> In addition, the `media()` function could return a `Media` object
> which would have an `__add__()` method (to handle adding the `css` and
> `js` element additions) and a helper `as_links()` method so you could
> do the following:
>
> {% with form1.media|add:form2.media|add:other_media as mymedia %}
> {% mymedia.as_links %}
> {% endwith %}
I think you mean {{ mymedia.as_links }}; and I'd be inclined to say
that Media.__unicode__() should just return mymedia.as_links(), so {{
mymedia }} would also work.
So - what we have now is:
class MyWidget(Widget):
...
class Media:
css = ('/path/to/css','path/to/other/css')
js = ('/path/to/js',)
The Widget metaclass turns the Media definitions into an instance of
the Media object, stored on the widget such that:
>>> w1 = MyWidget()
>>> print w1.media
<link ...css link 1.../>
<link ...css link 2.../>
<link ...js link 1.../>
<link ...js link 2.../>
>>> w2 = OtherWidget()
# Implement __add__ to allow aggregation of media
>>> print w1.media + w2.media
<link ...css link 1.../>
<link ...css link 2.../>
<link ...css link 3.../>
<link ...js link 1.../>
<link ...js link 2.../>
<link ...js link 3.../>
Form gets a media() method that aggregates the media of all contained
widgets, and returns a single Media object.
Thinking about the problem some more, I'm not convinced that
form-level media is such a good idea. The default rendering methods on
a Form are only intended as first approximations, with a good template
taking precedence. If a user _really_ wants to add form-level media,
they could always override and extend Form.media(). Opinions?
> Also, regarding output to a template, the paths returned would have to
> be absolute paths wouldn't they?
URL path. When rendered, it should take into account settings.MEDIA_URL.
In the case of admin widgets, I would expect to see
settings.ADMIN_MEDIA_PREFIX referenced in the admin widget media
definitions. I suspect that best practice would dictate that other
widget sets would require analogous settings.
Yours,
Russ Magee %-)
I would assume the the mechanism for exposing this would allow it to
be done anywhere in the template, so that CSS/JS could be pulled in as
part of the <head> of the resulting (X)HTML document (in the case of
CSS, to remain valid, in the case of JS to adhere to best practices
for unobtrusiveness).
--
"Bureaucrat Conrad, you are technically correct -- the best kind of correct."
I suppose I was assuming that the generation of the assets was
automatic, e.g. in form.as_table.
As a matter of fact it's not a universal practice to have css and js
filed under this root. A couple of my projects had them in separate
places (even js separate from css). It can be handled as simple as
checking if Widget's media() (or a Meta-class) already contain an
absolute links and only if they don't the join it with MEDIA_URL.
Perhaps a simple API, like: context.register_media( PATH )
the only problem I see is that in order for this to work properly from
within templatetags, the template would IMHO have to be ordered in a
way that the media would be included at the very bottom, after all the
other tags have been rendered. Is this possible? what is the order of
evaluating a template and do we want to rely on that?
comments are welcome
--
Honza Král
E-Mail: Honza...@gmail.com
ICQ#: 107471613
Phone: +420 606 678585
This is a bad thing. Requests are extremely expensive and this is
encouraging scenarios where you end up with 30, 40, maybe more little
CSS and JS files. I'd never deploy this; I try to never deploy with more
than one of each. I generally fail and end up with two of each for
technical reasons, but certainly not two files per widget type.
Is there any way to make it easier to bundle everything into one CSS
file and one JS file than to end up with this?
in that case you can have bundles, multiple widgets would refer to one
css files, so no matter how many you would use, only one big file
would go to the client...
>
> Is there any way to make it easier to bundle everything into one CSS
> file and one JS file than to end up with this?
>
> >
>
The example was mostly to point out that multiple files _could_be
returned, and that the resultant output is the union of all
requirements of all widgets. It isn't intended to encourage the
proliferation of CSS and JS files.
My use case is this: You have a form that uses three widgets that, in
turn, each require SuperWidgetToolkit.css. The template for the page
using the form needs to have an easy way to identify that
SuperWidgetToolkit.css is required for the page, and to make sure it
is included once (and only once).
To my mind, this proposal should _reduce_ the number of CSS/JS files
that are included, because it will prevent accidental multiple
inclusions, and if you remove the last calendar widget on a page, then
DateTimeShortcuts.js will no longer be included on the page.
> Is there any way to make it easier to bundle everything into one CSS
> file and one JS file than to end up with this?
If you have a specific proposal on how to bundle CSS files together in
the way you describe, feel free to suggest it.
This proposal isn't intended to address the issue of how you compose
your CSS or your Javascript. If you want to use less CSS files, you
can. And if you want to ignore form.media(), and continue manually
inserting your media <link> statements, you're free to do that too.
Yours,
Russ Magee %-)
I've done this for JavaScript, and it wasn't terribly hard -- I wrote
a little script that takes a list of JS files, runs JSMin over all of
them and concatenates them into one file. Doing the same for CSS
shouldn't be that tough, but I don't think that packing/minimizing JS
or CSS is really in scope for Django itself; if you need that, there
are tools for managing it (and, again, it's not that hard to roll your
own if none of the existing ones meet your needs).
Hrm. I see the problem, but I'm not sure I'm wild on that suggestion
for fixing it. One outcome I can see from this proposal is the
possibility of widget libraries; allowing absolute paths doesn't
really serve to promote this goal.
When you say JS and CSS are separate - how separate are they? What
relationship (if any) does the path to CSS and JS have with MEDIA_URL
and MEDIA_ROOT? Can you describe the 'worst case' URL space that Media
objects would need to be able to target?
Yours,
Russ Magee %-)
One "worst case" is using the Yahoo UI library and having the URLs for
the Javascript components point to Yahoo's external hosting. Which is
recommended practice (huge caching benefits).
What is you problem with Ivan's suggestion of not munging absolute URLs?
Regards,
Malcolm
Granted.
> What is you problem with Ivan's suggestion of not munging absolute URLs?
I have a bit of a Pavlovian response to the suggestion of output
behaviour that varies depending on the exact magic format of the
input. However, that said, I'm not completely opposed, and I can't
think of an obvious better solution. I mostly just want to make sure
that I fully understand Ivan's use cases, so we can make sure that
there isn't a better solution lurking around.
Thinking about the problem some more, I'm fairly certain my 'widget
library' objection is bogus - judicious application of a setting when
specifying media references should get around any library portability
problems.
Yours,
Russ Magee %-)
I normally do, too. However, in this case, I think it's valid (and good)
to differentiate between absolute URLs, which stand on their own
completely, and merely full paths (without scheme or hostname
components) or other types of relative URLs, which may or may not need
munging.
This is not something that's isolated to the media thing you're talking
about. We have a few "absolute URL" bugs in various places
(HttpResponseRedirect being the big example, syndication being another
area) and handling those smoothly is something I've been thinking about.
> However, that said, I'm not completely opposed, and I can't
> think of an obvious better solution. I mostly just want to make sure
> that I fully understand Ivan's use cases, so we can make sure that
> there isn't a better solution lurking around.
Fair enough. Personally, I feel that in most places Django handles URL
construction, it should be watching out for absolute URLs and not
damaging them and that's not always happening. So this particular
discussion isn't an isolated case; it's more tip-of-the-iceberg stuff.
It's something I'll smooth out as a side project before 1.0; we can do
it little bits at a time without any real disruption.
Regards,
Malcolm
My use case was that I just had STYLE_ROOT and JS_ROOT that both were
completely independent on MEDIA_ROOT. I don't remember exactly why it
was this way :-). Now I think that we can just reqire users to keep
their styles&scripts under MEDIA_URL since you one always alias URLs in
a web server onto whatever physical location it suits.
But Malcolm's use case is anyway better than mine.
-1 on this alteration -- it makes things less flexible:
- I might want to create MyWidget(fancy=true) which would return
different values for media()
- I might want to have a widget which subclasses an existing widget and
wraps the media() method of its parent class, adding some extra values.
Did I miss something about an why inner classes are more useful? In
general they just seem more awkward to me -- they are useful in Models
because of the way the metaclass uses them before any instances of the
models exist.
Luke
--
"If you're not part of the solution, you're part of the precipitate."
(Steven Wright)
Luke Plant || L.Plant.98 (at) cantab.net || http://lukeplant.me.uk/
I had this reaction initially - but then I came to the conclusion that
extension of that type isn't really a likely use case.
A widget is ultimately a form <input>, with some javascript/css to
help with data entry (e.g., a popup calendar). If you want slightly
different behaviour or appearance, you will need to specify different
javascript or css.
The only time inheritance would be useful is if you wanted to
remove/replace one javascript/css file from a (long) list with
another. However, as pointed out earlier in this thread, the 'lots of
little files' approach isn't conducive to good performance. For short
lists of js/css dependencies, redefining the media block would be just
as clear (if not more so), and just as efficient.
> Did I miss something about an why inner classes are more useful? In
> general they just seem more awkward to me -- they are useful in Models
> because of the way the metaclass uses them before any instances of the
> models exist.
The big reason is clarity of representation. The Widget gains a domain
specific language for specifying media requirements. This DSL is
terse, it doesn't require any additional imports, and it doesn't
require the definition of a method that returns a specific type of
data; it just becomes part of the way you specify the full
requirements of a widget.
The DSL approach also allows for input validation, in a way that is
difficult with a method. The DSL checks that the Media definition is
valid at time of Widget construction, rather than a possibly ambiguous
failure at runtime.
Yours,
Russ Magee %-)
I've just opened a ticket for Media descriptors, and uploaded a patch
implementing the API that has emerged from this thread.
http://code.djangoproject.com/ticket/4418
Hopefully, between this thread and the regression test, usage is obvious.
Feedback is welcome.
Yours,
Russ Magee %-)
+1 from me.
Jacob
Hmm, I'm worrying about the case when the author of the parent widget
decides there is an extra dependency and adds it in. The subclass now
is missing a file -- this is the classic scenario that inheritance
prevents. Because of this, the size of the list of dependencies seems
irrelevant to me.
I don't think the subclassing scenario which adds/replaces a file is
that unlikely a use case. There may be perfectly good reasons to do so
e.g. adding some scriptaculous-type library that you don't want to load
every time, nor include in your own javascript files. Of course, there
are still ways to do it using the inner class, but if you add in the
theme scenario, where you potentially want each instance of the widget
to have different dependencies, and to decide them at runtime (e.g.
based on user's prefs, base URL of request etc) then you have bigger
problems.
If we are saying that the 'lots of little files' approach is a bad idea
that we don't need to be concerned about, why are we even adding this
feature? The ideal situation is that we analyse all the dependencies
in advance, create one CSS file and one JS file to cope with them, and
just include those. It seems strange to be adding a method that is
supposed to give flexibility and then arbitrarily limit each widget so
that they cannot vary on a per instance basis.
Another use case: suppose the subclass widget needs some javascript,
fancywidgets.js. It reads its parents dependencies, finds calendar.js,
and replaces it with fancywidgets_plus_calendar.js, reducing a
performance problem (OK, contrived, but could be useful in some
circumstances).
Again: think localisation -- media() could respond to the language
settings of the user/request, which could affect both CSS and
javascript (I'm out of touch with the current Django way of doing
things in this area, so it might not be an issue, but we should
remember that people might not want to do things 'the Django way').
I've had to add hacks in before to get around the inflexibility of inner
classes. In those cases, there was good reason for those inner
classes -- Django needs to be able to get at the data they contained
before any instances of the outer class existed. I can't see the
corresponding reasons here, and I'm not at all convinced that the
neatness of representation of a DSL outweighs the functionality
disadvantages.
Luke
--
"If your parents never had children, the chances are you won't either."
> I've had to add hacks in before to get around the inflexibility of
> inner classes.
It looks like my hack for this will no longer be needed in
newforms-admin. That's because:
class MyModel:
...
class Admin:
fields = ....
has turned into:
class ModelAdmin:
def fieldsets():
...
I think that's a pretty powerful argument.
Regards,
The reason for that change was to get Admin options out of the Model.
Ultimately, the goal is to have Models be purely definitions of data.
The Admin inner class was standing in the way of that.
Yours,
Russ Magee %-)
You have some reasonable points here.
I've just uploaded a new version of the patch that hopefully addresses
your concerns. This new version preserves the DSL for the simple case,
but if you want to manually define a media property to perform
inheritance based changes, or if you want to select media based on
some programmatic construct (such as locale), you can; a manually
defined media property takes precedent over a DSL defined one. See the
regression test for examples of usage.
Does this satisfy your concerns?
Yours,
Russ Magee %-)
> I've just uploaded a new version of the patch that hopefully
> addresses your concerns. This new version preserves the DSL for the
> simple case, but if you want to manually define a media property to
> perform inheritance based changes, or if you want to select media
> based on some programmatic construct (such as locale), you can; a
> manually defined media property takes precedent over a DSL defined
> one. See the regression test for examples of usage.
>
> Does this satisfy your concerns?
That sounds great, thanks. I'm not personally that worried about this
feature (I'm not making great plans around Widgets at the moment for
myself), but just concerned that we won't be making an API for v 1.0
that will be needlessly inflexible.
Best regards,
Luke
--
"I have had a perfectly lovely evening. However, this wasn't it."
(Groucho Marx)