Ticket #13978: 13978-internal-media.patch

File 13978-internal-media.patch, 16.1 KB (added by Derek Payton, 8 years ago)
  • django/forms/widgets.py

    From ef6502060d193917e0e70b78049d8104ccad46fd Mon Sep 17 00:00:00 2001
    From: dmpayton <derek.payton@gmail.com>
    Date: Tue, 20 Nov 2012 15:34:44 -0800
    Subject: [PATCH 1/2] Allow inline JS/CSS in forms.Media (#13978)
    
    ---
     django/forms/widgets.py                    |   44 ++++++++++++++++++++--------
     docs/topics/forms/media.txt                |   37 +++++++++++++++++++++++
     tests/regressiontests/forms/tests/media.py |   37 +++++++++++++++++++++++
     3 files changed, 106 insertions(+), 12 deletions(-)
    
    diff --git a/django/forms/widgets.py b/django/forms/widgets.py
    index c761ea8..65e59e2 100644
    a b  
    3434
    3535@python_2_unicode_compatible
    3636class Media(object):
     37    class Inline(object):
     38        def __init__(self, content):
     39            self.content = content
     40
     41        def __eq__(self, other):
     42            return (
     43                type(self) is type(other) and
     44                self.content == other.content
     45            )
     46
    3747    def __init__(self, media=None, **kwargs):
    3848        if media:
    3949            media_attrs = media.__dict__
    def render(self): 
    5767        return mark_safe('\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES])))
    5868
    5969    def render_js(self):
    60         return [format_html('<script type="text/javascript" src="{0}"></script>', self.absolute_path(path)) for path in self._js]
     70        html = []
     71        for script in self._js:
     72            if isinstance(script, self.Inline):
     73                html.append(format_html('<script type="text/javascript">{0}</script>', mark_safe(script.content)))
     74            else:
     75                html.append(format_html('<script type="text/javascript" src="{0}"></script>', self.absolute_path(script)))
     76        return html
    6177
    6278    def render_css(self):
    6379        # To keep rendering order consistent, we can't just iterate over items().
    6480        # We need to sort the keys, and iterate over the sorted list.
     81        html = []
    6582        media = sorted(self._css.keys())
    66         return chain(*[
    67                 [format_html('<link href="{0}" type="text/css" media="{1}" rel="stylesheet" />', self.absolute_path(path), medium)
    68                     for path in self._css[medium]]
    69                 for medium in media])
     83        for medium in media:
     84            for style in self._css[medium]:
     85                if isinstance(style, self.Inline):
     86                    html.append(format_html('<style type="text/css" media="{0}">{1}</style>', medium, mark_safe(style.content)))
     87                else:
     88                    html.append(format_html('<link href="{0}" type="text/css" media="{1}" rel="stylesheet" />', self.absolute_path(style), medium))
     89        return html
    7090
    7191    def absolute_path(self, path, prefix=None):
    7292        if path.startswith(('http://', 'https://', '/')):
    def __getitem__(self, name): 
    87107
    88108    def add_js(self, data):
    89109        if data:
    90             for path in data:
    91                 if path not in self._js:
    92                     self._js.append(path)
     110            for script in data:
     111                if script not in self._js:
     112                    self._js.append(script)
    93113
    94114    def add_css(self, data):
    95115        if data:
    96             for medium, paths in data.items():
    97                 for path in paths:
    98                     if not self._css.get(medium) or path not in self._css[medium]:
    99                         self._css.setdefault(medium, []).append(path)
     116            for medium, styles in data.items():
     117                for style in styles:
     118                    if not self._css.get(medium) or style not in self._css[medium]:
     119                        self._css.setdefault(medium, []).append(style)
    100120
    101121    def __add__(self, other):
    102122        combined = Media()
  • docs/topics/forms/media.txt

    diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt
    index 98e70e5..34d5f89 100644
    a b If you require even more control over media inheritance, define your media 
    167167using a :ref:`dynamic property <dynamic-property>`. Dynamic properties give
    168168you complete control over which media files are inherited, and which are not.
    169169
     170Specifying inline media
     171-----------------------
     172
     173.. versionadded:: 1.5
     174
     175At some point you may run into a situation that calls for a bit of inline CSS
     176or JavaScript, such as initializing a JavaScript plugin. To do this, simply
     177pass your inline code as a ``forms.Media.Inline`` object in the Media class::
     178
     179    >>> class FancyCalendarWidget(CalendarWidget):
     180    ...     class Media:
     181    ...         css = {
     182    ...             'all': ('fancy.css',)
     183    ...         }
     184    ...         js = (
     185    ...             'whizbang.js',
     186    ...             forms.Media.Inline('init_fancification();'),
     187    ...         )
     188
     189    >>> w = FancyCalendarWidget()
     190    >>> print(w.media)
     191    <link href="/static/fancy.css" type="text/css" media="all" rel="stylesheet" />
     192    <script type="text/javascript" src="/static/whizbang.js"></script>
     193    <script type="text/javascript">init_fancification();</script>
     194
     195Inline CSS works the same way::
     196
     197    >>> class HoneypotWidget(forms.TextInput):
     198    ...     class Media:
     199    ...         css = {
     200    ...             'all': (forms.Media.Inline(".honeypot_row { display: none; }"),)
     201    ...         }
     202
     203    >>> w = HoneypotWidget()
     204    >>> print(w.media)
     205    <style type="text/css" media="all">.honeypot_row { display: none; }</style>
     206
    170207.. _dynamic-property:
    171208
    172209Media as a dynamic property
  • tests/regressiontests/forms/tests/media.py

    diff --git a/tests/regressiontests/forms/tests/media.py b/tests/regressiontests/forms/tests/media.py
    index c492a1e..71e969e 100644
    a b class Media: 
    455455<link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" />
    456456<link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />""")
    457457
     458    def test_inline_media(self):
     459        ## Inline JavaScript and CSS
     460        class MyWidget(TextInput):
     461            class Media:
     462                css = {'all': (Media.Inline('.mywidget { display: none; }'),)}
     463                js = (Media.Inline('init_mywidget();'),)
     464
     465        w = MyWidget()
     466        self.assertEqual('<style type="text/css" media="all">.mywidget { display: none; }</style>', str(w.media['css']))
     467        self.assertEqual('<script type="text/javascript">init_mywidget();</script>', str(w.media['js']))
     468
     469    def test_inline_media_property(self):
     470        ## Inline JavaScript and CSS as a media property
     471        class MyWidget(TextInput):
     472            def _media(self):
     473                return Media(css={'all': (Media.Inline('.mywidget { display: none; }'),)},
     474                             js=(Media.Inline('init_mywidget();'),))
     475            media = property(_media)
     476
     477        w = MyWidget()
     478        self.assertEqual('<style type="text/css" media="all">.mywidget { display: none; }</style>', str(w.media['css']))
     479        self.assertEqual('<script type="text/javascript">init_mywidget();</script>', str(w.media['js']))
     480
     481    def test_inline_media_mutiple(self):
     482        ## Multiple instances of inline media should only be rendered once
     483        class MyWidget(TextInput):
     484            class Media:
     485                css = {'all': (Media.Inline('.mywidget { display: none; }'),)}
     486                js = (Media.Inline('init_mywidget();'),)
     487
     488        class MyForm(Form):
     489            field1 = CharField(widget=MyWidget)
     490            field2 = CharField(widget=MyWidget)
     491
     492        f = MyForm()
     493        self.assertEqual("""<style type="text/css" media="all">.mywidget { display: none; }</style>
     494<script type="text/javascript">init_mywidget();</script>""", str(f.media))
    458495
    459496@override_settings(
    460497    STATIC_URL='http://media.example.com/static/',
  • django/forms/widgets.py

    -- 
    1.7.10
    
    
    From 92850ae865d2c2633a59f19fa77d713e5ed24464 Mon Sep 17 00:00:00 2001
    From: dmpayton <derek.payton@gmail.com>
    Date: Tue, 27 Nov 2012 18:54:37 -0800
    Subject: [PATCH 2/2] Updates for inline media (based on feedback in #13978):
     - Nuke Media.Inline in favor of separate top-level
     classes for JS and CSS - Switch naming from "inline" to
     "internal" (vs. an external script or stylesheet) -
     Update docs and tests to reflect changes
    
    ---
     django/forms/widgets.py                    |   50 +++++++++++++++++-----------
     docs/topics/forms/media.txt                |   17 +++++-----
     tests/regressiontests/forms/tests/media.py |   14 ++++----
     3 files changed, 47 insertions(+), 34 deletions(-)
    
    diff --git a/django/forms/widgets.py b/django/forms/widgets.py
    index 65e59e2..a925165 100644
    a b  
    2222from django.utils import datetime_safe, formats, six
    2323
    2424__all__ = (
    25     'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
    26     'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput',
    27     'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
    28     'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
    29     'CheckboxSelectMultiple', 'MultiWidget',
     25    'InternalCSS', 'InternalJS', 'Media', 'MediaDefiningClass', 'Widget',
     26    'TextInput', 'PasswordInput', 'HiddenInput', 'MultipleHiddenInput',
     27    'ClearableFileInput', 'FileInput', 'DateInput', 'DateTimeInput',
     28    'TimeInput', 'Textarea', 'CheckboxInput', 'Select', 'NullBooleanSelect',
     29    'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple', 'MultiWidget',
    3030    'SplitDateTimeWidget',
    3131)
    3232
    3333MEDIA_TYPES = ('css','js')
    3434
    35 @python_2_unicode_compatible
    36 class Media(object):
    37     class Inline(object):
    38         def __init__(self, content):
    39             self.content = content
     35class InternalMedia(object):
     36    """
     37    Base class for specifying internal CSS and JS documents in a Media class.
     38    """
     39    def __init__(self, content):
     40        self.content = content
     41
     42    def __eq__(self, other):
     43        return (
     44            type(self) is type(other) and
     45            self.content == other.content
     46        )
    4047
    41         def __eq__(self, other):
    42             return (
    43                 type(self) is type(other) and
    44                 self.content == other.content
    45             )
     48class InternalCSS(InternalMedia):
     49    def render(self, medium=None):
     50        medium = medium or 'all'
     51        return format_html('<style type="text/css" media="{0}">{1}</style>', medium, mark_safe(self.content))
    4652
     53class InternalJS(InternalMedia):
     54    def render(self):
     55        return format_html('<script type="text/javascript">{0}</script>', mark_safe(self.content))
     56
     57@python_2_unicode_compatible
     58class Media(object):
    4759    def __init__(self, media=None, **kwargs):
    4860        if media:
    4961            media_attrs = media.__dict__
    def render(self): 
    6981    def render_js(self):
    7082        html = []
    7183        for script in self._js:
    72             if isinstance(script, self.Inline):
    73                 html.append(format_html('<script type="text/javascript">{0}</script>', mark_safe(script.content)))
     84            if isinstance(script, InternalJS):
     85                html.append(script.render())
    7486            else:
    7587                html.append(format_html('<script type="text/javascript" src="{0}"></script>', self.absolute_path(script)))
    7688        return html
    def render_css(self): 
    8294        media = sorted(self._css.keys())
    8395        for medium in media:
    8496            for style in self._css[medium]:
    85                 if isinstance(style, self.Inline):
    86                     html.append(format_html('<style type="text/css" media="{0}">{1}</style>', medium, mark_safe(style.content)))
     97                if isinstance(style, InternalCSS):
     98                    html.append(style.render(medium))
    8799                else:
    88100                    html.append(format_html('<link href="{0}" type="text/css" media="{1}" rel="stylesheet" />', self.absolute_path(style), medium))
    89101        return html
  • docs/topics/forms/media.txt

    diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt
    index 34d5f89..1e607ce 100644
    a b If you require even more control over media inheritance, define your media 
    167167using a :ref:`dynamic property <dynamic-property>`. Dynamic properties give
    168168you complete control over which media files are inherited, and which are not.
    169169
    170 Specifying inline media
    171 -----------------------
     170Specifying internal media
     171-------------------------
    172172
    173173.. versionadded:: 1.5
    174174
    175 At some point you may run into a situation that calls for a bit of inline CSS
    176 or JavaScript, such as initializing a JavaScript plugin. To do this, simply
    177 pass your inline code as a ``forms.Media.Inline`` object in the Media class::
     175At some point you may run into a situation that calls for internal CSS
     176or JavaScript, perhaps to initialize a plugin. This can be accomplished with
     177``InternalCSS`` and ``InternalJS``::
    178178
    179179    >>> class FancyCalendarWidget(CalendarWidget):
    180180    ...     class Media:
    pass your inline code as a ``forms.Media.Inline`` object in the Media class:: 
    183183    ...         }
    184184    ...         js = (
    185185    ...             'whizbang.js',
    186     ...             forms.Media.Inline('init_fancification();'),
     186    ...             forms.InternalJS('init_fancification();'),
    187187    ...         )
    188188
    189189    >>> w = FancyCalendarWidget()
    pass your inline code as a ``forms.Media.Inline`` object in the Media class:: 
    192192    <script type="text/javascript" src="/static/whizbang.js"></script>
    193193    <script type="text/javascript">init_fancification();</script>
    194194
    195 Inline CSS works the same way::
     195Internal CSS is rendered with the correct ``media`` attribute::
    196196
    197197    >>> class HoneypotWidget(forms.TextInput):
    198198    ...     class Media:
    199199    ...         css = {
    200     ...             'all': (forms.Media.Inline(".honeypot_row { display: none; }"),)
     200    ...             'all': (forms.InternalCSS(".honeypot_row { display: none; }"),)
    201201    ...         }
    202202
    203203    >>> w = HoneypotWidget()
    204204    >>> print(w.media)
    205205    <style type="text/css" media="all">.honeypot_row { display: none; }</style>
    206206
     207
    207208.. _dynamic-property:
    208209
    209210Media as a dynamic property
  • tests/regressiontests/forms/tests/media.py

    diff --git a/tests/regressiontests/forms/tests/media.py b/tests/regressiontests/forms/tests/media.py
    index 71e969e..887e24c 100644
    a b  
    11# -*- coding: utf-8 -*-
    2 from django.forms import TextInput, Media, TextInput, CharField, Form, MultiWidget
     2from django.forms import TextInput, InternalCSS, InternalJS, Media, TextInput, CharField, Form, MultiWidget
    33from django.template import Template, Context
    44from django.test import TestCase
    55from django.test.utils import override_settings
    def test_inline_media(self): 
    459459        ## Inline JavaScript and CSS
    460460        class MyWidget(TextInput):
    461461            class Media:
    462                 css = {'all': (Media.Inline('.mywidget { display: none; }'),)}
    463                 js = (Media.Inline('init_mywidget();'),)
     462                css = {'all': (InternalCSS('.mywidget { display: none; }'),)}
     463                js = (InternalJS('init_mywidget();'),)
    464464
    465465        w = MyWidget()
    466466        self.assertEqual('<style type="text/css" media="all">.mywidget { display: none; }</style>', str(w.media['css']))
    def test_inline_media_property(self): 
    470470        ## Inline JavaScript and CSS as a media property
    471471        class MyWidget(TextInput):
    472472            def _media(self):
    473                 return Media(css={'all': (Media.Inline('.mywidget { display: none; }'),)},
    474                              js=(Media.Inline('init_mywidget();'),))
     473                return Media(css={'all': (InternalCSS('.mywidget { display: none; }'),)},
     474                             js=(InternalJS('init_mywidget();'),))
    475475            media = property(_media)
    476476
    477477        w = MyWidget()
    def test_inline_media_mutiple(self): 
    482482        ## Multiple instances of inline media should only be rendered once
    483483        class MyWidget(TextInput):
    484484            class Media:
    485                 css = {'all': (Media.Inline('.mywidget { display: none; }'),)}
    486                 js = (Media.Inline('init_mywidget();'),)
     485                css = {'all': (InternalCSS('.mywidget { display: none; }'),)}
     486                js = (InternalJS('init_mywidget();'),)
    487487
    488488        class MyForm(Form):
    489489            field1 = CharField(widget=MyWidget)
Back to Top