Skip to content

Commit ed137ef

Browse files
committed
♻️(backend) Making custom-translations its own endpoint
1. We adapt the implementation to align more with the existing footer config. (Custom-translations gets its own cached endpoint) This allows us to better refactor in the future. 2.Default .json configurations are moved to and served from the backend. This allows more flexible development, by not needing to use the frontend container to have the demo configurations available. 3. The debug utility is added to the backend, to allow shared debugging. It can be used to e.g. turn off caching during development Signed-off-by: Robin Weber <[email protected]>
1 parent 9a9b717 commit ed137ef

File tree

19 files changed

+220
-134
lines changed

19 files changed

+220
-134
lines changed

docs/customization.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom
4242

4343
### How to Use
4444

45-
To provide custom translations, set the `FRONTEND_CUSTOM_TRANSLATIONS_URL` environment variable to the URL of your custom translations JSON file:
45+
To provide custom translations, set the `FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS` environment variable to the URL of your custom translations JSON file:
4646

4747
```javascript
48-
FRONTEND_CUSTOM_TRANSLATIONS_URL=http://example.com/custom-translations.json
48+
FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS=http://example.com/custom-translations.json
4949
```
5050

5151
Once you've set this variable, our application will load your custom translations and apply them to the user interface.
@@ -64,16 +64,16 @@ Let's say you want to customize some key phrases in the application. Create a JS
6464
"en": {
6565
"translation": {
6666
"Docs": "MyApp",
67-
"Create New Document": "+"
67+
"New doc": "+"
6868
}
6969
},
7070
"de": {
7171
"translation": {
7272
"Docs": "MeineApp",
73-
"Create New Document": "+"
73+
"New doc": "+"
7474
}
7575
}
7676
}
7777
```
7878

79-
Then set the `FRONTEND_CUSTOM_TRANSLATIONS_URL` environment variable to the URL of this JSON file. The application will load these translations and override the default ones where specified.
79+
Then set the `FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS` environment variable to the URL of this JSON file. The application will load these translations and override the default ones where specified.

docs/env.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Here we describe all environment variables that can be set for the docs applicat
77
These are the environmental variables you can set for the impress-backend container.
88

99
| Option | Description | default |
10-
| ----------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
10+
|-------------------------------------------------|-----------------------------------------------------------------------------------------------|---------------------------------------------------------|
1111
| DJANGO_ALLOWED_HOSTS | allowed hosts | [] |
1212
| DJANGO_SECRET_KEY | secret key | |
1313
| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] |
@@ -51,10 +51,10 @@ These are the environmental variables you can set for the impress-backend contai
5151
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | frontend feature flag to display the homepage | false |
5252
| FRONTEND_FOOTER_FEATURE_ENABLED | frontend feature flag to display the footer | false |
5353
| FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT | Cache duration of the json footer | 86400 |
54-
| FRONTEND_URL_JSON_FOOTER | Url with a json to configure the footer | |
54+
| FRONTEND_CUSTOM_TRANSLATIONS_VIEW_CACHE_TIMEOUT | Cache duration of the json custom translations | 86400 |
55+
| FRONTEND_URL_JSON_FOOTER | Url to a JSON to configure the footer | |
56+
| FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS | Url to a JSON to overwrite the translations | |
5557
| FRONTEND_THEME | frontend theme to use | |
56-
| FRONTEND_CSS_URL | URL to a custom CSS file for theming the application | |
57-
| FRONTEND_CUSTOM_TRANSLATIONS_URL | URL to a JSON file containing custom translations for the application | |
5858
| POSTHOG_KEY | posthog key for analytics | |
5959
| CRISP_WEBSITE_ID | crisp website id for support | |
6060
| DJANGO_CELERY_BROKER_URL | celery broker url | redis://redis:6379/0 |

env.d/development/common.dist

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/
6565
# Frontend
6666
FRONTEND_THEME=default
6767
FRONTEND_FOOTER_FEATURE_ENABLED=True
68-
FRONTEND_URL_JSON_FOOTER=http://frontend:3000/contents/footer-demo.json
68+
FRONTEND_URL_JSON_FOOTER=http://0.0.0.0:8000/static/contents/footer-demo.json
69+
FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS=http://0.0.0.0:8000/static/contents/custom-translations-demo.json

src/backend/core/api/viewsets.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from django.utils.decorators import method_decorator
2020
from django.utils.text import capfirst
2121
from django.utils.translation import gettext_lazy as _
22-
from django.views.decorators.cache import cache_page
22+
from django.views.decorators.cache import cache_page, never_cache
2323

2424
import requests
2525
import rest_framework as drf
@@ -32,7 +32,7 @@
3232
from core import authentication, enums, models
3333
from core.services.ai_services import AIService
3434
from core.services.collaboration_services import CollaborationService
35-
from core.services.config_services import get_footer_json
35+
from core.services.config_services import debug_enabled, get_json_from_url
3636
from core.utils import extract_attachments, filter_descendants
3737

3838
from . import permissions, serializers, utils
@@ -1715,7 +1715,6 @@ def get(self, request):
17151715
"ENVIRONMENT",
17161716
"FRONTEND_CSS_URL",
17171717
"FRONTEND_HOMEPAGE_FEATURE_ENABLED",
1718-
"FRONTEND_CUSTOM_TRANSLATIONS_URL",
17191718
"FRONTEND_FOOTER_FEATURE_ENABLED",
17201719
"FRONTEND_THEME",
17211720
"MEDIA_BASE_URL",
@@ -1737,15 +1736,42 @@ class FooterView(drf.views.APIView):
17371736

17381737
permission_classes = [AllowAny]
17391738

1740-
@method_decorator(cache_page(settings.FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT))
1739+
@method_decorator(
1740+
never_cache
1741+
if debug_enabled("no-cache")
1742+
else cache_page(settings.FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT)
1743+
)
17411744
def get(self, request):
17421745
"""
17431746
GET /api/v1.0/footer/
17441747
Return the footer JSON.
17451748
"""
17461749
json_footer = (
1747-
get_footer_json(settings.FRONTEND_URL_JSON_FOOTER)
1750+
get_json_from_url(settings.FRONTEND_URL_JSON_FOOTER)
17481751
if settings.FRONTEND_URL_JSON_FOOTER
17491752
else {}
17501753
)
17511754
return drf.response.Response(json_footer)
1755+
1756+
1757+
class CustomTranslationsView(drf.views.APIView):
1758+
"""API ViewSet for sharing the custom-translations JSON."""
1759+
1760+
permission_classes = [AllowAny]
1761+
1762+
@method_decorator(
1763+
never_cache
1764+
if debug_enabled("no-cache")
1765+
else cache_page(settings.FRONTEND_CUSTOM_TRANSLATIONS_VIEW_CACHE_TIMEOUT)
1766+
)
1767+
def get(self, request):
1768+
"""
1769+
GET /api/v1.0/custom-translations/
1770+
Return the custom-translations JSON.
1771+
"""
1772+
json = (
1773+
get_json_from_url(settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS)
1774+
if settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS
1775+
else {}
1776+
)
1777+
return drf.response.Response(json)

src/backend/core/services/config_services.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,38 @@
22

33
import logging
44

5+
from django.conf import settings
6+
57
import requests
68

79
logger = logging.getLogger(__name__)
810

911

10-
def get_footer_json(footer_json_url: str) -> dict:
12+
def debug_enabled(namespace: str | None) -> bool:
13+
"""
14+
Quick way to check if debug is enabled for a specific namespace
15+
If no namespace is passed it just returns the value of settings.DEBUG
16+
"""
17+
if not namespace:
18+
return getattr(settings, "DEBUG", False)
19+
20+
if False is getattr(settings, "DEBUG", False):
21+
return False
22+
23+
return namespace in getattr(settings, "DEBUG_NAMESPACES", [])
24+
25+
26+
def get_json_from_url(json_url: str) -> dict:
1127
"""
12-
Fetches the footer JSON from the given URL."
28+
Fetches JSON from the given URL."
1329
"""
1430
try:
1531
response = requests.get(
16-
footer_json_url, timeout=5, headers={"User-Agent": "Docs-Application"}
32+
json_url, timeout=5, headers={"User-Agent": "Docs-Application"}
1733
)
1834
response.raise_for_status()
1935

20-
footer_json = response.json()
21-
22-
return footer_json
36+
return response.json()
2337
except (requests.RequestException, ValueError) as e:
24-
logger.error("Failed to fetch footer JSON: %s", e)
38+
logger.error("Failed to fetch JSON: %s", e)
2539
return {}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"en": {
3+
"translation": {
4+
"Docs": "MyDocs",
5+
"New doc": "+"
6+
}
7+
}
8+
}

src/backend/core/tests/test_api_config.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
COLLABORATION_WS_URL="http://testcollab/",
2020
CRISP_WEBSITE_ID="123",
2121
FRONTEND_CSS_URL="http://testcss/",
22-
FRONTEND_CUSTOM_TRANSLATIONS_URL="http://test-custom-translations/",
2322
FRONTEND_FOOTER_FEATURE_ENABLED=True,
2423
FRONTEND_THEME="test-theme",
2524
MEDIA_BASE_URL="http://testserver/",
@@ -43,7 +42,6 @@ def test_api_config(is_authenticated):
4342
"ENVIRONMENT": "test",
4443
"FRONTEND_CSS_URL": "http://testcss/",
4544
"FRONTEND_HOMEPAGE_FEATURE_ENABLED": True,
46-
"FRONTEND_CUSTOM_TRANSLATIONS_URL": "http://test-custom-translations/",
4745
"FRONTEND_FOOTER_FEATURE_ENABLED": True,
4846
"FRONTEND_THEME": "test-theme",
4947
"LANGUAGES": [
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Test the custom-translations API."""
2+
3+
import responses
4+
from rest_framework.test import APIClient
5+
6+
7+
def test_api_custom_translations_without_settings_configured(settings):
8+
"""Test the custom-translations API without settings configured."""
9+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS = None
10+
client = APIClient()
11+
response = client.get("/api/v1.0/custom-translations/")
12+
assert response.status_code == 200
13+
assert response.json() == {}
14+
15+
16+
@responses.activate
17+
def test_api_custom_translations_with_invalid_request(settings):
18+
"""Test the custom-translations API with an invalid request."""
19+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS = "https://invalid-request.com"
20+
21+
custom_translations_response = responses.get(
22+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS, status=404
23+
)
24+
25+
client = APIClient()
26+
response = client.get("/api/v1.0/custom-translations/")
27+
assert response.status_code == 200
28+
assert response.json() == {}
29+
assert custom_translations_response.call_count == 1
30+
31+
32+
@responses.activate
33+
def test_api_custom_translations_with_invalid_json(settings):
34+
"""Test the custom-translations API with an invalid JSON response."""
35+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS = "https://valid-request.com"
36+
37+
custom_translations_response = responses.get(
38+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS, status=200, body="invalid json"
39+
)
40+
41+
client = APIClient()
42+
response = client.get("/api/v1.0/custom-translations/")
43+
assert response.status_code == 200
44+
assert response.json() == {}
45+
assert custom_translations_response.call_count == 1
46+
47+
48+
@responses.activate
49+
def test_api_custom_translations_with_valid_json(settings):
50+
"""Test the custom-translations API with an invalid JSON response."""
51+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS = "https://valid-request.com"
52+
53+
custom_translations_response = responses.get(
54+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS, status=200, json={"foo": "bar"}
55+
)
56+
57+
client = APIClient()
58+
response = client.get("/api/v1.0/custom-translations/")
59+
assert response.status_code == 200
60+
assert response.json() == {"foo": "bar"}
61+
assert custom_translations_response.call_count == 1
62+
63+
64+
@responses.activate
65+
def test_api_custom_translations_with_valid_json_and_cache(settings):
66+
"""Test the custom-translations API with an invalid JSON response."""
67+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS = "https://valid-request.com"
68+
69+
custom_translations_response = responses.get(
70+
settings.FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS, status=200, json={"foo": "bar"}
71+
)
72+
73+
client = APIClient()
74+
response = client.get("/api/v1.0/custom-translations/")
75+
assert response.status_code == 200
76+
assert response.json() == {"foo": "bar"}
77+
assert custom_translations_response.call_count == 1
78+
79+
response = client.get("/api/v1.0/custom-translations/")
80+
assert response.status_code == 200
81+
assert response.json() == {"foo": "bar"}
82+
# The cache should have been used
83+
assert custom_translations_response.call_count == 1

src/backend/core/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,8 @@
5757
),
5858
path(f"api/{settings.API_VERSION}/config/", viewsets.ConfigView.as_view()),
5959
path(f"api/{settings.API_VERSION}/footer/", viewsets.FooterView.as_view()),
60+
path(
61+
f"api/{settings.API_VERSION}/custom-translations/",
62+
viewsets.CustomTranslationsView.as_view(),
63+
),
6064
]

src/backend/impress/settings.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,14 @@ class Base(Configuration):
5858
* DB_USER
5959
"""
6060

61-
DEBUG = False
61+
DEBUG_RAW = values.Value(
62+
default=False,
63+
environ_name="DEBUG",
64+
environ_prefix=None,
65+
)
66+
DEBUG_NAMESPACES = DEBUG_RAW.split(",") if isinstance(DEBUG_RAW, str) else []
67+
DEBUG = bool(DEBUG_RAW)
68+
6269
USE_SWAGGER = False
6370

6471
API_VERSION = "v1.0"
@@ -438,8 +445,13 @@ class Base(Configuration):
438445
FRONTEND_CSS_URL = values.Value(
439446
None, environ_name="FRONTEND_CSS_URL", environ_prefix=None
440447
)
441-
FRONTEND_CUSTOM_TRANSLATIONS_URL = values.Value(
442-
None, environ_name="FRONTEND_CUSTOM_TRANSLATIONS_URL", environ_prefix=None
448+
FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS = values.Value(
449+
None, environ_name="FRONTEND_URL_JSON_CUSTOM_TRANSLATIONS", environ_prefix=None
450+
)
451+
FRONTEND_CUSTOM_TRANSLATIONS_VIEW_CACHE_TIMEOUT = values.Value(
452+
60 * 60 * 24,
453+
environ_name="FRONTEND_CUSTOM_TRANSLATIONS_VIEW_CACHE_TIMEOUT",
454+
environ_prefix=None,
443455
)
444456

445457
# Posthog
@@ -783,6 +795,16 @@ class Development(Base):
783795
CSRF_TRUSTED_ORIGINS = ["http://localhost:8072", "http://localhost:3000"]
784796
DEBUG = True
785797

798+
# Enable debug namespaces as needed
799+
#
800+
# They can also be enabled via DEBUG environment variable
801+
# DEBUG="features:language,no-cache,..."
802+
#
803+
# DEBUG_NAMESPACES = [
804+
# 'no-cache',
805+
# 'features:language'
806+
# ]
807+
786808
SESSION_COOKIE_NAME = "impress_sessionid"
787809

788810
USE_SWAGGER = True

src/frontend/apps/e2e/__tests__/app-impress/common.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ export const CONFIG = {
77
ENVIRONMENT: 'development',
88
FRONTEND_CSS_URL: null,
99
FRONTEND_HOMEPAGE_FEATURE_ENABLED: true,
10-
FRONTEND_CUSTOM_TRANSLATIONS_URL: null,
1110
FRONTEND_FOOTER_FEATURE_ENABLED: true,
1211
FRONTEND_THEME: 'default',
1312
MEDIA_BASE_URL: 'http://localhost:8083',

0 commit comments

Comments
 (0)