summaryrefslogtreecommitdiff
path: root/django/forms/widgets.py
diff options
context:
space:
mode:
authorMatthias Kestenholz <mk@feinheit.ch>2019-02-23 16:41:56 +0100
committerTim Graham <timograham@gmail.com>2019-02-27 16:39:17 -0500
commit231b513926f2bfd71f08058ce5013bd81678ac01 (patch)
tree974917d280c6086eb25cfd1b32775b961a1edbc2 /django/forms/widgets.py
parent93e892bb645b16ebaf287beb5fe7f3ffe8d10408 (diff)
Fixed #30179 -- Fixed form Media merging when pairwise merging is insufficient.
Thanks gasman for the tests, and codingjoe and timgraham for the review.
Diffstat (limited to 'django/forms/widgets.py')
-rw-r--r--django/forms/widgets.py76
1 files changed, 36 insertions, 40 deletions
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index 02aa32b207..e944091f0d 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -6,16 +6,21 @@ import copy
import datetime
import re
import warnings
+from collections import defaultdict
from itertools import chain
from django.conf import settings
from django.forms.utils import to_current_timezone
from django.templatetags.static import static
from django.utils import datetime_safe, formats
+from django.utils.datastructures import OrderedSet
from django.utils.dates import MONTHS
from django.utils.formats import get_format
from django.utils.html import format_html, html_safe
from django.utils.safestring import mark_safe
+from django.utils.topological_sort import (
+ CyclicDependencyError, stable_topological_sort,
+)
from django.utils.translation import gettext_lazy as _
from .renderers import get_default_renderer
@@ -59,22 +64,15 @@ class Media:
@property
def _css(self):
- css = self._css_lists[0]
- # filter(None, ...) avoids calling merge with empty dicts.
- for obj in filter(None, self._css_lists[1:]):
- css = {
- medium: self.merge(css.get(medium, []), obj.get(medium, []))
- for medium in css.keys() | obj.keys()
- }
- return css
+ css = defaultdict(list)
+ for css_list in self._css_lists:
+ for medium, sublist in css_list.items():
+ css[medium].append(sublist)
+ return {medium: self.merge(*lists) for medium, lists in css.items()}
@property
def _js(self):
- js = self._js_lists[0]
- # filter(None, ...) avoids calling merge() with empty lists.
- for obj in filter(None, self._js_lists[1:]):
- js = self.merge(js, obj)
- return js
+ return self.merge(*self._js_lists)
def render(self):
return mark_safe('\n'.join(chain.from_iterable(getattr(self, 'render_' + name)() for name in MEDIA_TYPES)))
@@ -115,39 +113,37 @@ class Media:
raise KeyError('Unknown media type "%s"' % name)
@staticmethod
- def merge(list_1, list_2):
+ def merge(*lists):
"""
- 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.
+ Merge lists while trying to keep the relative order of the elements.
+ Warn if the lists have the same elements in a different relative order.
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
+ dependency_graph = defaultdict(set)
+ all_items = OrderedSet()
+ for list_ in filter(None, lists):
+ head = list_[0]
+ # The first items depend on nothing but have to be part of the
+ # dependency graph to be included in the result.
+ dependency_graph.setdefault(head, set())
+ for item in list_:
+ all_items.add(item)
+ # No self dependencies
+ if head != item:
+ dependency_graph[item].add(head)
+ head = item
+ try:
+ return stable_topological_sort(all_items, dependency_graph)
+ except CyclicDependencyError:
+ warnings.warn(
+ 'Detected duplicate Media files in an opposite order: {}'.format(
+ ', '.join(repr(l) for l in lists)
+ ), MediaOrderConflictWarning,
+ )
+ return list(all_items)
def __add__(self, other):
combined = Media()