Skip to content

Commit

Permalink
Merge pull request #2636 from digitalfabrik/fix/title-change-not-upda…
Browse files Browse the repository at this point in the history
…ting-link

Update page link before submitting page form
  • Loading branch information
charludo authored Jun 25, 2024
2 parents 1b3fb33 + 4113830 commit ec599d9
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 37 deletions.
14 changes: 14 additions & 0 deletions integreat_cms/cms/models/users/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from ..abstract_base_model import AbstractBaseModel
from ..chat.chat_message import ChatMessage
from ..decorators import modify_fields
from ..pages.page import Page
from ..regions.region import Region
from .organization import Organization

Expand Down Expand Up @@ -209,6 +210,19 @@ def update_chat_last_visited(self) -> datetime:
)
return previous_chat_last_visited

def access_granted_pages(self, region: Region) -> QuerySet[Page]:
"""
Get a list of all pages the user has been given explicit rights to edit
"""
access_granted_pages = Page.objects.filter(
models.Q(authors=self) | models.Q(editors=self)
).filter(region=region)
if self.organization:
access_granted_pages = access_granted_pages.union(
Page.objects.filter(organization=self.organization)
)
return access_granted_pages

def __str__(self) -> str:
"""
This overwrites the default Django :meth:`~django.db.models.Model.__str__` method which would return ``User object (id)``.
Expand Down
15 changes: 11 additions & 4 deletions integreat_cms/cms/templates/events/event_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,24 @@ <h1 class="heading overflow-hidden text-ellipsis">
<div class="flex flex-wrap gap-4 ml-auto mr-0 items-center">
{% include "generic_auto_save_note.html" with form_instance=event_form.instance %}
{% if perms.cms.publish_event %}
<button name="status" value="{{ DRAFT }}" class="btn btn-outline">
<button name="status"
value="{{ DRAFT }}"
class="btn btn-outline no-premature-submission">
{% translate "Save as draft" %}
</button>
<button name="status" value="{{ PUBLIC }}" class="btn">
<button name="status"
value="{{ PUBLIC }}"
class="btn no-premature-submission">
{% if event_translation_form.instance.status == PUBLIC %}
{% translate "Update" %}
{% else %}
{% translate "Publish" %}
{% endif %}
</button>
{% else %}
<button name="status" value="{{ REVIEW }}" class="btn">
<button name="status"
value="{{ REVIEW }}"
class="btn no-premature-submission">
{% translate "Submit for approval" %}
</button>
{% endif %}
Expand All @@ -67,7 +73,8 @@ <h1 class="heading overflow-hidden text-ellipsis">
<div class="w-full p-4 flex flex-col flex-auto">
<div class="flex justify-between">
<label for="{{ event_translation_form.title.id_for_label }}"
data-slugify-url="{% url 'slugify_ajax' region_slug=request.region.slug language_slug=language.slug model_type='event' %}{% if event_form.instance.id %}?model_id={{ event_form.instance.id }}{% endif %}">
data-slugify-url="{% url 'slugify_ajax' region_slug=request.region.slug language_slug=language.slug model_type='event' %}"
{% if event_form.instance.id %}data-model-id="{{ event_form.instance.id }}"{% endif %}>
{{ event_translation_form.title.label }}
</label>
{% if event_translation_form.instance.id %}
Expand Down
3 changes: 1 addition & 2 deletions integreat_cms/cms/templates/imprint/imprint_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ <h1 class="heading">
<div class="w-full rounded border border-blue-500 bg-white flex flex-col flex-auto">
<div class="w-full p-4 flex flex-col flex-auto">
<div class="flex justify-between mr-2">
<label for="{{ page_translation_form.title.id_for_label }}"
data-slugify-url="{% url 'slugify_ajax' region_slug=request.region.slug language_slug=language.slug model_type='page' %}{% if page_form.instance.id %}?model_id={{ page_form.instance.id }}{% endif %}">
<label for="{{ page_translation_form.title.id_for_label }}">
{{ imprint_translation_form.title.label }}
</label>
{% if imprint_translation_form.instance.id %}
Expand Down
15 changes: 11 additions & 4 deletions integreat_cms/cms/templates/pages/page_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,24 @@ <h1 class="heading">
</button>
{% has_perm 'cms.publish_page_object' request.user page as can_publish_page %}
{% if can_publish_page %}
<button name="status" value="{{ DRAFT }}" class="btn btn-outline">
<button name="status"
value="{{ DRAFT }}"
class="btn btn-outline no-premature-submission">
{% translate "Save as draft" %}
</button>
<button name="status" value="{{ PUBLIC }}" class="btn whitespace-nowrap">
<button name="status"
value="{{ PUBLIC }}"
class="btn whitespace-nowrap no-premature-submission">
{% if page_translation_form.instance.status == PUBLIC %}
{% translate "Update" %}
{% else %}
{% translate "Publish" %}
{% endif %}
</button>
{% elif can_edit_page %}
<button name="status" value="{{ REVIEW }}" class="btn">
<button name="status"
value="{{ REVIEW }}"
class="btn no-premature-submission">
{% translate "Submit for approval" %}
</button>
{% endif %}
Expand Down Expand Up @@ -92,7 +98,8 @@ <h1 class="heading">
{% endif %}
<div class="flex justify-between mr-2">
<label for="{{ page_translation_form.title.id_for_label }}"
data-slugify-url="{% url 'slugify_ajax' region_slug=request.region.slug language_slug=language.slug model_type='page' %}{% if page_form.instance.id %}?model_id={{ page_form.instance.id }}{% endif %}">
data-slugify-url="{% url 'slugify_ajax' region_slug=request.region.slug language_slug=language.slug model_type='page' %}"
{% if page_form.instance.id %}data-model-id="{{ page_form.instance.id }}"{% endif %}>
{{ page_translation_form.title.label }}
</label>
{% if page_translation_form.instance.id %}
Expand Down
9 changes: 6 additions & 3 deletions integreat_cms/cms/templates/pois/poi_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ <h1 class="overflow-hidden heading text-ellipsis">
{% if not poi_form.instance.archived and perms.cms.change_poi %}
<div class="flex flex-wrap items-center gap-4 ml-auto mr-0">
{% include "generic_auto_save_note.html" with form_instance=poi_translation_form.instance %}
<button name="status" value="{{ DRAFT }}" class="btn btn-outline">
<button name="status"
value="{{ DRAFT }}"
class="btn btn-outline no-premature-submission">
{% translate "Save as draft" %}
</button>
<button name="status"
value="{{ PUBLIC }}"
class="btn"
class="btn no-premature-submission"
{% if language != request.region.default_language and not poi_form.instance.default_public_translation %} title="{% translate "The default translation is not yet published" %}" disabled {% endif %}>
{% if poi_translation_form.instance.status == PUBLIC %}
{% translate "Update" %}
Expand All @@ -59,7 +61,8 @@ <h1 class="overflow-hidden heading text-ellipsis">
<div class="flex flex-col flex-auto w-full p-4">
<div class="flex justify-between">
<label for="{{ poi_translation_form.title.id_for_label }}"
data-slugify-url="{% url 'slugify_ajax' region_slug=request.region.slug language_slug=language.slug model_type='poi' %}{% if poi_form.instance.id %}?model_id={{ poi_form.instance.id }}{% endif %}">
data-slugify-url="{% url 'slugify_ajax' region_slug=request.region.slug language_slug=language.slug model_type='poi' %}"
{% if poi_form.instance.id %}data-model-id="{{ poi_form.instance.id }}"{% endif %}>
{{ poi_translation_form.title.label }}
</label>
{% if poi_translation_form.instance.id %}
Expand Down
11 changes: 2 additions & 9 deletions integreat_cms/cms/views/pages/page_tree_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from django.conf import settings
from django.contrib import messages
from django.db.models import Q
from django.shortcuts import redirect, render
from django.utils.decorators import method_decorator
from django.utils.html import format_html, format_html_join
Expand All @@ -14,7 +13,7 @@

from ...decorators import permission_required
from ...forms import PageFilterForm
from ...models import Page, PageTranslation
from ...models import PageTranslation
from ..mixins import MachineTranslationContextMixin
from .page_context_mixin import PageContextMixin

Expand Down Expand Up @@ -89,13 +88,7 @@ def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
)

if not request.user.has_perm("cms.change_page"):
access_granted_pages = Page.objects.filter(
Q(authors=request.user) | Q(editors=request.user)
).filter(region=request.region)
if request.user.organization:
access_granted_pages = access_granted_pages.union(
Page.objects.filter(organization=request.user.organization)
)
access_granted_pages = request.user.access_granted_pages(request.region)
if len(access_granted_pages) > 0:
messages.info(
request,
Expand Down
26 changes: 17 additions & 9 deletions integreat_cms/cms/views/utils/slugify_ajax.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,31 +39,39 @@ def slugify_ajax(
"page": "cms.change_page",
"poi": "cms.change_poi",
}[model_type]
if not request.user.has_perms((required_permission,)):
raise PermissionDenied

json_data = json.loads(request.body)
form_title = slugify(json_data["title"], allow_unicode=True)
region = request.region
language = region.get_language_or_404(language_slug, only_active=True)
model_id = request.GET.get("model_id")

managers = {
"event": EventTranslation,
"page": PageTranslation,
"poi": POITranslation,
}

json_data = json.loads(request.body)
language = request.region.get_language_or_404(language_slug, only_active=True)
model_id = json_data.get("model_id")

manager = managers[model_type].objects
object_instance = manager.filter(
**{model_type: model_id, "language": language}
).first()

if not (
request.user.has_perms((required_permission,))
or (
model_type == "page"
and object_instance.page
in request.user.access_granted_pages(request.region)
)
):
raise PermissionDenied

form_title = slugify(json_data["title"], allow_unicode=True)
kwargs: SlugKwargs = {
"slug": form_title,
"manager": manager,
"object_instance": object_instance,
"foreign_model": model_type,
"region": region,
"region": request.region,
"language": language,
}
unique_slug = generate_unique_slug(**kwargs)
Expand Down
34 changes: 34 additions & 0 deletions integreat_cms/static/src/js/forms/prevent-premature-submission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* We sometimes want to perform a background task when an input element looses focus.
* If the focus loss is due to the user clicking on a submission button however, the
* corresponding form will be submitted before the background task is initiated.
* This class is a convenient wrapper for preventing this.
*
* See here for more: https://github.com/digitalfabrik/integreat-cms/pull/2636
*/
export default class SubmissionPrevention {
watchedElements: HTMLElement[] = [];
mostRecentlyClicked: HTMLElement = null;

preventSubmission = (e: Event) => {
e.preventDefault();
this.mostRecentlyClicked = e.target as HTMLElement;
};

constructor(identifier: string) {
const elements = document.querySelectorAll<HTMLElement>(identifier);
elements.forEach((element) => {
element.addEventListener("click", this.preventSubmission);
});
this.watchedElements = Array.from(elements);
}

release() {
this.watchedElements.forEach((element) => {
element.removeEventListener("click", this.preventSubmission);
});
if (this.mostRecentlyClicked !== null) {
this.mostRecentlyClicked.click();
}
}
}
16 changes: 10 additions & 6 deletions integreat_cms/static/src/js/forms/update-permalink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ or when the user clicks the permalinks edit button */

import { getCsrfToken } from "../utils/csrf-token";
import { copyToClipboard } from "../copy-clipboard";
import SubmissionPrevention from "./prevent-premature-submission";

const slugify = async (url: string, data: any) => {
const response = await fetch(url, {
Expand Down Expand Up @@ -44,13 +45,16 @@ window.addEventListener("load", () => {
(document.querySelector('[for="id_title"]') as HTMLElement)?.dataset?.slugifyUrl
) {
document.getElementById("id_title").addEventListener("focusout", ({ target }) => {
const submissionLock = new SubmissionPrevention(".no-premature-submission");
const currentTitle = (target as HTMLInputElement).value;
const url = (document.querySelector('[for="id_title"]') as HTMLElement).dataset.slugifyUrl;
slugify(url, { title: currentTitle }).then((response) => {
/* on success write response to both slug field and permalink */
slugField.value = response.unique_slug;
updatePermalink(response.unique_slug);
});
const dataset = (document.querySelector('[for="id_title"]') as HTMLElement).dataset;
slugify(dataset.slugifyUrl, { title: currentTitle, model_id: dataset.modelId })
.then((response) => {
/* on success write response to both slug field and permalink */
slugField.value = response.unique_slug;
updatePermalink(response.unique_slug);
})
.finally(() => submissionLock.release());
});
}

Expand Down
3 changes: 3 additions & 0 deletions tests/cms/views/view_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@
json.dumps(
{
"title": "Slugify event",
"model_id": 1,
}
),
),
Expand All @@ -615,6 +616,7 @@
json.dumps(
{
"title": "Slugify poi",
"model_id": 4,
}
),
),
Expand All @@ -629,6 +631,7 @@
json.dumps(
{
"title": "Slugify page",
"model_id": 1,
}
),
),
Expand Down

0 comments on commit ec599d9

Please sign in to comment.