Ticket #4418: newforms-admin-media.5651.diff

File newforms-admin-media.5651.diff, 43.1 KB (added by russellm, 8 years ago)

Improved media descriptors for newforms-admin; against [5651]

  • django/contrib/admin/options.py

     
    22from django import newforms as forms
    33from django.newforms.formsets import all_valid
    44from django.newforms.models import inline_formset
     5from django.newforms.widgets import Media, MediaDefiningClass
    56from django.contrib.admin import widgets
    67from django.core.exceptions import ImproperlyConfigured, PermissionDenied
    78from django.db import models
     
    4849    def first_field(self):
    4950        for bf in self.form:
    5051            return bf
     52           
     53    def _media(self):
     54        media = self.form.media
     55        for fs in self.fieldsets:
     56            media = media + fs.media
     57        return media
     58    media = property(_media)
    5159
    5260class Fieldset(object):
    5361    def __init__(self, name=None, fields=(), classes=(), description=None):
     
    5563        self.classes = u' '.join(classes)
    5664        self.description = description
    5765
     66    def _media(self):
     67        from django.conf import settings
     68        if 'collapse' in self.classes:
     69            return Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX])
     70        return Media()
     71    media = property(_media)
     72   
    5873class BoundFieldset(object):
    5974    def __init__(self, form, fieldset):
    6075        self.form, self.fieldset = form, fieldset
     
    123138
    124139        # For DateFields, add a custom CSS class.
    125140        if isinstance(db_field, models.DateField):
    126             kwargs['widget'] = forms.TextInput(attrs={'class': 'vDateField', 'size': '10'})
     141            kwargs['widget'] = widgets.AdminDateWidget
    127142            return db_field.formfield(**kwargs)
    128143
    129144        # For TimeFields, add a custom CSS class.
    130145        if isinstance(db_field, models.TimeField):
    131             kwargs['widget'] = forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'})
     146            kwargs['widget'] = widgets.AdminTimeWidget
    132147            return db_field.formfield(**kwargs)
    133148
    134149        # For ForeignKey or ManyToManyFields, use a special widget.
     
    148163
    149164class ModelAdmin(BaseModelAdmin):
    150165    "Encapsulates all admin options and functionality for a given model."
    151 
     166    __metaclass__ = MediaDefiningClass
     167   
    152168    list_display = ('__str__',)
    153169    list_display_links = ()
    154170    list_filter = ()
     
    159175    save_as = False
    160176    save_on_top = False
    161177    ordering = None
    162     js = None
    163178    prepopulated_fields = {}
    164179    filter_vertical = ()
    165180    filter_horizontal = ()
     
    194209        else:
    195210            return self.change_view(request, unquote(url))
    196211
    197     def javascript(self, request, fieldsets):
    198         """
    199         Returns a list of URLs to include via <script> statements.
     212    def _media(self):
     213        from django.conf import settings
    200214
    201         The URLs can be absolute ('/js/admin/') or explicit
    202         ('http://example.com/foo.js').
    203         """
    204         from django.conf import settings
    205215        js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
    206216        if self.prepopulated_fields:
    207217            js.append('js/urlify.js')
    208         if self.opts.has_field_type(models.DateTimeField) or self.opts.has_field_type(models.TimeField) or self.opts.has_field_type(models.DateField):
    209             js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
    210218        if self.opts.get_ordered_objects():
    211219            js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
    212         if self.js:
    213             js.extend(self.js)
    214220        if self.filter_vertical or self.filter_horizontal:
    215221            js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
    216         for fs in fieldsets:
    217             if 'collapse' in fs.classes:
    218                 js.append('js/admin/CollapsedFieldsets.js')
    219                 break
    220         prefix = settings.ADMIN_MEDIA_PREFIX
    221         return ['%s%s' % (prefix, url) for url in js]
    222 
    223     def javascript_add(self, request):
    224         return self.javascript(request, self.fieldsets_add(request))
    225 
    226     def javascript_change(self, request, obj):
    227         return self.javascript(request, self.fieldsets_change(request, obj))
    228 
     222       
     223        return Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
     224    media = property(_media)
     225   
    229226    def fieldsets(self, request):
    230227        """
    231228        Generator that yields Fieldset objects for use on add and change admin
     
    244241
    245242    def fieldsets_add(self, request):
    246243        "Hook for specifying Fieldsets for the add form."
    247         for fs in self.fieldsets(request):
    248             yield fs
     244        return list(self.fieldsets(request))
    249245
    250246    def fieldsets_change(self, request, obj):
    251247        "Hook for specifying Fieldsets for the change form."
    252         for fs in self.fieldsets(request):
    253             yield fs
     248        return list(self.fieldsets(request))
    254249
    255250    def has_add_permission(self, request):
    256251        "Returns True if the given request has permission to add an object."
     
    433428                inline_formset = FormSet()
    434429                inline_formsets.append(inline_formset)
    435430
     431        adminForm = AdminForm(form, list(self.fieldsets_add(request)), self.prepopulated_fields)
     432        media = self.media + adminForm.media
     433        for fs in inline_formsets:
     434            media = media + fs.media
     435           
    436436        c = template.RequestContext(request, {
    437437            'title': _('Add %s') % opts.verbose_name,
    438             'adminform': AdminForm(form, self.fieldsets_add(request), self.prepopulated_fields),
     438            'adminform': adminForm,
    439439            'is_popup': request.REQUEST.has_key('_popup'),
    440440            'show_delete': False,
    441             'javascript_imports': self.javascript_add(request),
     441            'media': media,
    442442            'bound_inlines': [BoundInline(i, fs) for i, fs in zip(self.inlines, inline_formsets)],
    443443        })
    444444        return render_change_form(self, model, model.AddManipulator(), c, add=True)
     
    500500                        #related.get_accessor_name())
    501501                #orig_list = func()
    502502                #oldform.order_objects.extend(orig_list)
     503               
     504        adminForm = AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields)       
     505        media = self.media + adminForm.media
     506        for fs in inline_formsets:
     507            media = media + fs.media
     508           
    503509        c = template.RequestContext(request, {
    504510            'title': _('Change %s') % opts.verbose_name,
    505             'adminform': AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields),
     511            'adminform': adminForm,
    506512            'object_id': object_id,
    507513            'original': obj,
    508514            'is_popup': request.REQUEST.has_key('_popup'),
    509             'javascript_imports': self.javascript_change(request, obj),
     515            'media': media,
    510516            'bound_inlines': [BoundInline(i, fs) for i, fs in zip(self.inlines, inline_formsets)],
    511517        })
    512518        return render_change_form(self, model, model.ChangeManipulator(object_id), c, change=True)
  • django/contrib/admin/widgets.py

     
    55from django import newforms as forms
    66from django.utils.text import capfirst
    77from django.utils.translation import ugettext as _
     8from django.conf import settings
    89
    910class FilteredSelectMultiple(forms.SelectMultiple):
    1011    """
     
    2829            (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
    2930        return u''.join(output)
    3031
     32class AdminDateWidget(forms.TextInput):
     33    class Media:
     34        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
     35              settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
     36       
     37    def __init__(self, attrs={}):
     38        super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
     39
     40class AdminTimeWidget(forms.TextInput):
     41    class Media:
     42        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js",
     43              settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
     44
     45    def __init__(self, attrs={}):
     46        super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
     47   
    3148class AdminSplitDateTime(forms.SplitDateTimeWidget):
    3249    """
    3350    A SplitDateTime Widget that has some admin-specific styling.
    3451    """
    3552    def __init__(self, attrs=None):
    36         widgets = [forms.TextInput(attrs={'class': 'vDateField', 'size': '10'}),
    37                    forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'})]
     53        widgets = [AdminDateWidget, AdminTimeWidget]
    3854        # Note that we're calling MultiWidget, not SplitDateTimeWidget, because
    3955        # we want to define widgets.
    4056        forms.MultiWidget.__init__(self, widgets, attrs)
  • django/contrib/admin/templates/admin/auth/user/change_password.html

     
    22{% load i18n admin_modify adminmedia %}
    33{% block extrahead %}{{ block.super }}
    44<script type="text/javascript" src="../../../../jsi18n/"></script>
    5 {% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
    65{% endblock %}
    76{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
    87{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
  • django/contrib/admin/templates/admin/change_form.html

     
    33
    44{% block extrahead %}{{ block.super }}
    55<script type="text/javascript" src="../../../jsi18n/"></script>
    6 {% for js in javascript_imports %}<script type="text/javascript" src="{{ js }}"></script>
    7 {% endfor %}
     6{{ media }}
    87{% endblock %}
    98
    109{% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
  • django/newforms/formsets.py

     
    11from forms import Form, ValidationError
    22from fields import IntegerField, BooleanField
    3 from widgets import HiddenInput
     3from widgets import HiddenInput, Media
    44
    55# special field names
    66FORM_COUNT_FIELD_NAME = 'COUNT'
     
    149149        self.full_clean()
    150150        return self._is_valid
    151151
     152    def _get_media(self):
     153        # All the forms on a FormSet are the same, so you only need to
     154        # interrogate the first form for media.
     155        if self.forms:
     156            return self.forms[0].media
     157        else:
     158            return Media()
     159    media = property(_get_media)
     160   
    152161def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False):
    153162    """Return a FormSet for the given form class."""
    154163    attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
  • django/newforms/forms.py

     
    99from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
    1010
    1111from fields import Field
    12 from widgets import TextInput, Textarea
     12from widgets import Media, media_property, TextInput, Textarea
    1313from util import flatatt, ErrorDict, ErrorList, ValidationError
    1414
    1515__all__ = ('BaseForm', 'Form')
     
    3737    """
    3838    Metaclass that converts Field attributes to a dictionary called
    3939    'base_fields', taking into account parent class 'base_fields' as well.
     40    Also integrates any additional media definitions
    4041    """
    4142    def __new__(cls, name, bases, attrs):
    4243        fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
     
    5051                fields = base.base_fields.items() + fields
    5152
    5253        attrs['base_fields'] = SortedDictFromList(fields)
    53         return type.__new__(cls, name, bases, attrs)
    5454
     55        new_class = type.__new__(cls, name, bases, attrs)
     56        if 'media' not in attrs:
     57            new_class.media = media_property(new_class)
     58        return new_class
     59
    5560class BaseForm(StrAndUnicode):
    5661    # This is the main implementation of all the Form logic. Note that this
    5762    # class is different than Form. See the comments by the Form class for more
     
    234239        self.is_bound = False
    235240        self.__errors = None
    236241
     242    def _get_media(self):
     243        """
     244        Provide a description of all media required to render the widgets on this form
     245        """
     246        media = Media()
     247        for field in self.fields.values():
     248            media = media + field.widget.media
     249        return media
     250    media = property(_get_media)
     251   
    237252class Form(BaseForm):
    238253    "A collection of Fields, plus their associated data."
    239254    # This is a separate class from BaseForm in order to abstract the way
  • django/newforms/widgets.py

     
    88    from sets import Set as set   # Python 2.3 fallback
    99
    1010from itertools import chain
     11from django.conf import settings
    1112from django.utils.datastructures import MultiValueDict
    1213from django.utils.html import escape
    1314from django.utils.translation import ugettext
     
    1516from util import flatatt
    1617
    1718__all__ = (
    18     'Widget', 'TextInput', 'PasswordInput',
     19    'Media', 'Widget', 'TextInput', 'PasswordInput',
    1920    'HiddenInput', 'MultipleHiddenInput',
    2021    'FileInput', 'Textarea', 'CheckboxInput',
    2122    'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
    2223    'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
    2324)
    2425
     26MEDIA_TYPES = ('css','js')
     27
     28class Media(StrAndUnicode):
     29    def __init__(self, media=None, **kwargs):
     30        if media:
     31            media_attrs = media.__dict__
     32        else:
     33            media_attrs = kwargs
     34           
     35        self._css = {}
     36        self._js = []
     37       
     38        for name in MEDIA_TYPES:
     39            getattr(self, 'add_' + name)(media_attrs.get(name, None))
     40
     41        # Any leftover attributes must be invalid.
     42        # if media_attrs != {}:
     43        #     raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())
     44       
     45    def __unicode__(self):
     46        return self.render()
     47       
     48    def render(self):
     49        return u'\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))
     50       
     51    def render_js(self):
     52        return [u'<script type="text/javascript" src="%s"></script>' % self.absolute_path(path) for path in self._js]
     53       
     54    def render_css(self):
     55        # To keep rendering order consistent, we can't just iterate over items().
     56        # We need to sort the keys, and iterate over the sorted list.
     57        media = self._css.keys()
     58        media.sort()
     59        return chain(*[
     60            [u'<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium)
     61                    for path in self._css[medium]]
     62                for medium in media])
     63       
     64    def absolute_path(self, path):
     65        return (path.startswith(u'http://') or path.startswith(u'https://')) and path or u''.join([settings.MEDIA_URL,path])
     66
     67    def __getitem__(self, name):
     68        "Returns a Media object that only contains media of the given type"
     69        if name in MEDIA_TYPES:
     70            return Media(**{name: getattr(self, '_' + name)})
     71        raise KeyError('Unknown media type "%s"' % name)
     72
     73    def add_js(self, data):
     74        if data:   
     75            self._js.extend([path for path in data if path not in self._js])
     76           
     77    def add_css(self, data):
     78        if data:
     79            for medium, paths in data.items():
     80                self._css.setdefault(medium, []).extend([path for path in paths if path not in self._css[medium]])
     81
     82    def __add__(self, other):
     83        combined = Media()
     84        for name in MEDIA_TYPES:
     85            getattr(combined, 'add_' + name)(getattr(self, '_' + name, None))
     86            getattr(combined, 'add_' + name)(getattr(other, '_' + name, None))
     87        return combined
     88
     89def media_property(cls):
     90    def _media(self):
     91        # Get the media property of the superclass, if it exists
     92        if hasattr(super(cls, self), 'media'):
     93            base = super(cls, self).media
     94        else:
     95            base = Media()
     96       
     97        # Get the media definition for this class   
     98        definition = getattr(cls, 'Media', None)
     99        if definition:
     100            extend = getattr(definition, 'extend', True)
     101            if extend:
     102                if extend == True:
     103                    m = base
     104                else:
     105                    m = Media()
     106                    for medium in extend:
     107                        m = m + base[medium]
     108                    m = m + Media(definition)
     109                return m + Media(definition)
     110            else:
     111                 return Media(definition)
     112        else:
     113            return base
     114    return property(_media)
     115   
     116class MediaDefiningClass(type):
     117    "Metaclass for classes that can have media definitions"
     118    def __new__(cls, name, bases, attrs):           
     119        new_class = type.__new__(cls, name, bases, attrs)
     120        if 'media' not in attrs:
     121            new_class.media = media_property(new_class)
     122        return new_class
     123       
    25124class Widget(object):
     125    __metaclass__ = MediaDefiningClass
    26126    is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
    27127
    28128    def __init__(self, attrs=None):
     
    377477        """
    378478        raise NotImplementedError('Subclasses must implement this method.')
    379479
     480    def _get_media(self):
     481        "Media for a multiwidget is the combination of all media of the subwidgets"
     482        media = Media()
     483        for w in self.widgets:
     484            media = media + w.media
     485        return media
     486    media = property(_get_media)
     487   
    380488class SplitDateTimeWidget(MultiWidget):
    381489    """
    382490    A Widget that splits datetime input into two <input type="text"> boxes.
     
    389497        if value:
    390498            return [value.date(), value.time()]
    391499        return [None, None]
     500   
     501 No newline at end of file
  • tests/regressiontests/forms/media.py

     
     1# -*- coding: utf-8 -*-
     2# Tests for the media handling on widgets and forms
     3
     4media_tests = r"""
     5>>> from django.newforms import TextInput, Media, TextInput, CharField, Form, MultiWidget
     6>>> from django.conf import settings
     7>>> settings.MEDIA_URL = 'http://media.example.com'
     8
     9# Check construction of media objects
     10>>> 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'))
     11>>> print m
     12<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     13<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     14<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     15<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     16<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     17
     18>>> class Foo:
     19...     css = {
     20...        'all': ('/path/to/css1','/path/to/css2')
     21...     }
     22...     js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
     23>>> m3 = Media(Foo)
     24>>> print m3
     25<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     26<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     27<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     28<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     29<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     30
     31>>> m3 = Media(Foo)
     32>>> print m3
     33<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     34<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     35<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     36<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     37<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     38
     39# A widget can exist without a media definition
     40>>> class MyWidget(TextInput):
     41...     pass
     42
     43>>> w = MyWidget()
     44>>> print w.media
     45<BLANKLINE>
     46
     47###############################################################
     48# DSL Class-based media definitions
     49###############################################################
     50
     51# A widget can define media if it needs to.
     52# Any absolute path will be preserved; relative paths are combined
     53# with the value of settings.MEDIA_URL
     54>>> class MyWidget1(TextInput):
     55...     class Media:
     56...         css = {
     57...            'all': ('/path/to/css1','/path/to/css2')
     58...         }
     59...         js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
     60
     61>>> w1 = MyWidget1()
     62>>> print w1.media
     63<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     64<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     65<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     66<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     67<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     68
     69# Media objects can be interrogated by media type
     70>>> print w1.media['css']
     71<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     72<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     73
     74>>> print w1.media['js']
     75<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     76<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     77<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     78
     79# Media objects can be combined. Any given media resource will appear only
     80# once. Duplicated media definitions are ignored.
     81>>> class MyWidget2(TextInput):
     82...     class Media:
     83...         css = {
     84...            'all': ('/path/to/css2','/path/to/css3')
     85...         }
     86...         js = ('/path/to/js1','/path/to/js4')
     87
     88>>> class MyWidget3(TextInput):
     89...     class Media:
     90...         css = {
     91...            'all': ('/path/to/css3','/path/to/css1')
     92...         }
     93...         js = ('/path/to/js1','/path/to/js4')
     94
     95>>> w2 = MyWidget2()
     96>>> w3 = MyWidget3()
     97>>> print w1.media + w2.media + w3.media
     98<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     99<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     100<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     101<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     102<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     103<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     104<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     105
     106# Check that media addition hasn't affected the original objects
     107>>> print w1.media
     108<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     109<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     110<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     111<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     112<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     113
     114###############################################################
     115# Property-based media definitions
     116###############################################################
     117
     118# Widget media can be defined as a property
     119>>> class MyWidget4(TextInput):
     120...     def _media(self):
     121...         return Media(css={'all': ('/some/path',)}, js = ('/some/js',))
     122...     media = property(_media)
     123
     124>>> w4 = MyWidget4()
     125>>> print w4.media
     126<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
     127<script type="text/javascript" src="http://media.example.com/some/js"></script>
     128
     129# Media properties can reference the media of their parents
     130>>> class MyWidget5(MyWidget4):
     131...     def _media(self):
     132...         return super(MyWidget5, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
     133...     media = property(_media)
     134
     135>>> w5 = MyWidget5()
     136>>> print w5.media
     137<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
     138<link href="http://media.example.com/other/path" type="text/css" media="all" rel="stylesheet" />
     139<script type="text/javascript" src="http://media.example.com/some/js"></script>
     140<script type="text/javascript" src="http://media.example.com/other/js"></script>
     141
     142# Media properties can reference the media of their parents,
     143# even if the parent media was defined using a class
     144>>> class MyWidget6(MyWidget1):
     145...     def _media(self):
     146...         return super(MyWidget6, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',))
     147...     media = property(_media)
     148
     149>>> w6 = MyWidget6()
     150>>> print w6.media
     151<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     152<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     153<link href="http://media.example.com/other/path" type="text/css" media="all" rel="stylesheet" />
     154<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     155<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     156<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     157<script type="text/javascript" src="http://media.example.com/other/js"></script>
     158
     159###############################################################
     160# Inheritance of media
     161###############################################################
     162
     163# If a widget extends another but provides no media definition, it inherits the parent widget's media
     164>>> class MyWidget7(MyWidget1):
     165...     pass
     166
     167>>> w7 = MyWidget7()
     168>>> print w7.media
     169<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     170<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     171<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     172<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     173<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     174
     175# If a widget extends another but defines media, it extends the parent widget's media by default
     176>>> class MyWidget8(MyWidget1):
     177...     class Media:
     178...         css = {
     179...            'all': ('/path/to/css3','/path/to/css1')
     180...         }
     181...         js = ('/path/to/js1','/path/to/js4')
     182
     183>>> w8 = MyWidget8()
     184>>> print w8.media
     185<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     186<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     187<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     188<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     189<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     190<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     191<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     192
     193# If a widget extends another but defines media, it extends the parents widget's media,
     194# even if the parent defined media using a property.
     195>>> class MyWidget9(MyWidget4):
     196...     class Media:
     197...         css = {
     198...             'all': ('/other/path',)
     199...         }
     200...         js = ('/other/js',)
     201
     202>>> w9 = MyWidget9()
     203>>> print w9.media
     204<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
     205<link href="http://media.example.com/other/path" type="text/css" media="all" rel="stylesheet" />
     206<script type="text/javascript" src="http://media.example.com/some/js"></script>
     207<script type="text/javascript" src="http://media.example.com/other/js"></script>
     208
     209# A widget can disable media inheritance by specifying 'extend=False'
     210>>> class MyWidget10(MyWidget1):
     211...     class Media:
     212...         extend = False
     213...         css = {
     214...            'all': ('/path/to/css3','/path/to/css1')
     215...         }
     216...         js = ('/path/to/js1','/path/to/js4')
     217
     218>>> w10 = MyWidget10()
     219>>> print w10.media
     220<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     221<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     222<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     223<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     224
     225# A widget can explicitly enable full media inheritance by specifying 'extend=True'
     226>>> class MyWidget11(MyWidget1):
     227...     class Media:
     228...         extend = True
     229...         css = {
     230...            'all': ('/path/to/css3','/path/to/css1')
     231...         }
     232...         js = ('/path/to/js1','/path/to/js4')
     233
     234>>> w11 = MyWidget11()
     235>>> print w11.media
     236<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     237<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     238<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     239<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     240<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     241<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     242<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     243
     244# A widget can enable inheritance of one media type by specifying extend as a tuple
     245>>> class MyWidget12(MyWidget1):
     246...     class Media:
     247...         extend = ('css',)
     248...         css = {
     249...            'all': ('/path/to/css3','/path/to/css1')
     250...         }
     251...         js = ('/path/to/js1','/path/to/js4')
     252
     253>>> w12 = MyWidget12()
     254>>> print w12.media
     255<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     256<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     257<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     258<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     259<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     260
     261###############################################################
     262# Multi-media handling for CSS
     263###############################################################
     264
     265# A widget can define CSS media for multiple output media types
     266>>> class MultimediaWidget(TextInput):
     267...     class Media:
     268...         css = {
     269...            'screen, print': ('/file1','/file2'),
     270...            'screen': ('/file3',),
     271...            'print': ('/file4',)
     272...         }
     273...         js = ('/path/to/js1','/path/to/js4')
     274
     275>>> multimedia = MultimediaWidget()
     276>>> print multimedia.media
     277<link href="http://media.example.com/file4" type="text/css" media="print" rel="stylesheet" />
     278<link href="http://media.example.com/file3" type="text/css" media="screen" rel="stylesheet" />
     279<link href="http://media.example.com/file1" type="text/css" media="screen, print" rel="stylesheet" />
     280<link href="http://media.example.com/file2" type="text/css" media="screen, print" rel="stylesheet" />
     281<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     282<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     283
     284###############################################################
     285# Multiwidget media handling
     286###############################################################
     287
     288# MultiWidgets have a default media definition that gets all the
     289# media from the component widgets
     290>>> class MyMultiWidget(MultiWidget):
     291...     def __init__(self, attrs=None):
     292...         widgets = [MyWidget1, MyWidget2, MyWidget3]
     293...         super(MyMultiWidget, self).__init__(widgets, attrs)
     294           
     295>>> mymulti = MyMultiWidget()
     296>>> print mymulti.media   
     297<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     298<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     299<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     300<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     301<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     302<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     303<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     304
     305###############################################################
     306# Media processing for forms
     307###############################################################
     308
     309# You can ask a form for the media required by its widgets.
     310>>> class MyForm(Form):
     311...     field1 = CharField(max_length=20, widget=MyWidget1())
     312...     field2 = CharField(max_length=20, widget=MyWidget2())
     313>>> f1 = MyForm()
     314>>> print f1.media
     315<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     316<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     317<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     318<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     319<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     320<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     321<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     322
     323# Form media can be combined to produce a single media definition.
     324>>> class AnotherForm(Form):
     325...     field3 = CharField(max_length=20, widget=MyWidget3())
     326>>> f2 = AnotherForm()
     327>>> print f1.media + f2.media
     328<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     329<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     330<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     331<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     332<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     333<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     334<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     335
     336# Forms can also define media, following the same rules as widgets.
     337>>> class FormWithMedia(Form):
     338...     field1 = CharField(max_length=20, widget=MyWidget1())
     339...     field2 = CharField(max_length=20, widget=MyWidget2())
     340...     class Media:
     341...         js = ('/some/form/javascript',)
     342...         css = {
     343...             'all': ('/some/form/css',)
     344...         }
     345>>> f3 = FormWithMedia()
     346>>> print f3.media
     347<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     348<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     349<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     350<link href="http://media.example.com/some/form/css" type="text/css" media="all" rel="stylesheet" />
     351<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     352<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     353<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     354<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     355<script type="text/javascript" src="http://media.example.com/some/form/javascript"></script>
     356
     357"""
     358 No newline at end of file
  • tests/regressiontests/forms/tests.py

     
    22from localflavor import localflavor_tests
    33from regressions import regression_tests
    44from formsets import formset_tests
     5from media import media_tests
    56
    67form_tests = r"""
    78>>> from django.newforms import *
     
    36983699    'localflavor': localflavor_tests,
    36993700    'regressions': regression_tests,
    37003701    'formset_tests': formset_tests,
     3702    'media_tests': media_tests,
    37013703}
    37023704
    37033705if __name__ == "__main__":
  • docs/newforms.txt

     
    919919~~~~~~~~~~
    920920
    921921The ``widget`` argument lets you specify a ``Widget`` class to use when
    922 rendering this ``Field``. See "Widgets" below for more information.
     922rendering this ``Field``. See "Widgets"_ below for more information.
    923923
    924924``help_text``
    925925~~~~~~~~~~~~~
     
    13251325        senders = MultiEmailField()
    13261326        cc_myself = forms.BooleanField()
    13271327
     1328Widgets
     1329=======
     1330
     1331A widget is Django's representation of a HTML form widget. The widget
     1332handles the rendering of the HTML, and the extraction of data from a GET/POST
     1333dictionary that corresponds to the widget.
     1334
     1335Django provides a representation of all the basic HTML widgets, plus some
     1336commonly used groups of widgets:
     1337
     1338    ============================  ===========================================
     1339    Widget                        HTML Equivalent
     1340    ============================  ===========================================
     1341    ``TextInput``                 ``<input type='text' ...``
     1342    ``PasswordInput``             ``<input type='password' ...``
     1343    ``HiddenInput``               ``<input type='hidden' ...``
     1344    ``MultipleHiddenInput``       Multiple ``<input type='hidden' ...``
     1345                                  instances.
     1346    ``FileInput``                 ``<input type='file' ...``
     1347    ``Textarea``                  ``<textarea>...</textarea>``
     1348    ``CheckboxInput``             ``<input type='checkbox' ...``
     1349
     1350    ``Select``                    ``<select><option ...``
     1351
     1352    ``NullBooleanSelect``         Select widget with options 'Unknown',
     1353                                  'Yes' and 'No'
     1354
     1355    ``SelectMultiple``            ``<select multiple='multiple'><option ...``
     1356
     1357    ``RadioSelect``               ``<ul><li><input type='radio' ...``
     1358
     1359    ``CheckboxSelectMultiple``    ``<ul><li><input type='checkbox' ...``
     1360
     1361    ``MultiWidget``               Wrapper around multiple other widgets
     1362
     1363    ``SplitDateTimeWidget``       Wrapper around two ``TextInput`` widgets
     1364
     1365Specifying widgets
     1366------------------
     1367
     1368Whenever you specify a field on a form, Django will use a default widget
     1369that is appropriate to the type of data that is to be displayed.
     1370
     1371However, if you want to use a different widget, you can - just use the
     1372'widget' argument on the field definition. For example::
     1373
     1374    class MyForm(forms.Form):
     1375        name = forms.CharField(max_length=20, widget=forms.HiddenInput)
     1376
     1377This would specify a form with a CharField that uses a HiddenInput widget,
     1378rather than the default TextInput widget.
     1379
     1380Customizing widget instances
     1381----------------------------
     1382 - sometimes you may want to associate a CSS class with a widget,
     1383or modify the number of rows or columns displayed in a text area.
     1384
     1385When you specify a widget, you can provide a list of attributes that
     1386will be used when rendering the widget. The attributes are
     1387Django also provides a mechanism to customize the rendering of
     1388
     1389Django will then use these attributes
     1390
     1391For example, if you want to associate a specific CSS class with the
     1392HTML generated by the widget, you can provide the CSS class as an
     1393attribute definition::
     1394
     1395    class MyForm(forms.Form):
     1396        name = forms.CharField(
     1397                   max_length=20,
     1398                   widget=forms.TextInput(attrs={'class':'special'})
     1399
     1400These attributes will be
     1401
     1402Customized Widgets
     1403------------------
     1404
     1405If you need to reuse a particular set of customized widget attributes, it
     1406may be worth defining your own widget that encapsulates those customized
     1407
     1408Sometimes, a particular set of customized attributes will be s
     1409The widgets implemented in Django cover all the possibilities for HTML form
     1410input. While this set of widgets is entirely functional, it doesn't necessarily
     1411represent the best possible user interface for every application.
     1412
     1413
     1414
     1415For example, consider the case of a DateField in a model. By default,
     1416a DateField on a form will be rendered using a TextInput widget. While this
     1417is entirely functional, a calendar is a much better user interface for
     1418inputting dates than a simple text field.
     1419
     1420To get a calendar input on your page, you will need to use JavaScript
     1421
     1422..note:
     1423    Django has deliberately not blessed any one JavaScript toolkit.
     1424    There are many options out there, and they all have their relative
     1425    strengths and weaknesses.
     1426
     1427Whenever you specify
     1428
     1429However, if you're going to be using
     1430
     1431If you want to spice up your application, one approach you can take is
     1432to define some customized
     1433
     1434user interface for a
     1435
     1436One way to improve the user interface for DateFields would be to define
     1437a customized CalendarInput widget that displays a
     1438
     1439is to define and use a customized widget
     1440
     1441For the record - this is exactly what the Django Admin application does.
     1442
     1443The Django Admin application defines a number of customized widgets, and
     1444uses those widgets in place of the Django defaults.
     1445
    13281446Generating forms for models
    13291447===========================
    13301448
     
    16331751you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way
    16341752isn't that difficult, after all.
    16351753
     1754Media
     1755=====
     1756
     1757In a 'previous section'_
     1758provides a mechanism to simplify the definition and collation of the
     1759media requirements
     1760
     1761.. 'previous section'
     1762However, the CalendarInput widget requires more than just HTML - it
     1763also requires CSS and JavaScript files to be fully functional.
     1764
     1765
     1766. you may wish to display a calendar for the input of
     1767dates, rather than a simple text fie
     1768
    16361769More coming soon
    16371770================
    16381771
Back to Top