1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

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
This commit is contained in:
Russell Keith-Magee 2007-08-18 05:42:56 +00:00
parent 28e350301f
commit 549198b714
10 changed files with 866 additions and 45 deletions

View File

@ -2,6 +2,7 @@ from django import oldforms, template
from django import newforms as forms from django import newforms as forms
from django.newforms.formsets import all_valid from django.newforms.formsets import all_valid
from django.newforms.models import inline_formset from django.newforms.models import inline_formset
from django.newforms.widgets import Media, MediaDefiningClass
from django.contrib.admin import widgets from django.contrib.admin import widgets
from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.db import models from django.db import models
@ -48,6 +49,13 @@ class AdminForm(object):
def first_field(self): def first_field(self):
for bf in self.form: for bf in self.form:
return bf 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): class Fieldset(object):
def __init__(self, name=None, fields=(), classes=(), description=None): def __init__(self, name=None, fields=(), classes=(), description=None):
@ -55,6 +63,13 @@ class Fieldset(object):
self.classes = u' '.join(classes) self.classes = u' '.join(classes)
self.description = description 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): class BoundFieldset(object):
def __init__(self, form, fieldset): def __init__(self, form, fieldset):
self.form, self.fieldset = form, fieldset self.form, self.fieldset = form, fieldset
@ -123,12 +138,12 @@ class BaseModelAdmin(object):
# For DateFields, add a custom CSS class. # For DateFields, add a custom CSS class.
if isinstance(db_field, models.DateField): if isinstance(db_field, models.DateField):
kwargs['widget'] = forms.TextInput(attrs={'class': 'vDateField', 'size': '10'}) kwargs['widget'] = widgets.AdminDateWidget
return db_field.formfield(**kwargs) return db_field.formfield(**kwargs)
# For TimeFields, add a custom CSS class. # For TimeFields, add a custom CSS class.
if isinstance(db_field, models.TimeField): if isinstance(db_field, models.TimeField):
kwargs['widget'] = forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'}) kwargs['widget'] = widgets.AdminTimeWidget
return db_field.formfield(**kwargs) return db_field.formfield(**kwargs)
# For ForeignKey or ManyToManyFields, use a special widget. # For ForeignKey or ManyToManyFields, use a special widget.
@ -148,7 +163,8 @@ class BaseModelAdmin(object):
class ModelAdmin(BaseModelAdmin): class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model." "Encapsulates all admin options and functionality for a given model."
__metaclass__ = MediaDefiningClass
list_display = ('__str__',) list_display = ('__str__',)
list_display_links = () list_display_links = ()
list_filter = () list_filter = ()
@ -159,7 +175,6 @@ class ModelAdmin(BaseModelAdmin):
save_as = False save_as = False
save_on_top = False save_on_top = False
ordering = None ordering = None
js = None
prepopulated_fields = {} prepopulated_fields = {}
filter_vertical = () filter_vertical = ()
filter_horizontal = () filter_horizontal = ()
@ -194,38 +209,20 @@ class ModelAdmin(BaseModelAdmin):
else: else:
return self.change_view(request, unquote(url)) return self.change_view(request, unquote(url))
def javascript(self, request, fieldsets): def _media(self):
"""
Returns a list of URLs to include via <script> statements.
The URLs can be absolute ('/js/admin/') or explicit
('http://example.com/foo.js').
"""
from django.conf import settings from django.conf import settings
js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
if self.prepopulated_fields: if self.prepopulated_fields:
js.append('js/urlify.js') 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(): if self.opts.get_ordered_objects():
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) 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: if self.filter_vertical or self.filter_horizontal:
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
for fs in fieldsets:
if 'collapse' in fs.classes: return Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
js.append('js/admin/CollapsedFieldsets.js') media = property(_media)
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))
def fieldsets(self, request): def fieldsets(self, request):
""" """
Generator that yields Fieldset objects for use on add and change admin Generator that yields Fieldset objects for use on add and change admin
@ -244,13 +241,11 @@ class ModelAdmin(BaseModelAdmin):
def fieldsets_add(self, request): def fieldsets_add(self, request):
"Hook for specifying Fieldsets for the add form." "Hook for specifying Fieldsets for the add form."
for fs in self.fieldsets(request): return list(self.fieldsets(request))
yield fs
def fieldsets_change(self, request, obj): def fieldsets_change(self, request, obj):
"Hook for specifying Fieldsets for the change form." "Hook for specifying Fieldsets for the change form."
for fs in self.fieldsets(request): return list(self.fieldsets(request))
yield fs
def has_add_permission(self, request): def has_add_permission(self, request):
"Returns True if the given request has permission to add an object." "Returns True if the given request has permission to add an object."
@ -430,12 +425,17 @@ class ModelAdmin(BaseModelAdmin):
inline_formset = FormSet() inline_formset = FormSet()
inline_formsets.append(inline_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, { c = template.RequestContext(request, {
'title': _('Add %s') % opts.verbose_name, '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'), 'is_popup': request.REQUEST.has_key('_popup'),
'show_delete': False, '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)], '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) return render_change_form(self, model, model.AddManipulator(), c, add=True)
@ -494,13 +494,19 @@ class ModelAdmin(BaseModelAdmin):
#related.get_accessor_name()) #related.get_accessor_name())
#orig_list = func() #orig_list = func()
#oldform.order_objects.extend(orig_list) #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, { c = template.RequestContext(request, {
'title': _('Change %s') % opts.verbose_name, 'title': _('Change %s') % opts.verbose_name,
'adminform': AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields), 'adminform': adminForm,
'object_id': object_id, 'object_id': object_id,
'original': obj, 'original': obj,
'is_popup': request.REQUEST.has_key('_popup'), '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)], '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) return render_change_form(self, model, model.ChangeManipulator(object_id), c, change=True)

View File

@ -2,7 +2,6 @@
{% load i18n admin_modify adminmedia %} {% load i18n admin_modify adminmedia %}
{% block extrahead %}{{ block.super }} {% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../../jsi18n/"></script> <script type="text/javascript" src="../../../../jsi18n/"></script>
{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %} {% endblock %}
{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %} {% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %} {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}

View File

@ -3,8 +3,7 @@
{% block extrahead %}{{ block.super }} {% block extrahead %}{{ block.super }}
<script type="text/javascript" src="../../../jsi18n/"></script> <script type="text/javascript" src="../../../jsi18n/"></script>
{% for js in javascript_imports %}<script type="text/javascript" src="{{ js }}"></script> {{ media }}
{% endfor %}
{% endblock %} {% endblock %}
{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %} {% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}

View File

@ -5,6 +5,7 @@ Form Widget classes specific to the Django admin site.
from django import newforms as forms from django import newforms as forms
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.conf import settings
class FilteredSelectMultiple(forms.SelectMultiple): class FilteredSelectMultiple(forms.SelectMultiple):
""" """
@ -28,13 +29,28 @@ class FilteredSelectMultiple(forms.SelectMultiple):
(name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX)) (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
return u''.join(output) 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): class AdminSplitDateTime(forms.SplitDateTimeWidget):
""" """
A SplitDateTime Widget that has some admin-specific styling. A SplitDateTime Widget that has some admin-specific styling.
""" """
def __init__(self, attrs=None): def __init__(self, attrs=None):
widgets = [forms.TextInput(attrs={'class': 'vDateField', 'size': '10'}), widgets = [AdminDateWidget, AdminTimeWidget]
forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'})]
# Note that we're calling MultiWidget, not SplitDateTimeWidget, because # Note that we're calling MultiWidget, not SplitDateTimeWidget, because
# we want to define widgets. # we want to define widgets.
forms.MultiWidget.__init__(self, widgets, attrs) forms.MultiWidget.__init__(self, widgets, attrs)

View File

@ -9,7 +9,7 @@ from django.utils.html import escape
from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
from fields import Field from fields import Field
from widgets import TextInput, Textarea from widgets import Media, media_property, TextInput, Textarea
from util import flatatt, ErrorDict, ErrorList, ValidationError from util import flatatt, ErrorDict, ErrorList, ValidationError
__all__ = ('BaseForm', 'Form') __all__ = ('BaseForm', 'Form')
@ -37,6 +37,7 @@ class DeclarativeFieldsMetaclass(type):
""" """
Metaclass that converts Field attributes to a dictionary called Metaclass that converts Field attributes to a dictionary called
'base_fields', taking into account parent class 'base_fields' as well. 'base_fields', taking into account parent class 'base_fields' as well.
Also integrates any additional media definitions
""" """
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
@ -50,7 +51,11 @@ class DeclarativeFieldsMetaclass(type):
fields = base.base_fields.items() + fields fields = base.base_fields.items() + fields
attrs['base_fields'] = SortedDictFromList(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): class BaseForm(StrAndUnicode):
# This is the main implementation of all the Form logic. Note that this # This is the main implementation of all the Form logic. Note that this
@ -235,6 +240,16 @@ class BaseForm(StrAndUnicode):
self.is_bound = False self.is_bound = False
self.__errors = None 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): class Form(BaseForm):
"A collection of Fields, plus their associated data." "A collection of Fields, plus their associated data."
# This is a separate class from BaseForm in order to abstract the way # This is a separate class from BaseForm in order to abstract the way

View File

@ -1,6 +1,6 @@
from forms import Form, ValidationError from forms import Form, ValidationError
from fields import IntegerField, BooleanField from fields import IntegerField, BooleanField
from widgets import HiddenInput from widgets import HiddenInput, Media
# special field names # special field names
FORM_COUNT_FIELD_NAME = 'COUNT' FORM_COUNT_FIELD_NAME = 'COUNT'
@ -154,6 +154,15 @@ class BaseFormSet(object):
self.full_clean() self.full_clean()
return self._is_valid 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): def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False):
"""Return a FormSet for the given form class.""" """Return a FormSet for the given form class."""
attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable} attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}

View File

@ -8,6 +8,7 @@ except NameError:
from sets import Set as set # Python 2.3 fallback from sets import Set as set # Python 2.3 fallback
from itertools import chain from itertools import chain
from django.conf import settings
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.html import escape from django.utils.html import escape
from django.utils.translation import ugettext from django.utils.translation import ugettext
@ -15,14 +16,113 @@ from django.utils.encoding import StrAndUnicode, force_unicode
from util import flatatt from util import flatatt
__all__ = ( __all__ = (
'Widget', 'TextInput', 'PasswordInput', 'Media', 'Widget', 'TextInput', 'PasswordInput',
'HiddenInput', 'MultipleHiddenInput', 'HiddenInput', 'MultipleHiddenInput',
'FileInput', 'Textarea', 'CheckboxInput', 'FileInput', 'Textarea', 'CheckboxInput',
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget', '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): class Widget(object):
__metaclass__ = MediaDefiningClass
is_hidden = False # Determines whether this corresponds to an <input type="hidden">. is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
def __init__(self, attrs=None): def __init__(self, attrs=None):
@ -405,6 +505,14 @@ class MultiWidget(Widget):
""" """
raise NotImplementedError('Subclasses must implement this method.') 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): class SplitDateTimeWidget(MultiWidget):
""" """
A Widget that splits datetime input into two <input type="text"> boxes. A Widget that splits datetime input into two <input type="text"> boxes.
@ -417,3 +525,4 @@ class SplitDateTimeWidget(MultiWidget):
if value: if value:
return [value.date(), value.time()] return [value.date(), value.time()]
return [None, None] return [None, None]

View File

@ -76,6 +76,9 @@ The library deals with these concepts:
* **Form** -- A collection of fields that knows how to validate itself and * **Form** -- A collection of fields that knows how to validate itself and
display itself as HTML. 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 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 layer, views and templates. It relies only on Django settings, a couple of
``django.utils`` helper functions and Django's internationalization hooks (but ``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 you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way
isn't that difficult, after all. 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 More coming soon
================ ================

View File

@ -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>
"""

View File

@ -2,6 +2,7 @@
from localflavor import localflavor_tests from localflavor import localflavor_tests
from regressions import regression_tests from regressions import regression_tests
from formsets import formset_tests from formsets import formset_tests
from media import media_tests
form_tests = r""" form_tests = r"""
>>> from django.newforms import * >>> from django.newforms import *
@ -3804,6 +3805,7 @@ __test__ = {
'localflavor': localflavor_tests, 'localflavor': localflavor_tests,
'regressions': regression_tests, 'regressions': regression_tests,
'formset_tests': formset_tests, 'formset_tests': formset_tests,
'media_tests': media_tests,
} }
if __name__ == "__main__": if __name__ == "__main__":