Skip to content

Commit

Permalink
Add push page content API end point, fixes #1099
Browse files Browse the repository at this point in the history
  • Loading branch information
svenseeberg committed Mar 5, 2022
1 parent 56c3eec commit aa60545
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 25 deletions.
11 changes: 10 additions & 1 deletion integreat_cms/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@
from .v3.imprint import imprint
from .v3.languages import languages
from .v3.locations import locations
from .v3.pages import pages, children, parents, single_page
from .v3.pages import (
pages,
children,
parents,
single_page,
push_page_translation_content,
)
from .v3.pdf_export import pdf_export
from .v3.push_notifications import sent_push_notifications
from .v3.regions import regions, liveregions, hiddenregions
Expand Down Expand Up @@ -48,6 +54,9 @@
path("disclaimer/", imprint, name="imprint"),
path("offers/", offers, name="offers"),
path("extras/", offers, name="offers"),
path(
"pushpage/", push_page_translation_content, name="push_page_translation_content"
),
re_path(
r"^feedback/?$",
legacy_feedback_endpoint.legacy_feedback_endpoint,
Expand Down
50 changes: 50 additions & 0 deletions integreat_cms/api/v3/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
This module includes functions related to the pages API endpoint.
"""
import logging
import json
from django.conf import settings
from django.core.exceptions import MultipleObjectsReturned
from django.http import JsonResponse, Http404
from django.shortcuts import get_object_or_404

from ...cms.models import Page
from ...cms.forms import PageTranslationForm
from ..decorators import json_response, matomo_tracking

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -302,3 +304,51 @@ def get_public_ancestor_translations(current_page, language_slug):
raise Http404("No Page matches the given url or id.")
result.append(transform_page(public_translation))
return result


@json_response
# pylint: disable=unused-argument
def push_page_translation_content(request, region_slug, language_slug):
"""
Retrieves all ancestors (parent and all nodes up to the root node) of a page
:param request: The request that has been sent to the Django server
:type request: ~django.http.HttpRequest
:param region_slug: Slug defining the region
:type region_slug: str
:param language_slug: Code to identify the desired language
:type language_slug: str
:raises ~django.http.Http404: HTTP status 404 if the request is malformed or no page with the given id or url exists.
:return: JSON with the requested page ancestors
:rtype: ~django.http.JsonResponse
"""
try:
data = json.loads(request.body)
except json.decoder.JSONDecodeError:
data = {}
if not all(key in data for key in ["page_id", "content", "token"]):
logger.error("Push Content: failed to parse JSON.")
return JsonResponse({"status": "error"}, safe=False)

page = request.region.pages.get(api_token=data["token"])
if not page or data["token"] == "":
return JsonResponse({"status": "denied"}, safe=False)

translation = page.get_translation("language_slug")
data = {
"content": data["content"],
"title": translation.title,
"slug": translation.slug,
"status": translation.status,
"minor_edit": False,
}
page_translation_form = PageTranslationForm(data=data)
if page_translation_form.is_valid():
page_translation_form.save()
return JsonResponse({"status": "success"}, safe=False)
logger.error("Push Content: failed to validate page translation.")
return JsonResponse({"status": "error"}, safe=False)
16 changes: 15 additions & 1 deletion integreat_cms/cms/forms/pages/page_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ class PageForm(CustomModelForm, CustomTreeNodeForm):
required=False,
label=_("Source region for live content"),
)
api_token = forms.CharField(
max_length=24,
min_length=24,
required=False,
label=_("Access token to update the page content."),
)

class Meta:
"""
Expand All @@ -56,7 +62,13 @@ class Meta:
#: The model of this :class:`django.forms.ModelForm`
model = Page
#: The fields of the model which should be handled by this form
fields = ["icon", "mirrored_page", "mirrored_page_first", "organization"]
fields = [
"icon",
"mirrored_page",
"mirrored_page_first",
"organization",
"api_token",
]
#: The widgets for the fields if they differ from the standard widgets
widgets = {
"mirrored_page_first": forms.Select(choices=mirrored_page_first.CHOICES),
Expand All @@ -77,6 +89,8 @@ def __init__(self, **kwargs):
# Pass form object to ParentFieldWidget
self.fields["parent"].widget.form = self

self.fields["api_token"].widget.attrs["readonly"] = True

# Limit possible parents to pages of current region
parent_queryset = self.instance.region.pages.all()

Expand Down
18 changes: 18 additions & 0 deletions integreat_cms/cms/migrations/0010_page_api_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2022-03-05 16:41

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('cms', '0009_alter_translation_model_ordering'),
]

operations = [
migrations.AddField(
model_name='page',
name='api_token',
field=models.CharField(blank=True, help_text='API token to allow writing content to translations.', max_length=24, null=True, verbose_name='api_token'),
),
]
7 changes: 7 additions & 0 deletions integreat_cms/cms/models/pages/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ class Page(AbstractTreeNode, AbstractBasePage):
"This allows all members of the organization to edit and publish this page."
),
)
api_token = models.CharField(
null=True,
blank=True,
max_length=24,
verbose_name=_("api_token"),
help_text=_("API token to allow writing content to translations."),
)

#: Custom model manager to inherit methods from tree manager as well as the custom content queryset
objects = PageManager()
Expand Down
6 changes: 6 additions & 0 deletions integreat_cms/cms/templates/pages/page_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ <h3 class="heading font-bold text-black">
</div>
</div>
{% if request.user.expert_mode %}
<div>
<label>{% trans 'API Access Token' %}</label>
<input type="checkbox" id="checkbox_api_token" on_click="setApiToken()">
<label for="id_minor_edit" class="secondary">{% trans "Activate writable API for this page." %}</label>
{% render_field page_form.api_token|add_error_class:"border-red-500" %}
</div>
{% if perms.cms.grant_page_permissions and request.region.page_permissions_enabled %}
<div>
<label>{% trans 'Additional permissions for this page' %}</label>
Expand Down
66 changes: 43 additions & 23 deletions integreat_cms/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-03-05 11:16+0000\n"
"POT-Creation-Date: 2022-03-05 16:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Integreat <info@integreat-app.de>\n"
"Language-Team: Integreat <info@integreat-app.de>\n"
Expand Down Expand Up @@ -1297,7 +1297,7 @@ msgstr "Aktiv"
msgid "Hidden"
msgstr "Versteckt"

#: cms/constants/region_status.py:18 cms/models/pages/page.py:298
#: cms/constants/region_status.py:18 cms/models/pages/page.py:305
#: cms/models/regions/region.py:597 cms/templates/events/event_form.html:70
#: cms/templates/pages/page_form.html:93
#: cms/templates/pages/page_tree_archived_node.html:78
Expand Down Expand Up @@ -1683,6 +1683,10 @@ msgstr "Diese Benutzer können die Seite bearbeiten und veröffentlichen"
msgid "Source region for live content"
msgstr "Quell-Region für Live-Inhalte"

#: cms/forms/pages/page_form.py:53
msgid "Access token to update the page content."
msgstr ""

#: cms/forms/regions/region_form.py:28
msgid "Do no import initial content"
msgstr "Keine Inhalte übernehmen"
Expand Down Expand Up @@ -2569,11 +2573,19 @@ msgstr ""
"Dies erlaubt allen Mitgliedern der Organisation diese Seite zu bearbeiten "
"und veröffentlichen"

#: cms/models/pages/page.py:319 cms/models/pages/page_translation.py:30
#: cms/models/pages/page.py:195
msgid "api_token"
msgstr ""

#: cms/models/pages/page.py:196
msgid "API token to allow writing content to translations."
msgstr ""

#: cms/models/pages/page.py:326 cms/models/pages/page_translation.py:30
msgid "page"
msgstr "Seite"

#: cms/models/pages/page.py:321
#: cms/models/pages/page.py:328
msgid "pages"
msgstr "Seiten"

Expand Down Expand Up @@ -3671,7 +3683,7 @@ msgstr "Auf Google Maps öffnen"

#: cms/templates/events/event_form.html:301
#: cms/templates/imprint/imprint_form.html:112
#: cms/templates/pages/page_form.html:240 cms/templates/pois/poi_form.html:157
#: cms/templates/pages/page_form.html:246 cms/templates/pois/poi_form.html:157
#: cms/templates/regions/region_list.html:25
#: cms/templates/settings/user_settings.html:93
msgid "Actions"
Expand Down Expand Up @@ -4023,7 +4035,7 @@ msgid "Side-by-Side view"
msgstr "Side-by-Side-Ansicht"

#: cms/templates/imprint/imprint_form.html:137
#: cms/templates/pages/page_form.html:339
#: cms/templates/pages/page_form.html:345
msgid "Direction of translation"
msgstr "Übersetzungsrichtung"

Expand Down Expand Up @@ -4525,30 +4537,38 @@ msgstr "Reihenfolge"
msgid "Embed live content"
msgstr "Live-Inhalte einbinden"

#: cms/templates/pages/page_form.html:209
#: cms/templates/pages/page_form.html:208
msgid "API Access Token"
msgstr ""

#: cms/templates/pages/page_form.html:210
msgid "Activate writable API for this page."
msgstr ""

#: cms/templates/pages/page_form.html:215
msgid "Additional permissions for this page"
msgstr "Zusätzliche Berechtigungen für diese Seite"

#: cms/templates/pages/page_form.html:211
#: cms/templates/pages/page_form.html:217
msgid ""
"This affects only users, who don't have the permission to change arbitrary "
"pages anyway."
msgstr ""
"Dies betrifft nur Benutzer, die nicht ohnehin die Berechtigung haben, "
"beliebige Seiten zu ändern."

#: cms/templates/pages/page_form.html:246
#: cms/templates/pages/page_form.html:248
#: cms/templates/pages/page_form.html:258
#: cms/templates/pages/page_form.html:252
#: cms/templates/pages/page_form.html:254
#: cms/templates/pages/page_form.html:264
#: cms/templates/pages/page_tree_archived_node.html:98
msgid "Restore page"
msgstr "Seite wiederherstellen"

#: cms/templates/pages/page_form.html:254
#: cms/templates/pages/page_form.html:260
msgid "Restore this page"
msgstr "Diese Seite wiederherstellen"

#: cms/templates/pages/page_form.html:263
#: cms/templates/pages/page_form.html:269
msgid "To restore this page, you have to restore its parent page:"
msgid_plural ""
"To restore this page, you have to restore all its archived parent pages:"
Expand All @@ -4559,31 +4579,31 @@ msgstr[1] ""
"Um diese Seite wiederherzustellen, müssen Sie alle ihre archivierten "
"übergeordneten Seiten wiederherstellen:"

#: cms/templates/pages/page_form.html:278
#: cms/templates/pages/page_form.html:280
#: cms/templates/pages/page_form.html:284
#: cms/templates/pages/page_form.html:286
#: cms/templates/pages/page_tree_node.html:129
msgid "Archive page"
msgstr "Seite archivieren"

#: cms/templates/pages/page_form.html:287
#: cms/templates/pages/page_form.html:293
msgid "Archive this page"
msgstr "Diese Seite archivieren"

#: cms/templates/pages/page_form.html:292
#: cms/templates/pages/page_form.html:316
#: cms/templates/pages/page_form.html:298
#: cms/templates/pages/page_form.html:322
#: cms/templates/pages/page_tree_archived_node.html:116
#: cms/templates/pages/page_tree_node.html:142
msgid "Delete page"
msgstr "Seite löschen"

#: cms/templates/pages/page_form.html:299
#: cms/templates/pages/page_form.html:305
#: cms/templates/pages/page_tree_archived_node.html:112
#: cms/templates/pages/page_tree_node.html:138
#: cms/views/pages/page_actions.py:215
msgid "You cannot delete a page which has subpages."
msgstr "Sie können keine Seite löschen, die Unterseiten besitzt."

#: cms/templates/pages/page_form.html:301
#: cms/templates/pages/page_form.html:307
msgid "To delete this page, you have to delete or move its subpage first:"
msgid_plural ""
"To delete this page, you have to delete or move its subpages first:"
Expand All @@ -4594,15 +4614,15 @@ msgstr[1] ""
"Um diese Seite zu löschen, müssen Sie zuerst ihre Unterseiten löschen oder "
"verschieben:"

#: cms/templates/pages/page_form.html:323
#: cms/templates/pages/page_form.html:329
msgid "Delete this page"
msgstr "Diese Seite löschen"

#: cms/templates/pages/page_form.html:334
#: cms/templates/pages/page_form.html:340
msgid "Translator view"
msgstr "Übersetzeransicht"

#: cms/templates/pages/page_form.html:354
#: cms/templates/pages/page_form.html:360
msgid "Show translator view"
msgstr "Übersetzeransicht anzeigen"

Expand Down
1 change: 1 addition & 0 deletions integreat_cms/static/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import "./js/events/conditional-fields.ts";
import "./js/events/auto-complete.ts";

import "./js/pages/collapse-subpages.ts";
import "./js/pages/page-api-token.ts";
import "./js/pages/page-mirroring.ts";
import "./js/pages/page-order.ts";
import "./js/pages/page-permissions.ts";
Expand Down
36 changes: 36 additions & 0 deletions integreat_cms/static/src/js/pages/page-api-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Generate and (un)set the API token.
*/

// Set event handler
window.addEventListener("load", () => {
document.getElementById("checkbox_api_token")?.addEventListener("change", setApiToken);
let checkbox = document.getElementById("checkbox_api_token") as HTMLInputElement;
let text = document.getElementById("id_api_token") as HTMLInputElement;
if (text.value == ""){
checkbox.checked = false;
} else {
checkbox.checked = true;
}
});

/**
* Generate 24 characters API token and insert it into the text field
*
* @param {target} eventTarget - The changed file input
*/
function setApiToken({ target }: Event) {
let checkBox = document.getElementById("checkbox_api_token") as HTMLInputElement;
let text = document.getElementById("id_api_token") as HTMLInputElement;
let random = "";
if (checkBox.checked == true){
let random = "";
const characters ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for ( let i = 0; i < 24; i++ ) {
random += characters.charAt(Math.floor(Math.random() * characters.length));
}
text.value = random;
} else {
text.value = "";
}
}

0 comments on commit aa60545

Please sign in to comment.