diff options
| author | Johannes Hoppe <info@johanneshoppe.com> | 2017-07-20 17:06:30 +0200 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2017-07-20 11:06:30 -0400 |
| commit | c19b56f633e172b3c02094cbe12d28865ee57772 (patch) | |
| tree | 698bf39231100f9a95ab87aa37b416eb7056e4e0 /django | |
| parent | f86b6f351d45c366f733c586127251c598dfd541 (diff) | |
Fixed #28377 -- Made combining form Media retain relative asset order.
Thanks Florian Apolloner, Mariusz Felisiak, and Tim Graham for reviews.
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/admin/helpers.py | 2 | ||||
| -rw-r--r-- | django/contrib/admin/options.py | 2 | ||||
| -rw-r--r-- | django/contrib/admin/widgets.py | 26 | ||||
| -rw-r--r-- | django/contrib/gis/admin/options.py | 6 | ||||
| -rw-r--r-- | django/forms/widgets.py | 77 |
5 files changed, 80 insertions, 33 deletions
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 58e92c9689..2b47ed07fa 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -304,7 +304,7 @@ class InlineAdminFormSet: @property def media(self): - media = self.opts.media + self.formset.media + media = self.formset.media + self.opts.media for fs in self: media = media + fs.media return media diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index edd0d7c3ad..714a27e994 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -579,9 +579,9 @@ class ModelAdmin(BaseModelAdmin): def media(self): extra = '' if settings.DEBUG else '.min' js = [ - 'core.js', 'vendor/jquery/jquery%s.js' % extra, 'jquery.init.js', + 'core.js', 'admin/RelatedObjectLookups.js', 'actions%s.js' % extra, 'urlify.js', diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 4b932f2c01..7cf71bb6ce 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -4,6 +4,7 @@ Form Widget classes specific to the Django admin site. import copy from django import forms +from django.conf import settings from django.db.models.deletion import CASCADE from django.urls import reverse from django.urls.exceptions import NoReverseMatch @@ -22,7 +23,14 @@ class FilteredSelectMultiple(forms.SelectMultiple): """ @property def media(self): - js = ["core.js", "SelectBox.js", "SelectFilter2.js"] + extra = '' if settings.DEBUG else '.min' + js = [ + 'vendor/jquery/jquery%s.js' % extra, + 'jquery.init.js', + 'core.js', + 'SelectBox.js', + 'SelectFilter2.js', + ] return forms.Media(js=["admin/js/%s" % path for path in js]) def __init__(self, verbose_name, is_stacked, attrs=None, choices=()): @@ -43,7 +51,13 @@ class FilteredSelectMultiple(forms.SelectMultiple): class AdminDateWidget(forms.DateInput): @property def media(self): - js = ["calendar.js", "admin/DateTimeShortcuts.js"] + extra = '' if settings.DEBUG else '.min' + js = [ + 'vendor/jquery/jquery%s.js' % extra, + 'jquery.init.js', + 'calendar.js', + 'admin/DateTimeShortcuts.js', + ] return forms.Media(js=["admin/js/%s" % path for path in js]) def __init__(self, attrs=None, format=None): @@ -56,7 +70,13 @@ class AdminDateWidget(forms.DateInput): class AdminTimeWidget(forms.TimeInput): @property def media(self): - js = ["calendar.js", "admin/DateTimeShortcuts.js"] + extra = '' if settings.DEBUG else '.min' + js = [ + 'vendor/jquery/jquery%s.js' % extra, + 'jquery.init.js', + 'calendar.js', + 'admin/DateTimeShortcuts.js', + ] return forms.Media(js=["admin/js/%s" % path for path in js]) def __init__(self, attrs=None, format=None): diff --git a/django/contrib/gis/admin/options.py b/django/contrib/gis/admin/options.py index 431f138f1b..fe74df36ff 100644 --- a/django/contrib/gis/admin/options.py +++ b/django/contrib/gis/admin/options.py @@ -2,6 +2,7 @@ from django.contrib.admin import ModelAdmin from django.contrib.gis.admin.widgets import OpenLayersWidget from django.contrib.gis.db import models from django.contrib.gis.gdal import OGRGeomType +from django.forms import Media spherical_mercator_srid = 3857 @@ -46,10 +47,7 @@ class GeoModelAdmin(ModelAdmin): @property def media(self): "Injects OpenLayers JavaScript into the admin." - media = super().media - media.add_js([self.openlayers_url]) - media.add_js(self.extra_js) - return media + return super().media + Media(js=[self.openlayers_url] + self.extra_js) def formfield_for_dbfield(self, db_field, request, **kwargs): """ diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 01ada423c9..93f831e6cc 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -5,6 +5,7 @@ HTML Widget classes import copy import datetime import re +import warnings from contextlib import suppress from itertools import chain @@ -33,19 +34,23 @@ __all__ = ( MEDIA_TYPES = ('css', 'js') +class MediaOrderConflictWarning(RuntimeWarning): + pass + + @html_safe class Media: - def __init__(self, media=None, **kwargs): - if media: - media_attrs = media.__dict__ + def __init__(self, media=None, css=None, js=None): + if media is not None: + css = getattr(media, 'css', {}) + js = getattr(media, 'js', []) else: - media_attrs = kwargs - - self._css = {} - self._js = [] - - for name in MEDIA_TYPES: - getattr(self, 'add_' + name)(media_attrs.get(name)) + if css is None: + css = {} + if js is None: + js = [] + self._css = css + self._js = js def __str__(self): return self.render() @@ -88,24 +93,48 @@ class Media: return Media(**{str(name): getattr(self, '_' + name)}) raise KeyError('Unknown media type "%s"' % name) - def add_js(self, data): - if data: - for path in data: - if path not in self._js: - self._js.append(path) + @staticmethod + def merge(list_1, list_2): + """ + Merge two lists while trying to keep the relative order of the elements. + Warn if the lists have the same two elements in a different relative + order. - def add_css(self, data): - if data: - for medium, paths in data.items(): - for path in paths: - if not self._css.get(medium) or path not in self._css[medium]: - self._css.setdefault(medium, []).append(path) + For static assets it can be important to have them included in the DOM + in a certain order. In JavaScript you may not be able to reference a + global or in CSS you might want to override a style. + """ + # Start with a copy of list_1. + combined_list = list(list_1) + last_insert_index = len(list_1) + # Walk list_2 in reverse, inserting each element into combined_list if + # it doesn't already exist. + for path in reversed(list_2): + try: + # Does path already exist in the list? + index = combined_list.index(path) + except ValueError: + # Add path to combined_list since it doesn't exist. + combined_list.insert(last_insert_index, path) + else: + if index > last_insert_index: + warnings.warn( + 'Detected duplicate Media files in an opposite order:\n' + '%s\n%s' % (combined_list[last_insert_index], combined_list[index]), + MediaOrderConflictWarning, + ) + # path already exists in the list. Update last_insert_index so + # that the following elements are inserted in front of this one. + last_insert_index = index + return combined_list 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)) + combined._js = self.merge(self._js, other._js) + combined._css = { + medium: self.merge(self._css.get(medium, []), other._css.get(medium, [])) + for medium in self._css.keys() | other._css.keys() + } return combined |
