Skip to content

Commit

Permalink
use tomselect
Browse files Browse the repository at this point in the history
also refactor server side contact card rendering to template
  • Loading branch information
PeterNerlich committed Nov 7, 2024
1 parent 9e1ff12 commit 11f8851
Show file tree
Hide file tree
Showing 16 changed files with 471 additions and 344 deletions.
1 change: 1 addition & 0 deletions integreat_cms/cms/templates/_tinymce_config.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
data-contact-ajax-url="{% url 'search_content_ajax' region_slug=request.region.slug language_slug=language.slug %}"
data-contact-menu-text='{% translate "Contact..." %}'
data-contact-no-results-text='{% translate "- no results -" %}'
data-contact-url-regex="{{ contact_url_regex }}"
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
76 changes: 76 additions & 0 deletions integreat_cms/cms/templates/contacts/contact_card.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{% load static %}
{% spaceless %}
<div contenteditable="false"
{% if not contact %} data-invalid="true" {% endif %}
style="
display: inline-block;
box-sizing: border-box;
min-width: 50%;
padding: 0.1em 1em;
border-radius: 0.3em;
background: rgba(127, 127, 127, 0.15);
box-shadow: 0 .1em .1em rgba(0,0,0,0.4);
cursor: default !important;
color: initial;
text-decoration: initial;
background-image: linear-gradient(to right, rgba(255,255,255,0.9) 0 100%), url({% get_static_prefix %}/svg/contact.svg) !important;
background-blend-mode: difference;
background-position: calc(100% + 2em) calc(100% + 1em);
background-size: 7em;
background-repeat: no-repeat;
">
<a href={{ contact.full_url }}
style="
opacity: 0;
position: absolute;
font-size: 0;
">Contact</a>
{% if contact %}
<h4>{% if contact.title and contact.title.strip %}
{{ contact.title.strip }}:
{% endif %}{% if contact.name and contact.name.strip %}<span class="notranslate" dir="ltr" translate="no">{{ contact.name.strip }}
</span>
{% endif %}
</h4>
{% if contact.email and contact.email.strip %}
<p>
<img src="{% get_static_prefix %}/svg/email.svg"
alt="Email"
style="width: 15px; height: 15px;"
/>
<a href="mailto:{{ contact.email.strip }}">
<span class="notranslate" dir="ltr" translate="no">
{{ contact.email.strip }}
</span>
</a>
</p>
{% endif %}
{% if contact.phone_number and contact.phone_number.strip %}
<p>
<img src="{% get_static_prefix %}/svg/call.svg"
alt="Phone Number"
style="width: 15px; height: 15px;"
/>
<a href="tel:{{ contact.phone_number.strip }}">
<span class="notranslate" dir="ltr" translate="no">
{{ contact.phone_number.strip }}
</span>
</a>
</p>
{% endif %}
{% if contact.website and contact.website.strip %}
<p>
<img src="{% get_static_prefix %}/svg/www.svg"
alt="Email"
style="width: 15px; height: 15px;"
/>
<a href="{{ contact.website.strip }}">
<span class="notranslate" dir="ltr" translate="no">
{{ contact.website.strip }}
</span>
</a>
</p>
{% endif %}
{% endif %}
</div>
{% endspaceless %}
144 changes: 21 additions & 123 deletions integreat_cms/cms/utils/content_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from django.conf import settings
from django.db.models import Q
from django.template import loader
from lxml.etree import LxmlError
from lxml.html import Element, fromstring, HtmlElement, tostring

Expand Down Expand Up @@ -143,11 +144,9 @@ def update_internal_links(link: HtmlElement, language_slug: str) -> None:
link.append(new_html)


def render_contact_card(
contact_id: int, fetched_contacts: dict[int, Contact]
) -> HtmlElement:
def render_contact_card(contact_id: int, fetched_contacts: dict[int, Contact]) -> str:
"""
Produces an html element for the contact.
Produces a rendered html element for the contact.
:param contact_id: The id of the contact to render the card for
:param fetched_contacts: A dictionary of pre-fetched contact objects, indexed by their id
Expand All @@ -158,125 +157,14 @@ def render_contact_card(
nor will a contact at that key be double-checked to actually have the expected id.
If the contact for the id is not provided, the contact will be assumed missing from our database and be labelled as invalid.
"""
new_div = Element(
"div",
contenteditable="false",
style=" ".join(
"""
display: inline-block;
box-sizing: border-box;
min-width: 50%;
padding: 0.1em 1em;
border-radius: 0.3em;
background: rgba(127, 127, 127, 0.25);
outline: 4px solid #b4ffff !important;
cursor: default !important;
color: initial;
text-decoration: initial;
""".split()
template = loader.get_template("contacts/contact_card.html")
context = {
"contact_id": contact_id,
"contact": (
fetched_contacts[contact_id] if contact_id in fetched_contacts else None
),
)
if contact_id not in fetched_contacts:
new_div.set(**{"data-invalid": "true"})
logger.warning("Non-existent contact id %r in content", contact_id)
else:
contact = fetched_contacts[contact_id]
logger.debug("Rendering contact %r into content", contact)
a = Element(
"a",
href=contact.full_url,
style=" ".join(
"""
opacity: 0;
position: absolute;
font-size: 0;
""".split()
),
)
a.text = "Contact"
new_div.append(a)

def notranslate(text: str) -> HtmlElement:
"""
Put a notranslate span around some text
"""
span = Element(
"span",
**{
"class": "notranslate",
"translate": "no",
"dir": "ltr",
},
)
span.text = text
return span

title = (
f"{contact.title.strip()}: "
if contact.title and contact.title.strip()
else ""
)
name = (
notranslate(contact.name.strip())
if contact.name and contact.name.strip()
else ""
)
h4 = Element("h4")
h4.text = f"{title}{name}"
new_div.append(h4)

if contact.email and contact.email.strip():
email = Element("p")
img = Element(
"img",
style="width: 15px; height: 15px;",
src="/static/svg/email.svg",
alt="Email",
)
img.tail = " "
email.append(img)
link = Element(
"a",
href=f"mailto:{contact.email.strip()}",
)
link.append(notranslate(contact.email.strip()))
email.append(link)
new_div.append(email)
if contact.phone_number and contact.phone_number.strip():
phone_number = Element("p")
img = Element(
"img",
style="width: 15px; height: 15px;",
src="/static/svg/call.svg",
alt="Phone Number",
)
img.tail = " "
phone_number.append(img)
link = Element(
"a",
href=f"tel:{contact.phone_number.strip()}",
)
link.append(notranslate(contact.phone_number.strip()))
phone_number.append(link)
new_div.append(phone_number)
if contact.website and contact.website.strip():
website = Element("p")
img = Element(
"img",
style="width: 15px; height: 15px;",
src="/static/svg/www.svg",
alt="Website",
)
img.tail = " "
website.append(img)
link = Element(
"a",
href=contact.website.strip(),
)
link.append(notranslate(contact.website.strip()))
website.append(link)
new_div.append(website)
return new_div
}
return template.render(context, None)


def update_contacts(content: HtmlElement, only_ids: tuple[int] | None = None) -> None:
Expand Down Expand Up @@ -319,7 +207,17 @@ def update_contacts(content: HtmlElement, only_ids: tuple[int] | None = None) ->
}

for contact_id, divs in nodes_to_update.items():
new_div = render_contact_card(contact_id, fetched_contacts=fetched_contacts)
html = render_contact_card(contact_id, fetched_contacts=fetched_contacts)
try:
# We need the parsed form so we can plug it into our existing content tree
new_div = fromstring(html)
except LxmlError as e:
logger.debug(
"Failed to parse rendered HTML for contact card: %r\n→ %s\nEOF",
e,
html,
)
new_div = Element("pre", html)

# Finally, inject the card in every occurence
for div in divs:
Expand Down
8 changes: 6 additions & 2 deletions integreat_cms/cms/views/events/event_form_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ...models import Event, EventTranslation, Language, POI, RecurrenceRule
from ...utils.translation_utils import translate_link
from ..media.media_context_mixin import MediaContextMixin
from ..mixins import ContentEditLockMixin
from ..mixins import ContentEditLockMixin, HtmlEditorMixin
from .event_context_mixin import EventContextMixin

if TYPE_CHECKING:
Expand All @@ -32,7 +32,11 @@
@method_decorator(permission_required("cms.view_event"), name="dispatch")
@method_decorator(permission_required("cms.change_event"), name="post")
class EventFormView(
TemplateView, EventContextMixin, MediaContextMixin, ContentEditLockMixin
TemplateView,
EventContextMixin,
MediaContextMixin,
ContentEditLockMixin,
HtmlEditorMixin,
):
"""
Class for rendering the events form
Expand Down
5 changes: 4 additions & 1 deletion integreat_cms/cms/views/imprint/imprint_form_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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 HtmlEditorMixin
from .imprint_context_mixin import ImprintContextMixin

if TYPE_CHECKING:
Expand All @@ -32,7 +33,9 @@

@method_decorator(permission_required("cms.view_imprintpage"), name="dispatch")
@method_decorator(permission_required("cms.change_imprintpage"), name="post")
class ImprintFormView(TemplateView, ImprintContextMixin, MediaContextMixin):
class ImprintFormView(
TemplateView, ImprintContextMixin, MediaContextMixin, HtmlEditorMixin
):
"""
View for the imprint page form and imprint page translation form
"""
Expand Down
23 changes: 23 additions & 0 deletions integreat_cms/cms/views/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from django.views.generic.base import ContextMixin, TemplateResponseMixin

from ...core.utils.machine_translation_provider import MachineTranslationProvider
from ..models import Contact

if TYPE_CHECKING:
from typing import Any
Expand Down Expand Up @@ -140,3 +141,25 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
)
)
return context


class HtmlEditorMixin(ContextMixin):
"""
A mixin that provides some variables required for the HTML editor
"""

def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
r"""
Returns a dictionary representing the template context
(see :meth:`~django.views.generic.base.ContextMixin.get_context_data`).
:param \**kwargs: The given keyword arguments
:return: The template context
"""
context = super().get_context_data(**kwargs)
context.update(
{
"contact_url_regex": Contact.url_regex.pattern,
}
)
return context
8 changes: 6 additions & 2 deletions integreat_cms/cms/views/pages/page_form_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
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 ..mixins import ContentEditLockMixin, HtmlEditorMixin
from .page_context_mixin import PageContextMixin

if TYPE_CHECKING:
Expand All @@ -37,7 +37,11 @@

@method_decorator(permission_required("cms.view_page"), name="dispatch")
class PageFormView(
TemplateView, PageContextMixin, MediaContextMixin, ContentEditLockMixin
TemplateView,
PageContextMixin,
MediaContextMixin,
ContentEditLockMixin,
HtmlEditorMixin,
):
"""
View for the page form and page translation form
Expand Down
8 changes: 6 additions & 2 deletions integreat_cms/cms/views/pois/poi_form_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
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 ..mixins import ContentEditLockMixin, HtmlEditorMixin
from .poi_context_mixin import POIContextMixin

if TYPE_CHECKING:
Expand All @@ -35,7 +35,11 @@
@method_decorator(permission_required("cms.view_poi"), name="dispatch")
@method_decorator(permission_required("cms.change_poi"), name="post")
class POIFormView(
TemplateView, POIContextMixin, MediaContextMixin, ContentEditLockMixin
TemplateView,
POIContextMixin,
MediaContextMixin,
ContentEditLockMixin,
HtmlEditorMixin,
):
"""
View for editing POIs
Expand Down
8 changes: 6 additions & 2 deletions integreat_cms/cms/views/utils/search_content_ajax.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,12 @@ def search_content_ajax(
"url": contact.full_url,
"type": "contact",
}
for contact in Contact.search(region, query).filter(
archived=archived_flag
for contact in (
Contact.search(region, query).filter(archived=archived_flag)
if isinstance(query, str)
# This is dirty and shouldn't be done this way,
# but it's just so convenient to use as a fallback
else [Contact.objects.get(id=query)]
)
]
)
Expand Down
Loading

0 comments on commit 11f8851

Please sign in to comment.