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 authored and timobrembeck committed Mar 7, 2022
1 parent ee7c733 commit d9abf2a
Show file tree
Hide file tree
Showing 18 changed files with 1,455 additions and 1,034 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ UNRELEASED
----------

* [ [#1271](https://github.com/digitalfabrik/integreat-cms/issues/1271) ] Fix feedback API endpoint
* [ [#1099](https://github.com/digitalfabrik/integreat-cms/issues/1099) ] Add push content API


2022.3.2
Expand Down
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
61 changes: 61 additions & 0 deletions integreat_cms/api/v3/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
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 django.views.decorators.csrf import csrf_exempt

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 @@ -301,3 +304,61 @@ 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


@csrf_exempt
@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 as e:
logger.error("Push Content: failed to parse JSON: %s", e)
return JsonResponse({"status": "error"}, status=405)

if not all(key in data for key in ["content", "token"]):
logger.error("Push Content: missing required key.")
return JsonResponse({"status": "error"}, status=405)

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

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,
instance=translation,
additional_instance_attributes={
"page": page,
"creator": None,
},
)
if page_translation_form.is_valid():
page_translation_form.save()
return JsonResponse({"status": "success"})
logger.error("Push Content: failed to validate page translation.")
return JsonResponse({"status": "error"}, status=405)
3 changes: 2 additions & 1 deletion integreat_cms/cms/fixtures/test_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -2145,7 +2145,8 @@
"mirrored_page_first": true,
"organization": null,
"editors": [],
"publishers": []
"publishers": [],
"api_token": "valid-token"
}
},
{
Expand Down
10 changes: 10 additions & 0 deletions integreat_cms/cms/forms/pages/page_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class PageForm(CustomModelForm, CustomTreeNodeForm):
required=False,
label=_("Source region for live content"),
)
enable_api_token = forms.BooleanField(
required=False,
label=_("Enable write access via API for this page"),
)

class Meta:
"""
Expand All @@ -55,6 +59,7 @@ class Meta:
"mirrored_page_first",
"organization",
"parent",
"api_token",
]
#: The widgets for the fields if they differ from the standard widgets
widgets = {
Expand All @@ -77,6 +82,10 @@ def __init__(self, **kwargs):
# Pass form object to ParentFieldWidget
self.fields["parent"].widget.form = self

# The api token field should not be edited manually
self.fields["api_token"].widget.attrs["readonly"] = True
self.fields["enable_api_token"].initial = bool(self.instance.api_token)

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

Expand Down Expand Up @@ -153,6 +162,7 @@ def _clean_cleaned_data(self):
:rtype: tuple
"""
del self.cleaned_data["mirrored_page_region"]
del self.cleaned_data["enable_api_token"]
return super()._clean_cleaned_data()

def get_editor_queryset(self):
Expand Down
26 changes: 26 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,26 @@
# Generated by Django 3.2.12 on 2022-03-05 16:41

from django.db import migrations, models


class Migration(migrations.Migration):
"""
Migration file that adds the api access token field to pages.
"""

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=36,
verbose_name="API access token",
),
),
]
6 changes: 6 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,12 @@ class Page(AbstractTreeNode, AbstractBasePage):
"This allows all members of the organization to edit and publish this page."
),
)
api_token = models.CharField(
blank=True,
max_length=36,
verbose_name=_("API access 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
28 changes: 28 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,34 @@ <h3 class="heading font-bold text-black">
</div>
</div>
{% if request.user.expert_mode %}
<div>
<label>{{ page_form.api_token.label }}</label>
{% render_field page_form.enable_api_token %}
<label for="{{ page_form.enable_api_token.id_for_label }}"
class="secondary !inline">
{{ page_form.enable_api_token.label }}
</label>
<div id="api-token-container"
class="{% if not page_form.instance.api_token %} hidden{% endif %}">
<div class="flex my-2">
{% render_field page_form.api_token|add_error_class:"border-red-500" class+="flex-grow text-ellipsis pointer-events-none !rounded-none !rounded-l" %}
<button id="copy-api-token"
title="{% trans 'Copy to clipboard' %}"
class="bg-blue-500 hover:bg-blue-600 text-white px-3 rounded-r">
<i data-feather="copy" class="w-5 align-bottom"></i>
</button>
<button id="copy-api-token-success"
title="{% trans 'Access token was successfully copied to clipboard' %}"
class="bg-green-500 text-white px-3 rounded-r hidden"
disabled>
<i data-feather="check-circle" class="w-5 align-middle"></i>
</button>
</div>
<div class="help-text">
{{ page_form.api_token.help_text }}
</div>
</div>
</div>
{% if perms.cms.grant_page_permissions and request.region.page_permissions_enabled %}
<div>
<label>{% trans 'Additional permissions for this page' %}</label>
Expand Down
8 changes: 8 additions & 0 deletions integreat_cms/cms/views/pages/page_form_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ def get(self, request, *args, **kwargs):
"region": region,
},
)

if not request.user.expert_mode:
del page_form.fields["api_token"]

page_translation_form = PageTranslationForm(
instance=page_translation, disabled=disabled
)
Expand Down Expand Up @@ -216,6 +220,10 @@ def post(self, request, *args, **kwargs):
"region": region,
},
)

if not request.user.expert_mode:
del page_form.fields["api_token"]

page_translation_form = PageTranslationForm(
data=request.POST,
instance=page_translation_instance,
Expand Down

0 comments on commit d9abf2a

Please sign in to comment.