1
|
|
|
import os |
2
|
|
|
import logging |
3
|
|
|
from flask import Flask, render_template |
4
|
|
|
from flask_babel import Babel |
5
|
|
|
from docutils.core import publish_parts |
6
|
|
|
|
7
|
|
|
from groundwork.patterns import GwBasePattern |
8
|
|
|
|
9
|
|
|
from groundwork_web.patterns.gw_web_pattern.server import ServerManagerApplication, ServerManagerPlugin |
10
|
|
|
from groundwork_web.patterns.gw_web_pattern.context import ContextManagerApplication, ContextManagerPlugin |
11
|
|
|
from groundwork_web.patterns.gw_web_pattern.route import RouteManagerApplication, RouteManagerPlugin |
12
|
|
|
from groundwork_web.patterns.gw_web_pattern.menu import MenuApplication, MenuPlugin |
13
|
|
|
|
14
|
|
|
|
15
|
|
|
class GwWebPattern(GwBasePattern): |
16
|
|
|
|
17
|
|
|
def __init__(self, *args, **kwargs): |
18
|
|
|
super(GwWebPattern, self).__init__(*args, **kwargs) |
19
|
|
|
if not hasattr(self.app, "web"): |
20
|
|
|
self.app.web = WebApplication(self.app) |
21
|
|
|
|
22
|
|
|
#: Instance of :class:`~.WebPlugin`. |
23
|
|
|
#: Provides functions to manage web based objects |
24
|
|
|
self.web = WebPlugin(self) |
25
|
|
|
|
26
|
|
|
|
27
|
|
|
class WebPlugin: |
28
|
|
|
def __init__(self, plugin): |
29
|
|
|
self.plugin = plugin |
30
|
|
|
self.app = plugin.app |
31
|
|
|
self.log = plugin.log |
32
|
|
|
|
33
|
|
|
self.servers = ServerManagerPlugin(plugin) |
34
|
|
|
self.contexts = ContextManagerPlugin(plugin) |
35
|
|
|
self.routes = RouteManagerPlugin(plugin) |
36
|
|
|
self.menus = MenuPlugin(plugin) |
37
|
|
|
|
38
|
|
|
self.plugin.signals.connect(receiver="%s_gw_web_activation" % self.plugin.name, |
39
|
|
|
signal="plugin_activate_pre", |
40
|
|
|
function=self.__activate_gw_web, |
41
|
|
|
description="Initialised gw_web (flask) during plugin activation " |
42
|
|
|
"of %s" % self.plugin.name, |
43
|
|
|
sender=self.plugin) |
44
|
|
|
|
45
|
|
|
@property |
46
|
|
|
def flask(self): |
47
|
|
|
return self.app.web.flask |
48
|
|
|
|
49
|
|
|
@flask.setter |
50
|
|
|
def flask(self, value): |
51
|
|
|
self.app.web.flask = value |
52
|
|
|
|
53
|
|
|
def __activate_gw_web(self, plugin, *args, **kwargs): |
54
|
|
|
self.app.web.init_flask() |
55
|
|
|
# We need to send "gw_web_loaded" every time a plugin got activated with inherited GwWebPattern. |
56
|
|
|
# We can not send it only once during the "real/final" flask initialisation, because a plugin may be loaded |
57
|
|
|
# later and was not able to register a receiver for "gw_web_loaded" before |
58
|
|
|
# the "real/final" flask init execution. |
59
|
|
|
# So "gw_web_loaded" indicates that gw_web is ready for usage. Even if it was called x times. |
60
|
|
|
# The plugin, which has registered a receiver, is responsible for not executing related code each time |
61
|
|
|
# "gw_web_loaded" is send. |
62
|
|
|
self.app.signals.send("gw_web_loaded", self.app) |
63
|
|
|
|
64
|
|
|
def render(self, template, plugin=None, **kwargs): |
65
|
|
|
if plugin is None: |
66
|
|
|
plugin = self.plugin |
67
|
|
|
|
68
|
|
|
return self.app.web.render(template, plugin, **kwargs) |
69
|
|
|
|
70
|
|
|
|
71
|
|
|
class WebApplication: |
72
|
|
|
def __init__(self, app): |
73
|
|
|
self.app = app |
74
|
|
|
self.log = logging.getLogger(__name__) |
75
|
|
|
self.flask = None |
76
|
|
|
self.flask_babel = None |
77
|
|
|
|
78
|
|
|
self.servers = ServerManagerApplication(app) |
79
|
|
|
self.contexts = ContextManagerApplication(app) |
80
|
|
|
self.routes = RouteManagerApplication(app) |
81
|
|
|
self.menus = MenuApplication(app) |
82
|
|
|
|
83
|
|
|
self.app.signals.register("gw_web_loaded", self.app, "Indicates that gw_web (incl. flask) was loaded correctly" |
84
|
|
|
"Mostly used to load other flask extensions after this.") |
85
|
|
|
|
86
|
|
|
def init_flask(self): |
87
|
|
|
""" |
88
|
|
|
Initialises and configures flask, if not already done. |
89
|
|
|
:return: None |
90
|
|
|
""" |
91
|
|
|
if self.flask is None: |
92
|
|
|
self.flask = Flask(self.app.name) |
93
|
|
|
|
94
|
|
|
# Inject send_signal() to jinja templates |
95
|
|
|
# Use it like {{ send_signal("my_signal") }} |
96
|
|
|
self.flask.jinja_env.globals.update(send_signal=self.app.signals.send) |
97
|
|
|
|
98
|
|
|
self.flask.jinja_env.globals.update(app=self.app) |
99
|
|
|
|
100
|
|
|
self.flask.jinja_env.globals.update(get_menu=self.__get_menu) |
101
|
|
|
self.flask.jinja_env.globals.update(get_config=self.app.config.get) |
102
|
|
|
self.flask.jinja_env.globals.update(rst2html=self.__rst2html) |
103
|
|
|
|
104
|
|
|
# Adds {%break/continue %} statement to jinja |
105
|
|
|
self.flask.jinja_env.add_extension('jinja2.ext.loopcontrols') |
106
|
|
|
|
107
|
|
|
# Lets set the secret key for flask. This should be set in configuration files, so that |
108
|
|
|
# signed cookies are still valid if the server got restarted. |
109
|
|
|
# If there is no such parameter available, we create a temporary key, which is only |
110
|
|
|
# available during server runtime. |
111
|
|
|
self.flask.secret_key = self.app.config.get("FLASK_SECRET_KEY", os.urandom(24)) |
112
|
|
|
|
113
|
|
|
for key, value in self.app.config.__dict__.items(): |
114
|
|
|
if key.startswith("FLASK_"): |
115
|
|
|
self.flask.config[key[6:]] = value |
116
|
|
|
|
117
|
|
|
self.flask.config["SERVER_NAME"] = self.app.config.get("FLASK_SERVER_NAME", "127.0.0.1:5000") |
118
|
|
|
self.log.info("Using FLASK_SERVER_NAME=%s" % self.flask.config.get("SERVER_NAME")) |
119
|
|
|
|
120
|
|
|
# Load flask extensions |
121
|
|
|
self.flask_babel = Babel(self.flask) |
122
|
|
|
|
123
|
|
|
def __get_menu(self, cluster="base"): |
124
|
|
|
return self.menus.get(cluster=cluster) |
125
|
|
|
|
126
|
|
|
def __rst2html(self, document, part="body"): |
127
|
|
|
if document is not None and type(document) == str: |
128
|
|
|
doc_rendered = publish_parts(document, writer_name="html") |
129
|
|
|
if part not in doc_rendered.keys(): |
130
|
|
|
raise KeyError("%s is not a valid key for part parameter of rst2html.\nValid options: " % |
131
|
|
|
(part, ",".join(doc_rendered.keys()))) |
132
|
|
|
return doc_rendered[part] |
133
|
|
|
return document |
134
|
|
|
|
135
|
|
|
def render(self, template, plugin=None, **kwargs): |
136
|
|
|
""" |
137
|
|
|
Renders a template and returns a strings, which represents the rendered data. |
138
|
|
|
|
139
|
|
|
Internally render_template() from flask is used. |
140
|
|
|
|
141
|
|
|
:param template: Name of the template |
142
|
|
|
:param kwargs: Optional key-word arguments, which get passed to the template engine. |
143
|
|
|
:return: Rendered template as string |
144
|
|
|
""" |
145
|
|
|
|
146
|
|
|
return render_template(template, app=self.app, plugin=plugin, **kwargs) |
147
|
|
|
|