Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

newforms-admin: Fixed #4418 -- Added the ability for widgets, forms, …

…and Admin declarations to have media definitions.

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5926 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 549198b714ba0b032904639af0c388e739261fe6 1 parent 28e3503
Russell Keith-Magee freakboy3742 authored
78 django/contrib/admin/options.py
View
@@ -2,6 +2,7 @@
from django import newforms as forms
from django.newforms.formsets import all_valid
from django.newforms.models import inline_formset
+from django.newforms.widgets import Media, MediaDefiningClass
from django.contrib.admin import widgets
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.db import models
@@ -48,6 +49,13 @@ def __iter__(self):
def first_field(self):
for bf in self.form:
return bf
+
+ def _media(self):
+ media = self.form.media
+ for fs in self.fieldsets:
+ media = media + fs.media
+ return media
+ media = property(_media)
class Fieldset(object):
def __init__(self, name=None, fields=(), classes=(), description=None):
@@ -55,6 +63,13 @@ def __init__(self, name=None, fields=(), classes=(), description=None):
self.classes = u' '.join(classes)
self.description = description
+ def _media(self):
+ from django.conf import settings
+ if 'collapse' in self.classes:
+ return Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX])
+ return Media()
+ media = property(_media)
+
class BoundFieldset(object):
def __init__(self, form, fieldset):
self.form, self.fieldset = form, fieldset
@@ -123,12 +138,12 @@ def formfield_for_dbfield(self, db_field, **kwargs):
# For DateFields, add a custom CSS class.
if isinstance(db_field, models.DateField):
- kwargs['widget'] = forms.TextInput(attrs={'class': 'vDateField', 'size': '10'})
+ kwargs['widget'] = widgets.AdminDateWidget
return db_field.formfield(**kwargs)
# For TimeFields, add a custom CSS class.
if isinstance(db_field, models.TimeField):
- kwargs['widget'] = forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'})
+ kwargs['widget'] = widgets.AdminTimeWidget
return db_field.formfield(**kwargs)
# For ForeignKey or ManyToManyFields, use a special widget.
@@ -148,7 +163,8 @@ def formfield_for_dbfield(self, db_field, **kwargs):
class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model."
-
+ __metaclass__ = MediaDefiningClass
+
list_display = ('__str__',)
list_display_links = ()
list_filter = ()
@@ -159,7 +175,6 @@ class ModelAdmin(BaseModelAdmin):
save_as = False
save_on_top = False
ordering = None
- js = None
prepopulated_fields = {}
filter_vertical = ()
filter_horizontal = ()
@@ -194,38 +209,20 @@ def __call__(self, request, url):
else:
return self.change_view(request, unquote(url))
- def javascript(self, request, fieldsets):
- """
- Returns a list of URLs to include via <script> statements.
-
- The URLs can be absolute ('/js/admin/') or explicit
- ('http://example.com/foo.js').
- """
+ def _media(self):
from django.conf import settings
+
js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
if self.prepopulated_fields:
js.append('js/urlify.js')
- if self.opts.has_field_type(models.DateTimeField) or self.opts.has_field_type(models.TimeField) or self.opts.has_field_type(models.DateField):
- js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
if self.opts.get_ordered_objects():
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
- if self.js:
- js.extend(self.js)
if self.filter_vertical or self.filter_horizontal:
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
- for fs in fieldsets:
- if 'collapse' in fs.classes:
- js.append('js/admin/CollapsedFieldsets.js')
- break
- prefix = settings.ADMIN_MEDIA_PREFIX
- return ['%s%s' % (prefix, url) for url in js]
-
- def javascript_add(self, request):
- return self.javascript(request, self.fieldsets_add(request))
-
- def javascript_change(self, request, obj):
- return self.javascript(request, self.fieldsets_change(request, obj))
-
+
+ return Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
+ media = property(_media)
+
def fieldsets(self, request):
"""
Generator that yields Fieldset objects for use on add and change admin
@@ -244,13 +241,11 @@ def fieldsets(self, request):
def fieldsets_add(self, request):
"Hook for specifying Fieldsets for the add form."
- for fs in self.fieldsets(request):
- yield fs
+ return list(self.fieldsets(request))
def fieldsets_change(self, request, obj):
"Hook for specifying Fieldsets for the change form."
- for fs in self.fieldsets(request):
- yield fs
+ return list(self.fieldsets(request))
def has_add_permission(self, request):
"Returns True if the given request has permission to add an object."
@@ -430,12 +425,17 @@ def add_view(self, request, form_url=''):
inline_formset = FormSet()
inline_formsets.append(inline_formset)
+ adminForm = AdminForm(form, list(self.fieldsets_add(request)), self.prepopulated_fields)
+ media = self.media + adminForm.media
+ for fs in inline_formsets:
+ media = media + fs.media
+
c = template.RequestContext(request, {
'title': _('Add %s') % opts.verbose_name,
- 'adminform': AdminForm(form, self.fieldsets_add(request), self.prepopulated_fields),
+ 'adminform': adminForm,
'is_popup': request.REQUEST.has_key('_popup'),
'show_delete': False,
- 'javascript_imports': self.javascript_add(request),
+ 'media': media,
'bound_inlines': [BoundInline(i, fs) for i, fs in zip(self.inlines, inline_formsets)],
})
return render_change_form(self, model, model.AddManipulator(), c, add=True)
@@ -494,13 +494,19 @@ def change_view(self, request, object_id):
#related.get_accessor_name())
#orig_list = func()
#oldform.order_objects.extend(orig_list)
+
+ adminForm = AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields)
+ media = self.media + adminForm.media
+ for fs in inline_formsets:
+ media = media + fs.media
+
c = template.RequestContext(request, {
'title': _('Change %s') % opts.verbose_name,
- 'adminform': AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields),
+ 'adminform': adminForm,
'object_id': object_id,
'original': obj,
'is_popup': request.REQUEST.has_key('_popup'),
- 'javascript_imports': self.javascript_change(request, obj),
+ 'media': media,
'bound_inlines': [BoundInline(i, fs) for i, fs in zip(self.inlines, inline_formsets)],
})
return render_change_form(self, model, model.ChangeManipulator(object_id), c, change=True)
1  django/contrib/admin/templates/admin/auth/user/change_password.html
View
@@ -2,7 +2,6 @@
{% load i18n admin_modify adminmedia %}
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../../jsi18n/"></script>
-{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %}
{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
3  django/contrib/admin/templates/admin/change_form.html
View
@@ -3,8 +3,7 @@
{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../jsi18n/"></script>
-{% for js in javascript_imports %}<script type="text/javascript" src="{{ js }}"></script>
-{% endfor %}
+{{ media }}
{% endblock %}
{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
20 django/contrib/admin/widgets.py
View
@@ -5,6 +5,7 @@
from django import newforms as forms
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
+from django.conf import settings
class FilteredSelectMultiple(forms.SelectMultiple):
"""
@@ -28,13 +29,28 @@ def render(self, name, value, attrs=None, choices=()):
(name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
return u''.join(output)
+class AdminDateWidget(forms.TextInput):
+ class Media:
+ js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
+ settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
+
+ def __init__(self, attrs={}):
+ super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
+
+class AdminTimeWidget(forms.TextInput):
+ class Media:
+ js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
+ settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
+
+ def __init__(self, attrs={}):
+ super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
+
class AdminSplitDateTime(forms.SplitDateTimeWidget):
"""
A SplitDateTime Widget that has some admin-specific styling.
"""
def __init__(self, attrs=None):
- widgets = [forms.TextInput(attrs={'class': 'vDateField', 'size': '10'}),
- forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'})]
+ widgets = [AdminDateWidget, AdminTimeWidget]
# Note that we're calling MultiWidget, not SplitDateTimeWidget, because
# we want to define widgets.
forms.MultiWidget.__init__(self, widgets, attrs)
19 django/newforms/forms.py
View
@@ -9,7 +9,7 @@
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
from fields import Field
-from widgets import TextInput, Textarea
+from widgets import Media, media_property, TextInput, Textarea
from util import flatatt, ErrorDict, ErrorList, ValidationError
__all__ = ('BaseForm', 'Form')
@@ -37,6 +37,7 @@ class DeclarativeFieldsMetaclass(type):
"""
Metaclass that converts Field attributes to a dictionary called
'base_fields', taking into account parent class 'base_fields' as well.
+ Also integrates any additional media definitions
"""
def __new__(cls, name, bases, attrs):
fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
@@ -50,7 +51,11 @@ def __new__(cls, name, bases, attrs):
fields = base.base_fields.items() + fields
attrs['base_fields'] = SortedDictFromList(fields)
- return type.__new__(cls, name, bases, attrs)
+
+ new_class = type.__new__(cls, name, bases, attrs)
+ if 'media' not in attrs:
+ new_class.media = media_property(new_class)
+ return new_class
class BaseForm(StrAndUnicode):
# This is the main implementation of all the Form logic. Note that this
@@ -235,6 +240,16 @@ def reset(self):
self.is_bound = False
self.__errors = None
+ def _get_media(self):
+ """
+ Provide a description of all media required to render the widgets on this form
+ """
+ media = Media()
+ for field in self.fields.values():
+ media = media + field.widget.media
+ return media
+ media = property(_get_media)
+
class Form(BaseForm):
"A collection of Fields, plus their associated data."
# This is a separate class from BaseForm in order to abstract the way
11 django/newforms/formsets.py
View
@@ -1,6 +1,6 @@
from forms import Form, ValidationError
from fields import IntegerField, BooleanField
-from widgets import HiddenInput
+from widgets import HiddenInput, Media
# special field names
FORM_COUNT_FIELD_NAME = 'COUNT'
@@ -154,6 +154,15 @@ def is_valid(self):
self.full_clean()
return self._is_valid
+ def _get_media(self):
+ # All the forms on a FormSet are the same, so you only need to
+ # interrogate the first form for media.
+ if self.forms:
+ return self.forms[0].media
+ else:
+ return Media()
+ media = property(_get_media)
+
def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False):
"""Return a FormSet for the given form class."""
attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
111 django/newforms/widgets.py
View
@@ -8,6 +8,7 @@
from sets import Set as set # Python 2.3 fallback
from itertools import chain
+from django.conf import settings
from django.utils.datastructures import MultiValueDict
from django.utils.html import escape
from django.utils.translation import ugettext
@@ -15,14 +16,113 @@
from util import flatatt
__all__ = (
- 'Widget', 'TextInput', 'PasswordInput',
+ 'Media', 'Widget', 'TextInput', 'PasswordInput',
'HiddenInput', 'MultipleHiddenInput',
'FileInput', 'Textarea', 'CheckboxInput',
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
)
+MEDIA_TYPES = ('css','js')
+
+class Media(StrAndUnicode):
+ def __init__(self, media=None, **kwargs):
+ if media:
+ media_attrs = media.__dict__
+ else:
+ media_attrs = kwargs
+
+ self._css = {}
+ self._js = []
+
+ for name in MEDIA_TYPES:
+ getattr(self, 'add_' + name)(media_attrs.get(name, None))
+
+ # Any leftover attributes must be invalid.
+ # if media_attrs != {}:
+ # raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())
+
+ def __unicode__(self):
+ return self.render()
+
+ def render(self):
+ return u'\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))
+
+ def render_js(self):
+ return [u'<script type="text/javascript" src="%s"></script>' % self.absolute_path(path) for path in self._js]
+
+ def render_css(self):
+ # To keep rendering order consistent, we can't just iterate over items().
+ # We need to sort the keys, and iterate over the sorted list.
+ media = self._css.keys()
+ media.sort()
+ return chain(*[
+ [u'<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium)
+ for path in self._css[medium]]
+ for medium in media])
+
+ def absolute_path(self, path):
+ return (path.startswith(u'http://') or path.startswith(u'https://')) and path or u''.join([settings.MEDIA_URL,path])
+
+ def __getitem__(self, name):
+ "Returns a Media object that only contains media of the given type"
+ if name in MEDIA_TYPES:
+ return Media(**{name: getattr(self, '_' + name)})
+ raise KeyError('Unknown media type "%s"' % name)
+
+ def add_js(self, data):
+ if data:
+ self._js.extend([path for path in data if path not in self._js])
+
+ def add_css(self, data):
+ if data:
+ for medium, paths in data.items():
+ self._css.setdefault(medium, []).extend([path for path in paths if path not in self._css[medium]])
+
+ def __add__(self, other):
+ combined = Media()
+ for name in MEDIA_TYPES:
+ getattr(combined, 'add_' + name)(getattr(self, '_' + name, None))
+ getattr(combined, 'add_' + name)(getattr(other, '_' + name, None))
+ return combined
+
+def media_property(cls):
+ def _media(self):
+ # Get the media property of the superclass, if it exists
+ if hasattr(super(cls, self), 'media'):
+ base = super(cls, self).media
+ else:
+ base = Media()
+
+ # Get the media definition for this class
+ definition = getattr(cls, 'Media', None)
+ if definition:
+ extend = getattr(definition, 'extend', True)
+ if extend:
+ if extend == True:
+ m = base
+ else:
+ m = Media()
+ for medium in extend:
+ m = m + base[medium]
+ m = m + Media(definition)
+ return m + Media(definition)
+ else:
+ return Media(definition)
+ else:
+ return base
+ return property(_media)
+
+class MediaDefiningClass(type):
+ "Metaclass for classes that can have media definitions"
+ def __new__(cls, name, bases, attrs):
+ new_class = type.__new__(cls, name, bases, attrs)
+ if 'media' not in attrs:
+ new_class.media = media_property(new_class)
+ return new_class
+
class Widget(object):
+ __metaclass__ = MediaDefiningClass
is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
def __init__(self, attrs=None):
@@ -405,6 +505,14 @@ def decompress(self, value):
"""
raise NotImplementedError('Subclasses must implement this method.')
+ def _get_media(self):
+ "Media for a multiwidget is the combination of all media of the subwidgets"
+ media = Media()
+ for w in self.widgets:
+ media = media + w.media
+ return media
+ media = property(_get_media)
+
class SplitDateTimeWidget(MultiWidget):
"""
A Widget that splits datetime input into two <input type="text"> boxes.
@@ -417,3 +525,4 @@ def decompress(self, value):
if value:
return [value.date(), value.time()]
return [None, None]
+
309 docs/newforms.txt
View
@@ -76,6 +76,9 @@ The library deals with these concepts:
* **Form** -- A collection of fields that knows how to validate itself and
display itself as HTML.
+ * **Media** -- A definition of the CSS and JavaScript resources that are
+ required to render a form.
+
The library is decoupled from the other Django components, such as the database
layer, views and templates. It relies only on Django settings, a couple of
``django.utils`` helper functions and Django's internationalization hooks (but
@@ -1940,6 +1943,312 @@ more than one model, or a form that contains fields that *aren't* on a model,
you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way
isn't that difficult, after all.
+Media
+=====
+
+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 Django media definitions come in. Django allows you to
+associate different media files with the forms and widgets that require
+that media. 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 easy inclusion on your web page.
+
+.. admonition:: Media and Django Admin
+
+ The Django Admin application defines a number of customized widgets
+ for calendars, filtered selections, and so on. These widgets define
+ media requirements, and the Django Admin uses the custom widgets
+ in place of the Django defaults. The Admin templates will only include
+ those media 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.
+
+Media as a static definition
+----------------------------
+
+The easiest way to define media is as a static definition. Using this method,
+the media declaration is an inner class. The properties of the inner class
+define the media requirements.
+
+Here's a simple example::
+
+ 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 media definition is converted at runtime into a widget property
+named ``media``. The media for a CalendarWidget instance can be retrieved
+through this property::
+
+ >>> w = CalendarWidget()
+ >>> print w.media
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.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
+`the section on media paths`_ for details of how to specify paths to media
+files.
+
+.. _the section on media paths: `Paths in media definitions`_
+
+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://media.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet" />
+ <link href="http://media.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" />
+ <link href="http://media.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />
+
+``js``
+~~~~~~
+
+A tuple describing the required javascript files. See
+`the section on media paths`_ for details of how to specify paths to media
+files.
+
+``extend``
+~~~~~~~~~~
+
+A boolean defining inheritance behavior for media declarations.
+
+By default, any object using a static media definition will inherit all the
+media associated with the parent widget. This occurs regardless of how the
+parent defines its media 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://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+The FancyCalendar widget inherits all the media from it's parent widget. If
+you don't want media to be inherited in this way, add an ``extend=False``
+declaration to the media declaration::
+
+ class FancyCalendar(Calendar):
+ class Media:
+ extend = False
+ css = {
+ 'all': ('fancy.css',)
+ }
+ js = ('whizbang.js',)
+
+ >>> w = FancyCalendarWidget()
+ >>> print w.media
+ <link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+If you require even more control over media inheritance, define your media
+using a `dynamic property`_. Dynamic properties give you complete control over
+which media files are inherited, and which are not.
+
+.. _dynamic property: `Media as a dynamic property`_
+
+Media as a dynamic property
+---------------------------
+
+If you need to perform some more sophisticated manipulation of media
+requirements, you can define the media property directly. This is done
+by defining a model 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 media definition for our Calendar Widget could
+also be defined in a dynamic fashion::
+
+ class CalendarWidget(forms.TextInput):
+ def _media(self):
+ return forms.Media(css={'all': ('pretty.css',)},
+ js=('animations.js', 'actions.js'))
+ media = property(_media)
+
+See the section on `Media objects`_ for more details on how to construct
+return values for dynamic media properties.
+
+Paths in media definitions
+--------------------------
+
+Paths used to specify media 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
+``settings.MEDIA_URL``. For example, if the MEDIA_URL for your site was
+``http://media.example.com/``::
+
+ class CalendarWidget(forms.TextInput):
+ class Media:
+ js = ('animations.js', 'http://othersite.com/actions.js')
+
+ >>> w = CalendarWidget()
+ >>> print w.media
+ <script type="text/javascript" src="http://media.example.com/animations.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 media
+in the ``<head>`` block of your HTML page.
+
+However, Media objects have some other interesting properties.
+
+Media subsets
+~~~~~~~~~~~~~
+
+If you only want media 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://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+
+ >>> print w.media['css']
+ <link href="http://media.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 media from both files::
+
+ 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+w2
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+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::
+
+ class ContactForm(forms.Form):
+ date = DateField(widget=CalendarWidget)
+ name = CharField(max_length=40, widget=OtherWidget)
+
+ >>> f = ContactForm()
+ >>> f.media
+ <link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
+If you want to associate additional media with a form -- for example, CSS for form
+layout -- simply 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://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
+ <link href="http://media.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
+ <script type="text/javascript" src="http://media.example.com/animations.js"></script>
+ <script type="text/javascript" src="http://media.example.com/actions.js"></script>
+ <script type="text/javascript" src="http://media.example.com/whizbang.js"></script>
+
More coming soon
================
357 tests/regressiontests/forms/media.py
View
@@ -0,0 +1,357 @@
+# -*- coding: utf-8 -*-
+# Tests for the media handling on widgets and forms
+
+media_tests = r"""
+>>> from django.newforms import TextInput, Media, TextInput, CharField, Form, MultiWidget
+>>> from django.conf import settings
+>>> settings.MEDIA_URL = 'http://media.example.com'
+
+# Check construction of media objects
+>>> m = Media(css={'all': ('/path/to/css1','/path/to/css2')}, js=('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3'))
+>>> print m
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+>>> class Foo:
+... css = {
+... 'all': ('/path/to/css1','/path/to/css2')
+... }
+... js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+>>> m3 = Media(Foo)
+>>> print m3
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+>>> m3 = Media(Foo)
+>>> print m3
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# A widget can exist without a media definition
+>>> class MyWidget(TextInput):
+... pass
+
+>>> w = MyWidget()
+>>> print w.media
+<BLANKLINE>
+
+###############################################################
+# DSL Class-based media definitions
+###############################################################
+
+# A widget can define media if it needs to.
+# Any absolute path will be preserved; relative paths are combined
+# with the value of settings.MEDIA_URL
+>>> class MyWidget1(TextInput):
+... class Media:
+... css = {
+... 'all': ('/path/to/css1','/path/to/css2')
+... }
+... js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
+
+>>> w1 = MyWidget1()
+>>> print w1.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# Media objects can be interrogated by media type
+>>> print w1.media['css']
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+
+>>> print w1.media['js']
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# Media objects can be combined. Any given media resource will appear only
+# once. Duplicated media definitions are ignored.
+>>> class MyWidget2(TextInput):
+... class Media:
+... css = {
+... 'all': ('/path/to/css2','/path/to/css3')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> class MyWidget3(TextInput):
+... class Media:
+... css = {
+... 'all': ('/path/to/css3','/path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w2 = MyWidget2()
+>>> w3 = MyWidget3()
+>>> print w1.media + w2.media + w3.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# Check that media addition hasn't affected the original objects
+>>> print w1.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+###############################################################
+# Property-based media definitions
+###############################################################
+
+# Widget media can be defined as a property
+>>> class MyWidget4(TextInput):
+... def _media(self):
+... return Media(css={'all': ('/some/path',)}, js = ('/some/js',))
+... media = property(_media)
+
+>>> w4 = MyWidget4()
+>>> print w4.media
+<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/some/js"></script>
+
+# Media properties can reference the media of their parents
+>>> class MyWidget5(MyWidget4):
+... def _media(self):
+... return super(MyWidget5, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
+... media = property(_media)
+
+>>> w5 = MyWidget5()
+>>> print w5.media
+<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/some/js"></script>
+<script type="text/javascript" src="http://media.example.com/other/js"></script>
+
+# Media properties can reference the media of their parents,
+# even if the parent media was defined using a class
+>>> class MyWidget6(MyWidget1):
+... def _media(self):
+... return super(MyWidget6, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
+... media = property(_media)
+
+>>> w6 = MyWidget6()
+>>> print w6.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/other/js"></script>
+
+###############################################################
+# Inheritance of media
+###############################################################
+
+# If a widget extends another but provides no media definition, it inherits the parent widget's media
+>>> class MyWidget7(MyWidget1):
+... pass
+
+>>> w7 = MyWidget7()
+>>> print w7.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# If a widget extends another but defines media, it extends the parent widget's media by default
+>>> class MyWidget8(MyWidget1):
+... class Media:
+... css = {
+... 'all': ('/path/to/css3','/path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w8 = MyWidget8()
+>>> print w8.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# If a widget extends another but defines media, it extends the parents widget's media,
+# even if the parent defined media using a property.
+>>> class MyWidget9(MyWidget4):
+... class Media:
+... css = {
+... 'all': ('/other/path',)
+... }
+... js = ('/other/js',)
+
+>>> w9 = MyWidget9()
+>>> print w9.media
+<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/other/path" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/some/js"></script>
+<script type="text/javascript" src="http://media.example.com/other/js"></script>
+
+# A widget can disable media inheritance by specifying 'extend=False'
+>>> class MyWidget10(MyWidget1):
+... class Media:
+... extend = False
+... css = {
+... 'all': ('/path/to/css3','/path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w10 = MyWidget10()
+>>> print w10.media
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# A widget can explicitly enable full media inheritance by specifying 'extend=True'
+>>> class MyWidget11(MyWidget1):
+... class Media:
+... extend = True
+... css = {
+... 'all': ('/path/to/css3','/path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w11 = MyWidget11()
+>>> print w11.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# A widget can enable inheritance of one media type by specifying extend as a tuple
+>>> class MyWidget12(MyWidget1):
+... class Media:
+... extend = ('css',)
+... css = {
+... 'all': ('/path/to/css3','/path/to/css1')
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> w12 = MyWidget12()
+>>> print w12.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+###############################################################
+# Multi-media handling for CSS
+###############################################################
+
+# A widget can define CSS media for multiple output media types
+>>> class MultimediaWidget(TextInput):
+... class Media:
+... css = {
+... 'screen, print': ('/file1','/file2'),
+... 'screen': ('/file3',),
+... 'print': ('/file4',)
+... }
+... js = ('/path/to/js1','/path/to/js4')
+
+>>> multimedia = MultimediaWidget()
+>>> print multimedia.media
+<link href="http://media.example.com/file4" type="text/css" media="print" rel="stylesheet" />
+<link href="http://media.example.com/file3" type="text/css" media="screen" rel="stylesheet" />
+<link href="http://media.example.com/file1" type="text/css" media="screen, print" rel="stylesheet" />
+<link href="http://media.example.com/file2" type="text/css" media="screen, print" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+###############################################################
+# Multiwidget media handling
+###############################################################
+
+# MultiWidgets have a default media definition that gets all the
+# media from the component widgets
+>>> class MyMultiWidget(MultiWidget):
+... def __init__(self, attrs=None):
+... widgets = [MyWidget1, MyWidget2, MyWidget3]
+... super(MyMultiWidget, self).__init__(widgets, attrs)
+
+>>> mymulti = MyMultiWidget()
+>>> print mymulti.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+###############################################################
+# Media processing for forms
+###############################################################
+
+# You can ask a form for the media required by its widgets.
+>>> class MyForm(Form):
+... field1 = CharField(max_length=20, widget=MyWidget1())
+... field2 = CharField(max_length=20, widget=MyWidget2())
+>>> f1 = MyForm()
+>>> print f1.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# Form media can be combined to produce a single media definition.
+>>> class AnotherForm(Form):
+... field3 = CharField(max_length=20, widget=MyWidget3())
+>>> f2 = AnotherForm()
+>>> print f1.media + f2.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# Forms can also define media, following the same rules as widgets.
+>>> class FormWithMedia(Form):
+... field1 = CharField(max_length=20, widget=MyWidget1())
+... field2 = CharField(max_length=20, widget=MyWidget2())
+... class Media:
+... js = ('/some/form/javascript',)
+... css = {
+... 'all': ('/some/form/css',)
+... }
+>>> f3 = FormWithMedia()
+>>> print f3.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/some/form/css" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+<script type="text/javascript" src="http://media.example.com/some/form/javascript"></script>
+
+"""
2  tests/regressiontests/forms/tests.py
View
@@ -2,6 +2,7 @@
from localflavor import localflavor_tests
from regressions import regression_tests
from formsets import formset_tests
+from media import media_tests
form_tests = r"""
>>> from django.newforms import *
@@ -3804,6 +3805,7 @@
'localflavor': localflavor_tests,
'regressions': regression_tests,
'formset_tests': formset_tests,
+ 'media_tests': media_tests,
}
if __name__ == "__main__":
Please sign in to comment.
Something went wrong with that request. Please try again.