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:
parent
28e350301f
commit
549198b714
@ -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)
|
||||||
|
@ -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 %}
|
||||||
|
@ -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 %}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
@ -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]
|
||||||
|
|
@ -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
|
||||||
================
|
================
|
||||||
|
|
||||||
|
357
tests/regressiontests/forms/media.py
Normal file
357
tests/regressiontests/forms/media.py
Normal 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>
|
||||||
|
|
||||||
|
"""
|
@ -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__":
|
||||||
|
Loading…
x
Reference in New Issue
Block a user