✨(back) allow theme customnization using a configuration file
We want to customize the theme by using a configuration file. This configuration file path can be defined using the settings THEME_CUSTOMIZATION_FILE_PATH. If this file does not exists or is an invalid json, an empty json object will be added in the config endpoint.
This commit is contained in:
@@ -10,6 +10,7 @@ and this project adheres to
|
|||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
|
- ✨(back) allow theme customnization using a configuration file #948
|
||||||
- ✨ Add a custom callout block to the editor #892
|
- ✨ Add a custom callout block to the editor #892
|
||||||
- 🚩(frontend) version MIT only #911
|
- 🚩(frontend) version MIT only #911
|
||||||
- ✨(backend) integrate maleware_detection from django-lasuite #936
|
- ✨(backend) integrate maleware_detection from django-lasuite #936
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ RUN wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
|
|||||||
# Copy entrypoint
|
# Copy entrypoint
|
||||||
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
|
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
|
||||||
|
|
||||||
|
# Copy configuration
|
||||||
|
VOLUME [ "/configuration" ]
|
||||||
|
COPY ./configuration /configuration
|
||||||
|
|
||||||
# Give the "root" group the same permissions as the "root" user on /etc/passwd
|
# Give the "root" group the same permissions as the "root" user on /etc/passwd
|
||||||
# to allow a user belonging to the root group to add new users; typically the
|
# to allow a user belonging to the root group to add new users; typically the
|
||||||
# docker user (see entrypoint).
|
# docker user (see entrypoint).
|
||||||
|
|||||||
123
configuration/theme/default.json
Normal file
123
configuration/theme/default.json
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"footer": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,5 +98,8 @@ These are the environmental variables you can set for the impress-backend contai
|
|||||||
| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] |
|
| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] |
|
||||||
| REDIS_URL | cache url | redis://redis:6379/1 |
|
| REDIS_URL | cache url | redis://redis:6379/1 |
|
||||||
| CACHES_DEFAULT_TIMEOUT | cache default timeout | 30 |
|
| CACHES_DEFAULT_TIMEOUT | cache default timeout | 30 |
|
||||||
|
| CACHES_KEY_PREFIX | The prefix used to every cache keys. | docs |
|
||||||
| MALWARE_DETECTION_BACKEND | The malware detection backend use from the django-lasuite package | lasuite.malware_detection.backends.dummy.DummyBackend |
|
| MALWARE_DETECTION_BACKEND | The malware detection backend use from the django-lasuite package | lasuite.malware_detection.backends.dummy.DummyBackend |
|
||||||
| MALWARE_DETECTION_PARAMETERS | A dict containing all the parameters to initiate the malware detection backend | {"callback_path": "core.malware_detection.malware_detection_callback",} |
|
| MALWARE_DETECTION_PARAMETERS | A dict containing all the parameters to initiate the malware detection backend | {"callback_path": "core.malware_detection.malware_detection_callback",} |
|
||||||
|
| THEME_CUSTOMIZATION_FILE_PATH | full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json |
|
||||||
|
| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 |
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""API endpoints"""
|
"""API endpoints"""
|
||||||
# pylint: disable=too-many-lines
|
# pylint: disable=too-many-lines
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from urllib.parse import unquote, urlparse
|
from urllib.parse import unquote, urlparse
|
||||||
@@ -9,6 +10,7 @@ from django.conf import settings
|
|||||||
from django.contrib.postgres.aggregates import ArrayAgg
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.contrib.postgres.search import TrigramSimilarity
|
from django.contrib.postgres.search import TrigramSimilarity
|
||||||
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.db import connection, transaction
|
from django.db import connection, transaction
|
||||||
@@ -16,10 +18,8 @@ from django.db import models as db
|
|||||||
from django.db.models.expressions import RawSQL
|
from django.db.models.expressions import RawSQL
|
||||||
from django.db.models.functions import Left, Length
|
from django.db.models.functions import Left, Length
|
||||||
from django.http import Http404, StreamingHttpResponse
|
from django.http import Http404, StreamingHttpResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.text import capfirst, slugify
|
||||||
from django.utils.text import capfirst
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.cache import cache_page
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import rest_framework as drf
|
import rest_framework as drf
|
||||||
@@ -1747,23 +1747,41 @@ class ConfigView(drf.views.APIView):
|
|||||||
if hasattr(settings, setting):
|
if hasattr(settings, setting):
|
||||||
dict_settings[setting] = getattr(settings, setting)
|
dict_settings[setting] = getattr(settings, setting)
|
||||||
|
|
||||||
|
dict_settings["theme_customization"] = self._load_theme_customization()
|
||||||
|
|
||||||
return drf.response.Response(dict_settings)
|
return drf.response.Response(dict_settings)
|
||||||
|
|
||||||
|
def _load_theme_customization(self):
|
||||||
|
if not settings.THEME_CUSTOMIZATION_FILE_PATH:
|
||||||
|
return {}
|
||||||
|
|
||||||
class FooterView(drf.views.APIView):
|
cache_key = (
|
||||||
"""API ViewSet for sharing the footer JSON."""
|
f"theme_customization_{slugify(settings.THEME_CUSTOMIZATION_FILE_PATH)}"
|
||||||
|
|
||||||
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)
|
theme_customization = cache.get(cache_key, {})
|
||||||
|
if theme_customization:
|
||||||
|
return theme_customization
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(
|
||||||
|
settings.THEME_CUSTOMIZATION_FILE_PATH, "r", encoding="utf-8"
|
||||||
|
) as f:
|
||||||
|
theme_customization = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(
|
||||||
|
"Configuration file not found: %s",
|
||||||
|
settings.THEME_CUSTOMIZATION_FILE_PATH,
|
||||||
|
)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.error(
|
||||||
|
"Configuration file is not a valid JSON: %s",
|
||||||
|
settings.THEME_CUSTOMIZATION_FILE_PATH,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cache.set(
|
||||||
|
cache_key,
|
||||||
|
theme_customization,
|
||||||
|
settings.THEME_CUSTOMIZATION_CACHE_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
return theme_customization
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
Test config API endpoints in the Impress core app.
|
Test config API endpoints in the Impress core app.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -24,6 +26,7 @@ pytestmark = pytest.mark.django_db
|
|||||||
MEDIA_BASE_URL="http://testserver/",
|
MEDIA_BASE_URL="http://testserver/",
|
||||||
POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"},
|
POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"},
|
||||||
SENTRY_DSN="https://sentry.test/123",
|
SENTRY_DSN="https://sentry.test/123",
|
||||||
|
THEME_CUSTOMIZATION_FILE_PATH="",
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("is_authenticated", [False, True])
|
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||||
def test_api_config(is_authenticated):
|
def test_api_config(is_authenticated):
|
||||||
@@ -56,4 +59,98 @@ def test_api_config(is_authenticated):
|
|||||||
"POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"},
|
"POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"},
|
||||||
"SENTRY_DSN": "https://sentry.test/123",
|
"SENTRY_DSN": "https://sentry.test/123",
|
||||||
"AI_FEATURE_ENABLED": False,
|
"AI_FEATURE_ENABLED": False,
|
||||||
|
"theme_customization": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
THEME_CUSTOMIZATION_FILE_PATH="/not/existing/file.json",
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||||
|
def test_api_config_with_invalid_theme_customization_file(is_authenticated):
|
||||||
|
"""Anonymous users should be allowed to get the configuration."""
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
if is_authenticated:
|
||||||
|
user = factories.UserFactory()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get("/api/v1.0/config/")
|
||||||
|
assert response.status_code == HTTP_200_OK
|
||||||
|
content = response.json()
|
||||||
|
assert content["theme_customization"] == {}
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
THEME_CUSTOMIZATION_FILE_PATH="/configuration/theme/invalid.json",
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||||
|
def test_api_config_with_invalid_json_theme_customization_file(is_authenticated, fs):
|
||||||
|
"""Anonymous users should be allowed to get the configuration."""
|
||||||
|
fs.create_file(
|
||||||
|
"/configuration/theme/invalid.json",
|
||||||
|
contents="invalid json",
|
||||||
|
)
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
if is_authenticated:
|
||||||
|
user = factories.UserFactory()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get("/api/v1.0/config/")
|
||||||
|
assert response.status_code == HTTP_200_OK
|
||||||
|
content = response.json()
|
||||||
|
assert content["theme_customization"] == {}
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
THEME_CUSTOMIZATION_FILE_PATH="/configuration/theme/default.json",
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||||
|
def test_api_config_with_theme_customization(is_authenticated, fs):
|
||||||
|
"""Anonymous users should be allowed to get the configuration."""
|
||||||
|
fs.create_file(
|
||||||
|
"/configuration/theme/default.json",
|
||||||
|
contents=json.dumps(
|
||||||
|
{
|
||||||
|
"colors": {
|
||||||
|
"primary": "#000000",
|
||||||
|
"secondary": "#000000",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
if is_authenticated:
|
||||||
|
user = factories.UserFactory()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get("/api/v1.0/config/")
|
||||||
|
assert response.status_code == HTTP_200_OK
|
||||||
|
content = response.json()
|
||||||
|
assert content["theme_customization"] == {
|
||||||
|
"colors": {
|
||||||
|
"primary": "#000000",
|
||||||
|
"secondary": "#000000",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("is_authenticated", [False, True])
|
||||||
|
def test_api_config_with_original_theme_customization(is_authenticated, settings):
|
||||||
|
"""Anonymous users should be allowed to get the configuration."""
|
||||||
|
client = APIClient()
|
||||||
|
|
||||||
|
if is_authenticated:
|
||||||
|
user = factories.UserFactory()
|
||||||
|
client.force_login(user)
|
||||||
|
|
||||||
|
response = client.get("/api/v1.0/config/")
|
||||||
|
assert response.status_code == HTTP_200_OK
|
||||||
|
content = response.json()
|
||||||
|
|
||||||
|
with open(settings.THEME_CUSTOMIZATION_FILE_PATH, "r", encoding="utf-8") as f:
|
||||||
|
theme_customization = json.load(f)
|
||||||
|
|
||||||
|
assert content["theme_customization"] == theme_customization
|
||||||
|
|||||||
124
src/backend/impress/configuration/theme/default.json
Normal file
124
src/backend/impress/configuration/theme/default.json
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{
|
||||||
|
"footer": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -440,6 +440,18 @@ class Base(Configuration):
|
|||||||
None, environ_name="FRONTEND_CSS_URL", environ_prefix=None
|
None, environ_name="FRONTEND_CSS_URL", environ_prefix=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
THEME_CUSTOMIZATION_FILE_PATH = values.Value(
|
||||||
|
os.path.join(BASE_DIR, "impress/configuration/theme/default.json"),
|
||||||
|
environ_name="THEME_CUSTOMIZATION_FILE_PATH",
|
||||||
|
environ_prefix=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
THEME_CUSTOMIZATION_CACHE_TIMEOUT = values.Value(
|
||||||
|
60 * 60 * 24,
|
||||||
|
environ_name="THEME_CUSTOMIZATION_CACHE_TIMEOUT",
|
||||||
|
environ_prefix=None,
|
||||||
|
)
|
||||||
|
|
||||||
# Posthog
|
# Posthog
|
||||||
POSTHOG_KEY = values.DictValue(
|
POSTHOG_KEY = values.DictValue(
|
||||||
None, environ_name="POSTHOG_KEY", environ_prefix=None
|
None, environ_name="POSTHOG_KEY", environ_prefix=None
|
||||||
|
|||||||
Reference in New Issue
Block a user