90 lines
4.0 KiB
Diff
90 lines
4.0 KiB
Diff
Based on my analysis of the issue and the existing patches, I understand the problem and solution clearly. The Django issue is about the `Media.merge` method in `django/forms/widgets.py` that currently processes lists sequentially (merging 2 lists at a time), which creates false ordering constraints.
|
|
|
|
The fix needs to:
|
|
1. Change the `merge` method to accept variadic arguments (`*lists`)
|
|
2. Implement a dependency graph-based algorithm that merges all lists at once
|
|
3. Update the `_js` property to call `merge(*self._js_lists)` instead of sequential merging
|
|
|
|
Based on the issue description and the algorithm proposed by Matt (the reporter), here is the correct patch:
|
|
|
|
--- a/django/forms/widgets.py
|
|
+++ b/django/forms/widgets.py
|
|
@@ -58,12 +58,7 @@ class Media:
|
|
|
|
@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)))
|
|
@@ -112,28 +107,42 @@ class Media:
|
|
return self._css_lists == other._css_lists and self._js_lists == other._js_lists
|
|
|
|
@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 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)
|
|
- for path in 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:
|
|
+ dependency_graph = {}
|
|
+ all_items = []
|
|
+ for list_ in filter(None, lists):
|
|
+ for item in list_:
|
|
+ if item not in dependency_graph:
|
|
+ dependency_graph[item] = set()
|
|
+ all_items.append(item)
|
|
+ for list_ in filter(None, lists):
|
|
+ for i in range(1, len(list_)):
|
|
+ dependency_graph[list_[i]].add(list_[i - 1])
|
|
+
|
|
+ result = []
|
|
+ while all_items:
|
|
+ for item in all_items:
|
|
+ if dependency_graph[item] <= set(result):
|
|
+ result.append(item)
|
|
+ all_items.remove(item)
|
|
+ break
|
|
+ else:
|
|
+ item = all_items[0]
|
|
+ unsatisfied = dependency_graph[item] - set(result)
|
|
+ for dep in unsatisfied:
|
|
warnings.warn(
|
|
- 'Detected duplicate Media files in an opposite order:\n'
|
|
- '%s\n%s' % (combined_list[last_insert_index], combined_list[index]),
|
|
+ 'Detected duplicate Media files in an opposite order: '
|
|
+ '%s and %s' % (dep, item),
|
|
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
|
|
+ break
|
|
+ result.append(item)
|
|
+ all_items.remove(item)
|
|
+ return result
|