Ticket #4418: media.diff

File media.diff, 9.8 KB (added by russellm, 8 years ago)

Adds a Media descriptor to newforms widgets

  • django/newforms/forms.py

     
    99from django.utils.encoding import StrAndUnicode
    1010
    1111from fields import Field
    12 from widgets import TextInput, Textarea
     12from widgets import Media, TextInput, Textarea
    1313from util import flatatt, ErrorDict, ErrorList, ValidationError
    1414
    1515__all__ = ('BaseForm', 'Form')
     
    209209        """
    210210        return self.cleaned_data
    211211
     212    def _get_media(self):
     213        """
     214        Provide a description of all media required to render the widgets on this form
     215        """
     216        m = Media()
     217        for field in self.fields.values():
     218            m = m + field.widget.media
     219        return m
     220    media = property(_get_media)
     221   
    212222class Form(BaseForm):
    213223    "A collection of Fields, plus their associated data."
    214224    # 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
    99from itertools import chain
    1010
     11from django.conf import settings
    1112from django.utils.datastructures import MultiValueDict
    1213from django.utils.html import escape
    1314from django.utils.translation import gettext
     
    1617from util import flatatt
    1718
    1819__all__ = (
    19     'Widget', 'TextInput', 'PasswordInput',
     20    'Media', 'Widget', 'TextInput', 'PasswordInput',
    2021    'HiddenInput', 'MultipleHiddenInput',
    2122    'FileInput', 'Textarea', 'CheckboxInput',
    2223    'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
    2324    'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
    2425)
    2526
     27MEDIA_TYPES = ('css','js')
     28
     29class Media(StrAndUnicode):
     30    def __init__(self, media=None, **kwargs):
     31        if media:
     32            media_attrs = media.__dict__
     33            del media_attrs['__module__']
     34            del media_attrs['__doc__']
     35        else:
     36            media_attrs = kwargs
     37           
     38        for attr_name in MEDIA_TYPES:
     39            setattr(self, '_' + attr_name, media_attrs.pop(attr_name, []))
     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        output = []
     50        for name in MEDIA_TYPES:
     51            for path in getattr(self, '_' + name):
     52                output.append(getattr(self, 'render_' + name)(path))
     53        return u'\n'.join(output)
     54       
     55    def render_js(self, path):
     56        return u'<script type="text/javascript" src="%s" />' % self.absolute_path(path)
     57       
     58    def render_css(self, path):
     59        return u'<link href="%s" type="text/css" rel="stylesheet" />' % self.absolute_path(path)
     60
     61    def absolute_path(self, path):
     62        return path.startswith('http://') and path or u''.join([settings.MEDIA_URL,path])
     63
     64    def __getitem__(self, name):
     65        "Returns a Media object that only contains media of the given type"
     66        if name in MEDIA_TYPES:
     67            return Media(**{name: getattr(self, '_' + name)})
     68        raise KeyError('Unknown media type "%s"' % name)
     69
     70    def __add__(self, other):
     71        "Combine two media objects to produce the union of all media"
     72        combined = {}
     73        for name in MEDIA_TYPES:
     74            combined[name] = list(getattr(self, '_' + name, [])) + \
     75                [m for m in getattr(other, '_' + name, []) if m not in getattr(self, '_' + name, []) ]
     76        return Media(**combined)
     77       
     78class WidgetBase(type):
     79    "Metaclass for all widgets"
     80    def __new__(cls, name, bases, attrs):       
     81        media = Media(attrs.pop('Media', None))
     82        new_class = type.__new__(cls, name, bases, attrs)
     83        setattr(new_class, 'media', media)
     84        return new_class
     85       
    2686class Widget(object):
     87    __metaclass__ = WidgetBase
    2788    is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
    2889
    2990    def __init__(self, attrs=None):
  • 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
     6>>> from django.conf import settings
     7>>> settings.MEDIA_URL = 'http://media.example.com'
     8
     9# A widget can exist without a media definition
     10>>> class MyWidget(TextInput):
     11...     pass
     12
     13>>> w = MyWidget()
     14>>> print w.media
     15<BLANKLINE>
     16
     17# A widget can define media if it needs to.
     18# Any absolute path will be preserved; relative paths are combined
     19# with the value of settings.MEDIA_URL
     20>>> class MyWidget1(TextInput):
     21...     class Media:
     22...         css = ('/path/to/css1','/path/to/css2')
     23...         js = ('/path/to/js1','http://media.other.com/path/to/js2')
     24
     25>>> w1 = MyWidget1()
     26>>> print w1.media
     27<link href="http://media.example.com/path/to/css1" type="text/css" rel="stylesheet" />
     28<link href="http://media.example.com/path/to/css2" type="text/css" rel="stylesheet" />
     29<script type="text/javascript" src="http://media.example.com/path/to/js1" />
     30<script type="text/javascript" src="http://media.other.com/path/to/js2" />
     31
     32# Media objects can be interrogated by media type
     33>>> print w1.media['css']
     34<link href="http://media.example.com/path/to/css1" type="text/css" rel="stylesheet" />
     35<link href="http://media.example.com/path/to/css2" type="text/css" rel="stylesheet" />
     36
     37>>> print w1.media['js']
     38<script type="text/javascript" src="http://media.example.com/path/to/js1" />
     39<script type="text/javascript" src="http://media.other.com/path/to/js2" />
     40
     41# Media objects can be combined. Any given media resource will appear only
     42# once. Duplicated media definitions are ignored.
     43>>> class MyWidget2(TextInput):
     44...     class Media:
     45...         css = ('/path/to/css2','/path/to/css3')
     46...         js = ('/path/to/js1','/path/to/js3')
     47
     48>>> class MyWidget3(TextInput):
     49...     class Media:
     50...         css = ('/path/to/css3','/path/to/css1')
     51...         js = ('/path/to/js1','/path/to/js3')
     52
     53>>> w2 = MyWidget2()
     54>>> w3 = MyWidget3()
     55>>> print w1.media + w2.media + w3.media
     56<link href="http://media.example.com/path/to/css1" type="text/css" rel="stylesheet" />
     57<link href="http://media.example.com/path/to/css2" type="text/css" rel="stylesheet" />
     58<link href="http://media.example.com/path/to/css3" type="text/css" rel="stylesheet" />
     59<script type="text/javascript" src="http://media.example.com/path/to/js1" />
     60<script type="text/javascript" src="http://media.other.com/path/to/js2" />
     61<script type="text/javascript" src="http://media.example.com/path/to/js3" />
     62
     63# If a widget extends another, media must be redefined
     64>>> class MyWidget4(MyWidget1):
     65...     pass
     66
     67>>> w4 = MyWidget4()
     68>>> print w4.media
     69<BLANKLINE>
     70
     71# If a widget extends another, media from the parent widget is ignored
     72>>> class MyWidget5(MyWidget1):
     73...     class Media:
     74...         css = ('/path/to/css3','/path/to/css1')
     75...         js = ('/path/to/js1','/path/to/js3')
     76
     77>>> w5 = MyWidget5()
     78>>> print w5.media
     79<link href="http://media.example.com/path/to/css3" type="text/css" rel="stylesheet" />
     80<link href="http://media.example.com/path/to/css1" type="text/css" rel="stylesheet" />
     81<script type="text/javascript" src="http://media.example.com/path/to/js1" />
     82<script type="text/javascript" src="http://media.example.com/path/to/js3" />
     83
     84# You can ask a form for the media required by its widgets.
     85>>> class MyForm(Form):
     86...     field1 = CharField(max_length=20, widget=MyWidget1())
     87...     field2 = CharField(max_length=20, widget=MyWidget2())
     88>>> f1 = MyForm()
     89>>> print f1.media
     90<link href="http://media.example.com/path/to/css1" type="text/css" rel="stylesheet" />
     91<link href="http://media.example.com/path/to/css2" type="text/css" rel="stylesheet" />
     92<link href="http://media.example.com/path/to/css3" type="text/css" rel="stylesheet" />
     93<script type="text/javascript" src="http://media.example.com/path/to/js1" />
     94<script type="text/javascript" src="http://media.other.com/path/to/js2" />
     95<script type="text/javascript" src="http://media.example.com/path/to/js3" />
     96
     97# Form media can be combined to produce a single media definition.
     98>>> class AnotherForm(Form):
     99...     field3 = CharField(max_length=20, widget=MyWidget3())
     100>>> f2 = AnotherForm()
     101>>> print f1.media + f2.media
     102<link href="http://media.example.com/path/to/css1" type="text/css" rel="stylesheet" />
     103<link href="http://media.example.com/path/to/css2" type="text/css" rel="stylesheet" />
     104<link href="http://media.example.com/path/to/css3" type="text/css" rel="stylesheet" />
     105<script type="text/javascript" src="http://media.example.com/path/to/js1" />
     106<script type="text/javascript" src="http://media.other.com/path/to/js2" />
     107<script type="text/javascript" src="http://media.example.com/path/to/js3" />
     108
     109"""
     110 No newline at end of file
  • tests/regressiontests/forms/tests.py

     
    11# -*- coding: utf-8 -*-
    22from localflavor import localflavor_tests
    33from regressions import regression_tests
     4from media import media_tests
    45
    56form_tests = r"""
    67>>> from django.newforms import *
     
    36773678"""
    36783679
    36793680__test__ = {
     3681    'media_tests': media_tests,
    36803682    'form_tests': form_tests,
    36813683    'localflavor': localflavor_tests,
    36823684    'regressions': regression_tests,
Back to Top