Skip to content

Commit 3a36fe6

Browse files
author
Pierre
committed
Last patch for Flask++ (discontinued)
1 parent 0242fd3 commit 3a36fe6

20 files changed

Lines changed: 309 additions & 209 deletions

DOCS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -580,12 +580,13 @@ The default features of the FppSocket class are its event_context (inspired by F
580580

581581
```python
582582
from flaskpp.app.extensions import socket
583-
from flaskpp.utils.debugger import log
583+
from flaskpp.utils.logging import log
584+
584585

585586
@socket.on("my_event")
586587
async def event(
587-
sid: str, # if you did not set sid_passing to False
588-
payload: Any
588+
sid: str, # if you did not set sid_passing to False
589+
payload: Any
589590
):
590591
# here you can access:
591592
ctx = socket.event_context

src/flaskpp/app/data/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from flaskpp.modules import installed_modules
88
from flaskpp.utils import enabled
9-
from flaskpp.utils.debugger import log
9+
from flaskpp.utils.logging import debug
1010

1111
if TYPE_CHECKING:
1212
from flask import Flask
@@ -89,4 +89,4 @@ def _fix_missing(migrations: str):
8989
content = f"{import_str}\n{content}"
9090
with open(latest_file, "w", encoding="utf-8") as f:
9191
f.write(content)
92-
log("migrate", f"Fixed missing flask_security import in {latest_file}")
92+
debug(f"[MIGRATE] Fixed missing flask_security import in {latest_file}")
Lines changed: 7 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
from importlib import import_module
2-
from typing import Callable
3-
import json
4-
5-
from flaskpp.app.data import commit, _package, delete_model
6-
from flaskpp.app.data.babel import add_entry, get_entries
71
from flaskpp.babel import valid_state
82
from flaskpp.utils import enabled
9-
from flaskpp.utils.debugger import log
3+
from flaskpp.app.utils.i18n import update_translations
104
from flaskpp.exceptions import I18nError
115

6+
7+
translations: dict[str, dict[str, str]] = {}
8+
129
_msg_keys = [
1310
"NAV_BRAND",
1411
"NOT_FOUND_TITLE",
@@ -27,7 +24,7 @@
2724

2825
]
2926

30-
_translations_en = {
27+
translations["en"] = {
3128
_msg_keys[0]: "My Flask++ App",
3229
_msg_keys[1]: "Not Found",
3330
_msg_keys[2]: "We are sorry, but the requested page doesn't exist.",
@@ -45,7 +42,7 @@
4542

4643
}
4744

48-
_translations_de = {
45+
translations["de"] = {
4946
_msg_keys[0]: "Meine Flask++ App",
5047
_msg_keys[1]: "Nicht Gefunden",
5148
_msg_keys[2]: "Wir konnten die angefragte Seite leider nicht finden.",
@@ -64,93 +61,10 @@
6461
}
6562

6663

67-
def _add_entries(key: str, domain: str):
68-
add_entry("en", key, _translations_en[key], domain, False)
69-
add_entry("de", key, _translations_de[key], domain, False)
70-
71-
7264
def setup_db(domain: str = "flaskpp"):
7365
if not (enabled("EXT_BABEL") and enabled("EXT_SQLALCHEMY")):
7466
raise I18nError("To setup Flask++ base translations, you must enable EXT_BABEL and EXT_SQLALCHEMY.")
7567

7668
state = valid_state()
7769
state.fpp_fallback_domain = domain
78-
entries = get_entries(domain=domain, locale="en")
79-
80-
if entries:
81-
log("info", f"Updating Flask++ base translations...")
82-
83-
keys = [e.key for e in entries]
84-
for key in _msg_keys:
85-
if key not in keys:
86-
_add_entries(key, domain)
87-
88-
from .. import data
89-
for entry in entries:
90-
key = entry.key
91-
translations = getattr(data.noinit_translations, f"_translations_{entry.locale}", _translations_en)
92-
try:
93-
if translations[key] != entry.text:
94-
entry.text = translations[key]
95-
except KeyError:
96-
delete_model(entry, False)
97-
else:
98-
log("info", f"Setting up Flask++ translations...")
99-
100-
for key in _msg_keys:
101-
_add_entries(key, domain)
102-
103-
commit()
104-
105-
106-
def get_locale_data(locale: str) -> tuple[str, str]:
107-
if len(locale) != 2 and len(locale) != 5 or len(locale) == 5 and "_" not in locale:
108-
raise I18nError(f"Invalid locale code: {locale}")
109-
110-
if "_" in locale:
111-
locale = locale.split("_")[0]
112-
113-
try:
114-
locale_data = json.loads(
115-
(_package / "locales.json").read_text(encoding="utf-8")
116-
)
117-
except json.JSONDecodeError:
118-
raise I18nError("Failed to parse locales.json")
119-
120-
flags = locale_data.get("flags", {})
121-
names = locale_data.get("names", {})
122-
return flags.get(locale, "🇬🇧"), names.get(locale, "English")
123-
124-
125-
def update_translations(executor: str, msg_keys: list[str], add_entries_fn: Callable, translations_import_name: str, domain: str = None):
126-
default_domain = valid_state().domain.domain
127-
if not domain:
128-
domain = default_domain
129-
entries = get_entries(domain=domain)
130-
131-
if entries:
132-
log("info", f"[{executor}] Updating translations...")
133-
134-
keys = [e.key for e in entries]
135-
for key in msg_keys:
136-
if key not in keys:
137-
add_entries_fn(key, domain)
138-
139-
translations_module = import_module(translations_import_name)
140-
for entry in entries:
141-
key = entry.key
142-
translations = getattr(translations_module, f"_translations_{entry.locale}", _translations_en)
143-
try:
144-
if translations[key] != entry.text:
145-
entry.text = translations[key]
146-
except KeyError:
147-
if domain == default_domain:
148-
continue
149-
delete_model(entry, False)
150-
else:
151-
log("info", f"[{executor}] Setting up translations...")
152-
153-
for key in msg_keys:
154-
add_entries_fn(key, domain)
155-
156-
commit()
70+
update_translations("Flask++", __name__, domain)

src/flaskpp/app/static/js/base.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ socket.on('error', async (message) => {
215215
});
216216

217217

218+
export const initializedEvent = new Event("FPPBaseInitialized");
219+
218220
window.FPP = {
219221
showModal: showModal,
220222
hideModal: hideModal,
@@ -234,13 +236,16 @@ window.FPP = {
234236
socket: socket,
235237
emit: emit,
236238
emitAsync: emitAsync,
237-
}
238239

240+
initializedEvent: initializedEvent
241+
}
239242

240243
document.addEventListener("DOMContentLoaded", () => {
241244
document.querySelectorAll(".modal").forEach(modal => {
242245
modal.setAttribute("inert", "");
243246
hideModal(modal);
244247
bindModalCloseEvents(modal);
245248
});
246-
});
249+
250+
document.dispatchEvent(initializedEvent);
251+
});

src/flaskpp/app/utils/fst.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from pathlib import Path
22
from importlib import import_module
33
from flask_security.forms import LoginForm, RegisterFormV2
4-
from flask_mailman import EmailMessage
4+
from flask_mailman import EmailMessage, EmailMultiAlternatives
55
from threading import Thread
66
from typing import Callable, TYPE_CHECKING
77

@@ -27,7 +27,7 @@ def init_forms(app: "FlaskPP"):
2727
continue
2828

2929
try:
30-
import_module(f"modules.{p}.forms")
30+
import_module(f"modules.{p}.fst_forms")
3131
except ModuleNotFoundError:
3232
pass
3333

@@ -81,10 +81,12 @@ def build_register_form() -> type:
8181

8282

8383
def send_security_mail(msg: dict):
84-
message = EmailMessage(
84+
message = EmailMultiAlternatives(
8585
subject=msg["subject"],
8686
body=msg["body"],
8787
from_email=msg["sender"],
8888
to=[msg["recipient"]],
8989
)
90+
if "html" in msg:
91+
message.attach_alternative(msg["html"], "text/html")
9092
Thread(target=message.send).start()

src/flaskpp/app/utils/i18n.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from pathlib import Path
2+
from importlib import import_module
3+
import json
4+
5+
from flaskpp.app.data.babel import add_entry, get_entries
6+
from flaskpp.app.data import commit, delete_model
7+
from flaskpp.babel import valid_state
8+
from flaskpp.utils import enabled
9+
from flaskpp.utils.logging import log
10+
from flaskpp.exceptions import I18nError
11+
12+
13+
def _add_entries(translations: dict, key: str, domain: str):
14+
for locale, t in translations.items():
15+
if key not in t:
16+
continue
17+
add_entry(locale, key, t[key], domain, False)
18+
19+
20+
def get_locale_data(locale: str) -> tuple[str, str]:
21+
if len(locale) != 2 and len(locale) != 5 or len(locale) == 5 and "_" not in locale:
22+
raise I18nError(f"Invalid locale code: {locale}")
23+
24+
if "_" in locale:
25+
locale = locale.split("_")[0]
26+
27+
try:
28+
locale_data = json.loads(
29+
(Path(__file__).parent.parent / "data" / "locales.json").read_text(encoding="utf-8")
30+
)
31+
except json.JSONDecodeError:
32+
raise I18nError("Failed to parse locales.json")
33+
34+
flags = locale_data.get("flags", {})
35+
names = locale_data.get("names", {})
36+
return flags.get(locale, "🇬🇧"), names.get(locale, "English")
37+
38+
39+
def update_translations(executor: str, translations_import_name: str, domain: str = None):
40+
default_domain = valid_state().domain.domain
41+
if not domain:
42+
domain = default_domain
43+
entries = get_entries(domain=domain)
44+
45+
translations_module = import_module(translations_import_name)
46+
translations_dict = getattr(translations_module, "translations", {})
47+
48+
fb_translations = {}
49+
for t in translations_dict.values():
50+
fb_translations = t
51+
break
52+
msg_keys = [k for k in fb_translations]
53+
54+
if entries and enabled("I18N_AUTOUPDATE"):
55+
log(f"[{executor}] Updating translations...")
56+
57+
keys = [e.key for e in entries]
58+
for key in msg_keys:
59+
if key not in keys:
60+
_add_entries(translations_dict, key, domain)
61+
62+
63+
for entry in entries:
64+
key = entry.key
65+
translations = translations_dict.get(entry.locale, translations_dict.get("en"))
66+
if not translations:
67+
translations = fb_translations
68+
69+
try:
70+
if translations[key] != entry.text:
71+
entry.text = translations[key]
72+
except KeyError:
73+
if domain == default_domain:
74+
continue
75+
delete_model(entry, False)
76+
else:
77+
log(f"[{executor}] Setting up translations...")
78+
79+
for key in msg_keys:
80+
_add_entries(translations_dict, key, domain)
81+
82+
commit()

src/flaskpp/app/utils/mailing.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from flask import render_template, current_app
2+
from jinja2 import TemplateNotFound
3+
from flask_mailman import EmailMultiAlternatives
4+
from threading import Thread
5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from flaskpp import Module
9+
10+
11+
def _safe_render(template: str, module: "Module", context: dict) -> str:
12+
try:
13+
return render_mail_template(template, module, **context)
14+
except TemplateNotFound:
15+
return ""
16+
17+
18+
def render_mail_template(template: str, module: "Module" = None, **context) -> str:
19+
if module is None:
20+
return render_template(f"app/email/{template}", **context)
21+
return module.render_template(f"email/{template}", **context)
22+
23+
24+
def send_email(subject: str, recipient: str, email_template: str,
25+
sender_name: str = None, module: "Module" = None, **context):
26+
body = _safe_render(f"{email_template}.txt", module, context)
27+
html = _safe_render(f"{email_template}.html", module, context)
28+
29+
msg = EmailMultiAlternatives(
30+
subject=subject,
31+
body=body,
32+
to=[recipient]
33+
)
34+
msg.attach_alternative(html, "text/html")
35+
36+
if sender_name:
37+
user = current_app.config.get("MAIL_USERNAME", "noreply@example.com")
38+
msg.from_email = f"{sender_name} <{user}>"
39+
40+
Thread(target=msg.send).start()

src/flaskpp/app/utils/processing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
from markupsafe import Markup
44
from typing import Callable
55

6-
from flaskpp.app.data.noinit_translations import get_locale_data
76
from flaskpp.app.utils.translating import get_locale
87
from flaskpp.app.utils.auto_nav import build_nav
8+
from flaskpp.app.utils.i18n import get_locale_data
99
from flaskpp.utils import random_code, enabled
10-
from flaskpp.utils.debugger import log, exception
10+
from flaskpp.utils.logging import log, exception
1111

1212
_handlers = {}
1313

@@ -39,7 +39,7 @@ def _before_request():
3939
agent = request.headers.get("User-Agent")
4040
agent = agent if agent else "no-agent"
4141

42-
log("request", f"{method:4} '{path:48}' from {ip:15} via ({agent}).")
42+
log(f"[REQUEST] {method:4} '{path:50}'\t\tfrom {ip:15} via ({agent}).")
4343

4444

4545
def after_request(fn: Callable) -> Callable:

0 commit comments

Comments
 (0)