Ticket #4418: media3.diff

File media3.diff, 16.3 KB (added by russellm, 8 years ago)

Media descriptors for newforms, v3; against [5636]

  • 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, 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        media = Media()
     217        for field in self.fields.values():
     218            media = media + field.widget.media
     219        return media
     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
    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            del media_attrs['__module__']
     33            del media_attrs['__doc__']
     34        else:
     35            media_attrs = kwargs
     36           
     37        self._css = {}
     38        self._js = []
     39       
     40        for name in MEDIA_TYPES:
     41            getattr(self, 'add_' + name)(media_attrs.pop(name, None))
     42
     43        # Any leftover attributes must be invalid.
     44        if media_attrs != {}:
     45            raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())
     46       
     47    def __unicode__(self):
     48        return self.render()
     49       
     50    def render(self):
     51        output = []
     52        for name in MEDIA_TYPES:
     53            output.extend(getattr(self, 'render_' + name)())
     54        return u'\n'.join(output)
     55       
     56    def render_js(self):
     57        output = []
     58        for path in self._js:
     59            output.append(u'<script type="text/javascript" src="%s"></script>' % self.absolute_path(path))
     60        return output
     61       
     62    def render_css(self):
     63        output = []
     64        # To keep rendering order consistent, we can't just iterate over items().
     65        # We need to sort the keys, and iterate over the sorted list.
     66        media = self._css.keys()
     67        media.sort()
     68        for medium in media:
     69            for path in self._css[medium]:
     70                output.append(u'<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium))
     71        return output
     72       
     73    def absolute_path(self, path):
     74        return (path.startswith(u'http://') or path.startswith(u'https://')) and path or u''.join([settings.MEDIA_URL,path])
     75
     76    def __getitem__(self, name):
     77        "Returns a Media object that only contains media of the given type"
     78        if name in MEDIA_TYPES:
     79            return Media(**{name: getattr(self, '_' + name)})
     80        raise KeyError('Unknown media type "%s"' % name)
     81
     82    def add_js(self, data):
     83        if data:   
     84            self._js.extend([path for path in data if path not in self._js])
     85           
     86    def add_css(self, data):
     87        if data:
     88            for medium, paths in data.items():
     89                self._css.setdefault(medium, []).extend([path for path in paths if path not in self._css[medium]])
     90
     91    def __add__(self, other):
     92        combined = Media()
     93        for name in MEDIA_TYPES:
     94            getattr(combined, 'add_' + name)(getattr(self, '_' + name, None))
     95            getattr(combined, 'add_' + name)(getattr(other, '_' + name, None))
     96        return combined
     97       
     98class WidgetBase(type):
     99    "Metaclass for all widgets"
     100    def __new__(cls, name, bases, attrs):       
     101        media_definition = attrs.pop('Media', None)
     102        media_property = attrs.pop('media', None)
     103       
     104        # If this class definition doesn't have a media definition,
     105        # search the base classes.
     106        if media_definition == None and media_property == None:
     107            for base in bases:
     108                media_definition = getattr(base, 'Media', None)
     109                media_property = getattr(base, 'media', None)
     110                if media_definition or media_property:
     111                    break;
     112
     113        new_class = type.__new__(cls, name, bases, attrs)
     114        setattr(new_class, 'media', media_property and media_property or Media(media_definition))
     115        return new_class
     116       
    25117class Widget(object):
     118    __metaclass__ = WidgetBase
    26119    is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
    27120
    28121    def __init__(self, attrs=None):
     
    377470        """
    378471        raise NotImplementedError('Subclasses must implement this method.')
    379472
     473    def _get_media(self):
     474        "Media for a multiwidget is the combination of all media of the subwidgets"
     475        media = Media()
     476        for w in self.widgets:
     477            media = media + w.media
     478        return media
     479    media = property(_get_media)
     480   
    380481class SplitDateTimeWidget(MultiWidget):
    381482    """
    382483    A Widget that splits datetime input into two <input type="text"> boxes.
     
    389490        if value:
    390491            return [value.date(), value.time()]
    391492        return [None, None]
     493   
     494 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# 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 = {
     23...            'all': ('/path/to/css1','/path/to/css2')
     24...         }
     25...         js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')
     26
     27>>> w1 = MyWidget1()
     28>>> print w1.media
     29<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     30<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     31<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     32<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     33<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     34
     35# Media objects can be interrogated by media type
     36>>> print w1.media['css']
     37<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     38<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     39
     40>>> print w1.media['js']
     41<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     42<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     43<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     44
     45# Media objects can be combined. Any given media resource will appear only
     46# once. Duplicated media definitions are ignored.
     47>>> class MyWidget2(TextInput):
     48...     class Media:
     49...         css = {
     50...            'all': ('/path/to/css2','/path/to/css3')
     51...         }
     52...         js = ('/path/to/js1','/path/to/js4')
     53
     54>>> class MyWidget3(TextInput):
     55...     class Media:
     56...         css = {
     57...            'all': ('/path/to/css3','/path/to/css1')
     58...         }
     59...         js = ('/path/to/js1','/path/to/js4')
     60
     61>>> w2 = MyWidget2()
     62>>> w3 = MyWidget3()
     63>>> print w1.media + w2.media + w3.media
     64<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     65<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     66<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     67<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     68<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     69<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     70<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     71
     72# Check that media addition hasn't affected the original objects
     73>>> print w1.media
     74<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     75<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     76<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     77<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     78<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     79
     80
     81# If a widget extends another, it inherits the parent widget's media
     82>>> class MyWidget4(MyWidget1):
     83...     pass
     84
     85>>> w4 = MyWidget4()
     86>>> print w4.media
     87<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     88<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     89<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     90<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     91<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     92
     93# If a widget extends another but defines media, it overrides the parent widget's media.
     94>>> class MyWidget5(MyWidget1):
     95...     class Media:
     96...         css = {
     97...            'all': ('/path/to/css3','/path/to/css1')
     98...         }
     99...         js = ('/path/to/js1','/path/to/js4')
     100
     101>>> w5 = MyWidget5()
     102>>> print w5.media
     103<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     104<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     105<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     106<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     107
     108# You can ask a form for the media required by its widgets.
     109>>> class MyForm(Form):
     110...     field1 = CharField(max_length=20, widget=MyWidget1())
     111...     field2 = CharField(max_length=20, widget=MyWidget2())
     112>>> f1 = MyForm()
     113>>> print f1.media
     114<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     115<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     116<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     117<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     118<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     119<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     120<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     121
     122# Form media can be combined to produce a single media definition.
     123>>> class AnotherForm(Form):
     124...     field3 = CharField(max_length=20, widget=MyWidget3())
     125>>> f2 = AnotherForm()
     126>>> print f1.media + f2.media
     127<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     128<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     129<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     130<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     131<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     132<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     133<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     134
     135# Form media can be defined as a property, too
     136>>> class MyWidget6(TextInput):
     137...     def _media(self):
     138...         return Media(css={'all': ('/some/path',)})
     139...     media = property(_media)
     140
     141>>> w6 = MyWidget6()
     142>>> print w6.media
     143<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
     144
     145# You can extend media, if required.
     146>>> class MyWidget7(MyWidget6):
     147...     def _media(self):
     148...         return super(MyWidget7, self).media + Media(css={'all': ('/other/path',)})
     149...     media = property(_media)
     150
     151>>> w7 = MyWidget7()
     152>>> print w7.media
     153<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
     154<link href="http://media.example.com/other/path" type="text/css" media="all" rel="stylesheet" />
     155
     156# A widget can define CSS media for multiple output media types
     157>>> class MyWidget8(TextInput):
     158...     class Media:
     159...         css = {
     160...            'screen, print': ('/file1','/file2'),
     161...            'screen': ('/file3',),
     162...            'print': ('/file4',)
     163...         }
     164...         js = ('/path/to/js1','/path/to/js4')
     165
     166>>> w8 = MyWidget8()
     167>>> print w8.media
     168<link href="http://media.example.com/file4" type="text/css" media="print" rel="stylesheet" />
     169<link href="http://media.example.com/file3" type="text/css" media="screen" rel="stylesheet" />
     170<link href="http://media.example.com/file1" type="text/css" media="screen, print" rel="stylesheet" />
     171<link href="http://media.example.com/file2" type="text/css" media="screen, print" rel="stylesheet" />
     172<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     173<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     174
     175# MultiWidgets have a default media definition that gets all the
     176# media from the component widgets
     177>>> class MyWidget9(MultiWidget):
     178...     def __init__(self, attrs=None):
     179...         widgets = [MyWidget1, MyWidget2, MyWidget3]
     180...         MultiWidget.__init__(self, widgets, attrs)
     181           
     182>>> w9 = MyWidget9()
     183>>> print w9.media           
     184<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
     185<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
     186<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
     187<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
     188<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
     189<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
     190<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
     191
     192"""
  • 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 *
     
    36963697    'form_tests': form_tests,
    36973698    'localflavor': localflavor_tests,
    36983699    'regressions': regression_tests,
     3700    'media_tests': media_tests
    36993701}
    37003702
    37013703if __name__ == "__main__":
Back to Top