mirror of
https://github.com/django/django.git
synced 2024-12-27 11:35:53 +00:00
b784768eef
Thanks to J.V. Zammit, Paolo Melchiorre, and Mariusz Felisiak for
reviews.
Backport of 534ac48297
from main.
439 lines
16 KiB
Plaintext
439 lines
16 KiB
Plaintext
=================================
|
|
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 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:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> w = CalendarWidget()
|
|
>>> print(w.media)
|
|
<link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
|
|
<script src="http://static.example.com/animations.js"></script>
|
|
<script 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:
|
|
|
|
.. code-block:: html+django
|
|
|
|
<link href="http://static.example.com/pretty.css" media="screen" rel="stylesheet">
|
|
<link href="http://static.example.com/lo_res.css" media="tv,projector" rel="stylesheet">
|
|
<link href="http://static.example.com/newspaper.css" media="print" rel="stylesheet">
|
|
|
|
.. versionchanged:: 4.1
|
|
|
|
In older versions, the ``type="text/css"`` attribute is included in CSS
|
|
links.
|
|
|
|
``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:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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" media="all" rel="stylesheet">
|
|
<link href="http://static.example.com/fancy.css" media="all" rel="stylesheet">
|
|
<script src="http://static.example.com/animations.js"></script>
|
|
<script src="http://static.example.com/actions.js"></script>
|
|
<script 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:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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" media="all" rel="stylesheet">
|
|
<script 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 as strings
|
|
----------------
|
|
|
|
String 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``:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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" media="all" rel="stylesheet">
|
|
<script src="http://uploads.example.com/animations.js"></script>
|
|
<script src="http://othersite.com/actions.js"></script>
|
|
|
|
But if :setting:`STATIC_URL` is ``'http://static.example.com/'``:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> w = CalendarWidget()
|
|
>>> print(w.media)
|
|
<link href="/css/pretty.css" media="all" rel="stylesheet">
|
|
<script src="http://static.example.com/animations.js"></script>
|
|
<script src="http://othersite.com/actions.js"></script>
|
|
|
|
Or if :mod:`~django.contrib.staticfiles` is configured using the
|
|
:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> w = CalendarWidget()
|
|
>>> print(w.media)
|
|
<link href="/css/pretty.css" media="all" rel="stylesheet">
|
|
<script src="https://static.example.com/animations.27e20196a850.js"></script>
|
|
<script src="http://othersite.com/actions.js"></script>
|
|
|
|
Paths as objects
|
|
----------------
|
|
|
|
.. versionadded:: 4.1
|
|
|
|
Asset paths may also be given as hashable objects implementing an
|
|
``__html__()`` method. The ``__html__()`` method is typically added using the
|
|
:func:`~django.utils.html.html_safe` decorator. The object is responsible for
|
|
outputting the complete HTML ``<script>`` or ``<link>`` tag content:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> from django import forms
|
|
>>> from django.utils.html import html_safe
|
|
>>>
|
|
>>> @html_safe
|
|
>>> class JSPath:
|
|
... def __str__(self):
|
|
... return '<script src="https://example.org/asset.js" rel="stylesheet">'
|
|
|
|
>>> class SomeWidget(forms.TextInput):
|
|
... class Media:
|
|
... js = [JSPath()]
|
|
|
|
``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:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> w = CalendarWidget()
|
|
>>> print(w.media)
|
|
<link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
|
|
<script src="http://static.example.com/animations.js"></script>
|
|
<script src="http://static.example.com/actions.js"></script>
|
|
|
|
>>> print(w.media['css'])
|
|
<link href="http://static.example.com/pretty.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:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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" media="all" rel="stylesheet">
|
|
<script src="http://static.example.com/animations.js"></script>
|
|
<script src="http://static.example.com/actions.js"></script>
|
|
<script 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:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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 src="http://static.example.com/jQuery.js"></script>
|
|
<script src="http://static.example.com/calendar.js"></script>
|
|
<script src="http://static.example.com/time.js"></script>
|
|
<script 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:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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" media="all" rel="stylesheet">
|
|
<script src="http://static.example.com/animations.js"></script>
|
|
<script src="http://static.example.com/actions.js"></script>
|
|
<script 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:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> 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" media="all" rel="stylesheet">
|
|
<link href="http://static.example.com/layout.css" media="all" rel="stylesheet">
|
|
<script src="http://static.example.com/animations.js"></script>
|
|
<script src="http://static.example.com/actions.js"></script>
|
|
<script src="http://static.example.com/whizbang.js"></script>
|