Skip to content

Commit

Permalink
Prevent orphaned contact cards (#3282)
Browse files Browse the repository at this point in the history
  • Loading branch information
charludo authored Jan 17, 2025
1 parent 047b8a6 commit 1852c62
Show file tree
Hide file tree
Showing 21 changed files with 336 additions and 97 deletions.
89 changes: 88 additions & 1 deletion integreat_cms/cms/fixtures/test_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,21 @@
"created_date": "2024-08-06T13:23:45.256Z"
}
},
{
"model": "cms.contact",
"pk": 5,
"fields": {
"point_of_contact_for": "Testkontakt",
"name": "referred to in a page",
"location": 6,
"email": "",
"phone_number": "",
"website": "",
"archived": false,
"last_updated": "2025-01-08T13:53:31.813Z",
"created_date": "2025-01-08T13:53:31.806Z"
}
},
{
"model": "cms.recurrencerule",
"pk": 1,
Expand Down Expand Up @@ -2304,6 +2319,22 @@
"redirect_to": ""
}
},
{
"model": "linkcheck.url",
"pk": 21,
"fields": {
"url": "/augsburg/contact/5/",
"last_checked": "2025-01-08T13:54:42.851Z",
"anchor_status": null,
"ssl_status": null,
"status": true,
"status_code": 200,
"redirect_status_code": null,
"message": "",
"error_message": "",
"redirect_to": ""
}
},
{
"model": "linkcheck.link",
"pk": 1,
Expand Down Expand Up @@ -2760,6 +2791,18 @@
"ignore": false
}
},
{
"model": "linkcheck.link",
"pk": 40,
"fields": {
"content_type": ["cms", "pagetranslation"],
"object_id": 101,
"field": "content",
"url": 21,
"text": "Contact",
"ignore": false
}
},
{
"model": "cms.page",
"pk": 1,
Expand Down Expand Up @@ -3450,6 +3493,29 @@
"embedded_offers": []
}
},
{
"model": "cms.page",
"pk": 31,
"fields": {
"created_date": "2025-01-11T07:07:46.585Z",
"lft": 1,
"rgt": 2,
"tree_id": 11,
"depth": 1,
"parent": null,
"region": 1,
"explicitly_archived": false,
"icon": null,
"mirrored_page": null,
"mirrored_page_first": true,
"organization": null,
"api_token": "",
"hix_ignore": false,
"authors": [],
"editors": [],
"embedded_offers": []
}
},
{ "model": "cms.regionfeedback", "pk": 1, "fields": { "feedback_ptr": 1 } },
{ "model": "cms.regionfeedback", "pk": 4, "fields": { "feedback_ptr": 4 } },
{ "model": "cms.regionfeedback", "pk": 7, "fields": { "feedback_ptr": 7 } },
Expand Down Expand Up @@ -4262,7 +4328,7 @@
"title": "Gesundheitsamt",
"slug": "gesundheitsamt",
"status": "PUBLIC",
"content": "<div><p><strong>Leistungen des Gesundheitsamtes:</strong></p>\r\n<ul>\r\n<li>Beratung zur Gesundheitsfragen</li>\r\n<li>Beratung für Menschen, die an einer psychischen Krankheit leiden</li>\r\n<li>Kontaktstelle für Selbsthilfegruppen Infos auch unter:<br><a href=\"https://www.augsburg.de/umwelt-soziales/gesundheit/selbsthilfegruppen\">www.augsburg.de/selbsthilfe-schwaben</a></li>\r\n<li>Beratung für Schwangere</li>\r\n<li>Impfberatung</li>\r\n<li>Belehrung nach Infektionsschutzgesetz § 43 (Lebensmittelbelehrung, Gesundheitszeugnis)</li>\r\n<li>Untersuchung für Kinder, bevor sie in die Schule gehen können (Schuleingangsuntersuchung)</li>\r\n<li>Erstuntersuchung von unbegleiteten minderjährigen Ausländerinnen und Ausländern auf Veranlassung des AKJF</li>\r\n<li>Erstuntersuchung nach amtlicher Vorladung im Bereich Migration und Asyl</li>\r\n<li>Meldepflichtige Krankheiten (Tuberkulose, Gelbsucht etc.)</li>\r\n<li>Tuberkulosefürsorge</li>\r\n<li>Gesundheitsberatung und Untersuchung für Prostituierte</li>\r\n<li>anonyme AIDS-Beratung / HIV-Test</li>\r\n</ul>\r\n</div>",
"content": "<div><p><strong>Leistungen des Gesundheitsamtes:</strong></p>\r\n<ul>\r\n<li>Beratung zur Gesundheitsfragen</li>\r\n<li>Beratung für Menschen, die an einer psychischen Krankheit leiden</li>\r\n<li>Kontaktstelle für Selbsthilfegruppen Infos auch unter:<br><a href=\"https://www.augsburg.de/umwelt-soziales/gesundheit/selbsthilfegruppen\">www.augsburg.de/selbsthilfe-schwaben</a></li>\r\n<li>Beratung für Schwangere</li>\r\n<li>Impfberatung</li>\r\n<li>Belehrung nach Infektionsschutzgesetz § 43 (Lebensmittelbelehrung, Gesundheitszeugnis)</li>\r\n<li>Untersuchung für Kinder, bevor sie in die Schule gehen können (Schuleingangsuntersuchung)</li>\r\n<li>Erstuntersuchung von unbegleiteten minderjährigen Ausländerinnen und Ausländern auf Veranlassung des AKJF</li>\r\n<li>Erstuntersuchung nach amtlicher Vorladung im Bereich Migration und Asyl</li>\r\n<li>Meldepflichtige Krankheiten (Tuberkulose, Gelbsucht etc.)</li>\r\n<li>Tuberkulosefürsorge</li>\r\n<li>Gesundheitsberatung und Untersuchung für Prostituierte</li>\r\n<li>anonyme AIDS-Beratung / HIV-Test</li>\r\n</ul>\r\n</div>",
"language": 1,
"currently_in_translation": false,
"machine_translated": false,
Expand Down Expand Up @@ -5557,6 +5623,27 @@
"hix_feedback": null
}
},
{
"model": "cms.pagetranslation",
"pk": 101,
"fields": {
"title": "Wichtige Kontakte",
"slug": "wichtige-kontakte",
"status": "PUBLIC",
"content": "<div class=\"contact-card notranslate\" dir=\"ltr\" contenteditable=\"false\" translate=\"no\" data-contact-id=\"5\" data-contact-url=\"http://localhost:8000/augsburg/contact/5/\"><a style=\"display: none;\" href=\"http://localhost:8000/augsburg/contact/5/\">Contact</a>\r\n<h4>referred to in a page | Testkontakt</h4>\r\n<p><img src=\"http://localhost:8000/static/svg/pin.svg\" alt=\"Address: \" width=\"15\" height=\"15\"><a href=\"https://www.google.com/maps/search/?api=1&amp;query=Viktoriastra%C3%9Fe%201,Augsburg,Deutschland\" class=\"link-external\">Viktoriastraße 1, 86150 Augsburg</a></p>\r\n</div>",
"language": 1,
"currently_in_translation": false,
"machine_translated": false,
"version": 1,
"minor_edit": false,
"last_updated": "2025-01-11T07:07:46.642Z",
"creator": ["root"],
"automatic_translation": false,
"page": 31,
"hix_score": null,
"hix_feedback": null
}
},
{
"model": "cms.imprintpagetranslation",
"pk": 1,
Expand Down
8 changes: 4 additions & 4 deletions integreat_cms/cms/models/contact/contact.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import Generator, TYPE_CHECKING
from typing import List, TYPE_CHECKING

from django.conf import settings
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
Expand Down Expand Up @@ -225,16 +225,16 @@ def referring_event_translations(self) -> QuerySet[EventTranslation]:
)

@cached_property
def referring_objects(self) -> Generator[AbstractContentTranslation]:
def referring_objects(self) -> List[AbstractContentTranslation]:
"""
Returns a list of all objects linking to this contact.
:return: all objects referring to this contact
"""
return (
return [
link.content_object
for link in Link.objects.filter(url__url=self.absolute_url)
)
]

def archive(self) -> None:
"""
Expand Down
6 changes: 2 additions & 4 deletions integreat_cms/cms/templates/contacts/contact_card.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ <h4>
{% endif %}
<p>
<img src="{% get_svg_icon "pin" %}" alt="Address: " width="15" height="15" />
&nbsp; <a href="{{ contact.location.map_url }}">{{ contact.location.short_address }}</a>
<a href="{{ contact.location.map_url }}">{{ contact.location.short_address }}</a>
</p>
{% if contact.email %}
<p>
<img src="{% get_svg_icon "email" %}" alt="Email: " width="15" height="15" />
&nbsp;
<a href="mailto:{{ contact.email }}">{{ contact.email }}</a>
</p>
{% endif %}
Expand All @@ -41,13 +40,12 @@ <h4>
alt="Phone Number: "
width="15"
height="15" />
&nbsp; <a href="tel:{{ contact.phone_number }}">{{ contact.phone_number }}</a>
<a href="tel:{{ contact.phone_number }}">{{ contact.phone_number }}</a>
</p>
{% endif %}
{% if contact.website %}
<p>
<img src="{% get_svg_icon "www" %}" alt="Website: " width="15" height="15" />
&nbsp;
<a href="{{ contact.website }}">{{ contact.website }}</a>
</p>
{% endif %}
Expand Down
6 changes: 4 additions & 2 deletions integreat_cms/cms/templates/contacts/contact_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ <h3 class="heading font-bold text-black">
data-confirmation-title="{{ archive_dialog_title }}"
data-confirmation-text="{{ archive_dialog_text }}"
data-confirmation-subject="{{ contact_form.instance.name }}"
data-action="{% url 'archive_contact' contact_id=contact_form.instance.id region_slug=request.region.slug %}">
data-action="{% url 'archive_contact' contact_id=contact_form.instance.id region_slug=request.region.slug %}"
{% if contact_form.instance.referring_objects %}disabled{% endif %}>
<i icon-name="archive" class="mr-2"></i>
{% translate "Archive this contact" %}
</button>
Expand All @@ -122,7 +123,8 @@ <h3 class="heading font-bold text-black">
data-confirmation-title="{{ delete_dialog_title }}"
data-confirmation-text="{{ delete_dialog_text }}"
data-confirmation-subject="{{ contact_form.instance.name }}"
data-action="{% url 'delete_contact' contact_id=contact_form.instance.id region_slug=request.region.slug %}">
data-action="{% url 'delete_contact' contact_id=contact_form.instance.id region_slug=request.region.slug %}"
{% if contact_form.instance.referring_objects %}disabled{% endif %}>
<i icon-name="trash-2" class="mr-2"></i>
{% translate "Delete contact" %}
</button>
Expand Down
54 changes: 35 additions & 19 deletions integreat_cms/cms/views/contacts/contact_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,25 @@ def archive_contact(
to_be_archived_contact = get_object_or_404(
Contact, id=contact_id, location__region=request.region
)
to_be_archived_contact.archive()

messages.success(
if not to_be_archived_contact.referring_objects:
to_be_archived_contact.archive()
messages.success(
request,
_("Contact {0} was successfully archived").format(to_be_archived_contact),
)
return redirect(
"contacts",
**{
"region_slug": region_slug,
},
)
messages.error(
request,
_("Contact {0} was successfully archived").format(to_be_archived_contact),
)
return redirect(
"contacts",
**{
"region_slug": region_slug,
},
_('Cannot archive contact "{0}" while content objects refer to it.').format(
to_be_archived_contact,
),
)
return redirect("edit_contact", region_slug=region_slug, contact_id=contact_id)


@permission_required("cms.delete_contact")
Expand All @@ -57,16 +64,25 @@ def delete_contact(
to_be_deleted_contact = get_object_or_404(
Contact, id=contact_id, location__region=request.region
)
to_be_deleted_contact.delete()
messages.success(
request, _("Contact {0} was successfully deleted").format(to_be_deleted_contact)
)
return redirect(
"contacts",
**{
"region_slug": region_slug,
},
if not to_be_deleted_contact.referring_objects:
to_be_deleted_contact.delete()
messages.success(
request,
_("Contact {0} was successfully deleted").format(to_be_deleted_contact),
)
return redirect(
"contacts",
**{
"region_slug": region_slug,
},
)
messages.error(
request,
_('Cannot delete contact "{0}" while content objects refer to it.').format(
to_be_deleted_contact,
),
)
return redirect("edit_contact", region_slug=region_slug, contact_id=contact_id)


@permission_required("cms.change_contact")
Expand Down
45 changes: 41 additions & 4 deletions integreat_cms/cms/views/contacts/contact_bulk_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
"""

archive_successful = []
archive_failure = []

for content_object in self.get_queryset():
content_object.archive()
archive_successful.append(content_object)
if not content_object.referring_objects:
content_object.archive()
archive_successful.append(content_object)
else:
archive_failure.append(content_object)

if archive_successful:
messages.success(
Expand All @@ -69,6 +73,20 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
),
)

if archive_failure:
messages.error(
request,
ngettext_lazy(
"{model_name} {object_names} cannot be archived while content objects refer to it.",
"The following {model_name_plural} could be archived: {object_names}",
).format(
len(archive_failure),
model_name=self.model._meta.verbose_name.title(),
model_name_plural=self.model._meta.verbose_name_plural,
object_names=iter_to_string(archive_failure),
),
)

return super().post(request, *args, **kwargs)


Expand Down Expand Up @@ -131,9 +149,14 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
:return: The redirect
"""
delete_sucessful = []
delete_failure = []

for content_object in self.get_queryset():
content_object.delete()
delete_sucessful.append(content_object)
if not content_object.referring_objects:
content_object.delete()
delete_sucessful.append(content_object)
else:
delete_failure.append(content_object)

if delete_sucessful:
messages.success(
Expand All @@ -149,4 +172,18 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
),
)

if delete_failure:
messages.error(
request,
ngettext_lazy(
"{model_name} {object_names} cannot be deleted while content objects refer to it.",
"The following {model_name_plural} could be deleted: {object_names}",
len(delete_failure),
).format(
model_name=self.model._meta.verbose_name.title(),
model_name_plural=self.model._meta.verbose_name_plural,
object_names=iter_to_string(delete_failure),
),
)

return super().post(request, *args, **kwargs)
Loading

0 comments on commit 1852c62

Please sign in to comment.