Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2024-11-07] TinyMCE: Insert contact, but track using linkcheck #3169

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 129 additions & 1 deletion integreat_cms/cms/models/contact/contact.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
from __future__ import annotations

from typing import Generator, TYPE_CHECKING

from django.conf import settings
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
from django.db import models
from django.db.models import Q
from django.db.utils import DataError
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from linkcheck.models import Link

from ..abstract_base_model import AbstractBaseModel
from ..events.event_translation import EventTranslation
from ..fields.truncating_char_field import TruncatingCharField
from ..pages.page_translation import PageTranslation
from ..pois.poi import POI
from ..pois.poi_translation import POITranslation
from ..regions.region import Region

if TYPE_CHECKING:
from django.db.models.query import QuerySet

from ..abstract_content_translation import AbstractContentTranslation


class Contact(AbstractBaseModel):
"""
Expand Down Expand Up @@ -59,6 +73,28 @@ def region(self) -> Region:
"""
return self.location.region

@classmethod
def search(cls, region: Region, query: str) -> QuerySet:
"""
Searches for all contacts which match the given `query` in their comment.
:param region: The current region
:param query: The query string used for filtering the contacts
:return: A query for all matching objects
"""
vector = SearchVector(
"name",
"email",
"phone_number",
"website",
"point_of_contact_for",
)
query = SearchQuery(query)
return (
Contact.objects.filter(location__region=region, archived=False)
.annotate(rank=SearchRank(vector, query))
.order_by("-rank")
)

def __str__(self) -> str:
"""
This overwrites the default Django :meth:`~django.db.models.Model.__str__` method which would return ``Contact object (id)``.
Expand Down Expand Up @@ -116,6 +152,89 @@ def get_repr(self) -> str:
"""
return f"<Contact (id: {self.id}, point of contact for: {self.point_of_contact_for}, name: {self.name}, region: {self.region.slug})>"

@cached_property
def get_repr_short(self) -> str:
"""
Returns a short representation only contaiing the relevant data, no field names.

:return: The short representation of the contact
"""
point_of_contact_for = (
f"{self.point_of_contact_for}: " if self.point_of_contact_for else ""
)
name = f"{self.name} " if self.name else ""
details = [
detail for detail in [self.email, self.phone_number, self.website] if detail
]
details_repr = f"({', '.join(details)})" if details else ""

return f"{point_of_contact_for}{name}{details_repr}".strip()

@cached_property
def referring_page_translations(self) -> QuerySet[PageTranslation]:
"""
Returns a queryset containing all :class:`~integreat_cms.cms.models.pages.page_translation.PageTranslation` objects which reference this contact

:return: all PageTranslation objects referencing this contact
"""
from ...linklists import PageTranslationLinklist

return PageTranslation.objects.filter(
id__in=(
Link.objects.filter(
url__url=self.full_url,
content_type=PageTranslationLinklist.content_type(),
).values("object_id")
),
)

@cached_property
def referring_poi_translations(self) -> QuerySet[POITranslation]:
"""
Returns a queryset containing all :class:`~integreat_cms.cms.models.pois.poi_translation.POITranslation` objects which reference this contact

:return: all POITranslation objects referencing this contact
"""
from ...linklists import POITranslationLinklist

return POITranslation.objects.filter(
id__in=(
Link.objects.filter(
url__url=self.full_url,
content_type=POITranslationLinklist.content_type(),
).values("object_id")
),
)

@cached_property
def referring_event_translations(self) -> QuerySet[EventTranslation]:
"""
Returns a queryset containing all :class:`~integreat_cms.cms.models.events.event_translation.EventTranslation` objects which reference this contact

:return: all EventTranslation objects referencing this contact
"""
from ...linklists import EventTranslationLinklist

return EventTranslation.objects.filter(
id__in=(
Link.objects.filter(
url__url=self.full_url,
content_type=EventTranslationLinklist.content_type(),
).values("object_id")
),
)

@cached_property
def referring_objects(self) -> Generator[AbstractContentTranslation]:
"""
Returns a list of all objects linking to this contact.

:return: all objects referring to this contact
"""
return (
link.content_object for link in Link.objects.filter(url__url=self.full_url)
)

def archive(self) -> None:
"""
Archives the contact
Expand All @@ -138,6 +257,15 @@ def copy(self) -> None:
self.point_of_contact_for = self.point_of_contact_for + " " + _("(Copy)")
self.save()

@cached_property
def full_url(self) -> str:
"""
This property returns the full url of this contact

:return: The full url
"""
return f"{settings.BASE_URL}/{self.location.region.slug}/contact/{self.id}/"

class Meta:
verbose_name = _("contact")
default_related_name = "contact"
Expand Down
19 changes: 19 additions & 0 deletions integreat_cms/cms/models/pois/poi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils.functional import cached_property

if TYPE_CHECKING:
from typing import Any
Expand Down Expand Up @@ -200,6 +201,24 @@ def is_used(self) -> bool:
"""
return self.events.exists() or self.contacts.exists()

@cached_property
def short_address(self) -> str:
"""
:return: one-line representation of this POI's address
"""
return f"{self.address}, {self.postcode} {self.city}"

@cached_property
def map_url(self) -> str:
"""
:return: the link to the POI of the default (public) translation
"""
return (
self.default_public_translation.map_url
if self.default_public_translation
else self.default_translation.map_url
)

class Meta:
#: The verbose name of the model
verbose_name = _("location")
Expand Down
11 changes: 11 additions & 0 deletions integreat_cms/cms/models/pois/poi_translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import TYPE_CHECKING

from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.db.models import Q
Expand All @@ -17,6 +18,7 @@

from .. import POI, Region

from ...constants import status
from ...utils.translation_utils import gettext_many_lazy as __
from ..abstract_content_translation import AbstractContentTranslation
from ..decorators import modify_fields
Expand Down Expand Up @@ -98,6 +100,15 @@ def backend_edit_link(self) -> str:
},
)

@cached_property
def map_url(self) -> str:
"""
:return: the link to the POI on the Integreat map (if it exists), to google maps otherwise
"""
if not self.poi.location_on_map and not self.status == status.DRAFT:
return f"{settings.WEBAPP_URL}{self.get_absolute_url()}"
return f"https://www.google.com/maps/search/?api=1&query={self.poi.address},{self.poi.city},{self.poi.country}"

@staticmethod
def default_icon() -> str | None:
"""
Expand Down
104 changes: 54 additions & 50 deletions integreat_cms/cms/templates/_related_contents_table.html
Original file line number Diff line number Diff line change
@@ -1,65 +1,69 @@
{% load i18n %}
{% load content_filters %}
<div class="table-listing w-full px-4 pb-4">
<div>
<label>
{{ table_title }}
</label>
</div>
<table class="w-full mt-4 rounded border border-solid border-gray-200 shadow bg-white">
<thead>
<tr class="border-b border-solid border-gray-200">
<th class="text-sm text-left uppercase py-3 pl-4 pr-2">
{% translate "Name in " %} {{ region_default_language.translated_name }}
</th>
{% if region_default_language != backend_language %}
{% get_current_language as LANGUAGE_CODE %}
{% get_language LANGUAGE_CODE as backend_language %}
{% with request.region.default_language as region_default_language %}
<div class="table-listing w-full px-4 pb-4">
<div>
<label>
{{ table_title }}
</label>
</div>
<table class="w-full mt-4 rounded border border-solid border-gray-200 shadow bg-white">
<thead>
<tr class="border-b border-solid border-gray-200">
<th class="text-sm text-left uppercase py-3 pl-4 pr-2">
{% translate "Name in " %} {{ backend_language.translated_name }}
{% translate "Name in " %} {{ region_default_language.translated_name }}
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for content in contents %}
{% get_translation content region_default_language.slug as content_translation %}
{% get_translation content backend_language.slug as backendlang_content_translation %}
<tr class="border-b border-solid border-gray-200 hover:bg-gray-100">
{% if content_translation %}
<td class="text-sm text-left py-3 pl-4 pr-2">
<a href="{{ content_translation.backend_edit_link }}"
class="block py-1.5 px-2 overflow-hidden max-w-xs whitespace-nowrap text-ellipsis text-gray-800"
title="{% translate "View content" %}">
{{ content_translation.title }}
</a>
</td>
{% else %}
<td class="text-sm text-left py-3 pl-4 pr-2">
<i>{% translate "Translation not available" %}</i>
</td>
{% endif %}
{% if region_default_language != backend_language %}
{% if backendlang_content_translation %}
<th class="text-sm text-left uppercase py-3 pl-4 pr-2">
{% translate "Name in " %} {{ backend_language.translated_name }}
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for content in contents %}
{% get_translation content region_default_language.slug as content_translation %}
{% get_translation content backend_language.slug as backendlang_content_translation %}
<tr class="border-b border-solid border-gray-200 hover:bg-gray-100">
{% if content_translation %}
<td class="text-sm text-left py-3 pl-4 pr-2">
<a href="{{ backendlang_content_translation.backend_edit_link }}"
<a href="{{ content_translation.backend_edit_link }}"
class="block py-1.5 px-2 overflow-hidden max-w-xs whitespace-nowrap text-ellipsis text-gray-800"
title="{% translate "View content" %}">
{{ backendlang_content_translation.title }}
{{ content_translation.title }}
</a>
</td>
{% else %}
<td class="text-sm text-left py-3 pl-4 pr-2">
<i>{% translate "Translation not available" %}</i>
</td>
{% endif %}
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="2" class="px-4 py-3">
{% trans no_content_message %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if region_default_language != backend_language %}
{% if backendlang_content_translation %}
<td class="text-sm text-left py-3 pl-4 pr-2">
<a href="{{ backendlang_content_translation.backend_edit_link }}"
class="block py-1.5 px-2 overflow-hidden max-w-xs whitespace-nowrap text-ellipsis text-gray-800"
title="{% translate "View content" %}">
{{ backendlang_content_translation.title }}
</a>
</td>
{% else %}
<td class="text-sm text-left py-3 pl-4 pr-2">
<i>{% translate "Translation not available" %}</i>
</td>
{% endif %}
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="2" class="px-4 py-3">
{% trans no_content_message %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endwith %}
7 changes: 7 additions & 0 deletions integreat_cms/cms/templates/_tinymce_config.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,16 @@
data-group-icon-text='{% translate "Group" %}'
data-group-icon-src="{% get_base_url %}{% static 'svg/group.svg' %}"
data-group-icon-alt="{% translate "A group of people" %}"
data-contact-dialog-search-text='{% translate "Search for contact" %}'
data-contact-dialog-title-text='{% translate "Add Contact" %}'
data-contact-change-text='{% translate "Change Contact" %}'
data-contact-remove-text='{% translate "Remove Contact" %}'
data-contact-icon-text='{% translate "Contact Person" %}'
data-contact-icon-src="{% get_base_url %}{% static 'svg/contact.svg' %}"
data-contact-icon-alt="{% translate "Contact Person" %}"
data-contact-ajax-url="{% url 'search_contact_ajax' region_slug=request.region.slug %}"
data-contact-menu-text='{% translate "Contact..." %}'
data-contact-no-results-text='{% translate "no results" %}'
data-speech-icon-text='{% translate "Spoken Languages" %}'
data-speech-icon-src="{% get_base_url %}{% static 'svg/speech.svg' %}"
data-speech-icon-alt="{% translate "Spoken Languages" %}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
{{ poi.country }}
{% endif %}
</div>
<a href="https://www.google.com/maps/search/?api=1&query={% if poi %}{{ poi.address }},{{ poi.city }},{{ poi.country }}{% endif %}"
<a href="{{ poi.map_url }}"
class="text-blue-500 hover:underline"
target="_blank"
rel="noopener noreferrer">
{% translate "Open on Google Maps" %}
{% translate "Open on Maps" %}
</a>
<label class="secondary">
{% translate "Contact details" %}
Expand Down
Loading
Loading