platzky 0.3.5__py3-none-any.whl → 0.4.1__py3-none-any.whl

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.
platzky/admin/admin.py CHANGED
@@ -3,7 +3,14 @@ from os.path import dirname
3
3
  from flask import Blueprint, render_template, session
4
4
 
5
5
 
6
- def create_admin_blueprint(login_methods, db, locale_func):
6
+ def create_admin_blueprint(login_methods, cms_modules):
7
+ """Create admin blueprint with dynamic module routes.
8
+
9
+ Args:
10
+ login_methods: Available login methods
11
+ cms_modules: List of CMS modules to register routes for
12
+ """
13
+ # …rest of the function…
7
14
  admin = Blueprint(
8
15
  "admin",
9
16
  __name__,
@@ -11,23 +18,20 @@ def create_admin_blueprint(login_methods, db, locale_func):
11
18
  template_folder=f"{dirname(__file__)}/templates",
12
19
  )
13
20
 
14
- @admin.route("/", methods=["GET"])
15
- def admin_panel_home():
16
- user = session.get("user", None)
21
+ for module in cms_modules:
17
22
 
18
- if not user:
19
- return render_template("login.html", login_methods=login_methods)
23
+ @admin.route(f"/module/{module.slug}", methods=["GET"])
24
+ def module_route(module=module):
20
25
 
21
- cms_modules = {"plugins": [plugin.get("name") for plugin in db.get_plugins_data()]}
22
- return render_template("admin.html", user=user, cms_modules=cms_modules)
26
+ return render_template(module.template, module=module)
23
27
 
24
- @admin.route("/module/<module_name>", methods=["GET"])
25
- def module_settings(module_name):
28
+ @admin.route("/", methods=["GET"])
29
+ def admin_panel_home():
26
30
  user = session.get("user", None)
27
31
 
28
32
  if not user:
29
33
  return render_template("login.html", login_methods=login_methods)
30
34
 
31
- return render_template("module.html", user=user, module_name=module_name)
35
+ return render_template("admin.html", user=user, cms_modules=cms_modules)
32
36
 
33
37
  return admin
@@ -18,12 +18,13 @@
18
18
  {% block left_panel %}
19
19
  <div id="admin-panel">
20
20
  <p>User: {{ user.username }}</p>
21
- {% for cms_module_name, cms_entries in cms_modules.items() %}
21
+ {% for cms_module in cms_modules %}
22
22
  <div class="cms-module mb-2">
23
- <p>{{ cms_module_name }}</p>
24
- {% for cms_entry in cms_entries %}
25
- <a href="{{ url_for('admin.module', name=cms_entry) }}" class="cms-entry btn btn-primary btn-block mb-2">{{ cms_entry }}</a>
26
- {% endfor %}
23
+ <ul>
24
+ <li>
25
+ <a href="/{{ cms_module.slug }}"> {{ cms_module.name }} </a>
26
+ </li>
27
+ </ul>
27
28
  </div>
28
29
  {% endfor %}
29
30
  </div>
platzky/db/db.py CHANGED
@@ -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
platzky/db/graph_ql_db.py CHANGED
@@ -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(
@@ -0,0 +1,135 @@
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 _close_connection(self) -> None:
129
+ """Close the MongoDB connection"""
130
+ if self.client:
131
+ self.client.close()
132
+
133
+ def __del__(self):
134
+ """Ensure connection is closed when object is destroyed"""
135
+ self._close_connection()
platzky/engine.py CHANGED
@@ -1,9 +1,11 @@
1
1
  import os
2
+ from typing import List
2
3
 
3
4
  from flask import Flask, request, session
4
5
  from flask_babel import Babel
5
6
 
6
7
  from platzky.config import Config
8
+ from platzky.models import CmsModule
7
9
 
8
10
 
9
11
  class Engine(Flask):
@@ -25,6 +27,10 @@ class Engine(Flask):
25
27
  default_translation_directories=babel_translation_directories,
26
28
  )
27
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
+
28
34
  def notify(self, message: str):
29
35
  for notifier in self.notifiers:
30
36
  notifier(message)
@@ -32,6 +38,10 @@ class Engine(Flask):
32
38
  def add_notifier(self, notifier):
33
39
  self.notifiers.append(notifier)
34
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
+
35
45
  # TODO login_method should be interface
36
46
  def add_login_method(self, login_method):
37
47
  self.login_methods.append(login_method)
platzky/models.py CHANGED
@@ -4,6 +4,22 @@ import humanize
4
4
  from pydantic import BaseModel
5
5
 
6
6
 
7
+ class CmsModule(BaseModel):
8
+ """Represents a CMS module with basic metadata."""
9
+
10
+ name: str
11
+ description: str
12
+ template: str
13
+ slug: str
14
+
15
+
16
+ # CmsModuleGroup is also a CmsModule, but it contains other CmsModules
17
+ class CmsModuleGroup(CmsModule):
18
+ """Represents a group of CMS modules, inheriting module properties."""
19
+
20
+ modules: list[CmsModule] = []
21
+
22
+
7
23
  class Image(BaseModel):
8
24
  url: str = ""
9
25
  alternateText: str = ""
platzky/platzky.py CHANGED
@@ -86,7 +86,7 @@ def create_app_from_config(config: Config) -> Engine:
86
86
  engine = create_engine(config, db)
87
87
 
88
88
  admin_blueprint = admin.create_admin_blueprint(
89
- login_methods=engine.login_methods, db=engine.db, locale_func=engine.get_locale
89
+ login_methods=engine.login_methods, cms_modules=engine.cms_modules
90
90
  )
91
91
 
92
92
  if config.feature_flags and config.feature_flags.get("FAKE_LOGIN", False):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: platzky
3
- Version: 0.3.5
3
+ Version: 0.4.1
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)
@@ -1,7 +1,7 @@
1
1
  platzky/__init__.py,sha256=IhL91rSWxIIJQNfVsqJ1d4yY5D2WyWcefo4Xv2aX_lo,180
2
- platzky/admin/admin.py,sha256=nQq0IcBhrcX6S4gd71MejOcc2yizVv20UmF4ZRvZnBk,1019
2
+ platzky/admin/admin.py,sha256=PlwAXaR_YaM07knB88D33vNe6vCpJXOfuxpCaGnHQlY,1028
3
3
  platzky/admin/fake_login.py,sha256=tGsdofPyI3P65pol_3Nbj5075xjA_CU090brnBVNjxg,3480
4
- platzky/admin/templates/admin.html,sha256=4WdatUbuWakR3Yhrr7ClzKlUMXVcYdl_2kMBzW_faM0,813
4
+ platzky/admin/templates/admin.html,sha256=zgjROhSezayZqnNFezvVa0MEfgmXLvOM8HRRaZemkQw,688
5
5
  platzky/admin/templates/login.html,sha256=oBNuv130iMTwXrtRnDUDcGIGvu0O2VsIbjQxw-Tjd7Y,380
6
6
  platzky/admin/templates/module.html,sha256=WuQZxKQDD4INl-QF2uiKHf9Fmf2h7cEW9RLe1nWKC8k,175
7
7
  platzky/blog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -10,18 +10,19 @@ platzky/blog/comment_form.py,sha256=4lkNJ_S_2DZmJBbz-NPDqahvy2Zz5AGNH2spFeGIop4,
10
10
  platzky/config.py,sha256=M3gmZI9yI-ThgmTA4RKsAPcnJwJjcWhXipYzq3hO-Hk,2346
11
11
  platzky/db/README.md,sha256=IO-LoDsd4dLBZenaz423EZjvEOQu_8m2OC0G7du170w,1753
12
12
  platzky/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- platzky/db/db.py,sha256=o3jXcA97MwOepJhxtmXpyfDATRcMcJmX6O1Bro5-Rkw,2796
13
+ platzky/db/db.py,sha256=i8qOojEjV4xBzcB7Ic34aRuREK_khJTcP8CgS5yY8DE,2785
14
14
  platzky/db/db_loader.py,sha256=CuEiXxhIa4bFMm0vi7ugzm7j3WycilGRKCU6smgIImE,905
15
15
  platzky/db/github_json_db.py,sha256=G1GBIomeKOCeG05pA4qccaFntiGzkgyEMQJz_FQlvNY,2185
16
16
  platzky/db/google_json_db.py,sha256=rS__UEK7ed71htTg066_vzpg0etTlpke6YkcrAQ3Fgk,1325
17
- platzky/db/graph_ql_db.py,sha256=aGE1glBmLmx4mE1aysGe6sl0lP2uG89LUnu2hmkdvqk,8528
17
+ platzky/db/graph_ql_db.py,sha256=znt40tifzdh6ZdR6sbU8AUyKrs75SEZRJzsImjCor54,8579
18
18
  platzky/db/json_db.py,sha256=-k6NcMBK99SwJObK7UA15hqVSvElrvYR1Vl7G9p0re4,3772
19
19
  platzky/db/json_file_db.py,sha256=tPo92n5zG7vGpunn5vl66zISHBziQdxBttitvc5hPug,1030
20
- platzky/engine.py,sha256=mweAkMG-DCei84rXfggukcsMyje4rj9rSk5v5AwnF04,1896
20
+ platzky/db/mongodb_db.py,sha256=AM4WJjt8rstlnJtJiyYZq8kwTXtiJpUo989cm41eGJo,4903
21
+ platzky/engine.py,sha256=BrZNm2ooO0e1hp41vaABbxLWPN_WfYglTYEM5v2NkTk,2272
21
22
  platzky/locale/en/LC_MESSAGES/messages.po,sha256=WaZGlFAegKRq7CSz69dWKic-mKvQFhVvssvExxNmGaU,1400
22
23
  platzky/locale/pl/LC_MESSAGES/messages.po,sha256=sUPxMKDeEOoZ5UIg94rGxZD06YVWiAMWIby2XE51Hrc,1624
23
- platzky/models.py,sha256=-IIlyeLzACeTUpzuzvzJYxtT57E6wRiERoRgXJYMMtY,1502
24
- platzky/platzky.py,sha256=o4gvQc6i_fJhMembJ2FXEbfHOgmFyGqub5hgQ6roxDc,3881
24
+ platzky/models.py,sha256=DZZgKW2Q3fY2GMdikFUmAgpsRqT5VKAOwP6RmEsmO2M,1871
25
+ platzky/platzky.py,sha256=oWI-R2lZzpmmqrRqhswssZO1Z14R8fAdRFJCbyRTyeY,3868
25
26
  platzky/plugin/plugin.py,sha256=tV8aobIzMDJe1frKUAi4kLbrTAIS0FWE3oYpktSo6Ug,1633
26
27
  platzky/plugin/plugin_loader.py,sha256=MeQ8LNbrOomwXgc1ISHuyhjZd2mzYKen70eDShWs-Co,3497
27
28
  platzky/seo/seo.py,sha256=N_MmAA4KJZmmrDUh0hYNtD8ycOwpNKow4gVSAv8V3N4,2631
@@ -39,6 +40,6 @@ platzky/templates/post.html,sha256=GSgjIZsOQKtNx3cEbquSjZ5L4whPnG6MzRyoq9k4B8Q,1
39
40
  platzky/templates/robots.txt,sha256=2_j2tiYtYJnzZUrANiX9pvBxyw5Dp27fR_co18BPEJ0,116
40
41
  platzky/templates/sitemap.xml,sha256=iIJZ91_B5ZuNLCHsRtsGKZlBAXojOTP8kffqKLacgvs,578
41
42
  platzky/www_handler.py,sha256=pF6Rmvem1sdVqHD7z3RLrDuG-CwAqfGCti50_NPsB2w,725
42
- platzky-0.3.5.dist-info/METADATA,sha256=oZaDnSFu_8RO8Cni_bey2UobtQu53XYDq-83Xbw1MS4,1727
43
- platzky-0.3.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
44
- platzky-0.3.5.dist-info/RECORD,,
43
+ platzky-0.4.1.dist-info/METADATA,sha256=br67YHAkJvv9S-bEx6cNX-wWPzdFmNV17HhER6b-ls8,1818
44
+ platzky-0.4.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
45
+ platzky-0.4.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.2.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any