From 8959822d45df24f5826b2e1b58f2e1e93068dd4d Mon Sep 17 00:00:00 2001 From: jarlhengstmengel Date: Mon, 11 Nov 2024 12:56:52 +0000 Subject: [PATCH] Add contact creation in POI form --- integreat_cms/cms/models/contact/contact.py | 16 +++ .../_contact_form_widget.html | 33 ++++++ .../cms/templates/pois/poi_form.html | 6 + .../related_contacts_box.html | 38 ++++++- integreat_cms/cms/urls/protected.py | 10 ++ integreat_cms/cms/views/contacts/__init__.py | 1 + .../views/contacts/contact_form_ajax_view.py | 94 ++++++++++++++++ integreat_cms/locale/de/LC_MESSAGES/django.po | 30 +++-- integreat_cms/static/src/index.ts | 2 + integreat_cms/static/src/js/contact_box.ts | 106 ++++++++++++++++++ 10 files changed, 322 insertions(+), 14 deletions(-) create mode 100644 integreat_cms/cms/templates/ajax_contact_form/_contact_form_widget.html create mode 100644 integreat_cms/cms/views/contacts/contact_form_ajax_view.py create mode 100644 integreat_cms/static/src/js/contact_box.ts diff --git a/integreat_cms/cms/models/contact/contact.py b/integreat_cms/cms/models/contact/contact.py index 0e3217c271..68197a7352 100644 --- a/integreat_cms/cms/models/contact/contact.py +++ b/integreat_cms/cms/models/contact/contact.py @@ -6,6 +6,7 @@ from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector from django.db import models from django.db.models import Q +from django.urls import reverse from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext @@ -266,6 +267,21 @@ def full_url(self) -> str: """ return f"{settings.BASE_URL}/{self.location.region.slug}/contact/{self.id}/" + @cached_property + def backend_edit_link(self) -> str: + """ + This function returns the absolute url to the edit form of this region + + :return: The url + """ + return reverse( + "edit_contact", + kwargs={ + "region_slug": self.region.slug, + "contact_id": self.id, + }, + ) + class Meta: verbose_name = _("contact") default_related_name = "contact" diff --git a/integreat_cms/cms/templates/ajax_contact_form/_contact_form_widget.html b/integreat_cms/cms/templates/ajax_contact_form/_contact_form_widget.html new file mode 100644 index 0000000000..9933001f6e --- /dev/null +++ b/integreat_cms/cms/templates/ajax_contact_form/_contact_form_widget.html @@ -0,0 +1,33 @@ +{% load i18n %} +{% load widget_tweaks %} +
+ + {% render_field contact_form.point_of_contact_for|append_attr:"form:ajax_contact_form" %} + + {% render_field contact_form.name|append_attr:"form:ajax_contact_form" %} + + {% render_field contact_form.email|append_attr:"form:ajax_contact_form" %} + + {% render_field contact_form.phone_number|append_attr:"form:ajax_contact_form" %} + + {% render_field contact_form.website|append_attr:"form:ajax_contact_form" %} + +
diff --git a/integreat_cms/cms/templates/pois/poi_form.html b/integreat_cms/cms/templates/pois/poi_form.html index e8d38faf3a..0282e40733 100644 --- a/integreat_cms/cms/templates/pois/poi_form.html +++ b/integreat_cms/cms/templates/pois/poi_form.html @@ -184,6 +184,12 @@

+
+
{{ media_config_data|json_script:"media_config_data" }} {% if not perms.cms.change_poi or poi_form.instance.id and poi_form.instance.archived %} {% include "../_tinymce_config.html" with readonly=1 %} diff --git a/integreat_cms/cms/templates/pois/poi_form_sidebar/related_contacts_box.html b/integreat_cms/cms/templates/pois/poi_form_sidebar/related_contacts_box.html index 95a9cedd16..50cbdd3ff6 100644 --- a/integreat_cms/cms/templates/pois/poi_form_sidebar/related_contacts_box.html +++ b/integreat_cms/cms/templates/pois/poi_form_sidebar/related_contacts_box.html @@ -17,12 +17,38 @@ {% trans "This location is not currently referred to in any contact." %} {% endif %} - {% for contact in contacts %} - - {{ contact.label_in_reference_list }} - - {% endfor %} + + {% if perms.cms.change_contact %} +
+
+
+ +
+
+ + +
+
+
+ {% endif %} {% endwith %} {% endblock collapsible_box_content %} diff --git a/integreat_cms/cms/urls/protected.py b/integreat_cms/cms/urls/protected.py index d9e8db4711..0cef4da6ac 100644 --- a/integreat_cms/cms/urls/protected.py +++ b/integreat_cms/cms/urls/protected.py @@ -1462,6 +1462,16 @@ contacts.DeleteContactBulkAction.as_view(), name="bulk_delete_contacts", ), + path( + "show-contact-form-ajax//", + contacts.ContactFormAjaxView.as_view(), + name="show_contact_form_ajax", + ), + path( + "create-contact-ajax/", + contacts.ContactFormAjaxView.as_view(), + name="create_contact_ajax", + ), path( "/", include( diff --git a/integreat_cms/cms/views/contacts/__init__.py b/integreat_cms/cms/views/contacts/__init__.py index 90bb0b3ad4..eaa76fff83 100644 --- a/integreat_cms/cms/views/contacts/__init__.py +++ b/integreat_cms/cms/views/contacts/__init__.py @@ -9,5 +9,6 @@ DeleteContactBulkAction, RestoreContactBulkAction, ) +from .contact_form_ajax_view import ContactFormAjaxView from .contact_form_view import ContactFormView from .contact_list_view import ContactListView diff --git a/integreat_cms/cms/views/contacts/contact_form_ajax_view.py b/integreat_cms/cms/views/contacts/contact_form_ajax_view.py new file mode 100644 index 0000000000..58ec26e9e4 --- /dev/null +++ b/integreat_cms/cms/views/contacts/contact_form_ajax_view.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from django.http import JsonResponse +from django.shortcuts import render +from django.utils.decorators import method_decorator +from django.views.generic import TemplateView + +from ...decorators import permission_required +from ...forms import ContactForm +from ...models import Contact +from .contact_context_mixin import ContactContextMixin + +if TYPE_CHECKING: + from typing import Any + + from django.http import HttpRequest, HttpResponse + + +@method_decorator(permission_required("cms.view_contact"), name="dispatch") +@method_decorator(permission_required("cms.change_contact"), name="post") +class ContactFormAjaxView(TemplateView, ContactContextMixin): + """ + View for the ajax contact widget + """ + + #: Template for ajax POI widget + template = "ajax_contact_form/_contact_form_widget.html" + + def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: + r"""Render a contact form widget template + + :param request: The current request + :param \*args: The supplied arguments + :param \**kwargs: The supplied keyword arguments + :return: The html template of a POI form + """ + contact_form = ContactForm( + additional_instance_attributes={"region": request.region} + ) + + return render( + request, + "ajax_contact_form/_contact_form_widget.html", + { + **self.get_context_data(**kwargs), + "contact_form": contact_form, + "poi_id": kwargs.get( + "poi_id", + ), + }, + ) + + def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: + r"""Add a new POI to the database + + :param request: The current request + :param \*args: The supplied arguments + :param \**kwargs: The supplied keyword arguments + :raises ~django.http.Http404: If no language for the given language slug was found + + :return: A status message, either a success or an error message + """ + + contact_instance = Contact.objects.filter(id=None).first() + + data = request.POST.dict() + + contact_form = ContactForm( + data=data, + files=request.FILES, + instance=contact_instance, + additional_instance_attributes={ + "region": request.region, + }, + ) + + if not contact_form.is_valid(): + return JsonResponse( + data={ + "contact_form": contact_form.get_error_messages(), + } + ) + + contact = contact_form.save() + + return JsonResponse( + data={ + "success": "Successfully created location", + "contact_label": contact.label_in_reference_list(), + "edit_url": contact.backend_edit_link, + } + ) diff --git a/integreat_cms/locale/de/LC_MESSAGES/django.po b/integreat_cms/locale/de/LC_MESSAGES/django.po index 13c9b63108..64e8aaf990 100644 --- a/integreat_cms/locale/de/LC_MESSAGES/django.po +++ b/integreat_cms/locale/de/LC_MESSAGES/django.po @@ -4890,7 +4890,9 @@ msgstr "Faxnummer" msgid "Update" msgstr "Aktualisieren" -#: cms/templates/_tinymce_config.html cms/views/media/media_context_mixin.py +#: cms/templates/_tinymce_config.html +#: cms/templates/ajax_contact_form/_contact_form_widget.html +#: cms/views/media/media_context_mixin.py msgid "Submit" msgstr "Speichern" @@ -4980,6 +4982,7 @@ msgid "The new location was successfully created." msgstr "Ein neuer Ort wurde erfolgreich erstellt." #: cms/templates/ajax_poi_form/poi_box.html +#: cms/templates/pois/poi_form_sidebar/related_contacts_box.html msgid "An error occurred." msgstr "Es ist ein Fehler aufgetreten." @@ -7802,6 +7805,14 @@ msgstr "Dieser Ort wird in folgenden Kontakten verwendet." msgid "This location is not currently referred to in any contact." msgstr "Zur Zeit gibt es keine Kontakte zu diesem Ort." +#: cms/templates/pois/poi_form_sidebar/related_contacts_box.html +msgid "Create a new contact" +msgstr "Kontakt erstellen" + +#: cms/templates/pois/poi_form_sidebar/related_contacts_box.html +msgid "The new contact was successfully created." +msgstr "Ein neuer Kontakt wurde erfolgreich erstellt." + #: cms/templates/pois/poi_list.html cms/templates/pois/poi_list_archived.html msgid "Archived locations" msgstr "Archivierte Orte" @@ -11185,6 +11196,16 @@ msgstr "" "Diese Seite konnte nicht importiert werden, da sie zu einer anderen Region " "gehört ({})." +#~ msgid "Here you can create a new contact with this location." +#~ msgstr "Hier können Sie einen neuen Kontakt mit diesem Ort erstellen." + +#~ msgid "" +#~ "Add missing data or change existing data. Select the data you want to " +#~ "import." +#~ msgstr "" +#~ "Ergänzen Sie fehlende Daten oder ändern bestehende Daten. Wählen Sie die " +#~ "Daten aus, die übernommen werden sollen." + #~ msgid "An error occurred while importing events from this external calendar" #~ msgstr "Ein Fehler mit dem Import von Veranstaltungen ist aufgetreten" @@ -11232,13 +11253,6 @@ msgstr "" #~ msgid "Created by" #~ msgstr "Erstellt von" -#~ msgid "" -#~ "Add missing data or change existing data. Select the data you want to " -#~ "import." -#~ msgstr "" -#~ "Ergänzen Sie fehlende Daten oder ändern bestehende Daten. Wählen Sie die " -#~ "Daten aus, die übernommen werden sollen." - #~ msgid "E-mail from location" #~ msgstr "E-mail des Ortes" diff --git a/integreat_cms/static/src/index.ts b/integreat_cms/static/src/index.ts index b81aae6203..07f5032379 100644 --- a/integreat_cms/static/src/index.ts +++ b/integreat_cms/static/src/index.ts @@ -104,6 +104,8 @@ import "./js/menu"; import "./js/poi-categories/poicategory-colors-icons"; import "./js/dashboard/broken-links"; +import "./js/contact_box"; + // IE11: fetch /* eslint-disable-next-line @typescript-eslint/no-var-requires */ require("element-closest").default(window); diff --git a/integreat_cms/static/src/js/contact_box.ts b/integreat_cms/static/src/js/contact_box.ts new file mode 100644 index 0000000000..7ba9f20956 --- /dev/null +++ b/integreat_cms/static/src/js/contact_box.ts @@ -0,0 +1,106 @@ +import { getCsrfToken } from "./utils/csrf-token"; +import { createIconsAt } from "./utils/create-icons"; + +const hideContactFormWidget = () => { + const widget = document.getElementById("contact-form-widget") as HTMLElement; + if (widget) { + widget.textContent = ""; + document.getElementById("show-contact-form-button").classList.remove("hidden"); + } +}; + +const addNewContactToList = (label: string, url: string) => { + const relatedContactList = document.getElementById("related-contact-list"); + const newContactRow = document.createElement("a"); + + newContactRow.href = url; + newContactRow.innerHTML = ` ${label}`; + newContactRow.classList.add("block", "pt-2", "hover:underline"); + + relatedContactList.appendChild(newContactRow); + createIconsAt(relatedContactList); +}; + +const showMessage = (data: any) => { + const timeoutDuration = 10000; + if (data.success) { + hideContactFormWidget(); + addNewContactToList(data.contact_label, data.edit_url); + const successMessageField = document.getElementById("contact-ajax-success-message"); + successMessageField.classList.remove("hidden"); + setTimeout(() => { + successMessageField.classList.add("hidden"); + }, timeoutDuration); + } else { + const errorMessageField = document.getElementById("contact-ajax-error-message"); + errorMessageField.classList.remove("hidden"); + setTimeout(() => { + errorMessageField.classList.add("hidden"); + }, timeoutDuration); + } +}; + +const createContact = async (event: Event) => { + event.preventDefault(); + const btn = event.target as HTMLInputElement; + const form = btn.form as HTMLFormElement; + const formData: FormData = new FormData(form); + formData.append(btn.name, btn.value); + if (!form.reportValidity()) { + return; + } + + const response = await fetch(btn.getAttribute("data-url"), { + method: "POST", + headers: { + "X-CSRFToken": getCsrfToken(), + }, + body: formData, + }); + + const data = await response.json(); + console.debug(data); + showMessage(data); +}; + +const renderContactForm = async () => { + const response = await fetch(document.getElementById("show-contact-form-button").getAttribute("data-url")); + document.getElementById("contact-form-widget").innerHTML = await response.text(); + + document.getElementById("submit-contact-form-button").addEventListener("click", (event) => { + event.preventDefault(); + createContact(event); + }); + document.getElementById("show-contact-form-button").classList.add("hidden"); +}; + +window.addEventListener("load", () => { + document.getElementById("show-contact-form-button")?.addEventListener("click", (event) => { + event.preventDefault(); + renderContactForm(); + }); + + document.querySelectorAll("[contact-poi-box]").forEach((el) => { + el.addEventListener("submit", async (event) => { + event.preventDefault(); + const btn = event.target as HTMLInputElement; + const form = btn.form as HTMLFormElement; + const formData: FormData = new FormData(form); + formData.append(btn.name, btn.value); + if (!form.reportValidity()) { + return; + } + const response = await fetch(btn.getAttribute("data-url"), { + method: "POST", + headers: { + "X-CSRFToken": getCsrfToken(), + }, + body: formData, + }); + // Handle messages + const messages = await response.json(); + console.debug(messages); + showMessage(messages); + }); + }); +});