🔧(backend) add view to manage footer json
We added the `FRONTEND_URL_JSON_FOOTER` environment variable. It will give the possibility to generate your own footer content in the frontend. If the variable is not set, the footer will not be displayed.
This commit is contained in:
@@ -8,6 +8,10 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## Added
|
||||
|
||||
- 🔧(backend) add view to manage footer json #841
|
||||
|
||||
## Changed
|
||||
|
||||
- 🚨(frontend) block button when creating doc #749
|
||||
|
||||
@@ -64,3 +64,4 @@ COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/
|
||||
|
||||
# Frontend
|
||||
FRONTEND_THEME=default
|
||||
FRONTEND_URL_JSON_FOOTER=http://frontend:3000/contents/footer-demo.json
|
||||
|
||||
@@ -16,8 +16,10 @@ from django.db import transaction
|
||||
from django.db.models.expressions import RawSQL
|
||||
from django.db.models.functions import Left, Length
|
||||
from django.http import Http404, StreamingHttpResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.cache import cache_page
|
||||
|
||||
import requests
|
||||
import rest_framework as drf
|
||||
@@ -30,6 +32,7 @@ from rest_framework.throttling import UserRateThrottle
|
||||
from core import authentication, enums, models
|
||||
from core.services.ai_services import AIService
|
||||
from core.services.collaboration_services import CollaborationService
|
||||
from core.services.config_services import get_footer_json
|
||||
from core.utils import extract_attachments, filter_descendants
|
||||
|
||||
from . import permissions, serializers, utils
|
||||
@@ -1688,8 +1691,8 @@ class ConfigView(drf.views.APIView):
|
||||
"COLLABORATION_WS_URL",
|
||||
"CRISP_WEBSITE_ID",
|
||||
"ENVIRONMENT",
|
||||
"FRONTEND_THEME",
|
||||
"FRONTEND_CSS_URL",
|
||||
"FRONTEND_THEME",
|
||||
"MEDIA_BASE_URL",
|
||||
"POSTHOG_KEY",
|
||||
"LANGUAGES",
|
||||
@@ -1702,3 +1705,22 @@ class ConfigView(drf.views.APIView):
|
||||
dict_settings[setting] = getattr(settings, setting)
|
||||
|
||||
return drf.response.Response(dict_settings)
|
||||
|
||||
|
||||
class FooterView(drf.views.APIView):
|
||||
"""API ViewSet for sharing the footer JSON."""
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
@method_decorator(cache_page(settings.FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT))
|
||||
def get(self, request):
|
||||
"""
|
||||
GET /api/v1.0/footer/
|
||||
Return the footer JSON.
|
||||
"""
|
||||
json_footer = (
|
||||
get_footer_json(settings.FRONTEND_URL_JSON_FOOTER)
|
||||
if settings.FRONTEND_URL_JSON_FOOTER
|
||||
else {}
|
||||
)
|
||||
return drf.response.Response(json_footer)
|
||||
|
||||
25
src/backend/core/services/config_services.py
Normal file
25
src/backend/core/services/config_services.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Config services."""
|
||||
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_footer_json(footer_json_url: str) -> dict:
|
||||
"""
|
||||
Fetches the footer JSON from the given URL."
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
footer_json_url, timeout=5, headers={"User-Agent": "Docs-Application"}
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
footer_json = response.json()
|
||||
|
||||
return footer_json
|
||||
except (requests.RequestException, ValueError) as e:
|
||||
logger.error("Failed to fetch footer JSON: %s", e)
|
||||
return {}
|
||||
81
src/backend/core/tests/test_api_footer.py
Normal file
81
src/backend/core/tests/test_api_footer.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""Test the footer API."""
|
||||
|
||||
import responses
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
|
||||
def test_api_footer_without_settings_configured(settings):
|
||||
"""Test the footer API without settings configured."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = None
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {}
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_footer_with_invalid_request(settings):
|
||||
"""Test the footer API with an invalid request."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = "https://invalid-request.com"
|
||||
|
||||
footer_response = responses.get(settings.FRONTEND_URL_JSON_FOOTER, status=404)
|
||||
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {}
|
||||
assert footer_response.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_footer_with_invalid_json(settings):
|
||||
"""Test the footer API with an invalid JSON response."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = "https://valid-request.com"
|
||||
|
||||
footer_response = responses.get(
|
||||
settings.FRONTEND_URL_JSON_FOOTER, status=200, body="invalid json"
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {}
|
||||
assert footer_response.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_footer_with_valid_json(settings):
|
||||
"""Test the footer API with an invalid JSON response."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = "https://valid-request.com"
|
||||
|
||||
footer_response = responses.get(
|
||||
settings.FRONTEND_URL_JSON_FOOTER, status=200, json={"foo": "bar"}
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"foo": "bar"}
|
||||
assert footer_response.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_footer_with_valid_json_and_cache(settings):
|
||||
"""Test the footer API with an invalid JSON response."""
|
||||
settings.FRONTEND_URL_JSON_FOOTER = "https://valid-request.com"
|
||||
|
||||
footer_response = responses.get(
|
||||
settings.FRONTEND_URL_JSON_FOOTER, status=200, json={"foo": "bar"}
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"foo": "bar"}
|
||||
assert footer_response.call_count == 1
|
||||
|
||||
response = client.get("/api/v1.0/footer/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"foo": "bar"}
|
||||
# The cache should have been used
|
||||
assert footer_response.call_count == 1
|
||||
@@ -56,4 +56,5 @@ urlpatterns = [
|
||||
),
|
||||
),
|
||||
path(f"api/{settings.API_VERSION}/config/", viewsets.ConfigView.as_view()),
|
||||
path(f"api/{settings.API_VERSION}/footer/", viewsets.FooterView.as_view()),
|
||||
]
|
||||
|
||||
@@ -410,7 +410,14 @@ class Base(Configuration):
|
||||
FRONTEND_THEME = values.Value(
|
||||
None, environ_name="FRONTEND_THEME", environ_prefix=None
|
||||
)
|
||||
|
||||
FRONTEND_URL_JSON_FOOTER = values.Value(
|
||||
None, environ_name="FRONTEND_URL_JSON_FOOTER", environ_prefix=None
|
||||
)
|
||||
FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT = values.Value(
|
||||
60 * 60 * 24,
|
||||
environ_name="FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT",
|
||||
environ_prefix=None,
|
||||
)
|
||||
FRONTEND_CSS_URL = values.Value(
|
||||
None, environ_name="FRONTEND_CSS_URL", environ_prefix=None
|
||||
)
|
||||
|
||||
121
src/frontend/apps/impress/public/contents/footer-demo.json
Normal file
121
src/frontend/apps/impress/public/contents/footer-demo.json
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"default": {
|
||||
"externalLinks": [
|
||||
{
|
||||
"label": "Github",
|
||||
"href": "https://github.com/suitenumerique/docs/"
|
||||
},
|
||||
{
|
||||
"label": "DINUM",
|
||||
"href": "https://www.numerique.gouv.fr/dinum/"
|
||||
},
|
||||
{
|
||||
"label": "ZenDiS",
|
||||
"href": "https://zendis.de/"
|
||||
},
|
||||
{
|
||||
"label": "BlockNote.js",
|
||||
"href": "https://www.blocknotejs.org/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Legal Notice",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Personal data and cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Accessibility",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Mentions légales",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Données personnelles et cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Accessibilité",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sauf mention contraire, tout le contenu de ce site est sous",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Impressum",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Personenbezogene Daten und Cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Barrierefreiheit",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Wettelijke bepalingen",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Persoonlijke gegevens en cookies",
|
||||
"href": "#"
|
||||
},
|
||||
{
|
||||
"label": "Toegankelijkheid",
|
||||
"href": "#"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder",
|
||||
"link": {
|
||||
"label": "licence MIT",
|
||||
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
135
src/frontend/apps/impress/public/contents/footer-dsfr.json
Normal file
135
src/frontend/apps/impress/public/contents/footer-dsfr.json
Normal file
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"default": {
|
||||
"externalLinks": [
|
||||
{
|
||||
"label": "legifrance.gouv.fr",
|
||||
"href": "https://legifrance.gouv.fr/"
|
||||
},
|
||||
{
|
||||
"label": "info.gouv.fr",
|
||||
"href": "https://info.gouv.fr/"
|
||||
},
|
||||
{
|
||||
"label": "service-public.fr",
|
||||
"href": "https://service-public.fr/"
|
||||
},
|
||||
{
|
||||
"label": "data.gouv.fr",
|
||||
"href": "https://data.gouv.fr/"
|
||||
}
|
||||
],
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Legal Notice",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Personal data and cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Accessibility",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Legal Notice",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Personal data and cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Accessibility",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Unless otherwise stated, all content on this site is under",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Mentions légales",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Données personnelles et cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Accessibilité",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sauf mention contraire, tout le contenu de ce site est sous",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Impressum",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Personenbezogene Daten und Cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Barrierefreiheit",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"legalLinks": [
|
||||
{
|
||||
"label": "Wettelijke bepalingen",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/4db744e3-5b47-4ed9-b9e7-d4312318bbce/"
|
||||
},
|
||||
{
|
||||
"label": "Persoonlijke gegevens en cookies",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/eb863389-a5e5-4d18-879d-149a1122380e/"
|
||||
},
|
||||
{
|
||||
"label": "Toegankelijkheid",
|
||||
"href": "https://docs.numerique.gouv.fr/docs/9694e570-1427-4ef7-b0a0-c3e894360e1b/"
|
||||
}
|
||||
],
|
||||
"bottomInformation": {
|
||||
"label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder",
|
||||
"link": {
|
||||
"label": "licence etalab-2.0",
|
||||
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ backend:
|
||||
DB_USER: dinum
|
||||
DB_PASSWORD: pass
|
||||
DB_PORT: 5432
|
||||
FRONTEND_URL_JSON_FOOTER: https://impress.127.0.0.1.nip.io/contents/footer-demo.json
|
||||
POSTGRES_DB: impress
|
||||
POSTGRES_USER: dinum
|
||||
POSTGRES_PASSWORD: pass
|
||||
|
||||
Reference in New Issue
Block a user