summaryrefslogtreecommitdiff
path: root/docs/topics/forms/media.txt
blob: c88679dda640927976d3113a53e7e49370edc5e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
=================================
Form Assets (the ``Media`` class)
=================================

Rendering an attractive and easy-to-use Web form requires more than just
HTML - it also requires CSS stylesheets, and if you want to use fancy
"Web2.0" widgets, you may also need to include some JavaScript on each
page. The exact combination of CSS and JavaScript that is required for
any given page will depend upon the widgets that are in use on that page.

This is where asset definitions come in. Django allows you to
associate different files -- like stylesheets and scripts -- with the
forms and widgets that require those assets. For example, if you want
to use a calendar to render DateFields, you can define a custom
Calendar widget. This widget can then be associated with the CSS and
JavaScript that is required to render the calendar. When the Calendar
widget is used on a form, Django is able to identify the CSS and
JavaScript files that are required, and provide the list of file names
in a form suitable for inclusion on your Web page.

.. admonition:: Assets and Django Admin

    The Django Admin application defines a number of customized
    widgets for calendars, filtered selections, and so on. These
    widgets define asset requirements, and the Django Admin uses the
    custom widgets in place of the Django defaults. The Admin
    templates will only include those files that are required to
    render the widgets on any given page.

    If you like the widgets that the Django Admin application uses,
    feel free to use them in your own application! They're all stored
    in ``django.contrib.admin.widgets``.

.. admonition:: Which JavaScript toolkit?

    Many JavaScript toolkits exist, and many of them include widgets (such
    as calendar widgets) that can be used to enhance your application.
    Django has deliberately avoided blessing any one JavaScript toolkit.
    Each toolkit has its own relative strengths and weaknesses - use
    whichever toolkit suits your requirements. Django is able to integrate
    with any JavaScript toolkit.

.. _assets-as-a-static-definition:

Assets as a static definition
=============================

The easiest way to define assets is as a static definition. Using this
method, the declaration is an inner ``Media`` class. The properties of the
inner class define the requirements.

Here's an example::

    from django import forms

    class CalendarWidget(forms.TextInput):
        class Media:
            css = {
                'all': ('pretty.css',)
            }
            js = ('animations.js', 'actions.js')

This code defines a ``CalendarWidget``, which will be based on ``TextInput``.
Every time the CalendarWidget is used on a form, that form will be directed
to include the CSS file ``pretty.css``, and the JavaScript files
``animations.js`` and ``actions.js``.

This static definition is converted at runtime into a widget property
named ``media``. The list of assets for a ``CalendarWidget`` instance
can be retrieved through this property::

    >>> w = CalendarWidget()
    >>> print(w.media)
    <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://static.example.com/animations.js"></script>
    <script type="text/javascript" src="http://static.example.com/actions.js"></script>

Here's a list of all possible ``Media`` options. There are no required options.

``css``
-------

A dictionary describing the CSS files required for various forms of output
media.

The values in the dictionary should be a tuple/list of file names. See
:ref:`the section on paths <form-asset-paths>` for details of how to
specify paths to these files.

The keys in the dictionary are the output media types. These are the same
types accepted by CSS files in media declarations: 'all', 'aural', 'braille',
'embossed', 'handheld', 'print', 'projection', 'screen', 'tty' and 'tv'. If
you need to have different stylesheets for different media types, provide
a list of CSS files for each output medium. The following example would
provide two CSS options -- one for the screen, and one for print::

    class Media:
        css = {
            'screen': ('pretty.css',),
            'print': ('newspaper.css',)
        }

If a group of CSS files are appropriate for multiple output media types,
the dictionary key can be a comma separated list of output media types.
In the following example, TV's and projectors will have the same media
requirements::

    class Media:
        css = {
            'screen': ('pretty.css',),
            'tv,projector': ('lo_res.css',),
            'print': ('newspaper.css',)
        }

If this last CSS definition were to be rendered, it would become the following HTML::

    <link href="http://static.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet">
    <link href="http://static.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet">
    <link href="http://static.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet">

``js``
------

A tuple describing the required JavaScript files. See :ref:`the
section on paths <form-asset-paths>` for details of how to specify
paths to these files.

``extend``
----------

A boolean defining inheritance behavior for ``Media`` declarations.

By default, any object using a static ``Media`` definition will
inherit all the assets associated with the parent widget. This occurs
regardless of how the parent defines its own requirements. For
example, if we were to extend our basic Calendar widget from the
example above::

    >>> class FancyCalendarWidget(CalendarWidget):
    ...     class Media:
    ...         css = {
    ...             'all': ('fancy.css',)
    ...         }
    ...         js = ('whizbang.js',)

    >>> w = FancyCalendarWidget()
    >>> print(w.media)
    <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet">
    <link href="http://static.example.com/fancy.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://static.example.com/animations.js"></script>
    <script type="text/javascript" src="http://static.example.com/actions.js"></script>
    <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

The FancyCalendar widget inherits all the assets from its parent
widget. If you don't want ``Media`` to be inherited in this way, add
an ``extend=False`` declaration to the ``Media`` declaration::

    >>> class FancyCalendarWidget(CalendarWidget):
    ...     class Media:
    ...         extend = False
    ...         css = {
    ...             'all': ('fancy.css',)
    ...         }
    ...         js = ('whizbang.js',)

    >>> w = FancyCalendarWidget()
    >>> print(w.media)
    <link href="http://static.example.com/fancy.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

If you require even more control over inheritance, define your assets using a
:ref:`dynamic property <dynamic-property>`. Dynamic properties give you
complete control over which files are inherited, and which are not.

.. _dynamic-property:

``Media`` as a dynamic property
===============================

If you need to perform some more sophisticated manipulation of asset
requirements, you can define the ``media`` property directly. This is
done by defining a widget property that returns an instance of
``forms.Media``.  The constructor for ``forms.Media`` accepts ``css``
and ``js`` keyword arguments in the same format as that used in a
static media definition.

For example, the static definition for our Calendar Widget could also
be defined in a dynamic fashion::

    class CalendarWidget(forms.TextInput):
        @property
        def media(self):
            return forms.Media(css={'all': ('pretty.css',)},
                               js=('animations.js', 'actions.js'))

See the section on `Media objects`_ for more details on how to construct
return values for dynamic ``media`` properties.

.. _form-asset-paths:

Paths in asset definitions
==========================

Paths used to specify assets can be either relative or absolute. If a
path starts with ``/``, ``http://`` or ``https://``, it will be
interpreted as an absolute path, and left as-is. All other paths will
be prepended with the value of the appropriate prefix. If the
:mod:`django.contrib.staticfiles` app is installed, it will be used to serve
assets.

Whether or not you use :mod:`django.contrib.staticfiles`,  the
:setting:`STATIC_URL` and :setting:`STATIC_ROOT` settings are required to
render a complete web page.

To find the appropriate prefix to use, Django will check if the
:setting:`STATIC_URL` setting is not ``None`` and automatically fall back
to using :setting:`MEDIA_URL`. For example, if the :setting:`MEDIA_URL` for
your site was ``'http://uploads.example.com/'`` and :setting:`STATIC_URL`
was ``None``::

    >>> from django import forms
    >>> class CalendarWidget(forms.TextInput):
    ...     class Media:
    ...         css = {
    ...             'all': ('/css/pretty.css',),
    ...         }
    ...         js = ('animations.js', 'http://othersite.com/actions.js')

    >>> w = CalendarWidget()
    >>> print(w.media)
    <link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://uploads.example.com/animations.js"></script>
    <script type="text/javascript" src="http://othersite.com/actions.js"></script>

But if :setting:`STATIC_URL` is ``'http://static.example.com/'``::

    >>> w = CalendarWidget()
    >>> print(w.media)
    <link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://static.example.com/animations.js"></script>
    <script type="text/javascript" src="http://othersite.com/actions.js"></script>

Or if :mod:`~django.contrib.staticfiles` is configured using the
:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`::

    >>> w = CalendarWidget()
    >>> print(w.media)
    <link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="https://static.example.com/animations.27e20196a850.js"></script>
    <script type="text/javascript" src="http://othersite.com/actions.js"></script>

``Media`` objects
=================

When you interrogate the ``media`` attribute of a widget or form, the
value that is returned is a ``forms.Media`` object. As we have already
seen, the string representation of a ``Media`` object is the HTML
required to include the relevant files in the ``<head>`` block of your
HTML page.

However, ``Media`` objects have some other interesting properties.

Subsets of assets
-----------------

If you only want files of a particular type, you can use the subscript
operator to filter out a medium of interest. For example::

    >>> w = CalendarWidget()
    >>> print(w.media)
    <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://static.example.com/animations.js"></script>
    <script type="text/javascript" src="http://static.example.com/actions.js"></script>

    >>> print(w.media['css'])
    <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet">

When you use the subscript operator, the value that is returned is a
new ``Media`` object -- but one that only contains the media of interest.

Combining ``Media`` objects
---------------------------

``Media`` objects can also be added together. When two ``Media`` objects are
added, the resulting ``Media`` object contains the union of the assets
specified by both::

    >>> from django import forms
    >>> class CalendarWidget(forms.TextInput):
    ...     class Media:
    ...         css = {
    ...             'all': ('pretty.css',)
    ...         }
    ...         js = ('animations.js', 'actions.js')

    >>> class OtherWidget(forms.TextInput):
    ...     class Media:
    ...         js = ('whizbang.js',)

    >>> w1 = CalendarWidget()
    >>> w2 = OtherWidget()
    >>> print(w1.media + w2.media)
    <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://static.example.com/animations.js"></script>
    <script type="text/javascript" src="http://static.example.com/actions.js"></script>
    <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

.. _form-media-asset-order:

Order of assets
---------------

The order in which assets are inserted into the DOM is often important. For
example, you may have a script that depends on jQuery. Therefore, combining
``Media`` objects attempts to preserve the relative order in which assets are
defined in each ``Media`` class.

For example::

    >>> from django import forms
    >>> class CalendarWidget(forms.TextInput):
    ...     class Media:
    ...         js = ('jQuery.js', 'calendar.js', 'noConflict.js')
    >>> class TimeWidget(forms.TextInput):
    ...     class Media:
    ...         js = ('jQuery.js', 'time.js', 'noConflict.js')
    >>> w1 = CalendarWidget()
    >>> w2 = TimeWidget()
    >>> print(w1.media + w2.media)
    <script type="text/javascript" src="http://static.example.com/jQuery.js"></script>
    <script type="text/javascript" src="http://static.example.com/calendar.js"></script>
    <script type="text/javascript" src="http://static.example.com/time.js"></script>
    <script type="text/javascript" src="http://static.example.com/noConflict.js"></script>

Combining ``Media`` objects with assets in a conflicting order results in a
``MediaOrderConflictWarning``.

``Media`` on Forms
==================

Widgets aren't the only objects that can have ``media`` definitions --
forms can also define ``media``. The rules for ``media`` definitions
on forms are the same as the rules for widgets: declarations can be
static or dynamic; path and inheritance rules for those declarations
are exactly the same.

Regardless of whether you define a ``media`` declaration, *all* Form
objects have a ``media`` property. The default value for this property
is the result of adding the ``media`` definitions for all widgets that
are part of the form::

    >>> from django import forms
    >>> class ContactForm(forms.Form):
    ...     date = DateField(widget=CalendarWidget)
    ...     name = CharField(max_length=40, widget=OtherWidget)

    >>> f = ContactForm()
    >>> f.media
    <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://static.example.com/animations.js"></script>
    <script type="text/javascript" src="http://static.example.com/actions.js"></script>
    <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

If you want to associate additional assets with a form -- for example,
CSS for form layout -- add a ``Media`` declaration to the form::

    >>> class ContactForm(forms.Form):
    ...     date = DateField(widget=CalendarWidget)
    ...     name = CharField(max_length=40, widget=OtherWidget)
    ...
    ...     class Media:
    ...         css = {
    ...             'all': ('layout.css',)
    ...         }

    >>> f = ContactForm()
    >>> f.media
    <link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet">
    <link href="http://static.example.com/layout.css" type="text/css" media="all" rel="stylesheet">
    <script type="text/javascript" src="http://static.example.com/animations.js"></script>
    <script type="text/javascript" src="http://static.example.com/actions.js"></script>
    <script type="text/javascript" src="http://static.example.com/whizbang.js"></script>