platzky 1.1.0__py3-none-any.whl → 1.2.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 +25 -3
- platzky/admin/fake_login.py +2 -2
- platzky/blog/blog.py +72 -5
- platzky/blog/comment_form.py +10 -0
- platzky/config.py +1 -1
- platzky/db/db.py +59 -9
- platzky/db/db_loader.py +5 -2
- platzky/db/github_json_db.py +42 -4
- platzky/db/google_json_db.py +63 -7
- platzky/db/graph_ql_db.py +216 -31
- platzky/db/json_db.py +162 -36
- platzky/db/json_file_db.py +46 -6
- platzky/db/mongodb_db.py +134 -6
- platzky/engine.py +4 -3
- platzky/models.py +19 -9
- platzky/plugin/plugin_loader.py +4 -3
- platzky/seo/seo.py +53 -17
- {platzky-1.1.0.dist-info → platzky-1.2.1.dist-info}/METADATA +1 -1
- {platzky-1.1.0.dist-info → platzky-1.2.1.dist-info}/RECORD +21 -21
- {platzky-1.1.0.dist-info → platzky-1.2.1.dist-info}/WHEEL +0 -0
- {platzky-1.1.0.dist-info → platzky-1.2.1.dist-info}/licenses/LICENSE +0 -0
platzky/db/json_file_db.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
"""Local file-based JSON database implementation."""
|
|
2
|
+
|
|
1
3
|
import json
|
|
4
|
+
from typing import Any
|
|
2
5
|
|
|
3
6
|
from pydantic import Field
|
|
4
7
|
|
|
@@ -6,25 +9,55 @@ from platzky.db.db import DBConfig
|
|
|
6
9
|
from platzky.db.json_db import Json
|
|
7
10
|
|
|
8
11
|
|
|
9
|
-
def db_config_type():
|
|
12
|
+
def db_config_type() -> type["JsonFileDbConfig"]:
|
|
13
|
+
"""Return the configuration class for JSON file database.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
JsonFileDbConfig class
|
|
17
|
+
"""
|
|
10
18
|
return JsonFileDbConfig
|
|
11
19
|
|
|
12
20
|
|
|
13
21
|
class JsonFileDbConfig(DBConfig):
|
|
22
|
+
"""Configuration for JSON file database."""
|
|
23
|
+
|
|
14
24
|
path: str = Field(alias="PATH")
|
|
15
25
|
|
|
16
26
|
|
|
17
|
-
def get_db(config):
|
|
27
|
+
def get_db(config: dict[str, Any]) -> "JsonFile":
|
|
28
|
+
"""Get a JSON file database instance from raw configuration.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config: Raw configuration dictionary
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Configured JSON file database instance
|
|
35
|
+
"""
|
|
18
36
|
json_file_db_config = JsonFileDbConfig.model_validate(config)
|
|
19
37
|
return JsonFile(json_file_db_config.path)
|
|
20
38
|
|
|
21
39
|
|
|
22
|
-
def db_from_config(config: JsonFileDbConfig):
|
|
40
|
+
def db_from_config(config: JsonFileDbConfig) -> "JsonFile":
|
|
41
|
+
"""Create a JSON file database instance from configuration.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
config: JSON file database configuration
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Configured JSON file database instance
|
|
48
|
+
"""
|
|
23
49
|
return JsonFile(config.path)
|
|
24
50
|
|
|
25
51
|
|
|
26
52
|
class JsonFile(Json):
|
|
27
|
-
|
|
53
|
+
"""JSON database stored in a local file with read/write support."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, path: str) -> None:
|
|
56
|
+
"""Initialize JSON file database from a local file path.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
path: Absolute or relative path to the JSON file
|
|
60
|
+
"""
|
|
28
61
|
self.data_file_path = path
|
|
29
62
|
with open(self.data_file_path) as json_file:
|
|
30
63
|
data = json.load(json_file)
|
|
@@ -32,10 +65,17 @@ class JsonFile(Json):
|
|
|
32
65
|
self.module_name = "json_file_db"
|
|
33
66
|
self.db_name = "JsonFileDb"
|
|
34
67
|
|
|
35
|
-
def __save_file(self):
|
|
68
|
+
def __save_file(self) -> None:
|
|
36
69
|
with open(self.data_file_path, "w") as json_file:
|
|
37
70
|
json.dump(self.data, json_file)
|
|
38
71
|
|
|
39
|
-
def add_comment(self, author_name, comment, post_slug):
|
|
72
|
+
def add_comment(self, author_name: str, comment: str, post_slug: str) -> None:
|
|
73
|
+
"""Add a comment to a blog post and persist to file.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
author_name: Name of the comment author
|
|
77
|
+
comment: Comment text content
|
|
78
|
+
post_slug: URL-friendly identifier of the post
|
|
79
|
+
"""
|
|
40
80
|
super().add_comment(author_name, comment, post_slug)
|
|
41
81
|
self.__save_file()
|
platzky/db/mongodb_db.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""MongoDB database implementation."""
|
|
2
|
+
|
|
1
3
|
import datetime
|
|
2
4
|
from typing import Any
|
|
3
5
|
|
|
@@ -10,26 +12,57 @@ from platzky.db.db import DB, DBConfig
|
|
|
10
12
|
from platzky.models import MenuItem, Page, Post
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
def db_config_type():
|
|
15
|
+
def db_config_type() -> type["MongoDbConfig"]:
|
|
16
|
+
"""Return the configuration class for MongoDB database.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
MongoDbConfig class
|
|
20
|
+
"""
|
|
14
21
|
return MongoDbConfig
|
|
15
22
|
|
|
16
23
|
|
|
17
24
|
class MongoDbConfig(DBConfig):
|
|
25
|
+
"""Configuration for MongoDB database connection."""
|
|
26
|
+
|
|
18
27
|
connection_string: str = Field(alias="CONNECTION_STRING")
|
|
19
28
|
database_name: str = Field(alias="DATABASE_NAME")
|
|
20
29
|
|
|
21
30
|
|
|
22
|
-
def get_db(config):
|
|
31
|
+
def get_db(config: dict[str, Any]) -> "MongoDB":
|
|
32
|
+
"""Get a MongoDB database instance from raw configuration.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
config: Raw configuration dictionary
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Configured MongoDB database instance
|
|
39
|
+
"""
|
|
23
40
|
mongodb_config = MongoDbConfig.model_validate(config)
|
|
24
41
|
return MongoDB(mongodb_config.connection_string, mongodb_config.database_name)
|
|
25
42
|
|
|
26
43
|
|
|
27
|
-
def db_from_config(config: MongoDbConfig):
|
|
44
|
+
def db_from_config(config: MongoDbConfig) -> "MongoDB":
|
|
45
|
+
"""Create a MongoDB database instance from configuration.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
config: MongoDB database configuration
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Configured MongoDB database instance
|
|
52
|
+
"""
|
|
28
53
|
return MongoDB(config.connection_string, config.database_name)
|
|
29
54
|
|
|
30
55
|
|
|
31
56
|
class MongoDB(DB):
|
|
57
|
+
"""MongoDB database implementation with connection pooling."""
|
|
58
|
+
|
|
32
59
|
def __init__(self, connection_string: str, database_name: str):
|
|
60
|
+
"""Initialize MongoDB database connection.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
connection_string: MongoDB connection URI
|
|
64
|
+
database_name: Name of the database to use
|
|
65
|
+
"""
|
|
33
66
|
super().__init__()
|
|
34
67
|
self.connection_string = connection_string
|
|
35
68
|
self.database_name = database_name
|
|
@@ -46,38 +79,103 @@ class MongoDB(DB):
|
|
|
46
79
|
self.plugins: Collection[Any] = self.db.plugins
|
|
47
80
|
|
|
48
81
|
def get_app_description(self, lang: str) -> str:
|
|
82
|
+
"""Retrieve the application description for a specific language.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
lang: Language code (e.g., 'en', 'pl')
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Application description text or empty string if not found
|
|
89
|
+
"""
|
|
49
90
|
site_content = self.site_content.find_one({"_id": "config"})
|
|
50
91
|
if site_content and "app_description" in site_content:
|
|
51
92
|
return site_content["app_description"].get(lang, "")
|
|
52
93
|
return ""
|
|
53
94
|
|
|
54
95
|
def get_all_posts(self, lang: str) -> list[Post]:
|
|
96
|
+
"""Retrieve all posts for a specific language.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
lang: Language code (e.g., 'en', 'pl')
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of Post objects
|
|
103
|
+
"""
|
|
55
104
|
posts_cursor = self.posts.find({"language": lang})
|
|
56
105
|
return [Post.model_validate(post) for post in posts_cursor]
|
|
57
106
|
|
|
58
107
|
def get_menu_items_in_lang(self, lang: str) -> list[MenuItem]:
|
|
108
|
+
"""Retrieve menu items for a specific language.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
lang: Language code (e.g., 'en', 'pl')
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
List of MenuItem objects
|
|
115
|
+
"""
|
|
59
116
|
menu_items_doc = self.menu_items.find_one({"_id": lang})
|
|
60
117
|
if menu_items_doc and "items" in menu_items_doc:
|
|
61
118
|
return [MenuItem.model_validate(item) for item in menu_items_doc["items"]]
|
|
62
119
|
return []
|
|
63
120
|
|
|
64
121
|
def get_post(self, slug: str) -> Post:
|
|
122
|
+
"""Retrieve a single post by its slug.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
slug: URL-friendly identifier for the post
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Post object
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
ValueError: If post not found
|
|
132
|
+
"""
|
|
65
133
|
post_doc = self.posts.find_one({"slug": slug})
|
|
66
134
|
if post_doc is None:
|
|
67
135
|
raise ValueError(f"Post with slug {slug} not found")
|
|
68
136
|
return Post.model_validate(post_doc)
|
|
69
137
|
|
|
70
138
|
def get_page(self, slug: str) -> Page:
|
|
139
|
+
"""Retrieve a page by its slug.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
slug: URL-friendly identifier for the page
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Page object
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
ValueError: If page not found
|
|
149
|
+
"""
|
|
71
150
|
page_doc = self.pages.find_one({"slug": slug})
|
|
72
151
|
if page_doc is None:
|
|
73
152
|
raise ValueError(f"Page with slug {slug} not found")
|
|
74
153
|
return Page.model_validate(page_doc)
|
|
75
154
|
|
|
76
|
-
def get_posts_by_tag(self, tag: str, lang: str) ->
|
|
155
|
+
def get_posts_by_tag(self, tag: str, lang: str) -> list[Post]:
|
|
156
|
+
"""Retrieve posts filtered by tag and language.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
tag: Tag name to filter by
|
|
160
|
+
lang: Language code (e.g., 'en', 'pl')
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
List of Post objects matching the tag and language
|
|
164
|
+
"""
|
|
77
165
|
posts_cursor = self.posts.find({"tags": tag, "language": lang})
|
|
78
|
-
return posts_cursor
|
|
166
|
+
return [Post.model_validate(post) for post in posts_cursor]
|
|
79
167
|
|
|
80
168
|
def add_comment(self, author_name: str, comment: str, post_slug: str) -> None:
|
|
169
|
+
"""Add a new comment to a post.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
author_name: Name of the comment author
|
|
173
|
+
comment: Comment text content
|
|
174
|
+
post_slug: URL-friendly identifier of the post
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
ValueError: If post not found
|
|
178
|
+
"""
|
|
81
179
|
now_utc = datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds")
|
|
82
180
|
comment_doc = {
|
|
83
181
|
"author": str(author_name),
|
|
@@ -90,36 +188,66 @@ class MongoDB(DB):
|
|
|
90
188
|
raise ValueError(f"Post with slug {post_slug} not found")
|
|
91
189
|
|
|
92
190
|
def get_logo_url(self) -> str:
|
|
191
|
+
"""Retrieve the URL of the application logo.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Logo image URL or empty string if not found
|
|
195
|
+
"""
|
|
93
196
|
site_content = self.site_content.find_one({"_id": "config"})
|
|
94
197
|
if site_content:
|
|
95
198
|
return site_content.get("logo_url", "")
|
|
96
199
|
return ""
|
|
97
200
|
|
|
98
201
|
def get_favicon_url(self) -> str:
|
|
202
|
+
"""Retrieve the URL of the application favicon.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Favicon URL or empty string if not found
|
|
206
|
+
"""
|
|
99
207
|
site_content = self.site_content.find_one({"_id": "config"})
|
|
100
208
|
if site_content:
|
|
101
209
|
return site_content.get("favicon_url", "")
|
|
102
210
|
return ""
|
|
103
211
|
|
|
104
212
|
def get_primary_color(self) -> str:
|
|
213
|
+
"""Retrieve the primary color for the application theme.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Primary color value, defaults to 'white'
|
|
217
|
+
"""
|
|
105
218
|
site_content = self.site_content.find_one({"_id": "config"})
|
|
106
219
|
if site_content:
|
|
107
220
|
return site_content.get("primary_color", "white")
|
|
108
221
|
return "white"
|
|
109
222
|
|
|
110
223
|
def get_secondary_color(self) -> str:
|
|
224
|
+
"""Retrieve the secondary color for the application theme.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Secondary color value, defaults to 'navy'
|
|
228
|
+
"""
|
|
111
229
|
site_content = self.site_content.find_one({"_id": "config"})
|
|
112
230
|
if site_content:
|
|
113
231
|
return site_content.get("secondary_color", "navy")
|
|
114
232
|
return "navy"
|
|
115
233
|
|
|
116
|
-
def get_plugins_data(self) -> list[Any]:
|
|
234
|
+
def get_plugins_data(self) -> list[dict[str, Any]]:
|
|
235
|
+
"""Retrieve configuration data for all plugins.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
List of plugin configuration dictionaries
|
|
239
|
+
"""
|
|
117
240
|
plugins_doc = self.plugins.find_one({"_id": "config"})
|
|
118
241
|
if plugins_doc and "data" in plugins_doc:
|
|
119
242
|
return plugins_doc["data"]
|
|
120
243
|
return []
|
|
121
244
|
|
|
122
245
|
def get_font(self) -> str:
|
|
246
|
+
"""Get the font configuration for the application.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Font name or empty string if not configured
|
|
250
|
+
"""
|
|
123
251
|
site_content = self.site_content.find_one({"_id": "config"})
|
|
124
252
|
if site_content:
|
|
125
253
|
return site_content.get("font", "")
|
platzky/engine.py
CHANGED
|
@@ -7,11 +7,12 @@ from flask import Blueprint, Flask, jsonify, make_response, request, session
|
|
|
7
7
|
from flask_babel import Babel
|
|
8
8
|
|
|
9
9
|
from platzky.config import Config
|
|
10
|
+
from platzky.db.db import DB
|
|
10
11
|
from platzky.models import CmsModule
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class Engine(Flask):
|
|
14
|
-
def __init__(self, config: Config, db, import_name):
|
|
15
|
+
def __init__(self, config: Config, db: DB, import_name: str) -> None:
|
|
15
16
|
super().__init__(import_name)
|
|
16
17
|
self.config.from_mapping(config.model_dump(by_alias=True))
|
|
17
18
|
self.db = db
|
|
@@ -40,7 +41,7 @@ class Engine(Flask):
|
|
|
40
41
|
for notifier in self.notifiers:
|
|
41
42
|
notifier(message)
|
|
42
43
|
|
|
43
|
-
def add_notifier(self, notifier):
|
|
44
|
+
def add_notifier(self, notifier: Callable[[str], None]) -> None:
|
|
44
45
|
self.notifiers.append(notifier)
|
|
45
46
|
|
|
46
47
|
def add_cms_module(self, module: CmsModule):
|
|
@@ -48,7 +49,7 @@ class Engine(Flask):
|
|
|
48
49
|
self.cms_modules.append(module)
|
|
49
50
|
|
|
50
51
|
# TODO login_method should be interface
|
|
51
|
-
def add_login_method(self, login_method):
|
|
52
|
+
def add_login_method(self, login_method: Callable[[], str]) -> None:
|
|
52
53
|
self.login_methods.append(login_method)
|
|
53
54
|
|
|
54
55
|
def add_dynamic_body(self, body: str):
|
platzky/models.py
CHANGED
|
@@ -160,29 +160,32 @@ class Post(BaseModel):
|
|
|
160
160
|
slug: URL-friendly unique identifier for the post
|
|
161
161
|
title: Post title
|
|
162
162
|
contentInMarkdown: Post content in Markdown format
|
|
163
|
-
comments: List of comments on this post
|
|
164
163
|
excerpt: Short summary or preview of the post
|
|
165
|
-
tags: List of tags for categorization
|
|
166
|
-
language: Language code for the post content
|
|
167
164
|
coverImage: Cover image for the post
|
|
168
|
-
|
|
165
|
+
language: Language code for the post content (defaults to 'en')
|
|
166
|
+
comments: Optional list of comments on this post
|
|
167
|
+
tags: Optional list of tags for categorization
|
|
168
|
+
date: Optional datetime when the post was published (timezone-aware recommended)
|
|
169
169
|
"""
|
|
170
170
|
|
|
171
171
|
author: str
|
|
172
172
|
slug: str
|
|
173
173
|
title: str
|
|
174
174
|
contentInMarkdown: str
|
|
175
|
-
comments: list[Comment]
|
|
176
175
|
excerpt: str
|
|
177
|
-
|
|
178
|
-
language: str
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
coverImage: Image = Field(default_factory=Image)
|
|
177
|
+
language: str = "en"
|
|
178
|
+
comments: list[Comment] = Field(default_factory=list)
|
|
179
|
+
tags: list[str] = Field(default_factory=list)
|
|
180
|
+
date: DateTimeField | None = None
|
|
181
181
|
|
|
182
182
|
def __lt__(self, other: object) -> bool:
|
|
183
183
|
"""Compare posts by date for sorting.
|
|
184
184
|
|
|
185
185
|
Uses datetime comparison to ensure robust and correct ordering.
|
|
186
|
+
Posts without dates are treated as "less than" dated posts, meaning they
|
|
187
|
+
appear last when using descending sort (reverse=True, newest-first) and
|
|
188
|
+
first when using ascending sort.
|
|
186
189
|
|
|
187
190
|
Args:
|
|
188
191
|
other: Another Post instance to compare against
|
|
@@ -192,6 +195,13 @@ class Post(BaseModel):
|
|
|
192
195
|
or NotImplemented if comparing with a non-Post object
|
|
193
196
|
"""
|
|
194
197
|
if isinstance(other, Post):
|
|
198
|
+
# Posts without dates are sorted last
|
|
199
|
+
if self.date is None and other.date is None:
|
|
200
|
+
return False
|
|
201
|
+
if self.date is None:
|
|
202
|
+
return True # None is "less than" any date (sorted last)
|
|
203
|
+
if other.date is None:
|
|
204
|
+
return False
|
|
195
205
|
return self.date < other.date
|
|
196
206
|
return NotImplemented
|
|
197
207
|
|
platzky/plugin/plugin_loader.py
CHANGED
|
@@ -2,6 +2,7 @@ import importlib.util
|
|
|
2
2
|
import inspect
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
from types import ModuleType
|
|
5
6
|
from typing import Any, Optional, Type
|
|
6
7
|
|
|
7
8
|
import deprecation
|
|
@@ -12,7 +13,7 @@ from platzky.plugin.plugin import PluginBase, PluginError
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def find_plugin(plugin_name: str) ->
|
|
16
|
+
def find_plugin(plugin_name: str) -> ModuleType:
|
|
16
17
|
"""Find plugin by name and return it as module.
|
|
17
18
|
|
|
18
19
|
Args:
|
|
@@ -33,7 +34,7 @@ def find_plugin(plugin_name: str) -> Any:
|
|
|
33
34
|
) from e
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
def _is_class_plugin(plugin_module:
|
|
37
|
+
def _is_class_plugin(plugin_module: ModuleType) -> Optional[Type[PluginBase[Any]]]:
|
|
37
38
|
"""Check if the plugin module contains a PluginBase implementation.
|
|
38
39
|
|
|
39
40
|
Args:
|
|
@@ -60,7 +61,7 @@ def _is_class_plugin(plugin_module: Any) -> Optional[Type[PluginBase[Any]]]:
|
|
|
60
61
|
),
|
|
61
62
|
)
|
|
62
63
|
def _process_legacy_plugin(
|
|
63
|
-
plugin_module:
|
|
64
|
+
plugin_module: ModuleType, app: Engine, plugin_config: dict[str, Any], plugin_name: str
|
|
64
65
|
) -> Engine:
|
|
65
66
|
"""Process a legacy plugin using the entrypoint approach.
|
|
66
67
|
|
platzky/seo/seo.py
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
+
"""Flask blueprint for SEO functionality including robots.txt and sitemap.xml."""
|
|
2
|
+
|
|
1
3
|
import typing as t
|
|
2
4
|
import urllib.parse
|
|
3
5
|
from os.path import dirname
|
|
4
6
|
|
|
5
|
-
from flask import Blueprint, current_app, make_response, render_template, request
|
|
7
|
+
from flask import Blueprint, Response, current_app, make_response, render_template, request
|
|
8
|
+
|
|
9
|
+
from platzky.db.db import DB
|
|
10
|
+
|
|
6
11
|
|
|
12
|
+
def create_seo_blueprint(
|
|
13
|
+
db: DB, config: dict[str, t.Any], locale_func: t.Callable[[], str]
|
|
14
|
+
) -> Blueprint:
|
|
15
|
+
"""Create SEO blueprint with routes for robots.txt and sitemap.xml.
|
|
7
16
|
|
|
8
|
-
|
|
17
|
+
Args:
|
|
18
|
+
db: Database instance for accessing blog content
|
|
19
|
+
config: Configuration dictionary with SEO and blog settings
|
|
20
|
+
locale_func: Function that returns the current locale/language code
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Configured Flask Blueprint for SEO functionality
|
|
24
|
+
"""
|
|
9
25
|
seo = Blueprint(
|
|
10
26
|
"seo",
|
|
11
27
|
__name__,
|
|
@@ -14,30 +30,50 @@ def create_seo_blueprint(db, config: dict[str, t.Any], locale_func: t.Callable[[
|
|
|
14
30
|
)
|
|
15
31
|
|
|
16
32
|
@seo.route("/robots.txt")
|
|
17
|
-
def robots():
|
|
33
|
+
def robots() -> Response:
|
|
34
|
+
"""Generate robots.txt file for search engine crawlers.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Text response containing robots.txt directives
|
|
38
|
+
"""
|
|
18
39
|
robots_response = render_template("robots.txt", domain=request.host, mimetype="text/plain")
|
|
19
40
|
response = make_response(robots_response)
|
|
20
41
|
response.headers["Content-Type"] = "text/plain"
|
|
21
42
|
return response
|
|
22
43
|
|
|
23
|
-
def get_blog_entries(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
for
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
def get_blog_entries(
|
|
45
|
+
host_base: str, lang: str, db: DB, blog_prefix: str
|
|
46
|
+
) -> list[dict[str, str]]:
|
|
47
|
+
"""Generate sitemap entries for all blog posts.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
host_base: Base URL of the website (e.g., 'https://example.com')
|
|
51
|
+
lang: Language code for posts to include
|
|
52
|
+
db: Database instance for accessing blog posts
|
|
53
|
+
blog_prefix: URL prefix for blog routes
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
List of dictionaries with sitemap URL entries (loc, lastmod)
|
|
57
|
+
"""
|
|
58
|
+
dynamic_urls = []
|
|
59
|
+
# TODO: Add get_list_of_posts for faster getting just list of it
|
|
60
|
+
for post in db.get_all_posts(lang):
|
|
29
61
|
slug = post.slug
|
|
30
|
-
|
|
31
|
-
|
|
62
|
+
url: dict[str, str] = {"loc": f"{host_base}{blog_prefix}/{slug}"}
|
|
63
|
+
if post.date is not None:
|
|
64
|
+
url["lastmod"] = post.date.date().isoformat()
|
|
32
65
|
dynamic_urls.append(url)
|
|
33
66
|
return dynamic_urls
|
|
34
67
|
|
|
35
|
-
@seo.route("/sitemap.xml") # TODO
|
|
36
|
-
def sitemap():
|
|
37
|
-
"""
|
|
38
|
-
|
|
68
|
+
@seo.route("/sitemap.xml") # TODO: Try to replace sitemap logic with flask-sitemap module
|
|
69
|
+
def sitemap() -> Response:
|
|
70
|
+
"""Route to dynamically generate a sitemap of your website/application.
|
|
71
|
+
|
|
39
72
|
lastmod and priority tags omitted on static pages.
|
|
40
|
-
lastmod included on dynamic content such as
|
|
73
|
+
lastmod included on dynamic content such as blog posts.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
XML response containing the sitemap
|
|
41
77
|
"""
|
|
42
78
|
lang = locale_func()
|
|
43
79
|
|
|
@@ -46,7 +82,7 @@ def create_seo_blueprint(db, config: dict[str, t.Any], locale_func: t.Callable[[
|
|
|
46
82
|
host_base = host_components.scheme + "://" + host_components.netloc
|
|
47
83
|
|
|
48
84
|
# Static routes with static content
|
|
49
|
-
static_urls =
|
|
85
|
+
static_urls = []
|
|
50
86
|
for rule in current_app.url_map.iter_rules():
|
|
51
87
|
if rule.methods is not None and "GET" in rule.methods and len(rule.arguments) == 0:
|
|
52
88
|
url = {"loc": f"{host_base}{rule!s}"}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
platzky/__init__.py,sha256=IhL91rSWxIIJQNfVsqJ1d4yY5D2WyWcefo4Xv2aX_lo,180
|
|
2
|
-
platzky/admin/admin.py,sha256=
|
|
3
|
-
platzky/admin/fake_login.py,sha256=
|
|
2
|
+
platzky/admin/admin.py,sha256=QhuxGtUjfX-xeDd_xmSChoeD5Z1UMu1jTGtUck-9jJU,1699
|
|
3
|
+
platzky/admin/fake_login.py,sha256=Z_4M4PLQ73qL-sKh05CmDx_nFy8S30PdsNfPPDeFSmE,3528
|
|
4
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
|
|
8
|
-
platzky/blog/blog.py,sha256=
|
|
9
|
-
platzky/blog/comment_form.py,sha256=
|
|
10
|
-
platzky/config.py,sha256=
|
|
8
|
+
platzky/blog/blog.py,sha256=n3bsZ1GpVCmvxCFMiF7QUDb_PHbmBiTu0GDu3r_Su24,5490
|
|
9
|
+
platzky/blog/comment_form.py,sha256=yOuXvX9PZLc6qQLIWZWLFcbwFQD4a849X82PlXKUzdk,805
|
|
10
|
+
platzky/config.py,sha256=_TQNZ8w8-xQImtm6Gw2SawBqf-UFxF9okIlZi_DGrGA,7540
|
|
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
|
|
14
|
-
platzky/db/db_loader.py,sha256=
|
|
15
|
-
platzky/db/github_json_db.py,sha256=
|
|
16
|
-
platzky/db/google_json_db.py,sha256=
|
|
17
|
-
platzky/db/graph_ql_db.py,sha256=
|
|
18
|
-
platzky/db/json_db.py,sha256=
|
|
19
|
-
platzky/db/json_file_db.py,sha256=
|
|
20
|
-
platzky/db/mongodb_db.py,sha256=
|
|
21
|
-
platzky/engine.py,sha256=
|
|
13
|
+
platzky/db/db.py,sha256=gi5uxvY8Ww8O4y2rxaH1Zj_12Yno8SbILvIaWnQPbYQ,4778
|
|
14
|
+
platzky/db/db_loader.py,sha256=YgR16K5Mj5pN0vWYVQxTD4Z6ihG5fZyjbUUCS8LNqJs,999
|
|
15
|
+
platzky/db/github_json_db.py,sha256=0z-aCz7Pm6Al--SbIHx4T_FyzwfwQcZDqBduTCOE5_A,3314
|
|
16
|
+
platzky/db/google_json_db.py,sha256=RnvirGFo5a41vkd1iD-M4-HwHlWDZ19EqpF0vIer_Xo,3049
|
|
17
|
+
platzky/db/graph_ql_db.py,sha256=a8LGPJKoNpmTkJ6Bb89eg6m6Q9GFetp5z8A0xuemuSk,14504
|
|
18
|
+
platzky/db/json_db.py,sha256=pANXJZzVPAO890TRy3IvzHjpCaeDgNNgNOR5Uhkk2h4,8078
|
|
19
|
+
platzky/db/json_file_db.py,sha256=Tl6b67p4hNViXSAjujXZ9vtVHN61QhrzJUgOuvngyMI,2232
|
|
20
|
+
platzky/db/mongodb_db.py,sha256=nq07j0NldK014qRL1mF-cvBXQF1LKKTTeWxaZdyzjAs,8595
|
|
21
|
+
platzky/engine.py,sha256=9Y74nrO4gwr9_CqRTzPs9LtcY2t0fs0XtPyjPiqRlVQ,5573
|
|
22
22
|
platzky/locale/en/LC_MESSAGES/messages.po,sha256=WaZGlFAegKRq7CSz69dWKic-mKvQFhVvssvExxNmGaU,1400
|
|
23
23
|
platzky/locale/pl/LC_MESSAGES/messages.po,sha256=sUPxMKDeEOoZ5UIg94rGxZD06YVWiAMWIby2XE51Hrc,1624
|
|
24
|
-
platzky/models.py,sha256=
|
|
24
|
+
platzky/models.py,sha256=Ws5ZSWf5EhcpFxl3Yeze2pQiesjnjAA_haBJ90bN6lk,7435
|
|
25
25
|
platzky/platzky.py,sha256=1LKYq8pLm1QBlOcEPhugxWi8W0vuWqjjINIFK8b2Kow,9319
|
|
26
26
|
platzky/plugin/plugin.py,sha256=KZb6VEph__lx9xrv5Ay4h4XkFFYbodV5OimaG6B9IDc,2812
|
|
27
|
-
platzky/plugin/plugin_loader.py,sha256=
|
|
28
|
-
platzky/seo/seo.py,sha256=
|
|
27
|
+
platzky/plugin/plugin_loader.py,sha256=eKG6zodUCkiRLxJ2ZX9zdN4-ZrZ9EwssoY1SDtThaFo,6707
|
|
28
|
+
platzky/seo/seo.py,sha256=cguipDKHYBd0J4M8q8t5QNk3bmUIavXp9V3Ugv0UouU,3854
|
|
29
29
|
platzky/static/blog.css,sha256=TrppzgQbj4UtuTufDCdblyNTVAqgIbhD66Cziyv_xnY,7893
|
|
30
30
|
platzky/static/styles.css,sha256=U5ddGIK-VcGRJZ3BdOpMp0pR__k6rNEMsuQXkP4tFQ0,686
|
|
31
31
|
platzky/telemetry.py,sha256=iXYvEt0Uw5Hx8lAxyr45dpQ_SiE2NxmJkoSx-JSRJyM,5011
|
|
@@ -41,7 +41,7 @@ platzky/templates/post.html,sha256=GSgjIZsOQKtNx3cEbquSjZ5L4whPnG6MzRyoq9k4B8Q,1
|
|
|
41
41
|
platzky/templates/robots.txt,sha256=2_j2tiYtYJnzZUrANiX9pvBxyw5Dp27fR_co18BPEJ0,116
|
|
42
42
|
platzky/templates/sitemap.xml,sha256=iIJZ91_B5ZuNLCHsRtsGKZlBAXojOTP8kffqKLacgvs,578
|
|
43
43
|
platzky/www_handler.py,sha256=pF6Rmvem1sdVqHD7z3RLrDuG-CwAqfGCti50_NPsB2w,725
|
|
44
|
-
platzky-1.1.
|
|
45
|
-
platzky-1.1.
|
|
46
|
-
platzky-1.1.
|
|
47
|
-
platzky-1.1.
|
|
44
|
+
platzky-1.2.1.dist-info/METADATA,sha256=0LIO-7G1YnqVwinorr24GPlOBBE0RPIR_Q99FfuvrkA,2556
|
|
45
|
+
platzky-1.2.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
46
|
+
platzky-1.2.1.dist-info/licenses/LICENSE,sha256=wCdfk-qEosi6BDwiBulMfKMi0hxp1UXV0DdjLrRm788,1077
|
|
47
|
+
platzky-1.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|