platzky 0.3.6__tar.gz → 0.4.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {platzky-0.3.6 → platzky-0.4.3}/PKG-INFO +4 -2
  2. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/db.py +12 -3
  3. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/graph_ql_db.py +20 -5
  4. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/json_db.py +8 -0
  5. platzky-0.4.3/platzky/db/mongodb_db.py +143 -0
  6. platzky-0.4.3/platzky/engine.py +140 -0
  7. {platzky-0.3.6 → platzky-0.4.3}/pyproject.toml +31 -5
  8. platzky-0.3.6/platzky/engine.py +0 -71
  9. {platzky-0.3.6 → platzky-0.4.3}/README.md +0 -0
  10. {platzky-0.3.6 → platzky-0.4.3}/platzky/__init__.py +0 -0
  11. {platzky-0.3.6 → platzky-0.4.3}/platzky/admin/admin.py +0 -0
  12. {platzky-0.3.6 → platzky-0.4.3}/platzky/admin/fake_login.py +0 -0
  13. {platzky-0.3.6 → platzky-0.4.3}/platzky/admin/templates/admin.html +0 -0
  14. {platzky-0.3.6 → platzky-0.4.3}/platzky/admin/templates/login.html +0 -0
  15. {platzky-0.3.6 → platzky-0.4.3}/platzky/admin/templates/module.html +0 -0
  16. {platzky-0.3.6 → platzky-0.4.3}/platzky/blog/__init__.py +0 -0
  17. {platzky-0.3.6 → platzky-0.4.3}/platzky/blog/blog.py +0 -0
  18. {platzky-0.3.6 → platzky-0.4.3}/platzky/blog/comment_form.py +0 -0
  19. {platzky-0.3.6 → platzky-0.4.3}/platzky/config.py +0 -0
  20. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/README.md +0 -0
  21. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/__init__.py +0 -0
  22. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/db_loader.py +0 -0
  23. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/github_json_db.py +0 -0
  24. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/google_json_db.py +0 -0
  25. {platzky-0.3.6 → platzky-0.4.3}/platzky/db/json_file_db.py +0 -0
  26. {platzky-0.3.6 → platzky-0.4.3}/platzky/locale/en/LC_MESSAGES/messages.po +0 -0
  27. {platzky-0.3.6 → platzky-0.4.3}/platzky/locale/pl/LC_MESSAGES/messages.po +0 -0
  28. {platzky-0.3.6 → platzky-0.4.3}/platzky/models.py +0 -0
  29. {platzky-0.3.6 → platzky-0.4.3}/platzky/platzky.py +0 -0
  30. {platzky-0.3.6 → platzky-0.4.3}/platzky/plugin/plugin.py +0 -0
  31. {platzky-0.3.6 → platzky-0.4.3}/platzky/plugin/plugin_loader.py +0 -0
  32. {platzky-0.3.6 → platzky-0.4.3}/platzky/seo/seo.py +0 -0
  33. {platzky-0.3.6 → platzky-0.4.3}/platzky/static/blog.css +0 -0
  34. {platzky-0.3.6 → platzky-0.4.3}/platzky/static/styles.css +0 -0
  35. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/404.html +0 -0
  36. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/base.html +0 -0
  37. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/blog.html +0 -0
  38. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/body_meta.html +0 -0
  39. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/dynamic_css.html +0 -0
  40. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/feed.xml +0 -0
  41. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/head_meta.html +0 -0
  42. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/page.html +0 -0
  43. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/post.html +0 -0
  44. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/robots.txt +0 -0
  45. {platzky-0.3.6 → platzky-0.4.3}/platzky/templates/sitemap.xml +0 -0
  46. {platzky-0.3.6 → platzky-0.4.3}/platzky/www_handler.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: platzky
3
- Version: 0.3.6
3
+ Version: 0.4.3
4
4
  Summary: Not only blog engine
5
5
  License: MIT
6
6
  Requires-Python: >=3.10,<4.0
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
13
14
  Requires-Dist: Flask (==3.0.3)
14
15
  Requires-Dist: Flask-Babel (>=4.0.0,<5.0.0)
15
16
  Requires-Dist: Flask-Minify (>=0.42,<0.43)
@@ -22,6 +23,7 @@ Requires-Dist: gql (>=3.4.0,<4.0.0)
22
23
  Requires-Dist: humanize (>=4.9.0,<5.0.0)
23
24
  Requires-Dist: pydantic (>=2.7.1,<3.0.0)
24
25
  Requires-Dist: pygithub (>=2.6.1,<3.0.0)
26
+ Requires-Dist: pymongo (>=4.7.0,<5.0.0)
25
27
  Description-Content-Type: text/markdown
26
28
 
27
29
  ![Github Actions](https://github.com/platzky/platzky/actions/workflows/tests.yml/badge.svg?event=push&branch=main)
@@ -4,7 +4,7 @@ from typing import Any, Callable
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
7
- from platzky.models import Color, MenuItem, Page, Post
7
+ from platzky.models import MenuItem, Page, Post
8
8
 
9
9
 
10
10
  class DB(ABC):
@@ -81,11 +81,11 @@ class DB(ABC):
81
81
  pass
82
82
 
83
83
  @abstractmethod
84
- def get_primary_color(self) -> Color:
84
+ def get_primary_color(self) -> str:
85
85
  pass
86
86
 
87
87
  @abstractmethod
88
- def get_secondary_color(self) -> Color:
88
+ def get_secondary_color(self) -> str:
89
89
  pass
90
90
 
91
91
  @abstractmethod
@@ -96,6 +96,15 @@ class DB(ABC):
96
96
  def get_font(self) -> str:
97
97
  pass
98
98
 
99
+ @abstractmethod
100
+ def health_check(self) -> None:
101
+ """Perform a health check on the database.
102
+
103
+ Should raise an exception if the database is not healthy.
104
+ This should be a lightweight operation suitable for health checks.
105
+ """
106
+ pass
107
+
99
108
 
100
109
  class DBConfig(BaseModel):
101
110
  type: str = Field(alias="TYPE")
@@ -7,7 +7,7 @@ from gql.transport.exceptions import TransportQueryError
7
7
  from pydantic import Field
8
8
 
9
9
  from platzky.db.db import DB, DBConfig
10
- from platzky.models import Color, Post
10
+ from platzky.models import Post
11
11
 
12
12
 
13
13
  def db_config_type():
@@ -289,11 +289,11 @@ class GraphQL(DB):
289
289
 
290
290
  return self.client.execute(favicon)["favicons"][0]["favicon"]["url"]
291
291
 
292
- def get_primary_color(self) -> Color:
293
- return Color()
292
+ def get_primary_color(self) -> str:
293
+ return "white" # Default color as string
294
294
 
295
- def get_secondary_color(self):
296
- return Color()
295
+ def get_secondary_color(self) -> str:
296
+ return "navy" # Default color as string
297
297
 
298
298
  def get_plugins_data(self):
299
299
  plugins_data = gql(
@@ -307,3 +307,18 @@ class GraphQL(DB):
307
307
  """
308
308
  )
309
309
  return self.client.execute(plugins_data)["pluginConfigs"]
310
+
311
+ def health_check(self) -> None:
312
+ """Perform a health check on the GraphQL database.
313
+
314
+ Raises an exception if the database is not accessible.
315
+ """
316
+ # Simple query to check connectivity
317
+ health_query = gql(
318
+ """
319
+ query {
320
+ __typename
321
+ }
322
+ """
323
+ )
324
+ self.client.execute(health_query)
@@ -116,3 +116,11 @@ class Json(DB):
116
116
 
117
117
  def get_plugins_data(self):
118
118
  return self.data.get("plugins", [])
119
+
120
+ def health_check(self) -> None:
121
+ """Perform a health check on the JSON database.
122
+
123
+ Raises an exception if the database is not accessible.
124
+ """
125
+ # Try to access site_content to ensure basic structure is valid
126
+ self._get_site_content()
@@ -0,0 +1,143 @@
1
+ import datetime
2
+ from typing import Any
3
+
4
+ from pydantic import Field
5
+ from pymongo import MongoClient
6
+ from pymongo.collection import Collection
7
+ from pymongo.database import Database
8
+
9
+ from platzky.db.db import DB, DBConfig
10
+ from platzky.models import MenuItem, Page, Post
11
+
12
+
13
+ def db_config_type():
14
+ return MongoDbConfig
15
+
16
+
17
+ class MongoDbConfig(DBConfig):
18
+ connection_string: str = Field(alias="CONNECTION_STRING")
19
+ database_name: str = Field(alias="DATABASE_NAME")
20
+
21
+
22
+ def get_db(config):
23
+ mongodb_config = MongoDbConfig.model_validate(config)
24
+ return MongoDB(mongodb_config.connection_string, mongodb_config.database_name)
25
+
26
+
27
+ def db_from_config(config: MongoDbConfig):
28
+ return MongoDB(config.connection_string, config.database_name)
29
+
30
+
31
+ class MongoDB(DB):
32
+ def __init__(self, connection_string: str, database_name: str):
33
+ super().__init__()
34
+ self.connection_string = connection_string
35
+ self.database_name = database_name
36
+ self.client: MongoClient[Any] = MongoClient(connection_string)
37
+ self.db: Database[Any] = self.client[database_name]
38
+ self.module_name = "mongodb_db"
39
+ self.db_name = "MongoDB"
40
+
41
+ # Collection references
42
+ self.site_content: Collection[Any] = self.db.site_content
43
+ self.posts: Collection[Any] = self.db.posts
44
+ self.pages: Collection[Any] = self.db.pages
45
+ self.menu_items: Collection[Any] = self.db.menu_items
46
+ self.plugins: Collection[Any] = self.db.plugins
47
+
48
+ def get_app_description(self, lang: str) -> str:
49
+ site_content = self.site_content.find_one({"_id": "config"})
50
+ if site_content and "app_description" in site_content:
51
+ return site_content["app_description"].get(lang, "")
52
+ return ""
53
+
54
+ def get_all_posts(self, lang: str) -> list[Post]:
55
+ posts_cursor = self.posts.find({"language": lang})
56
+ return [Post.model_validate(post) for post in posts_cursor]
57
+
58
+ def get_menu_items_in_lang(self, lang: str) -> list[MenuItem]:
59
+ menu_items_doc = self.menu_items.find_one({"_id": lang})
60
+ if menu_items_doc and "items" in menu_items_doc:
61
+ return [MenuItem.model_validate(item) for item in menu_items_doc["items"]]
62
+ return []
63
+
64
+ def get_post(self, slug: str) -> Post:
65
+ post_doc = self.posts.find_one({"slug": slug})
66
+ if post_doc is None:
67
+ raise ValueError(f"Post with slug {slug} not found")
68
+ return Post.model_validate(post_doc)
69
+
70
+ def get_page(self, slug: str) -> Page:
71
+ page_doc = self.pages.find_one({"slug": slug})
72
+ if page_doc is None:
73
+ raise ValueError(f"Page with slug {slug} not found")
74
+ return Page.model_validate(page_doc)
75
+
76
+ def get_posts_by_tag(self, tag: str, lang: str) -> Any:
77
+ posts_cursor = self.posts.find({"tags": tag, "language": lang})
78
+ return posts_cursor
79
+
80
+ def add_comment(self, author_name: str, comment: str, post_slug: str) -> None:
81
+ now_utc = datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds")
82
+ comment_doc = {
83
+ "author": str(author_name),
84
+ "comment": str(comment),
85
+ "date": now_utc,
86
+ }
87
+
88
+ result = self.posts.update_one({"slug": post_slug}, {"$push": {"comments": comment_doc}})
89
+ if result.matched_count == 0:
90
+ raise ValueError(f"Post with slug {post_slug} not found")
91
+
92
+ def get_logo_url(self) -> str:
93
+ site_content = self.site_content.find_one({"_id": "config"})
94
+ if site_content:
95
+ return site_content.get("logo_url", "")
96
+ return ""
97
+
98
+ def get_favicon_url(self) -> str:
99
+ site_content = self.site_content.find_one({"_id": "config"})
100
+ if site_content:
101
+ return site_content.get("favicon_url", "")
102
+ return ""
103
+
104
+ def get_primary_color(self) -> str:
105
+ site_content = self.site_content.find_one({"_id": "config"})
106
+ if site_content:
107
+ return site_content.get("primary_color", "white")
108
+ return "white"
109
+
110
+ def get_secondary_color(self) -> str:
111
+ site_content = self.site_content.find_one({"_id": "config"})
112
+ if site_content:
113
+ return site_content.get("secondary_color", "navy")
114
+ return "navy"
115
+
116
+ def get_plugins_data(self) -> list[Any]:
117
+ plugins_doc = self.plugins.find_one({"_id": "config"})
118
+ if plugins_doc and "data" in plugins_doc:
119
+ return plugins_doc["data"]
120
+ return []
121
+
122
+ def get_font(self) -> str:
123
+ site_content = self.site_content.find_one({"_id": "config"})
124
+ if site_content:
125
+ return site_content.get("font", "")
126
+ return ""
127
+
128
+ def health_check(self) -> None:
129
+ """Perform a health check on the MongoDB database.
130
+
131
+ Raises an exception if the database is not accessible.
132
+ """
133
+ # Simple ping to check if database is accessible
134
+ self.client.admin.command("ping")
135
+
136
+ def _close_connection(self) -> None:
137
+ """Close the MongoDB connection"""
138
+ if self.client:
139
+ self.client.close()
140
+
141
+ def __del__(self):
142
+ """Ensure connection is closed when object is destroyed"""
143
+ self._close_connection()
@@ -0,0 +1,140 @@
1
+ import os
2
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError
3
+ from typing import Any, Callable, Dict, List, Tuple
4
+
5
+ from flask import Blueprint, Flask, jsonify, make_response, request, session
6
+ from flask_babel import Babel
7
+
8
+ from platzky.config import Config
9
+ from platzky.models import CmsModule
10
+
11
+
12
+ class Engine(Flask):
13
+ def __init__(self, config: Config, db, import_name):
14
+ super().__init__(import_name)
15
+ self.config.from_mapping(config.model_dump(by_alias=True))
16
+ self.db = db
17
+ self.notifiers = []
18
+ self.login_methods = []
19
+ self.dynamic_body = ""
20
+ self.dynamic_head = ""
21
+ self.health_checks: List[Tuple[str, Callable[[], None]]] = []
22
+ directory = os.path.dirname(os.path.realpath(__file__))
23
+ locale_dir = os.path.join(directory, "locale")
24
+ config.translation_directories.append(locale_dir)
25
+ babel_translation_directories = ";".join(config.translation_directories)
26
+ self.babel = Babel(
27
+ self,
28
+ locale_selector=self.get_locale,
29
+ default_translation_directories=babel_translation_directories,
30
+ )
31
+ self._register_default_health_endpoints()
32
+
33
+ self.cms_modules: List[CmsModule] = []
34
+ # TODO add plugins as CMS Module - all plugins should be visible from
35
+ # admin page at least as configuration
36
+
37
+ def notify(self, message: str):
38
+ for notifier in self.notifiers:
39
+ notifier(message)
40
+
41
+ def add_notifier(self, notifier):
42
+ self.notifiers.append(notifier)
43
+
44
+ def add_cms_module(self, module: CmsModule):
45
+ """Add a CMS module to the modules list."""
46
+ self.cms_modules.append(module)
47
+
48
+ # TODO login_method should be interface
49
+ def add_login_method(self, login_method):
50
+ self.login_methods.append(login_method)
51
+
52
+ def add_dynamic_body(self, body: str):
53
+ self.dynamic_body += body
54
+
55
+ def add_dynamic_head(self, body: str):
56
+ self.dynamic_head += body
57
+
58
+ def get_locale(self) -> str:
59
+ domain = request.headers.get("Host", "localhost")
60
+ domain_to_lang = self.config.get("DOMAIN_TO_LANG")
61
+
62
+ languages = self.config.get("LANGUAGES", {}).keys()
63
+ backup_lang = session.get(
64
+ "language",
65
+ request.accept_languages.best_match(languages, "en"),
66
+ )
67
+
68
+ if domain_to_lang:
69
+ lang = domain_to_lang.get(domain, backup_lang)
70
+ else:
71
+ lang = backup_lang
72
+
73
+ session["language"] = lang
74
+ return lang
75
+
76
+ def add_health_check(self, name: str, check_function: Callable[[], None]) -> None:
77
+ """Register a health check function"""
78
+ if not callable(check_function):
79
+ raise TypeError(f"check_function must be callable, got {type(check_function)}")
80
+ self.health_checks.append((name, check_function))
81
+
82
+ def _register_default_health_endpoints(self):
83
+ """Register default health endpoints"""
84
+
85
+ health_bp = Blueprint("health", __name__)
86
+ HEALTH_CHECK_TIMEOUT = 10 # seconds
87
+
88
+ @health_bp.route("/health/liveness")
89
+ def liveness():
90
+ """Simple liveness check - is the app running?"""
91
+ return jsonify({"status": "alive"}), 200
92
+
93
+ @health_bp.route("/health/readiness")
94
+ def readiness():
95
+ """Readiness check - can the app serve traffic?"""
96
+ health_status: Dict[str, Any] = {"status": "ready", "checks": {}}
97
+ status_code = 200
98
+
99
+ executor = ThreadPoolExecutor(max_workers=1)
100
+ try:
101
+ # Database health check with timeout
102
+ future = executor.submit(self.db.health_check)
103
+ try:
104
+ future.result(timeout=HEALTH_CHECK_TIMEOUT)
105
+ health_status["checks"]["database"] = "ok"
106
+ except TimeoutError:
107
+ health_status["checks"]["database"] = "failed: timeout"
108
+ health_status["status"] = "not_ready"
109
+ status_code = 503
110
+ except Exception as e:
111
+ health_status["checks"]["database"] = f"failed: {e!s}"
112
+ health_status["status"] = "not_ready"
113
+ status_code = 503
114
+
115
+ # Run application-registered health checks
116
+ for check_name, check_func in self.health_checks:
117
+ future = executor.submit(check_func)
118
+ try:
119
+ future.result(timeout=HEALTH_CHECK_TIMEOUT)
120
+ health_status["checks"][check_name] = "ok"
121
+ except TimeoutError:
122
+ health_status["checks"][check_name] = "failed: timeout"
123
+ health_status["status"] = "not_ready"
124
+ status_code = 503
125
+ except Exception as e:
126
+ health_status["checks"][check_name] = f"failed: {e!s}"
127
+ health_status["status"] = "not_ready"
128
+ status_code = 503
129
+ finally:
130
+ # Shutdown without waiting if any futures are still running
131
+ executor.shutdown(wait=False)
132
+
133
+ return make_response(jsonify(health_status), status_code)
134
+
135
+ # Simple /health alias for liveness
136
+ @health_bp.route("/health")
137
+ def health():
138
+ return liveness()
139
+
140
+ self.register_blueprint(health_bp)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "platzky"
3
- version = "0.3.6"
3
+ version = "0.4.3"
4
4
  description = "Not only blog engine"
5
5
  authors = []
6
6
  license = "MIT"
@@ -20,6 +20,7 @@ humanize = "^4.9.0"
20
20
  pydantic = "^2.7.1"
21
21
  deprecation = "^2.1.0"
22
22
  pygithub = "^2.6.1"
23
+ pymongo = "^4.7.0"
23
24
 
24
25
  [tool.poetry.group.dev.dependencies]
25
26
  pytest = "^8.2.1"
@@ -30,7 +31,7 @@ black = "^24.8.0"
30
31
  ruff = "^0.4.4"
31
32
  pyright = "^1.1.364"
32
33
  beautifulsoup4 = "^4.12.3"
33
-
34
+ python-semantic-release = "^9.8.0"
34
35
 
35
36
  [build-system]
36
37
  requires = ["poetry-core"]
@@ -40,18 +41,17 @@ build-backend = "poetry.core.masonry.api"
40
41
  omit = [
41
42
  "tests/*",
42
43
  "*/__init__.py"
43
- ]
44
+ ]
44
45
 
45
46
  [tool.coverage.report]
46
47
  exclude_lines = [
47
48
  "@abstractmethod",
48
49
  "@abc.abstractmethod"
49
- ]
50
+ ]
50
51
 
51
52
  [tool.pyright]
52
53
  pythonVersion = "3.10"
53
54
  pythonPlatform = "All"
54
-
55
55
  typeCheckingMode = "strict"
56
56
  reportMissingImports = true
57
57
  reportMissingTypeStubs = false
@@ -87,3 +87,29 @@ lint.ignore = []
87
87
  markers = [
88
88
  "skip_coverage: skip coverage for this test"
89
89
  ]
90
+
91
+ [tool.semantic_release]
92
+ version_toml = ["pyproject.toml:tool.poetry.version"]
93
+ build_command = "poetry build"
94
+ dist_path = "dist/"
95
+ upload_to_release = true
96
+ remove_dist = false
97
+ commit_author = "semantic-release <semantic-release>"
98
+ commit_message = "chore(release): {version}"
99
+ tag_format = "{version}"
100
+ commit_parser = "conventional"
101
+ changelog_file = "CHANGELOG.md"
102
+
103
+ [tool.semantic_release.branches.main]
104
+ match = "main"
105
+ prerelease = false
106
+
107
+ [tool.semantic_release.publish]
108
+ dist_glob_patterns = ["dist/*"]
109
+ upload_to_vcs_release = true
110
+
111
+ [tool.semantic_release.remote]
112
+ type = "github"
113
+
114
+ [tool.semantic_release.remote.token]
115
+ env = "GH_TOKEN"
@@ -1,71 +0,0 @@
1
- import os
2
- from typing import List
3
-
4
- from flask import Flask, request, session
5
- from flask_babel import Babel
6
-
7
- from platzky.config import Config
8
- from platzky.models import CmsModule
9
-
10
-
11
- class Engine(Flask):
12
- def __init__(self, config: Config, db, import_name):
13
- super().__init__(import_name)
14
- self.config.from_mapping(config.model_dump(by_alias=True))
15
- self.db = db
16
- self.notifiers = []
17
- self.login_methods = []
18
- self.dynamic_body = ""
19
- self.dynamic_head = ""
20
- directory = os.path.dirname(os.path.realpath(__file__))
21
- locale_dir = os.path.join(directory, "locale")
22
- config.translation_directories.append(locale_dir)
23
- babel_translation_directories = ";".join(config.translation_directories)
24
- self.babel = Babel(
25
- self,
26
- locale_selector=self.get_locale,
27
- default_translation_directories=babel_translation_directories,
28
- )
29
-
30
- self.cms_modules: List[CmsModule] = []
31
- # TODO add plugins as CMS Module - all plugins should be visible from
32
- # admin page at least as configuration
33
-
34
- def notify(self, message: str):
35
- for notifier in self.notifiers:
36
- notifier(message)
37
-
38
- def add_notifier(self, notifier):
39
- self.notifiers.append(notifier)
40
-
41
- def add_cms_module(self, module: CmsModule):
42
- """Add a CMS module to the modules list."""
43
- self.cms_modules.append(module)
44
-
45
- # TODO login_method should be interface
46
- def add_login_method(self, login_method):
47
- self.login_methods.append(login_method)
48
-
49
- def add_dynamic_body(self, body: str):
50
- self.dynamic_body += body
51
-
52
- def add_dynamic_head(self, body: str):
53
- self.dynamic_head += body
54
-
55
- def get_locale(self) -> str:
56
- domain = request.headers.get("Host", "localhost")
57
- domain_to_lang = self.config.get("DOMAIN_TO_LANG")
58
-
59
- languages = self.config.get("LANGUAGES", {}).keys()
60
- backup_lang = session.get(
61
- "language",
62
- request.accept_languages.best_match(languages, "en"),
63
- )
64
-
65
- if domain_to_lang:
66
- lang = domain_to_lang.get(domain, backup_lang)
67
- else:
68
- lang = backup_lang
69
-
70
- session["language"] = lang
71
- return lang
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes