Index: django/newforms/formsets.py
===================================================================
--- django/newforms/formsets.py	(revision 5636)
+++ django/newforms/formsets.py	(working copy)
@@ -1,6 +1,6 @@
 from forms import Form, ValidationError
 from fields import IntegerField, BooleanField
-from widgets import HiddenInput
+from widgets import HiddenInput, Media
 
 # special field names
 FORM_COUNT_FIELD_NAME = 'COUNT'
@@ -149,6 +149,15 @@
         self.full_clean()
         return self._is_valid
 
+    def _get_media(self):
+        # All the forms on a FormSet are the same, so you only need to 
+        # interrogate the first form for media.
+        if self.forms:
+            return self.forms[0].media
+        else:
+            return Media()
+    media = property(_get_media)
+    
 def formset_for_form(form, formset=BaseFormSet, num_extra=1, orderable=False, deletable=False):
     """Return a FormSet for the given form class."""
     attrs = {'form_class': form, 'num_extra': num_extra, 'orderable': orderable, 'deletable': deletable}
Index: django/newforms/forms.py
===================================================================
--- django/newforms/forms.py	(revision 5636)
+++ django/newforms/forms.py	(working copy)
@@ -9,7 +9,7 @@
 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
 
 from fields import Field
-from widgets import TextInput, Textarea
+from widgets import Media, TextInput, Textarea
 from util import flatatt, ErrorDict, ErrorList, ValidationError
 
 __all__ = ('BaseForm', 'Form')
@@ -234,6 +234,16 @@
         self.is_bound = False
         self.__errors = None
 
+    def _get_media(self):
+        """
+        Provide a description of all media required to render the widgets on this form
+        """
+        media = Media()
+        for field in self.fields.values():
+            media = media + field.widget.media
+        return media
+    media = property(_get_media)
+    
 class Form(BaseForm):
     "A collection of Fields, plus their associated data."
     # This is a separate class from BaseForm in order to abstract the way
Index: django/newforms/widgets.py
===================================================================
--- django/newforms/widgets.py	(revision 5636)
+++ django/newforms/widgets.py	(working copy)
@@ -8,6 +8,7 @@
     from sets import Set as set   # Python 2.3 fallback
 
 from itertools import chain
+from django.conf import settings
 from django.utils.datastructures import MultiValueDict
 from django.utils.html import escape
 from django.utils.translation import ugettext
@@ -15,14 +16,106 @@
 from util import flatatt
 
 __all__ = (
-    'Widget', 'TextInput', 'PasswordInput',
+    'Media', 'Widget', 'TextInput', 'PasswordInput',
     'HiddenInput', 'MultipleHiddenInput',
     'FileInput', 'Textarea', 'CheckboxInput',
     'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
     'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
 )
 
+MEDIA_TYPES = ('css','js')
+
+class Media(StrAndUnicode):
+    def __init__(self, media=None, **kwargs):
+        if media:
+            media_attrs = media.__dict__
+            del media_attrs['__module__']
+            del media_attrs['__doc__']
+        else:
+            media_attrs = kwargs
+            
+        self._css = {}
+        self._js = []
+        
+        for name in MEDIA_TYPES:
+            getattr(self, 'add_' + name)(media_attrs.pop(name, None))
+
+        # Any leftover attributes must be invalid.
+        if media_attrs != {}:
+            raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())
+        
+    def __unicode__(self):
+        return self.render()
+        
+    def render(self):
+        output = []
+        for name in MEDIA_TYPES:
+            output.extend(getattr(self, 'render_' + name)())
+        return u'\n'.join(output)
+        
+    def render_js(self):
+        output = []
+        for path in self._js:
+            output.append(u'<script type="text/javascript" src="%s"></script>' % self.absolute_path(path))
+        return output
+        
+    def render_css(self):
+        output = []
+        # To keep rendering order consistent, we can't just iterate over items().
+        # We need to sort the keys, and iterate over the sorted list.
+        media = self._css.keys()
+        media.sort()
+        for medium in media:
+            for path in self._css[medium]:
+                output.append(u'<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium))
+        return output
+        
+    def absolute_path(self, path):
+        return (path.startswith(u'http://') or path.startswith(u'https://')) and path or u''.join([settings.MEDIA_URL,path])
+
+    def __getitem__(self, name):
+        "Returns a Media object that only contains media of the given type"
+        if name in MEDIA_TYPES:
+            return Media(**{name: getattr(self, '_' + name)})
+        raise KeyError('Unknown media type "%s"' % name)
+
+    def add_js(self, data):
+        if data:    
+            self._js.extend([path for path in data if path not in self._js])
+            
+    def add_css(self, data):
+        if data:
+            for medium, paths in data.items():
+                self._css.setdefault(medium, []).extend([path for path in paths if path not in self._css[medium]])
+
+    def __add__(self, other):
+        combined = Media()
+        for name in MEDIA_TYPES:
+            getattr(combined, 'add_' + name)(getattr(self, '_' + name, None))
+            getattr(combined, 'add_' + name)(getattr(other, '_' + name, None))
+        return combined
+        
+class WidgetBase(type):
+    "Metaclass for all widgets"
+    def __new__(cls, name, bases, attrs):        
+        media_definition = attrs.pop('Media', None)
+        media_property = attrs.pop('media', None)
+        
+        # If this class definition doesn't have a media definition, 
+        # search the base classes.
+        if media_definition == None and media_property == None:
+            for base in bases:
+                media_definition = getattr(base, 'Media', None)
+                media_property = getattr(base, 'media', None)
+                if media_definition or media_property:
+                    break;
+
+        new_class = type.__new__(cls, name, bases, attrs)
+        setattr(new_class, 'media', media_property and media_property or Media(media_definition))
+        return new_class
+        
 class Widget(object):
+    __metaclass__ = WidgetBase
     is_hidden = False          # Determines whether this corresponds to an <input type="hidden">.
 
     def __init__(self, attrs=None):
@@ -377,6 +470,14 @@
         """
         raise NotImplementedError('Subclasses must implement this method.')
 
+    def _get_media(self):
+        "Media for a multiwidget is the combination of all media of the subwidgets"
+        media = Media()
+        for w in self.widgets:
+            media = media + w.media
+        return media
+    media = property(_get_media)
+    
 class SplitDateTimeWidget(MultiWidget):
     """
     A Widget that splits datetime input into two <input type="text"> boxes.
@@ -389,3 +490,4 @@
         if value:
             return [value.date(), value.time()]
         return [None, None]
+    
\ No newline at end of file
Index: django/contrib/admin/options.py
===================================================================
--- django/contrib/admin/options.py	(revision 5636)
+++ django/contrib/admin/options.py	(working copy)
@@ -2,6 +2,7 @@
 from django import newforms as forms
 from django.newforms.formsets import all_valid
 from django.newforms.models import inline_formset
+from django.newforms.widgets import Media
 from django.contrib.admin import widgets
 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
 from django.db import models
@@ -48,6 +49,13 @@
     def first_field(self):
         for bf in self.form:
             return bf
+            
+    def _get_media(self):
+        media = self.form.media
+        for fs in self.fieldsets:
+            media = media + fs.media
+        return media
+    media = property(_get_media)
 
 class Fieldset(object):
     def __init__(self, name=None, fields=(), classes=(), description=None):
@@ -55,6 +63,13 @@
         self.classes = u' '.join(classes)
         self.description = description
 
+    def _get_media(self):
+        from django.conf import settings
+        if 'collapse' in self.classes:
+            return Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX])
+        return Media()
+    media = property(_get_media)
+    
 class BoundFieldset(object):
     def __init__(self, form, fieldset):
         self.form, self.fieldset = form, fieldset
@@ -123,12 +138,12 @@
 
         # For DateFields, add a custom CSS class.
         if isinstance(db_field, models.DateField):
-            kwargs['widget'] = forms.TextInput(attrs={'class': 'vDateField', 'size': '10'})
+            kwargs['widget'] = widgets.AdminDateWidget
             return db_field.formfield(**kwargs)
 
         # For TimeFields, add a custom CSS class.
         if isinstance(db_field, models.TimeField):
-            kwargs['widget'] = forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'})
+            kwargs['widget'] = widgets.AdminTimeWidget
             return db_field.formfield(**kwargs)
 
         # For ForeignKey or ManyToManyFields, use a special widget.
@@ -194,38 +209,21 @@
         else:
             return self.change_view(request, unquote(url))
 
-    def javascript(self, request, fieldsets):
-        """
-        Returns a list of URLs to include via <script> statements.
+    def media(self, request):
+        from django.conf import settings
 
-        The URLs can be absolute ('/js/admin/') or explicit
-        ('http://example.com/foo.js').
-        """
-        from django.conf import settings
         js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
         if self.prepopulated_fields:
             js.append('js/urlify.js')
-        if self.opts.has_field_type(models.DateTimeField) or self.opts.has_field_type(models.TimeField) or self.opts.has_field_type(models.DateField):
-            js.extend(['js/calendar.js', 'js/admin/DateTimeShortcuts.js'])
         if self.opts.get_ordered_objects():
             js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
         if self.js:
             js.extend(self.js)
         if self.filter_vertical or self.filter_horizontal:
             js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
-        for fs in fieldsets:
-            if 'collapse' in fs.classes:
-                js.append('js/admin/CollapsedFieldsets.js')
-                break
-        prefix = settings.ADMIN_MEDIA_PREFIX
-        return ['%s%s' % (prefix, url) for url in js]
-
-    def javascript_add(self, request):
-        return self.javascript(request, self.fieldsets_add(request))
-
-    def javascript_change(self, request, obj):
-        return self.javascript(request, self.fieldsets_change(request, obj))
-
+        
+        return Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
+                
     def fieldsets(self, request):
         """
         Generator that yields Fieldset objects for use on add and change admin
@@ -244,13 +242,11 @@
 
     def fieldsets_add(self, request):
         "Hook for specifying Fieldsets for the add form."
-        for fs in self.fieldsets(request):
-            yield fs
+        return list(self.fieldsets(request))
 
     def fieldsets_change(self, request, obj):
         "Hook for specifying Fieldsets for the change form."
-        for fs in self.fieldsets(request):
-            yield fs
+        return list(self.fieldsets(request))
 
     def has_add_permission(self, request):
         "Returns True if the given request has permission to add an object."
@@ -433,12 +429,17 @@
                 inline_formset = FormSet()
                 inline_formsets.append(inline_formset)
 
+        adminForm = AdminForm(form, list(self.fieldsets_add(request)), self.prepopulated_fields)
+        media = self.media(request) + adminForm.media
+        for fs in inline_formsets:
+            media = media + fs.media
+            
         c = template.RequestContext(request, {
             'title': _('Add %s') % opts.verbose_name,
-            'adminform': AdminForm(form, self.fieldsets_add(request), self.prepopulated_fields),
+            'adminform': adminForm,
             'is_popup': request.REQUEST.has_key('_popup'),
             'show_delete': False,
-            'javascript_imports': self.javascript_add(request),
+            'media': media,
             'bound_inlines': [BoundInline(i, fs) for i, fs in zip(self.inlines, inline_formsets)],
         })
         return render_change_form(self, model, model.AddManipulator(), c, add=True)
@@ -500,13 +501,19 @@
                         #related.get_accessor_name())
                 #orig_list = func()
                 #oldform.order_objects.extend(orig_list)
+                
+        adminForm = AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields)        
+        media = self.media(request) + adminForm.media
+        for fs in inline_formsets:
+            media = media + fs.media
+            
         c = template.RequestContext(request, {
             'title': _('Change %s') % opts.verbose_name,
-            'adminform': AdminForm(form, self.fieldsets_change(request, obj), self.prepopulated_fields),
+            'adminform': adminForm,
             'object_id': object_id,
             'original': obj,
             'is_popup': request.REQUEST.has_key('_popup'),
-            'javascript_imports': self.javascript_change(request, obj),
+            'media': media,
             'bound_inlines': [BoundInline(i, fs) for i, fs in zip(self.inlines, inline_formsets)],
         })
         return render_change_form(self, model, model.ChangeManipulator(object_id), c, change=True)
Index: django/contrib/admin/widgets.py
===================================================================
--- django/contrib/admin/widgets.py	(revision 5636)
+++ django/contrib/admin/widgets.py	(working copy)
@@ -5,6 +5,7 @@
 from django import newforms as forms
 from django.utils.text import capfirst
 from django.utils.translation import ugettext as _
+from django.conf import settings
 
 class FilteredSelectMultiple(forms.SelectMultiple):
     """
@@ -28,13 +29,28 @@
             (name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX))
         return u''.join(output)
 
+class AdminDateWidget(forms.TextInput):
+    class Media:
+        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", 
+              settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
+        
+    def __init__(self, attrs={}):
+        super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'})
+
+class AdminTimeWidget(forms.TextInput):
+    class Media:
+        js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", 
+              settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js")
+
+    def __init__(self, attrs={}):
+        super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'})
+    
 class AdminSplitDateTime(forms.SplitDateTimeWidget):
     """
     A SplitDateTime Widget that has some admin-specific styling.
     """
     def __init__(self, attrs=None):
-        widgets = [forms.TextInput(attrs={'class': 'vDateField', 'size': '10'}),
-                   forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'})]
+        widgets = [AdminDateWidget, AdminTimeWidget]
         # Note that we're calling MultiWidget, not SplitDateTimeWidget, because
         # we want to define widgets.
         forms.MultiWidget.__init__(self, widgets, attrs)
Index: django/contrib/admin/templates/admin/auth/user/change_password.html
===================================================================
--- django/contrib/admin/templates/admin/auth/user/change_password.html	(revision 5636)
+++ django/contrib/admin/templates/admin/auth/user/change_password.html	(working copy)
@@ -2,7 +2,6 @@
 {% load i18n admin_modify adminmedia %}
 {% block extrahead %}{{ block.super }}
 <script type="text/javascript" src="../../../../jsi18n/"></script>
-{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
 {% endblock %}
 {% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
 {% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
Index: django/contrib/admin/templates/admin/change_form.html
===================================================================
--- django/contrib/admin/templates/admin/change_form.html	(revision 5636)
+++ django/contrib/admin/templates/admin/change_form.html	(working copy)
@@ -3,8 +3,7 @@
 
 {% block extrahead %}{{ block.super }}
 <script type="text/javascript" src="../../../jsi18n/"></script>
-{% for js in javascript_imports %}<script type="text/javascript" src="{{ js }}"></script>
-{% endfor %}
+{{ media }}
 {% endblock %}
 
 {% block stylesheet %}{% admin_media_prefix %}css/forms.css{% endblock %}
Index: tests/regressiontests/forms/media.py
===================================================================
--- tests/regressiontests/forms/media.py	(revision 0)
+++ tests/regressiontests/forms/media.py	(revision 0)
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+# Tests for the media handling on widgets and forms
+
+media_tests = r"""
+>>> from django.newforms import TextInput, Media, TextInput, CharField, Form, MultiWidget
+>>> from django.conf import settings
+>>> settings.MEDIA_URL = 'http://media.example.com'
+
+# A widget can exist without a media definition
+>>> class MyWidget(TextInput):
+...     pass
+
+>>> w = MyWidget()
+>>> print w.media
+<BLANKLINE>
+
+# A widget can define media if it needs to.
+# Any absolute path will be preserved; relative paths are combined
+# with the value of settings.MEDIA_URL
+>>> class MyWidget1(TextInput):
+...     class 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')
+
+>>> w1 = MyWidget1()
+>>> print w1.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# Media objects can be interrogated by media type
+>>> print w1.media['css']
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+
+>>> print w1.media['js']
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# Media objects can be combined. Any given media resource will appear only
+# once. Duplicated media definitions are ignored.
+>>> class MyWidget2(TextInput):
+...     class Media:
+...         css = {
+...            'all': ('/path/to/css2','/path/to/css3')
+...         }
+...         js = ('/path/to/js1','/path/to/js4')
+
+>>> class MyWidget3(TextInput):
+...     class Media:
+...         css = {
+...            'all': ('/path/to/css3','/path/to/css1')
+...         }
+...         js = ('/path/to/js1','/path/to/js4')
+
+>>> w2 = MyWidget2()
+>>> w3 = MyWidget3()
+>>> print w1.media + w2.media + w3.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# Check that media addition hasn't affected the original objects
+>>> print w1.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+
+# If a widget extends another, it inherits the parent widget's media
+>>> class MyWidget4(MyWidget1):
+...     pass
+
+>>> w4 = MyWidget4()
+>>> print w4.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+
+# If a widget extends another but defines media, it overrides the parent widget's media.
+>>> class MyWidget5(MyWidget1):
+...     class Media:
+...         css = {
+...            'all': ('/path/to/css3','/path/to/css1')
+...         }
+...         js = ('/path/to/js1','/path/to/js4')
+
+>>> w5 = MyWidget5()
+>>> print w5.media
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# You can ask a form for the media required by its widgets.
+>>> class MyForm(Form):
+...     field1 = CharField(max_length=20, widget=MyWidget1())
+...     field2 = CharField(max_length=20, widget=MyWidget2())
+>>> f1 = MyForm()
+>>> print f1.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# Form media can be combined to produce a single media definition.
+>>> class AnotherForm(Form):
+...     field3 = CharField(max_length=20, widget=MyWidget3())
+>>> f2 = AnotherForm()
+>>> print f1.media + f2.media
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# Form media can be defined as a property, too
+>>> class MyWidget6(TextInput):
+...     def _media(self):
+...         return Media(css={'all': ('/some/path',)})
+...     media = property(_media)
+
+>>> w6 = MyWidget6()
+>>> print w6.media
+<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
+
+# You can extend media, if required.
+>>> class MyWidget7(MyWidget6):
+...     def _media(self):
+...         return super(MyWidget7, self).media + Media(css={'all': ('/other/path',)})
+...     media = property(_media)
+
+>>> w7 = MyWidget7()
+>>> print w7.media
+<link href="http://media.example.com/some/path" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/other/path" type="text/css" media="all" rel="stylesheet" />
+
+# A widget can define CSS media for multiple output media types
+>>> class MyWidget8(TextInput):
+...     class Media:
+...         css = {
+...            'screen, print': ('/file1','/file2'),
+...            'screen': ('/file3',),
+...            'print': ('/file4',)
+...         }
+...         js = ('/path/to/js1','/path/to/js4')
+
+>>> w8 = MyWidget8()
+>>> print w8.media
+<link href="http://media.example.com/file4" type="text/css" media="print" rel="stylesheet" />
+<link href="http://media.example.com/file3" type="text/css" media="screen" rel="stylesheet" />
+<link href="http://media.example.com/file1" type="text/css" media="screen, print" rel="stylesheet" />
+<link href="http://media.example.com/file2" type="text/css" media="screen, print" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+# MultiWidgets have a default media definition that gets all the 
+# media from the component widgets
+>>> class MyWidget9(MultiWidget):
+...     def __init__(self, attrs=None):
+...         widgets = [MyWidget1, MyWidget2, MyWidget3]
+...         MultiWidget.__init__(self, widgets, attrs)
+            
+>>> w9 = MyWidget9()
+>>> print w9.media            
+<link href="http://media.example.com/path/to/css1" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css2" type="text/css" media="all" rel="stylesheet" />
+<link href="http://media.example.com/path/to/css3" type="text/css" media="all" rel="stylesheet" />
+<script type="text/javascript" src="http://media.example.com/path/to/js1"></script>
+<script type="text/javascript" src="http://media.other.com/path/to/js2"></script>
+<script type="text/javascript" src="https://secure.other.com/path/to/js3"></script>
+<script type="text/javascript" src="http://media.example.com/path/to/js4"></script>
+
+"""
\ No newline at end of file
Index: tests/regressiontests/forms/tests.py
===================================================================
--- tests/regressiontests/forms/tests.py	(revision 5636)
+++ tests/regressiontests/forms/tests.py	(working copy)
@@ -2,6 +2,7 @@
 from localflavor import localflavor_tests
 from regressions import regression_tests
 from formsets import formset_tests
+from media import media_tests
 
 form_tests = r"""
 >>> from django.newforms import *
@@ -3698,6 +3699,7 @@
     'localflavor': localflavor_tests,
     'regressions': regression_tests,
     'formset_tests': formset_tests,
+    'media_tests': media_tests,
 }
 
 if __name__ == "__main__":
