platzky 0.1.18__py3-none-any.whl → 0.2.0__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/blog/__init__.py +0 -0
- platzky/blog/blog.py +72 -42
- platzky/blog/comment_form.py +8 -4
- platzky/config.py +63 -57
- platzky/db/__init__.py +0 -0
- platzky/db/db.py +107 -0
- platzky/db/db_loader.py +32 -0
- platzky/db/google_json_db.py +33 -10
- platzky/db/graph_ql_db.py +117 -27
- platzky/db/json_db.py +83 -19
- platzky/db/json_file_db.py +25 -9
- platzky/models.py +64 -0
- platzky/platzky.py +122 -61
- platzky/plugin_loader.py +31 -34
- platzky/plugins/redirections/entrypoint.py +41 -19
- platzky/plugins/sendmail/entrypoint.py +32 -11
- platzky/seo/seo.py +37 -26
- platzky/static/blog.css +5 -12
- platzky/templates/base.html +137 -22
- platzky/templates/blog.html +3 -2
- platzky/templates/body_meta.html +4 -21
- platzky/templates/feed.xml +5 -5
- platzky/templates/head_meta.html +5 -15
- platzky/templates/page.html +2 -2
- platzky/templates/post.html +2 -2
- platzky/www_handler.py +7 -4
- platzky-0.2.0.dist-info/METADATA +39 -0
- platzky-0.2.0.dist-info/RECORD +34 -0
- {platzky-0.1.18.dist-info → platzky-0.2.0.dist-info}/WHEEL +1 -1
- platzky/blog/db.py +0 -18
- platzky/blog/post_formatter.py +0 -16
- platzky/db_loader.py +0 -25
- platzky-0.1.18.dist-info/METADATA +0 -39
- platzky-0.1.18.dist-info/RECORD +0 -32
platzky/db/graph_ql_db.py
CHANGED
|
@@ -1,22 +1,59 @@
|
|
|
1
|
-
#TODO rename file, extract it to another library, remove qgl and aiohttp from dependencies
|
|
1
|
+
# TODO rename file, extract it to another library, remove qgl and aiohttp from dependencies
|
|
2
2
|
|
|
3
|
-
from gql import gql, Client
|
|
4
|
-
from gql.transport.aiohttp import AIOHTTPTransport
|
|
5
3
|
import json
|
|
6
|
-
from platzky.blog.db import DB
|
|
7
4
|
|
|
5
|
+
from gql import Client, gql
|
|
6
|
+
from gql.transport.aiohttp import AIOHTTPTransport
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from .db import DB, DBConfig
|
|
10
|
+
from ..models import Color, Post
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def db_config_type():
|
|
14
|
+
return GraphQlDbConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GraphQlDbConfig(DBConfig):
|
|
18
|
+
endpoint: str = Field(alias="CMS_ENDPOINT")
|
|
19
|
+
token: str = Field(alias="CMS_TOKEN")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_db(config: GraphQlDbConfig):
|
|
23
|
+
return GraphQL(config.endpoint, config.token)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def db_from_config(config: GraphQlDbConfig):
|
|
27
|
+
return GraphQL(config.endpoint, config.token)
|
|
8
28
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
29
|
+
|
|
30
|
+
def _standarize_post(post):
|
|
31
|
+
return {
|
|
32
|
+
"author": post["author"]["name"],
|
|
33
|
+
"slug": post["slug"],
|
|
34
|
+
"title": post["title"],
|
|
35
|
+
"excerpt": post["excerpt"],
|
|
36
|
+
"contentInMarkdown": post["contentInRichText"]["html"],
|
|
37
|
+
"comments": post["comments"],
|
|
38
|
+
"tags": post["tags"],
|
|
39
|
+
"language": post["language"],
|
|
40
|
+
"coverImage": {
|
|
41
|
+
"url": post["coverImage"]["image"]["url"],
|
|
42
|
+
},
|
|
43
|
+
"date": post["date"],
|
|
44
|
+
}
|
|
13
45
|
|
|
14
46
|
|
|
15
47
|
class GraphQL(DB):
|
|
16
48
|
def __init__(self, endpoint, token):
|
|
17
|
-
|
|
18
|
-
|
|
49
|
+
self.module_name = "graph_ql_db"
|
|
50
|
+
self.db_name = "GraphQLDb"
|
|
51
|
+
full_token = "bearer " + token
|
|
52
|
+
transport = AIOHTTPTransport(
|
|
53
|
+
url=endpoint, headers={"Authorization": full_token}
|
|
54
|
+
)
|
|
19
55
|
self.client = Client(transport=transport)
|
|
56
|
+
super().__init__()
|
|
20
57
|
|
|
21
58
|
def get_all_posts(self, lang):
|
|
22
59
|
all_posts = gql(
|
|
@@ -24,22 +61,38 @@ class GraphQL(DB):
|
|
|
24
61
|
query MyQuery($lang: Lang!) {
|
|
25
62
|
posts(where: {language: $lang}, orderBy: date_DESC, stage: PUBLISHED){
|
|
26
63
|
createdAt
|
|
64
|
+
author {
|
|
65
|
+
name
|
|
66
|
+
}
|
|
67
|
+
contentInRichText {
|
|
68
|
+
html
|
|
69
|
+
}
|
|
70
|
+
comments {
|
|
71
|
+
comment
|
|
72
|
+
author
|
|
73
|
+
createdAt
|
|
74
|
+
}
|
|
27
75
|
date
|
|
28
76
|
title
|
|
29
77
|
excerpt
|
|
30
78
|
slug
|
|
31
79
|
tags
|
|
80
|
+
language
|
|
32
81
|
coverImage {
|
|
33
82
|
alternateText
|
|
34
83
|
image {
|
|
35
84
|
url
|
|
36
85
|
}
|
|
37
|
-
}
|
|
86
|
+
}
|
|
38
87
|
}
|
|
39
88
|
}
|
|
40
89
|
"""
|
|
41
90
|
)
|
|
42
|
-
|
|
91
|
+
raw_ql_posts = self.client.execute(all_posts, variable_values={"lang": lang})[
|
|
92
|
+
"posts"
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
return [Post.model_validate(_standarize_post(post)) for post in raw_ql_posts]
|
|
43
96
|
|
|
44
97
|
def get_menu_items(self):
|
|
45
98
|
menu_items = gql(
|
|
@@ -52,17 +105,23 @@ class GraphQL(DB):
|
|
|
52
105
|
}
|
|
53
106
|
"""
|
|
54
107
|
)
|
|
55
|
-
return self.client.execute(menu_items)[
|
|
108
|
+
return self.client.execute(menu_items)["menuItems"]
|
|
56
109
|
|
|
57
110
|
def get_post(self, slug):
|
|
58
111
|
post = gql(
|
|
59
112
|
"""
|
|
60
113
|
query MyQuery($slug: String!) {
|
|
61
114
|
post(where: {slug: $slug}, stage: PUBLISHED) {
|
|
115
|
+
date
|
|
116
|
+
language
|
|
62
117
|
title
|
|
118
|
+
slug
|
|
119
|
+
author {
|
|
120
|
+
name
|
|
121
|
+
}
|
|
63
122
|
contentInRichText {
|
|
64
|
-
text
|
|
65
123
|
markdown
|
|
124
|
+
html
|
|
66
125
|
}
|
|
67
126
|
excerpt
|
|
68
127
|
tags
|
|
@@ -76,13 +135,16 @@ class GraphQL(DB):
|
|
|
76
135
|
author
|
|
77
136
|
comment
|
|
78
137
|
date: createdAt
|
|
79
|
-
}
|
|
138
|
+
}
|
|
80
139
|
}
|
|
81
140
|
}
|
|
82
|
-
"""
|
|
83
|
-
|
|
141
|
+
"""
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
post_raw = self.client.execute(post, variable_values={"slug": slug})["post"]
|
|
145
|
+
return Post.model_validate(_standarize_post(post_raw))
|
|
84
146
|
|
|
85
|
-
#TODO Cleanup page logic of internationalization (now it depends on translation of slugs)
|
|
147
|
+
# TODO Cleanup page logic of internationalization (now it depends on translation of slugs)
|
|
86
148
|
def get_page(self, slug):
|
|
87
149
|
post = gql(
|
|
88
150
|
"""
|
|
@@ -96,8 +158,9 @@ class GraphQL(DB):
|
|
|
96
158
|
}
|
|
97
159
|
}
|
|
98
160
|
}
|
|
99
|
-
"""
|
|
100
|
-
|
|
161
|
+
"""
|
|
162
|
+
)
|
|
163
|
+
return self.client.execute(post, variable_values={"slug": slug})["page"]
|
|
101
164
|
|
|
102
165
|
def get_posts_by_tag(self, tag, lang):
|
|
103
166
|
post = gql(
|
|
@@ -117,8 +180,11 @@ class GraphQL(DB):
|
|
|
117
180
|
}
|
|
118
181
|
}
|
|
119
182
|
}
|
|
120
|
-
"""
|
|
121
|
-
|
|
183
|
+
"""
|
|
184
|
+
)
|
|
185
|
+
return self.client.execute(post, variable_values={"tag": tag, "lang": lang})[
|
|
186
|
+
"posts"
|
|
187
|
+
]
|
|
122
188
|
|
|
123
189
|
def get_all_providers(self):
|
|
124
190
|
all_providers = gql(
|
|
@@ -151,7 +217,7 @@ class GraphQL(DB):
|
|
|
151
217
|
"""
|
|
152
218
|
)
|
|
153
219
|
query = self.client.execute(all_questions)
|
|
154
|
-
return query[
|
|
220
|
+
return query["questions"]
|
|
155
221
|
|
|
156
222
|
def add_comment(self, author_name, comment, post_slug):
|
|
157
223
|
add_comment = gql(
|
|
@@ -167,7 +233,31 @@ class GraphQL(DB):
|
|
|
167
233
|
id
|
|
168
234
|
}
|
|
169
235
|
}
|
|
170
|
-
"""
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
236
|
+
"""
|
|
237
|
+
)
|
|
238
|
+
self.client.execute(
|
|
239
|
+
add_comment,
|
|
240
|
+
variable_values={
|
|
241
|
+
"author": author_name,
|
|
242
|
+
"comment": comment,
|
|
243
|
+
"slug": post_slug,
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def get_font(self):
|
|
248
|
+
return str("")
|
|
249
|
+
|
|
250
|
+
def get_logo_url(self):
|
|
251
|
+
return ""
|
|
252
|
+
|
|
253
|
+
def get_primary_color(self) -> Color:
|
|
254
|
+
return Color()
|
|
255
|
+
|
|
256
|
+
def get_secondary_color(self):
|
|
257
|
+
return Color()
|
|
258
|
+
|
|
259
|
+
def get_site_content(self):
|
|
260
|
+
return ""
|
|
261
|
+
|
|
262
|
+
def get_plugins_data(self):
|
|
263
|
+
return []
|
platzky/db/json_db.py
CHANGED
|
@@ -1,34 +1,72 @@
|
|
|
1
|
-
from platzky.blog.db import DB
|
|
2
1
|
import datetime
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from .db import DB, DBConfig
|
|
7
|
+
from ..models import MenuItem, Post
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def db_config_type():
|
|
11
|
+
return JsonDbConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JsonDbConfig(DBConfig):
|
|
15
|
+
data: Dict[str, Any] = Field(alias="DATA")
|
|
3
16
|
|
|
4
17
|
|
|
5
18
|
def get_db(config):
|
|
6
|
-
|
|
19
|
+
json_db_config = JsonDbConfig.model_validate(config)
|
|
20
|
+
return Json(json_db_config.data)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def db_from_config(config: JsonDbConfig):
|
|
24
|
+
return Json(config.data)
|
|
7
25
|
|
|
8
26
|
|
|
9
27
|
class Json(DB):
|
|
10
|
-
def __init__(self,
|
|
11
|
-
|
|
28
|
+
def __init__(self, data: Dict[str, Any]):
|
|
29
|
+
super().__init__()
|
|
30
|
+
self.data: Dict[str, Any] = data
|
|
31
|
+
self.module_name = "json_db"
|
|
32
|
+
self.db_name = "JsonDb"
|
|
12
33
|
|
|
13
34
|
def get_all_posts(self, lang):
|
|
14
|
-
|
|
15
|
-
|
|
35
|
+
return [
|
|
36
|
+
Post.model_validate(post)
|
|
37
|
+
for post in self.get_site_content().get("posts", ())
|
|
38
|
+
if post["language"] == lang
|
|
39
|
+
]
|
|
16
40
|
|
|
17
|
-
def get_post(self, slug):
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
def get_post(self, slug: str) -> Post:
|
|
42
|
+
"""Returns a post matching the given slug."""
|
|
43
|
+
all_posts = self.get_site_content().get("posts")
|
|
44
|
+
if all_posts is None:
|
|
45
|
+
raise ValueError("Posts data is missing")
|
|
46
|
+
wanted_post = next((post for post in all_posts if post["slug"] == slug), None)
|
|
47
|
+
if wanted_post is None:
|
|
48
|
+
raise ValueError(f"Post with slug {slug} not found")
|
|
49
|
+
return Post.model_validate(wanted_post)
|
|
20
50
|
|
|
51
|
+
# TODO: add test for non-existing page
|
|
21
52
|
def get_page(self, slug):
|
|
22
|
-
|
|
23
|
-
|
|
53
|
+
list_of_pages = (
|
|
54
|
+
page
|
|
55
|
+
for page in self.get_site_content().get("pages")
|
|
56
|
+
if page["slug"] == slug
|
|
57
|
+
)
|
|
58
|
+
page = Post.model_validate(next(list_of_pages))
|
|
59
|
+
return page
|
|
24
60
|
|
|
25
|
-
def get_menu_items(self):
|
|
26
|
-
|
|
27
|
-
|
|
61
|
+
def get_menu_items(self) -> list[MenuItem]:
|
|
62
|
+
menu_items_raw = self.get_site_content().get("menu_items", [])
|
|
63
|
+
menu_items_list = [MenuItem.model_validate(x) for x in menu_items_raw]
|
|
64
|
+
return menu_items_list
|
|
28
65
|
|
|
29
66
|
def get_posts_by_tag(self, tag, lang):
|
|
30
|
-
|
|
31
|
-
|
|
67
|
+
return (
|
|
68
|
+
post for post in self.get_site_content()["posts"] if tag in post["tags"]
|
|
69
|
+
)
|
|
32
70
|
|
|
33
71
|
def get_all_providers(self):
|
|
34
72
|
return self.data["providers"]
|
|
@@ -36,11 +74,37 @@ class Json(DB):
|
|
|
36
74
|
def get_all_questions(self):
|
|
37
75
|
return self.data["questions"]
|
|
38
76
|
|
|
77
|
+
def get_site_content(self):
|
|
78
|
+
content = self.data.get("site_content")
|
|
79
|
+
if content is None:
|
|
80
|
+
raise Exception("Content should not be None")
|
|
81
|
+
return content
|
|
82
|
+
|
|
83
|
+
def get_logo_url(self):
|
|
84
|
+
return self.get_site_content().get("logo_url", "")
|
|
85
|
+
|
|
86
|
+
def get_font(self) -> str:
|
|
87
|
+
return self.get_site_content().get("font", "")
|
|
88
|
+
|
|
89
|
+
def get_primary_color(self):
|
|
90
|
+
return self.get_site_content().get("primary_color", "white")
|
|
91
|
+
|
|
92
|
+
def get_secondary_color(self):
|
|
93
|
+
return self.get_site_content().get("secondary_color", "navy")
|
|
94
|
+
|
|
39
95
|
def add_comment(self, author_name, comment, post_slug):
|
|
40
96
|
comment = {
|
|
41
97
|
"author": str(author_name),
|
|
42
98
|
"comment": str(comment),
|
|
43
|
-
"date": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
|
99
|
+
"date": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
|
|
44
100
|
}
|
|
45
|
-
|
|
46
|
-
|
|
101
|
+
|
|
102
|
+
post_index = next(
|
|
103
|
+
i
|
|
104
|
+
for i in range(len(self.get_site_content()["posts"]))
|
|
105
|
+
if self.get_site_content()["posts"][i]["slug"] == post_slug
|
|
106
|
+
)
|
|
107
|
+
self.get_site_content()["posts"][post_index]["comments"].append(comment)
|
|
108
|
+
|
|
109
|
+
def get_plugins_data(self):
|
|
110
|
+
return self.data.get("plugins", [])
|
platzky/db/json_file_db.py
CHANGED
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import os.path
|
|
3
2
|
|
|
4
|
-
from
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from .db import DBConfig
|
|
6
|
+
from .json_db import Json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def db_config_type():
|
|
10
|
+
return JsonFileDbConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JsonFileDbConfig(DBConfig):
|
|
14
|
+
path: str = Field(alias="PATH")
|
|
5
15
|
|
|
6
16
|
|
|
7
17
|
def get_db(config):
|
|
8
|
-
|
|
9
|
-
return JsonFile(
|
|
18
|
+
json_file_db_config = JsonFileDbConfig.model_validate(config)
|
|
19
|
+
return JsonFile(json_file_db_config.path)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def db_from_config(config: JsonFileDbConfig):
|
|
23
|
+
return JsonFile(config.path)
|
|
10
24
|
|
|
11
25
|
|
|
12
26
|
class JsonFile(Json):
|
|
13
|
-
def __init__(self,
|
|
14
|
-
self.data_file_path =
|
|
27
|
+
def __init__(self, path: str):
|
|
28
|
+
self.data_file_path = path
|
|
15
29
|
with open(self.data_file_path) as json_file:
|
|
16
30
|
data = json.load(json_file)
|
|
17
31
|
super().__init__(data)
|
|
32
|
+
self.module_name = "json_file_db"
|
|
33
|
+
self.db_name = "JsonFileDb"
|
|
18
34
|
|
|
19
|
-
def
|
|
20
|
-
with open(self.data_file_path,
|
|
35
|
+
def __save_file(self):
|
|
36
|
+
with open(self.data_file_path, "w") as json_file:
|
|
21
37
|
json.dump(self.data, json_file)
|
|
22
38
|
|
|
23
39
|
def add_comment(self, author_name, comment, post_slug):
|
|
24
40
|
super().add_comment(author_name, comment, post_slug)
|
|
25
|
-
self.
|
|
41
|
+
self.__save_file()
|
platzky/models.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
import datetime
|
|
3
|
+
import humanize
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Image(BaseModel):
|
|
7
|
+
url: str = ""
|
|
8
|
+
alternateText: str = ""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MenuItem(BaseModel):
|
|
12
|
+
name: str
|
|
13
|
+
url: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Comment(BaseModel):
|
|
17
|
+
author: str
|
|
18
|
+
comment: str
|
|
19
|
+
date: str # TODO change its type to datetime
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def time_delta(self) -> str:
|
|
23
|
+
now = datetime.datetime.now()
|
|
24
|
+
date = datetime.datetime.strptime(self.date.split(".")[0], "%Y-%m-%dT%H:%M:%S")
|
|
25
|
+
return humanize.naturaltime(now - date)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Post(BaseModel):
|
|
29
|
+
author: str
|
|
30
|
+
slug: str
|
|
31
|
+
title: str
|
|
32
|
+
contentInMarkdown: str
|
|
33
|
+
comments: list[Comment]
|
|
34
|
+
excerpt: str
|
|
35
|
+
tags: list[str]
|
|
36
|
+
language: str
|
|
37
|
+
coverImage: Image
|
|
38
|
+
date: str
|
|
39
|
+
|
|
40
|
+
def __lt__(self, other):
|
|
41
|
+
if isinstance(other, Post):
|
|
42
|
+
return self.date < other.date
|
|
43
|
+
return NotImplemented
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Page = Post
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Color(BaseModel):
|
|
50
|
+
def __init__(self, r: int = 0, g: int = 0, b: int = 0, a: int = 255):
|
|
51
|
+
if not (0 <= r <= 255):
|
|
52
|
+
raise ValueError("r must be between 0 and 255")
|
|
53
|
+
if not (0 <= g <= 255):
|
|
54
|
+
raise ValueError("g must be between 0 and 255")
|
|
55
|
+
if not (0 <= b <= 255):
|
|
56
|
+
raise ValueError("b must be between 0 and 255")
|
|
57
|
+
if not (0 <= a <= 255):
|
|
58
|
+
raise ValueError("a must be between 0 and 255")
|
|
59
|
+
super().__init__(r=r, g=g, b=b, a=a)
|
|
60
|
+
|
|
61
|
+
r: int
|
|
62
|
+
g: int
|
|
63
|
+
b: int
|
|
64
|
+
a: int
|
platzky/platzky.py
CHANGED
|
@@ -1,93 +1,154 @@
|
|
|
1
|
-
|
|
1
|
+
import typing as t
|
|
2
|
+
import urllib.parse
|
|
3
|
+
|
|
4
|
+
from flask import Flask, redirect, render_template, request, session
|
|
2
5
|
from flask_babel import Babel
|
|
3
6
|
from flask_minify import Minify
|
|
4
|
-
from flaskext.markdown import Markdown
|
|
5
|
-
import os
|
|
6
|
-
import urllib.parse
|
|
7
7
|
|
|
8
|
-
from . import config, db_loader
|
|
9
8
|
from .blog import blog
|
|
9
|
+
from .config import (
|
|
10
|
+
Config,
|
|
11
|
+
languages_dict,
|
|
12
|
+
)
|
|
13
|
+
from .db.db_loader import get_db
|
|
10
14
|
from .plugin_loader import plugify
|
|
11
15
|
from .seo import seo
|
|
12
|
-
from .www_handler import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
16
|
+
from .www_handler import redirect_nonwww_to_www, redirect_www_to_nonwww
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Engine(Flask):
|
|
20
|
+
def __init__(self, config: Config, db, import_name):
|
|
21
|
+
super().__init__(import_name)
|
|
22
|
+
self.config.from_mapping(config.model_dump(by_alias=True))
|
|
23
|
+
self.db = db
|
|
24
|
+
self.notifiers = []
|
|
25
|
+
self.dynamic_body = ""
|
|
26
|
+
self.dynamic_head = ""
|
|
27
|
+
babel_translation_directories = ";".join(config.translation_directories)
|
|
28
|
+
self.babel = Babel(
|
|
29
|
+
self,
|
|
30
|
+
locale_selector=self.get_locale,
|
|
31
|
+
default_translation_directories=babel_translation_directories,
|
|
32
|
+
)
|
|
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_dynamic_body(self, body: str):
|
|
42
|
+
self.dynamic_body += body
|
|
43
|
+
|
|
44
|
+
def add_dynamic_head(self, body: str):
|
|
45
|
+
self.dynamic_head += body
|
|
46
|
+
|
|
47
|
+
def get_locale(self) -> str:
|
|
48
|
+
domain = request.headers["Host"]
|
|
49
|
+
domain_to_lang = self.config.get("DOMAIN_TO_LANG")
|
|
50
|
+
|
|
51
|
+
languages = self.config.get("LANGUAGES", {}).keys()
|
|
52
|
+
backup_lang = session.get(
|
|
53
|
+
"language",
|
|
54
|
+
request.accept_languages.best_match(languages, "en"),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if domain_to_lang:
|
|
58
|
+
lang = domain_to_lang.get(domain, backup_lang)
|
|
59
|
+
else:
|
|
60
|
+
lang = backup_lang
|
|
39
61
|
|
|
62
|
+
session["language"] = lang
|
|
63
|
+
return lang
|
|
40
64
|
|
|
41
|
-
def create_engine(config_object):
|
|
42
|
-
app = Flask(__name__)
|
|
43
|
-
Markdown(app)
|
|
44
|
-
app.config.from_mapping(config_object.asdict())
|
|
45
65
|
|
|
46
|
-
|
|
47
|
-
app
|
|
48
|
-
app.babel = Babel(app)
|
|
49
|
-
languages = app.config["LANGUAGES"]
|
|
50
|
-
domain_langs = app.config["DOMAIN_TO_LANG"]
|
|
66
|
+
def create_engine(config: Config, db) -> Engine:
|
|
67
|
+
app = Engine(config, db, __name__)
|
|
51
68
|
|
|
52
69
|
@app.before_request
|
|
53
70
|
def handle_www_redirection():
|
|
54
|
-
if
|
|
71
|
+
if config.use_www:
|
|
55
72
|
return redirect_nonwww_to_www()
|
|
56
73
|
else:
|
|
57
74
|
return redirect_www_to_nonwww()
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
request.accept_languages.best_match(languages.keys(), 'en')))
|
|
65
|
-
session['language'] = lang
|
|
66
|
-
return lang
|
|
67
|
-
|
|
68
|
-
def get_langs_domain(lang):
|
|
69
|
-
return languages.get(lang).get('domain')
|
|
76
|
+
def get_langs_domain(lang: str) -> t.Optional[str]:
|
|
77
|
+
lang_cfg = config.languages.get(lang)
|
|
78
|
+
if lang_cfg is None:
|
|
79
|
+
return None
|
|
80
|
+
return lang_cfg.domain
|
|
70
81
|
|
|
71
|
-
@app.route(
|
|
82
|
+
@app.route("/lang/<string:lang>", methods=["GET"])
|
|
72
83
|
def change_language(lang):
|
|
73
84
|
if new_domain := get_langs_domain(lang):
|
|
74
85
|
return redirect("http://" + new_domain, code=301)
|
|
75
86
|
else:
|
|
76
|
-
session[
|
|
87
|
+
session["language"] = lang
|
|
77
88
|
return redirect(request.referrer)
|
|
78
89
|
|
|
90
|
+
@app.route("/logo", methods=["GET"])
|
|
91
|
+
def logo():
|
|
92
|
+
return redirect(
|
|
93
|
+
"https://www.problematy.pl/wp-content/uploads/2023/08/kolor_poziom.png"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def url_link(x: str) -> str:
|
|
97
|
+
return urllib.parse.quote(x, safe="")
|
|
98
|
+
|
|
79
99
|
@app.context_processor
|
|
80
100
|
def utils():
|
|
101
|
+
locale = app.get_locale()
|
|
102
|
+
flag = lang.flag if (lang := config.languages.get(locale)) is not None else ""
|
|
81
103
|
return {
|
|
82
|
-
"app_name":
|
|
83
|
-
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
104
|
+
"app_name": config.app_name,
|
|
105
|
+
"languages": languages_dict(config.languages),
|
|
106
|
+
"current_flag": flag,
|
|
107
|
+
"current_language": locale,
|
|
108
|
+
"url_link": url_link,
|
|
109
|
+
"menu_items": app.db.get_menu_items(),
|
|
110
|
+
"logo_url": app.db.get_logo_url(),
|
|
111
|
+
"font": app.db.get_font(),
|
|
112
|
+
"primary_color": app.db.get_primary_color(),
|
|
113
|
+
"secondary_color": app.db.get_secondary_color(),
|
|
87
114
|
}
|
|
88
115
|
|
|
116
|
+
@app.context_processor
|
|
117
|
+
def dynamic_body():
|
|
118
|
+
return {"dynamic_body": app.dynamic_body}
|
|
119
|
+
|
|
120
|
+
@app.context_processor
|
|
121
|
+
def dynamic_head():
|
|
122
|
+
return {"dynamic_head": app.dynamic_head}
|
|
123
|
+
|
|
89
124
|
@app.errorhandler(404)
|
|
90
125
|
def page_not_found(e):
|
|
91
|
-
return render_template(
|
|
126
|
+
return render_template("404.html", title="404"), 404
|
|
92
127
|
|
|
93
128
|
return plugify(app)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def create_app_from_config(config: Config) -> Engine:
|
|
132
|
+
engine = create_engine_from_config(config)
|
|
133
|
+
blog_blueprint = blog.create_blog_blueprint(
|
|
134
|
+
db=engine.db,
|
|
135
|
+
blog_prefix=config.blog_prefix,
|
|
136
|
+
locale_func=engine.get_locale,
|
|
137
|
+
)
|
|
138
|
+
seo_blueprint = seo.create_seo_blueprint(db=engine.db, config=engine.config)
|
|
139
|
+
engine.register_blueprint(blog_blueprint)
|
|
140
|
+
engine.register_blueprint(seo_blueprint)
|
|
141
|
+
|
|
142
|
+
Minify(app=engine, html=True, js=True, cssless=True)
|
|
143
|
+
return engine
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def create_engine_from_config(config: Config) -> Engine:
|
|
147
|
+
"""Create an engine from a config."""
|
|
148
|
+
db = get_db(config.db)
|
|
149
|
+
return create_engine(config, db)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def create_app(config_path: str) -> Engine:
|
|
153
|
+
config = Config.parse_yaml(config_path)
|
|
154
|
+
return create_app_from_config(config)
|