SciPost Code Repository

Skip to content
Snippets Groups Projects
Commit def47407 authored by George Katsikas's avatar George Katsikas :goat:
Browse files

feat(common): :sparkles: add generic merge preview view (GET)

parent 91a90788
No related branches found
No related tags found
No related merge requests found
{% extends 'scipost/base.html' %}
{% load scipost_extras %}
{% block title %}Merging {{ model_name_plural }}{% endblock %}
{% block breadcrumbs %}
<li>
<a href="{% url 'scipost:home' %}">Home</a>
</li>
<li>
<a href="{% url 'scipost:object_merger:compare' model_name=model_name %}">{{ model_name_plural }}</a>
</li>
<li class="active">Merging {{ model_name_plural }}</li>
{% endblock %}
{% block content %}
<hgroup>
<h1>Merging {{ model_name_plural.title }}</h1>
<p>
<span class="ms-2 fs-4"><a href="{{ object_b.get_absolute_url }}">{{ object_to }}</a></span>
{% include "bi/arrow-left.html" %}
<span class="fs-4"><a class="text-muted" href="{{ object_a.get_absolute_url }}">{{ object_from }}</a></span>
</p>
</hgroup>
{% for group_name, group in diff_groups.items %}
<h2>{{ group_name|title }}</h2>
{% if group_name == "related_objects_many" %}
{% for field_name, field_changes in group.values %}
<details open class="mb-3 border border-info border-2 rounded bg-info bg-opacity-10">
<summary class="list-triangle">
<span class="fs-3">{{ field_name|title }}</span>
</summary>
<div class="row">
<div class="col p-2">
<ul class="m-0">
{% for change in field_changes %}
{% with status=change.0 value=change.1 %}
{% if status == "added" %}
<li class="text-success">{{ value }}</li>
{% elif status == "removed" %}
<li class="text-danger">{{ value }}</li>
{% else %}
<li>{{ value }}</li>
{% endif %}
{% endwith %}
{% endfor %}
</ul>
</div>
</div>
</details>
{% endfor %}
{% else %}
<table class="table table-hover">
<tr>
<th></th>
<th class="fs-4">
<a href="{{ object_to.get_absolute_url }}">{{ object_to }}</a>
{% include "bi/arrow-left.html" %}
<a class="text-muted" href="{{ object_a.get_absolute_url }}">{{ object_from }}</a>
</th>
</tr>
{% for field_name, changes in group.values %}
<tr>
<th>{{ field_name|title }}</th>
<td>
{% for change in changes %}
{% with status=change.0 value=change.1 %}
{% if status == "added" %}
<div class="text-success">{{ value }}</div>
{% elif status == "removed" %}
<div class="text-danger">{{ value }}</div>
{% else %}
{{ value }}
{% endif %}
{% endwith %}
{% endfor %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endfor %}
{% endblock %}
......@@ -203,3 +203,77 @@ class HXCompareView(CompareView):
return context
class HXMergeView(CompareView):
"""
If GET: Display a "diff" view of the two objects, with buttons to merge them.
If POST: Merge the two objects, keeping a record of the merge in the database.
"""
template_name = "common/object_merger/merge.html"
@staticmethod
def _preview_merge_field(
field: FieldOrRel, from_val: Any, to_val: Any
) -> list[tuple[str, FieldValue]]:
"""
Produces a preview of what the merged field will look like.
Return a list of tuples, where the first element is the status of the object (unchanged, added, removed)
"""
changes = []
# For _2o relations and fields, values are unchanged if they are the same,
# "from" values are added if they are not in "to" values,
# and any other "from" values are removed
if not field.is_relation or field.one_to_one or field.many_to_one:
if from_val == to_val or (from_val is None and to_val is None):
changes = [("unchanged", to_val)]
elif not to_val:
changes = [("added", from_val)]
else:
changes = [("removed", from_val), ("unchanged", to_val)]
# For _2m relations, all objects from to_val are kept,
# and any new objects from from_val are added
elif field.many_to_many or field.one_to_many:
changes = [("unchanged", obj) for obj in to_val] + [
("added", obj) for obj in from_val if obj not in to_val
]
else:
changes = []
return sorted(changes, key=lambda x: str(x[1]))
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
context = super().get_context_data(**kwargs)
content_type = self.get_content_type()
object_from, object_to = self.get_objects()
context |= {
"content_type": content_type,
"objects": [object_from, object_to],
"object_from": object_from,
"object_to": object_to,
}
if model := content_type.model_class():
obj_a_field_data = self.get_object_field_data(object_from)
obj_b_field_data = self.get_object_field_data(object_to)
diff = {
field: (field_name, changes)
for (field, (field_name, from_val)), (_, (_, to_val)) in zip(
obj_a_field_data.items(), obj_b_field_data.items()
)
if (changes := self._preview_merge_field(field, from_val, to_val))
}
context |= {
"model_name": model._meta.verbose_name,
"model_name_plural": model._meta.verbose_name_plural,
"diff_groups": self.group_fields(diff),
}
return context
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment