diff --git a/geniza/entities/admin.py b/geniza/entities/admin.py
index c08a0f684..643a1b8d4 100644
--- a/geniza/entities/admin.py
+++ b/geniza/entities/admin.py
@@ -50,7 +50,11 @@
PlacePlaceRelation,
PlacePlaceRelationType,
)
-from geniza.entities.views import PersonDocumentRelationTypeMerge, PersonMerge
+from geniza.entities.views import (
+ PersonDocumentRelationTypeMerge,
+ PersonMerge,
+ PersonPersonRelationTypeMerge,
+)
from geniza.footnotes.models import Footnote
@@ -409,17 +413,10 @@ class RoleAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
ordering = ("display_label", "name")
-@admin.register(PersonDocumentRelationType)
-class PersonDocumentRelationTypeAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
- """Admin for managing the controlled vocabulary of people's relationships to documents"""
-
- fields = ("name",)
- search_fields = ("name",)
- ordering = ("name",)
-
- @admin.display(description="Merge selected Person-Document relationships")
- def merge_person_document_relation_types(self, request, queryset=None):
- """Admin action to merge selected person-document relation types. This
+class RelationTypeMergeAdminMixin:
+ @admin.display(description="Merge selected %(verbose_name_plural)s")
+ def merge_relation_types(self, request, queryset=None):
+ """Admin action to merge selected entity-entity relation types. This
action redirects to an intermediate page, which displays a form to
review for confirmation and choose the primary type before merging.
"""
@@ -427,15 +424,15 @@ def merge_person_document_relation_types(self, request, queryset=None):
if len(selected) < 2:
messages.error(
request,
- "You must select at least two person-document relationships to merge",
+ "You must select at least two person-person relationships to merge",
)
return HttpResponseRedirect(
- reverse("admin:entities_persondocumentrelationtype_changelist")
+ reverse("admin:entities_%s_changelist" % self.model._meta.model_name)
)
return HttpResponseRedirect(
"%s?ids=%s"
% (
- reverse("admin:person-document-relation-type-merge"),
+ reverse(f"admin:{self.merge_path_name}"),
",".join(selected),
),
status=303,
@@ -446,22 +443,39 @@ def get_urls(self):
urls = [
path(
"merge/",
- PersonDocumentRelationTypeMerge.as_view(),
- name="person-document-relation-type-merge",
+ self.view_class.as_view(),
+ name=self.merge_path_name,
),
]
return urls + super().get_urls()
- actions = (merge_person_document_relation_types,)
+ actions = (merge_relation_types,)
+
+
+@admin.register(PersonDocumentRelationType)
+class PersonDocumentRelationTypeAdmin(
+ RelationTypeMergeAdminMixin, TabbedTranslationAdmin, admin.ModelAdmin
+):
+ """Admin for managing the controlled vocabulary of people's relationships to documents"""
+
+ fields = ("name",)
+ search_fields = ("name",)
+ ordering = ("name",)
+ merge_path_name = "person-document-relation-type-merge"
+ view_class = PersonDocumentRelationTypeMerge
@admin.register(PersonPersonRelationType)
-class PersonPersonRelationTypeAdmin(TabbedTranslationAdmin, admin.ModelAdmin):
+class PersonPersonRelationTypeAdmin(
+ RelationTypeMergeAdminMixin, TabbedTranslationAdmin, admin.ModelAdmin
+):
"""Admin for managing the controlled vocabulary of people's relationships to other people"""
fields = ("name", "converse_name", "category")
search_fields = ("name",)
ordering = ("name",)
+ merge_path_name = "person-person-relation-type-merge"
+ view_class = PersonPersonRelationTypeMerge
@admin.register(PersonPlaceRelationType)
diff --git a/geniza/entities/forms.py b/geniza/entities/forms.py
index c9ac5f253..9127e1578 100644
--- a/geniza/entities/forms.py
+++ b/geniza/entities/forms.py
@@ -10,6 +10,7 @@
PersonDocumentRelationType,
PersonEventRelation,
PersonPersonRelation,
+ PersonPersonRelationType,
PersonPlaceRelation,
PersonRole,
PlaceEventRelation,
@@ -54,6 +55,22 @@ def __init__(self, *args, **kwargs):
)
+class RelationTypeMergeFormMixin:
+ def __init__(self, *args, **kwargs):
+ ids = kwargs.get("ids", [])
+
+ # Remove the added kwarg so that the super method doesn't error
+ try:
+ del kwargs["ids"]
+ except KeyError:
+ pass
+
+ super().__init__(*args, **kwargs)
+ self.fields[
+ "primary_relation_type"
+ ].queryset = self.reltype_model.objects.filter(id__in=ids)
+
+
class PersonDocumentRelationTypeChoiceField(forms.ModelChoiceField):
"""Add a summary of each PersonDocumentRelationType to a form (used for merging)"""
@@ -65,7 +82,7 @@ def label_from_instance(self, relation_type):
return self.label_template.render({"relation_type": relation_type})
-class PersonDocumentRelationTypeMergeForm(forms.Form):
+class PersonDocumentRelationTypeMergeForm(RelationTypeMergeFormMixin, forms.Form):
primary_relation_type = PersonDocumentRelationTypeChoiceField(
label="Select primary person-document relationship",
queryset=None,
@@ -77,20 +94,33 @@ class PersonDocumentRelationTypeMergeForm(forms.Form):
empty_label=None,
widget=forms.RadioSelect,
)
+ reltype_model = PersonDocumentRelationType
- def __init__(self, *args, **kwargs):
- ids = kwargs.get("ids", [])
- # Remove the added kwarg so that the super method doesn't error
- try:
- del kwargs["ids"]
- except KeyError:
- pass
+class PersonPersonRelationTypeChoiceField(forms.ModelChoiceField):
+ """Add a summary of each PersonPersonRelationType to a form (used for merging)"""
- super().__init__(*args, **kwargs)
- self.fields[
- "primary_relation_type"
- ].queryset = PersonDocumentRelationType.objects.filter(id__in=ids)
+ label_template = get_template(
+ "entities/snippets/personpersonrelationtype_option_label.html"
+ )
+
+ def label_from_instance(self, relation_type):
+ return self.label_template.render({"relation_type": relation_type})
+
+
+class PersonPersonRelationTypeMergeForm(RelationTypeMergeFormMixin, forms.Form):
+ primary_relation_type = PersonPersonRelationTypeChoiceField(
+ label="Select primary person-person relationship",
+ queryset=None,
+ help_text=(
+ "Select the primary person-person relationship, which will be "
+ "used as the canonical entry. All associated relations and log "
+ "entries will be combined on the primary relationship."
+ ),
+ empty_label=None,
+ widget=forms.RadioSelect,
+ )
+ reltype_model = PersonPersonRelationType
class PersonPersonForm(forms.ModelForm):
diff --git a/geniza/entities/models.py b/geniza/entities/models.py
index c051a70c7..5df3d20ef 100644
--- a/geniza/entities/models.py
+++ b/geniza/entities/models.py
@@ -1020,29 +1020,10 @@ def get_by_natural_key(self, name):
return self.get(name_en=name)
-class PersonDocumentRelationType(models.Model):
- """Controlled vocabulary of people's relationships to documents."""
-
- name = models.CharField(max_length=255, unique=True)
- objects = PersonDocumentRelationTypeManager()
- log_entries = GenericRelation(
- LogEntry, related_query_name="persondocumentrelationtype"
- )
-
- class Meta:
- verbose_name = "Person-Document relationship"
- verbose_name_plural = "Person-Document relationships"
-
- def __str__(self):
- return self.name
-
- @cached_class_property
- def objects_by_label(cls):
- return {
- # lookup on name_en since solr should always index in English
- obj.name_en: obj
- for obj in cls.objects.all()
- }
+class MergeRelationTypesMixin:
+ """Mixin to include shared merge logic for relation types.
+ Requires inheriting relation type model to make its relationships
+ queryset available generically by the method name :meth:`relation_set`"""
def merge_with(self, merge_relation_types, user=None):
"""Merge the specified relation types into this one. Combines all
@@ -1069,12 +1050,12 @@ def merge_with(self, merge_relation_types, user=None):
# - associate with the primary relation type
log_entry.object_id = self.id
log_entry.content_type_id = ContentType.objects.get_for_model(
- PersonDocumentRelationType
+ self.__class__
)
log_entry.save()
- # combine person-document relationships
- for relationship in rel_type.persondocumentrelation_set.all():
+ # combine relationships
+ for relationship in rel_type.relation_set():
# set type of each relationship to primary relation type
relationship.type = self
# handle unique constraint violation (one relationship per type
@@ -1093,12 +1074,10 @@ def merge_with(self, merge_relation_types, user=None):
for rel_type in merge_relation_types:
rel_type.delete()
# create log entry documenting the merge; include rationale
- pdrtype_contenttype = ContentType.objects.get_for_model(
- PersonDocumentRelationType
- )
+ rtype_contenttype = ContentType.objects.get_for_model(self.__class__)
LogEntry.objects.log_action(
user_id=user.id,
- content_type_id=pdrtype_contenttype.pk,
+ content_type_id=rtype_contenttype.pk,
object_id=self.pk,
object_repr=str(self),
change_message="merged with %s" % (merged_types,),
@@ -1106,6 +1085,35 @@ def merge_with(self, merge_relation_types, user=None):
)
+class PersonDocumentRelationType(MergeRelationTypesMixin, models.Model):
+ """Controlled vocabulary of people's relationships to documents."""
+
+ name = models.CharField(max_length=255, unique=True)
+ objects = PersonDocumentRelationTypeManager()
+ log_entries = GenericRelation(
+ LogEntry, related_query_name="persondocumentrelationtype"
+ )
+
+ class Meta:
+ verbose_name = "Person-Document relationship"
+ verbose_name_plural = "Person-Document relationships"
+
+ def __str__(self):
+ return self.name
+
+ @cached_class_property
+ def objects_by_label(cls):
+ return {
+ # lookup on name_en since solr should always index in English
+ obj.name_en: obj
+ for obj in cls.objects.all()
+ }
+
+ def relation_set(self):
+ # own relationships QuerySet as required by MergeRelationTypesMixin
+ return self.persondocumentrelation_set.all()
+
+
class PersonDocumentRelation(models.Model):
"""A relationship between a person and a document."""
@@ -1140,7 +1148,7 @@ def get_by_natural_key(self, name):
return self.get(name_en=name)
-class PersonPersonRelationType(models.Model):
+class PersonPersonRelationType(MergeRelationTypesMixin, models.Model):
"""Controlled vocabulary of people's relationships to other people."""
# name of the relationship
@@ -1171,6 +1179,9 @@ class PersonPersonRelationType(models.Model):
choices=CATEGORY_CHOICES,
)
objects = PersonPersonRelationTypeManager()
+ log_entries = GenericRelation(
+ LogEntry, related_query_name="personpersonrelationtype"
+ )
class Meta:
verbose_name = "Person-Person relationship"
@@ -1179,6 +1190,10 @@ class Meta:
def __str__(self):
return self.name
+ def relation_set(self):
+ # own relationships QuerySet as required by MergeRelationTypesMixin
+ return self.personpersonrelation_set.all()
+
class PersonPersonRelation(models.Model):
"""A relationship between two people."""
diff --git a/geniza/entities/templates/admin/entities/persondocumentrelationtype/merge.html b/geniza/entities/templates/admin/entities/persondocumentrelationtype/merge.html
index e1bd360c8..fcb4061a4 100644
--- a/geniza/entities/templates/admin/entities/persondocumentrelationtype/merge.html
+++ b/geniza/entities/templates/admin/entities/persondocumentrelationtype/merge.html
@@ -31,7 +31,7 @@
Note: there is no automated way to unmerge! Please review to make sure these
{% endblock %}
{% block content %}
-
+{% endblock %}
diff --git a/geniza/entities/templates/entities/snippets/personpersonrelationtype_option_label.html b/geniza/entities/templates/entities/snippets/personpersonrelationtype_option_label.html
new file mode 100644
index 000000000..ee99ec0a3
--- /dev/null
+++ b/geniza/entities/templates/entities/snippets/personpersonrelationtype_option_label.html
@@ -0,0 +1,33 @@
+{# template snippet for displaying person-person relationship label on a form #}
+{# used on merge form to provide enough information to merge accurately #}
+
+
+
{{ relation_type }}
+
+
+
+
+ View all {{ relation_type.personpersonrelation_set.count }} {{ relation_type }} relations
+
+ {% for rel in relation_type.personpersonrelation_set.all %}
+ -
+
+
+ {% empty %}
+ -
+
+
+ {% endfor %}
+
+
+
diff --git a/geniza/entities/tests/test_entities_admin.py b/geniza/entities/tests/test_entities_admin.py
index 77609de9f..5b39c63f3 100644
--- a/geniza/entities/tests/test_entities_admin.py
+++ b/geniza/entities/tests/test_entities_admin.py
@@ -427,7 +427,7 @@ def test_merge_person_document_relation_types(self):
mockrequest = Mock()
test_ids = ["1", "2", "3"]
mockrequest.POST.getlist.return_value = test_ids
- resp = pdr_admin.merge_person_document_relation_types(mockrequest, Mock())
+ resp = pdr_admin.merge_relation_types(mockrequest, Mock())
assert isinstance(resp, HttpResponseRedirect)
assert resp.status_code == 303
assert resp["location"].startswith(
@@ -437,7 +437,7 @@ def test_merge_person_document_relation_types(self):
test_ids = ["1"]
mockrequest.POST.getlist.return_value = test_ids
- resp = pdr_admin.merge_person_document_relation_types(mockrequest, Mock())
+ resp = pdr_admin.merge_relation_types(mockrequest, Mock())
assert isinstance(resp, HttpResponseRedirect)
assert resp.status_code == 302
assert resp["location"] == reverse(
diff --git a/geniza/entities/tests/test_entities_forms.py b/geniza/entities/tests/test_entities_forms.py
index 196840730..47f259b82 100644
--- a/geniza/entities/tests/test_entities_forms.py
+++ b/geniza/entities/tests/test_entities_forms.py
@@ -6,12 +6,22 @@
from geniza.corpus.forms import FacetChoiceField
from geniza.entities.forms import (
PersonChoiceField,
+ PersonDocumentRelationTypeChoiceField,
PersonDocumentRelationTypeMergeForm,
PersonListForm,
PersonMergeForm,
+ PersonPersonRelationTypeChoiceField,
PlaceListForm,
)
-from geniza.entities.models import Name, Person, PersonDocumentRelationType, PersonRole
+from geniza.entities.models import (
+ Name,
+ Person,
+ PersonDocumentRelation,
+ PersonDocumentRelationType,
+ PersonPersonRelation,
+ PersonPersonRelationType,
+ PersonRole,
+)
class TestPersonChoiceField:
@@ -52,6 +62,28 @@ def test_init(self):
assert people.last() not in mergeform.fields["primary_person"].queryset
+class TestPersonDocumentRelationTypeChoiceField:
+ @pytest.mark.django_db
+ def test_label_from_instance(self, person, document):
+ # adapted from TestDocumentChoiceField
+ choicefield = PersonDocumentRelationTypeChoiceField(Mock())
+
+ # Should not error on a relation with the most minimal information
+ minimal = PersonDocumentRelationType.objects.create()
+ label = choicefield.label_from_instance(minimal)
+ assert str(minimal.id) in label
+
+ # Check that the necessary information is in the label
+ reltype = PersonDocumentRelationType.objects.create(name="test")
+ PersonDocumentRelation.objects.create(
+ person=person, document=document, type=reltype
+ )
+ label = choicefield.label_from_instance(reltype)
+ assert "test relation" in label
+ assert str(person) in label
+ assert str(document) in label
+
+
class TestPersonDocumentRelationTypeMergeForm:
@pytest.mark.django_db
def test_init(self):
@@ -77,6 +109,31 @@ def test_init(self):
assert types.last() not in mergeform.fields["primary_relation_type"].queryset
+class TestPersonPersonRelationTypeChoiceField:
+ @pytest.mark.django_db
+ def test_label_from_instance(self, person, person_multiname):
+ # adapted from TestDocumentChoiceField
+ choicefield = PersonPersonRelationTypeChoiceField(Mock())
+
+ # Should not error on a relation with the most minimal information
+ minimal = PersonPersonRelationType.objects.create()
+ label = choicefield.label_from_instance(minimal)
+ assert str(minimal.id) in label
+
+ # Check that the necessary information is in the label
+ reltype = PersonPersonRelationType.objects.create(
+ name="test", converse_name="converse"
+ )
+ label = choicefield.label_from_instance(reltype)
+ assert "test" in label
+ assert "converse" not in label
+ PersonPersonRelation.objects.create(
+ type=reltype, from_person=person, to_person=person_multiname
+ )
+ label = choicefield.label_from_instance(reltype)
+ assert "converse" in label
+
+
@pytest.mark.django_db
class TestPersonListForm:
def test_set_choices_from_facets(self, person, person_diacritic):
diff --git a/geniza/entities/tests/test_entities_models.py b/geniza/entities/tests/test_entities_models.py
index 23ae26eae..a73af2785 100644
--- a/geniza/entities/tests/test_entities_models.py
+++ b/geniza/entities/tests/test_entities_models.py
@@ -708,7 +708,7 @@ def test_str(self):
@pytest.mark.django_db
class TestPersonDocumentRelationType:
def test_merge_with(self, person, person_multiname, document, join):
- # create two PersonDocumentRelationTypes and some associations
+ # create three PersonDocumentRelationTypes and some associations
rel_type = PersonDocumentRelationType.objects.create(name="test")
type_2 = PersonDocumentRelationType.objects.create(name="to be merged")
type_3 = PersonDocumentRelationType.objects.create(name="also merge me")
@@ -780,6 +780,105 @@ def test_merge_with(self, person, person_multiname, document, join):
in rel_type.log_entries.all()[2].change_message
)
+ @pytest.mark.django_db
+ def test_objects_by_label(self):
+ """Should return dict of PersonDocumentRelationType objects keyed on English label"""
+ # invalidate cached property (it is computed in other tests in the suite)
+ if "objects_by_label" in PersonDocumentRelationType.__dict__:
+ # __dict__["objects_by_label"] returns a classmethod
+ # __func__ returns a property
+ # fget returns the actual cached function
+ PersonDocumentRelationType.__dict__[
+ "objects_by_label"
+ ].__func__.fget.cache_clear()
+ # add some new relation types
+ rel_type = PersonDocumentRelationType(name="Some kind of official")
+ rel_type.save()
+ rel_type_2 = PersonDocumentRelationType(name="Example")
+ rel_type_2.save()
+ # should be able to get a relation type by label
+ assert isinstance(
+ PersonDocumentRelationType.objects_by_label.get("Some kind of official"),
+ PersonDocumentRelationType,
+ )
+ assert (
+ PersonDocumentRelationType.objects_by_label.get("Some kind of official").pk
+ == rel_type.pk
+ )
+ assert (
+ PersonDocumentRelationType.objects_by_label.get("Example").pk
+ == rel_type_2.pk
+ )
+
+
+@pytest.mark.django_db
+class TestPersonPersonRelationType:
+ def test_merge_with(self, person, person_multiname, person_diacritic):
+ # create two PersonPersonRelationTypes and some associations
+ rel_type = PersonPersonRelationType.objects.create(name="test")
+ type_2 = PersonPersonRelationType.objects.create(name="to be merged")
+ PersonPersonRelation.objects.create(
+ type=rel_type, from_person=person, to_person=person_multiname
+ )
+ PersonPersonRelation.objects.create(
+ type=type_2, from_person=person, to_person=person_diacritic
+ )
+
+ # create some log entries
+ pprtype_contenttype = ContentType.objects.get_for_model(
+ PersonPersonRelationType
+ )
+ creation_date = timezone.make_aware(datetime(2025, 1, 22))
+ creator = User.objects.get_or_create(username="editor")[0]
+ type_2_str = str(type_2)
+ type_2_pk = type_2.pk
+ LogEntry.objects.bulk_create(
+ [
+ LogEntry(
+ user_id=creator.id,
+ content_type_id=pprtype_contenttype.pk,
+ object_id=type_2_pk,
+ object_repr=type_2_str,
+ change_message="first input",
+ action_flag=ADDITION,
+ action_time=creation_date,
+ ),
+ LogEntry(
+ user_id=creator.id,
+ content_type_id=pprtype_contenttype.pk,
+ object_id=type_2_pk,
+ object_repr=type_2_str,
+ change_message="major revision",
+ action_flag=CHANGE,
+ action_time=timezone.now(),
+ ),
+ ]
+ )
+ assert rel_type.personpersonrelation_set.count() == 1
+ rel_type.merge_with([type_2])
+ assert rel_type.personpersonrelation_set.count() == 2
+ # should delete other types and create merge log entry
+ assert not type_2.pk
+ assert LogEntry.objects.filter(
+ object_id=rel_type.pk,
+ change_message__contains=f"merged with {type_2}",
+ ).exists()
+ assert rel_type.log_entries.count() == 3
+ # based on default sorting, most recent log entry will be first
+ # - should document the merge event
+ merge_log = rel_type.log_entries.first()
+ assert merge_log.action_flag == CHANGE
+
+ # reassociated log entries should include merged type's name, id
+ assert (
+ " [merged type %s (id = %s)]" % (type_2_str, type_2_pk)
+ in rel_type.log_entries.all()[1].change_message
+ )
+ assert (
+ " [merged type %s (id = %s)]" % (type_2_str, type_2_pk)
+ in rel_type.log_entries.all()[2].change_message
+ )
+
@pytest.mark.django_db
class TestPlace:
diff --git a/geniza/entities/tests/test_entities_views.py b/geniza/entities/tests/test_entities_views.py
index 8189e4b85..1bafa256a 100644
--- a/geniza/entities/tests/test_entities_views.py
+++ b/geniza/entities/tests/test_entities_views.py
@@ -28,6 +28,7 @@
PersonDocumentRelationTypeMerge,
PersonListView,
PersonMerge,
+ PersonPersonRelationTypeMerge,
PlaceAutocompleteView,
PlaceListView,
)
@@ -196,6 +197,20 @@ def test_person_document_relation_type_merge(self, admin_client, client):
assert "test message" in messages
+class TestPersonPersonRelationTypeMergeView:
+ # adapted from TestPersonMergeView
+ @pytest.mark.django_db
+ def test_get_success_url(self):
+ rel_type = PersonPersonRelationType.objects.create(name="test")
+ merge_view = PersonPersonRelationTypeMerge()
+ merge_view.primary_relation_type = rel_type
+
+ resolved_url = resolve(merge_view.get_success_url())
+ assert "admin" in resolved_url.app_names
+ assert resolved_url.url_name == "entities_personpersonrelationtype_change"
+ assert resolved_url.kwargs["object_id"] == str(rel_type.pk)
+
+
class TestPersonAutocompleteView:
@pytest.mark.django_db
def test_get_queryset(self):
diff --git a/geniza/entities/views.py b/geniza/entities/views.py
index 12432aba7..f49bbffb4 100644
--- a/geniza/entities/views.py
+++ b/geniza/entities/views.py
@@ -23,6 +23,7 @@
PersonDocumentRelationTypeMergeForm,
PersonListForm,
PersonMergeForm,
+ PersonPersonRelationTypeMergeForm,
PlaceListForm,
)
from geniza.entities.models import (
@@ -101,20 +102,7 @@ def form_valid(self, form):
return super().form_valid(form)
-class PersonDocumentRelationTypeMerge(PermissionRequiredMixin, FormView):
- permission_required = (
- "entities.change_persondocumentrelationtype",
- "entities.delete_persondocumentrelationtype",
- )
- form_class = PersonDocumentRelationTypeMergeForm
- template_name = "admin/entities/persondocumentrelationtype/merge.html"
-
- def get_success_url(self):
- return reverse(
- "admin:entities_persondocumentrelationtype_change",
- args=[self.primary_relation_type.pk],
- )
-
+class RelationTypeMergeViewMixin:
def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
form_kwargs["ids"] = self.ids
@@ -131,12 +119,12 @@ def get_initial(self):
self.ids = []
def form_valid(self, form):
- """Merge the selected person-document relation types into the primary one."""
+ """Merge the selected relation types into the primary one."""
primary_relation_type = form.cleaned_data["primary_relation_type"]
self.primary_relation_type = primary_relation_type
secondary_ids = [id for id in self.ids if id != primary_relation_type.pk]
- secondary_relation_types = PersonDocumentRelationType.objects.filter(
+ secondary_relation_types = self.relation_type_class.objects.filter(
pk__in=secondary_ids
)
@@ -160,7 +148,7 @@ def form_valid(self, form):
return HttpResponseRedirect(
"%s?ids=%s"
% (
- reverse("admin:person-document-relation-type-merge"),
+ reverse(f"admin:{self.merge_path_name}"),
self.request.GET.get("ids", ""),
),
)
@@ -176,6 +164,44 @@ def form_valid(self, form):
return super().form_valid(form)
+class PersonDocumentRelationTypeMerge(
+ RelationTypeMergeViewMixin, PermissionRequiredMixin, FormView
+):
+ permission_required = (
+ "entities.change_persondocumentrelationtype",
+ "entities.delete_persondocumentrelationtype",
+ )
+ form_class = PersonDocumentRelationTypeMergeForm
+ template_name = "admin/entities/persondocumentrelationtype/merge.html"
+ relation_type_class = PersonDocumentRelationType
+ merge_path_name = "person-document-relation-type-merge"
+
+ def get_success_url(self):
+ return reverse(
+ "admin:entities_persondocumentrelationtype_change",
+ args=[self.primary_relation_type.pk],
+ )
+
+
+class PersonPersonRelationTypeMerge(
+ RelationTypeMergeViewMixin, PermissionRequiredMixin, FormView
+):
+ permission_required = (
+ "entities.change_personpersonrelationtype",
+ "entities.delete_personpersonrelationtype",
+ )
+ form_class = PersonPersonRelationTypeMergeForm
+ template_name = "admin/entities/personpersonrelationtype/merge.html"
+ relation_type_class = PersonPersonRelationType
+ merge_path_name = "person-person-relation-type-merge"
+
+ def get_success_url(self):
+ return reverse(
+ "admin:entities_personpersonrelationtype_change",
+ args=[self.primary_relation_type.pk],
+ )
+
+
class UnaccentedNameAutocompleteView(autocomplete.Select2QuerySetView):
def get_queryset(self):
"""entities filtered by entered query, or all entities, ordered by name"""
diff --git a/sitemedia/css/admin-local.css b/sitemedia/css/admin-local.css
index 2c5132a6a..0d3eb76a1 100644
--- a/sitemedia/css/admin-local.css
+++ b/sitemedia/css/admin-local.css
@@ -242,12 +242,12 @@ fieldset.transcriptions-field
max-height: 350px;
overflow-y: scroll;
}
-.merge-document li > label {
+[class^="merge-"] li > label {
display: flex;
justify-content: flex-start;
}
-.merge-document .submit-row {
+[class^="merge-"] .submit-row {
clear: left;
padding: 12px 14px;
margin: 20px 0 0;
@@ -279,10 +279,10 @@ fieldset.transcriptions-field
list-style: auto;
}
-.merge-document input[type="radio"] {
+[class^="merge-"] input[type="radio"] {
margin-right: 0.5rem;
}
-.merge-document textarea {
+[class^="merge-"] textarea {
margin: 0 0 20px 180px;
}