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 %} -
+ {% csrf_token %} {% if form.errors|length > 0 %}

diff --git a/geniza/entities/templates/admin/entities/personpersonrelationtype/merge.html b/geniza/entities/templates/admin/entities/personpersonrelationtype/merge.html new file mode 100644 index 000000000..880a3f363 --- /dev/null +++ b/geniza/entities/templates/admin/entities/personpersonrelationtype/merge.html @@ -0,0 +1,53 @@ +{% extends 'admin/base_site.html' %} + +{% load admin_urls static %} + +{% block extrastyle %} + {{ block.super }} + + +{% endblock %} + +{% block title %} Merge selected person-person relationships {% endblock %} + +{% block breadcrumbs %} + {% if not is_popup %} +

+ {% endif %} +{% endblock %} + + +{% block content_title %} +

Merge selected person-person relationships

+

Note: there is no automated way to unmerge! Please review to make sure these relationships should be merged before submitting the form.

+{% endblock %} + +{% block content %} + + {% csrf_token %} + {% if form.errors|length > 0 %} +

+ Please correct the error below. +

+ {% endif %} +
+
+ {{ form.primary_relation_type.label_tag }} + {{ form.primary_relation_type }} +

{{ form.primary_relation_type.help_text|safe }}

+
+ +
+ +
+ + +{% 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 }} + + Change +

+
+ View all {{ relation_type.personpersonrelation_set.count }} {{ relation_type }} relations +
    + {% for rel in relation_type.personpersonrelation_set.all %} +
  1. +
    + + +
    +
  2. + {% empty %} +
  3. +
    +
    +
    +
  4. + {% 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; }