Ticket #13978: 13978-internal-media.patch
File 13978-internal-media.patch, 16.1 KB (added by , 12 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 34 34 35 35 @python_2_unicode_compatible 36 36 class 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 37 47 def __init__(self, media=None, **kwargs): 38 48 if media: 39 49 media_attrs = media.__dict__ … … def render(self): 57 67 return mark_safe('\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))) 58 68 59 69 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 61 77 62 78 def render_css(self): 63 79 # To keep rendering order consistent, we can't just iterate over items(). 64 80 # We need to sort the keys, and iterate over the sorted list. 81 html = [] 65 82 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 70 90 71 91 def absolute_path(self, path, prefix=None): 72 92 if path.startswith(('http://', 'https://', '/')): … … def __getitem__(self, name): 87 107 88 108 def add_js(self, data): 89 109 if data: 90 for pathin data:91 if pathnot 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) 93 113 94 114 def add_css(self, data): 95 115 if data: 96 for medium, paths in data.items():97 for path in paths:98 if not self._css.get(medium) or pathnot 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) 100 120 101 121 def __add__(self, other): 102 122 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 167 167 using a :ref:`dynamic property <dynamic-property>`. Dynamic properties give 168 168 you complete control over which media files are inherited, and which are not. 169 169 170 Specifying inline media 171 ----------------------- 172 173 .. versionadded:: 1.5 174 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:: 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 195 Inline 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 170 207 .. _dynamic-property: 171 208 172 209 Media 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: 455 455 <link href="/path/to/css3" type="text/css" media="all" rel="stylesheet" /> 456 456 <link href="/some/form/css" type="text/css" media="all" rel="stylesheet" />""") 457 457 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)) 458 495 459 496 @override_settings( 460 497 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 22 22 from django.utils import datetime_safe, formats, six 23 23 24 24 __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', 30 30 'SplitDateTimeWidget', 31 31 ) 32 32 33 33 MEDIA_TYPES = ('css','js') 34 34 35 @python_2_unicode_compatible 36 class Media(object): 37 class Inline(object): 38 def __init__(self, content): 39 self.content = content 35 class 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 ) 40 47 41 def __eq__(self, other): 42 return ( 43 type(self) is type(other) and 44 self.content == other.content 45 ) 48 class 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)) 46 52 53 class 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 58 class Media(object): 47 59 def __init__(self, media=None, **kwargs): 48 60 if media: 49 61 media_attrs = media.__dict__ … … def render(self): 69 81 def render_js(self): 70 82 html = [] 71 83 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()) 74 86 else: 75 87 html.append(format_html('<script type="text/javascript" src="{0}"></script>', self.absolute_path(script))) 76 88 return html … … def render_css(self): 82 94 media = sorted(self._css.keys()) 83 95 for medium in media: 84 96 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)) 87 99 else: 88 100 html.append(format_html('<link href="{0}" type="text/css" media="{1}" rel="stylesheet" />', self.absolute_path(style), medium)) 89 101 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 167 167 using a :ref:`dynamic property <dynamic-property>`. Dynamic properties give 168 168 you complete control over which media files are inherited, and which are not. 169 169 170 Specifying in linemedia171 ----------------------- 170 Specifying internal media 171 ------------------------- 172 172 173 173 .. versionadded:: 1.5 174 174 175 At some point you may run into a situation that calls for a bit of inlineCSS176 or JavaScript, such as initializing a JavaScript plugin. To do this, simply177 pass your inline code as a ``forms.Media.Inline`` object in the Media class::175 At some point you may run into a situation that calls for internal CSS 176 or JavaScript, perhaps to initialize a plugin. This can be accomplished with 177 ``InternalCSS`` and ``InternalJS``:: 178 178 179 179 >>> class FancyCalendarWidget(CalendarWidget): 180 180 ... class Media: … … pass your inline code as a ``forms.Media.Inline`` object in the Media class:: 183 183 ... } 184 184 ... js = ( 185 185 ... 'whizbang.js', 186 ... forms. Media.Inline('init_fancification();'),186 ... forms.InternalJS('init_fancification();'), 187 187 ... ) 188 188 189 189 >>> w = FancyCalendarWidget() … … pass your inline code as a ``forms.Media.Inline`` object in the Media class:: 192 192 <script type="text/javascript" src="/static/whizbang.js"></script> 193 193 <script type="text/javascript">init_fancification();</script> 194 194 195 In line CSS works the same way::195 Internal CSS is rendered with the correct ``media`` attribute:: 196 196 197 197 >>> class HoneypotWidget(forms.TextInput): 198 198 ... class Media: 199 199 ... css = { 200 ... 'all': (forms. Media.Inline(".honeypot_row { display: none; }"),)200 ... 'all': (forms.InternalCSS(".honeypot_row { display: none; }"),) 201 201 ... } 202 202 203 203 >>> w = HoneypotWidget() 204 204 >>> print(w.media) 205 205 <style type="text/css" media="all">.honeypot_row { display: none; }</style> 206 206 207 207 208 .. _dynamic-property: 208 209 209 210 Media 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 1 1 # -*- coding: utf-8 -*- 2 from django.forms import TextInput, Media, TextInput, CharField, Form, MultiWidget2 from django.forms import TextInput, InternalCSS, InternalJS, Media, TextInput, CharField, Form, MultiWidget 3 3 from django.template import Template, Context 4 4 from django.test import TestCase 5 5 from django.test.utils import override_settings … … def test_inline_media(self): 459 459 ## Inline JavaScript and CSS 460 460 class MyWidget(TextInput): 461 461 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();'),) 464 464 465 465 w = MyWidget() 466 466 self.assertEqual('<style type="text/css" media="all">.mywidget { display: none; }</style>', str(w.media['css'])) … … def test_inline_media_property(self): 470 470 ## Inline JavaScript and CSS as a media property 471 471 class MyWidget(TextInput): 472 472 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();'),)) 475 475 media = property(_media) 476 476 477 477 w = MyWidget() … … def test_inline_media_mutiple(self): 482 482 ## Multiple instances of inline media should only be rendered once 483 483 class MyWidget(TextInput): 484 484 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();'),) 487 487 488 488 class MyForm(Form): 489 489 field1 = CharField(widget=MyWidget)