integreat_cms/cms/views/pois/poi_form_view.py
"""
A view representing an instance of a point of interest. POIs can be created or updated via this view.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from django.conf import settings
from django.contrib import messages
from django.shortcuts import redirect, render
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView
from ...constants import status
from ...decorators import permission_required
from ...forms import POIForm, POITranslationForm
from ...models import Language, POI, POITranslation
from ...utils.translation_utils import gettext_many_lazy as __
from ...utils.translation_utils import translate_link
from ..media.media_context_mixin import MediaContextMixin
from ..mixins import ContentEditLockMixin
from .poi_context_mixin import POIContextMixin
if TYPE_CHECKING:
from typing import Any
from django.http import HttpRequest, HttpResponse
logger = logging.getLogger(__name__)
@method_decorator(permission_required("cms.view_poi"), name="dispatch")
@method_decorator(permission_required("cms.change_poi"), name="post")
class POIFormView(
TemplateView, POIContextMixin, MediaContextMixin, ContentEditLockMixin
):
"""
View for editing POIs
"""
#: The template to render (see :class:`~django.views.generic.base.TemplateResponseMixin`)
template_name = "pois/poi_form.html"
#: The url name of the view to show if the user decides to go back (see :class:`~integreat_cms.cms.views.mixins.ContentEditLockMixin`)
back_url_name: str | None = "pois"
def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
r"""
Render :class:`~integreat_cms.cms.forms.pois.poi_form.POIForm` and :class:`~integreat_cms.cms.forms.pois.poi_translation_form.POITranslationForm`
:param request: The current request
:param \*args: The supplied arguments
:param \**kwargs: The supplied keyword arguments
:return: The rendered template response
"""
region = request.region
language = Language.objects.get(slug=kwargs.get("language_slug"))
# get poi and translation objects if they exist
poi = POI.objects.filter(id=kwargs.get("poi_id")).first()
poi_translation = POITranslation.objects.filter(
poi=poi,
language=language,
).first()
if poi and poi.archived:
disabled = True
messages.warning(
request, _("You cannot edit this location because it is archived.")
)
elif not request.user.has_perm("cms.change_poi"):
disabled = True
messages.warning(
request, _("You don't have the permission to edit locations.")
)
else:
disabled = False
poi_form = POIForm(
instance=poi,
disabled=disabled,
additional_instance_attributes={
"region": region,
},
)
poi_translation_form = POITranslationForm(
request=request,
language=language,
instance=poi_translation,
disabled=disabled,
default_language_title=poi.default_translation.title if poi else None,
)
url_link = f"{settings.WEBAPP_URL}/{region.slug}/{language.slug}/{poi_translation_form.instance.url_infix}/"
return render(
request,
self.template_name,
{
**self.get_context_data(**kwargs),
"poi_form": poi_form,
"poi_translation_form": poi_translation_form,
"language": language,
# Languages for tab view
"languages": region.active_languages if poi else [language],
"url_link": url_link,
"translation_states": poi.translation_states if poi else [],
},
)
# pylint: disable=too-many-locals
def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
r"""
Submit :class:`~integreat_cms.cms.forms.pois.poi_form.POIForm` and
:class:`~integreat_cms.cms.forms.pois.poi_translation_form.POITranslationForm` and save :class:`~integreat_cms.cms.models.pois.poi.POI` and
:class:`~integreat_cms.cms.models.pois.poi_translation.POITranslation` objects
:param request: The current request
:param \*args: The supplied arguments
:param \**kwargs: The supplied keyword arguments
:return: The rendered template response
"""
region = request.region
language = Language.objects.get(slug=kwargs.get("language_slug"))
poi_instance = POI.objects.filter(id=kwargs.get("poi_id")).first()
poi_translation_instance = POITranslation.objects.filter(
poi=poi_instance,
language=language,
).first()
if poi_instance and poi_instance.archived:
return redirect(
"edit_poi",
**{
"poi_id": poi_instance.id,
"region_slug": region.slug,
"language_slug": language.slug,
},
)
poi_form = POIForm(
data=request.POST,
files=request.FILES,
instance=poi_instance,
additional_instance_attributes={
"region": region,
},
)
poi_translation_form = POITranslationForm(
request=request,
language=language,
data=request.POST,
instance=poi_translation_instance,
additional_instance_attributes={
"creator": request.user,
"language": language,
"poi": poi_form.instance,
},
changed_by_user=request.user,
)
user_slug = poi_translation_form.data.get("slug")
if not poi_form.is_valid() or not poi_translation_form.is_valid():
# Add error messages
poi_form.add_error_messages(request)
poi_translation_form.add_error_messages(request)
elif (
poi_translation_form.instance.status == status.AUTO_SAVE
and not poi_form.has_changed()
and not poi_translation_form.has_changed()
):
messages.info(request, _("No changes detected, autosave skipped"))
else:
# Save forms
poi_translation_form.instance.poi = poi_form.save()
poi_translation_instance = poi_translation_form.save(
foreign_form_changed=poi_form.has_changed()
)
# If any source translation changes to draft, set all depending translations/versions to draft
if poi_translation_form.instance.status == status.DRAFT:
language_tree_node = region.language_node_by_slug.get(language.slug)
languages = [language] + [
node.language for node in language_tree_node.get_descendants()
]
poi_translation_form.instance.poi.translations.filter(
language__in=languages
).update(status=status.DRAFT)
elif (
poi_translation_form.instance.status == status.PUBLIC
and poi_translation_form.instance.minor_edit
):
poi_translation_form.instance.poi.translations.filter(
language=language
).update(status=status.PUBLIC)
# Show a message that the slug was changed if it was not unique
if user_slug and user_slug != poi_translation_form.cleaned_data["slug"]:
other_translation = POITranslation.objects.filter(
poi__region=region, slug=user_slug, language=language
).first()
other_translation_link = other_translation.backend_edit_link
message = _(
"The slug was changed from '{user_slug}' to '{slug}', "
"because '{user_slug}' is already used by <a>{translation}</a> or one of its previous versions.",
).format(
user_slug=user_slug,
slug=poi_translation_form.cleaned_data["slug"],
translation=other_translation,
)
messages.warning(
request,
translate_link(
message,
attributes={
"href": other_translation_link,
"class": "underline hover:no-underline",
},
),
)
# Add the success message and redirect to the edit page
if not poi_instance:
messages.success(
request,
_('Location "{}" was successfully created').format(
poi_translation_form.instance
),
)
elif not poi_form.has_changed() and not poi_translation_form.has_changed():
messages.info(request, _("No changes detected, but date refreshed"))
else:
# Add the success message
poi_translation_form.add_success_message(request)
# Show warning if nominatim result is more than 10km away from manually entered coordinates
if poi_form.nominatim_distance_delta > 10:
messages.warning(
request,
__(
_(
"The distance between the manually entered coordinates and the coordinates of the address is {}km."
).format(poi_form.nominatim_distance_delta),
_("Please make sure the entered values are correct."),
),
)
return redirect(
"edit_poi",
**{
"poi_id": poi_form.instance.id,
"region_slug": region.slug,
"language_slug": language.slug,
},
)
url_link = f"{settings.WEBAPP_URL}/{region.slug}/{language.slug}/{poi_translation_form.instance.url_infix}/"
return render(
request,
self.template_name,
{
**self.get_context_data(**kwargs),
"poi_form": poi_form,
"poi_translation_form": poi_translation_form,
"language": language,
# Languages for tab view
"languages": region.active_languages if poi_instance else [language],
"url_link": url_link,
"translation_states": (
poi_instance.translation_states if poi_instance else []
),
},
)