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

Update page link before submitting page form #2636

Merged
merged 1 commit into from
Jun 25, 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
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' %}"
charludo marked this conversation as resolved.
Show resolved Hide resolved
{% 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' %}"
charludo marked this conversation as resolved.
Show resolved Hide resolved
{% 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' %}"
charludo marked this conversation as resolved.
Show resolved Hide resolved
{% 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 {
charludo marked this conversation as resolved.
Show resolved Hide resolved
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
Loading