__init__.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. """
  2. Backoffice user interface & charting support.
  3. """
  4. import os
  5. from flask import current_app, Flask, Blueprint, send_from_directory, request
  6. from flask_security import login_required, roles_accepted
  7. from flask_login import current_user
  8. import pandas as pd
  9. import rq_dashboard
  10. from humanize import naturaldelta
  11. from werkzeug.exceptions import Forbidden
  12. from flexmeasures.auth import policy as auth_policy
  13. from flexmeasures.auth.policy import ADMIN_ROLE, ADMIN_READER_ROLE
  14. from flexmeasures.utils.flexmeasures_inflection import (
  15. capitalize,
  16. parameterize,
  17. pluralize,
  18. )
  19. from flexmeasures.utils.time_utils import (
  20. localized_datetime_str,
  21. naturalized_datetime_str,
  22. to_utc_timestamp,
  23. )
  24. from flexmeasures.utils.app_utils import (
  25. parse_config_entry_by_account_roles,
  26. find_first_applicable_config_entry,
  27. )
  28. # The ui blueprint. It is registered with the Flask app (see app.py)
  29. flexmeasures_ui = Blueprint(
  30. "flexmeasures_ui",
  31. __name__,
  32. static_folder="static",
  33. static_url_path="/ui/static",
  34. template_folder="templates",
  35. )
  36. def register_at(app: Flask):
  37. """This can be used to register this blueprint together with other ui-related things"""
  38. from flexmeasures.ui.views.assets import AssetCrudUI
  39. from flexmeasures.ui.views.users.views import UserCrudUI
  40. from flexmeasures.ui.views.accounts import AccountCrudUI
  41. from flexmeasures.ui.views.sensors import SensorUI
  42. from flexmeasures.ui.utils.color_defaults import get_color_settings
  43. AssetCrudUI.register(app)
  44. UserCrudUI.register(app)
  45. SensorUI.register(app)
  46. AccountCrudUI.register(app)
  47. import flexmeasures.ui.views # noqa: F401 this is necessary to load the views
  48. app.register_blueprint(
  49. flexmeasures_ui
  50. ) # now registering the blueprint will affect all views
  51. register_rq_dashboard(app)
  52. # Injects Flexmeasures default colors into all templates
  53. @app.context_processor
  54. def inject_global_vars():
  55. return get_color_settings(None)
  56. @app.route("/favicon.ico")
  57. def favicon():
  58. return send_from_directory(
  59. flexmeasures_ui.static_folder,
  60. "favicon.ico",
  61. mimetype="image/vnd.microsoft.icon",
  62. )
  63. from flexmeasures.ui.error_handlers import add_html_error_views
  64. add_html_error_views(app)
  65. add_jinja_filters(app)
  66. add_jinja_variables(app)
  67. def register_rq_dashboard(app):
  68. app.config.update(
  69. RQ_DASHBOARD_REDIS_URL=[
  70. "redis://:%s@%s:%s/%s"
  71. % (
  72. app.config.get("FLEXMEASURES_REDIS_PASSWORD", ""),
  73. app.config.get("FLEXMEASURES_REDIS_URL", ""),
  74. app.config.get("FLEXMEASURES_REDIS_PORT", ""),
  75. app.config.get("FLEXMEASURES_REDIS_DB_NR", ""),
  76. ),
  77. # it is possible to add additional rq instances to this list
  78. ]
  79. )
  80. @login_required
  81. def basic_auth():
  82. """Ensure basic authorization."""
  83. return
  84. @login_required
  85. @roles_accepted(ADMIN_ROLE, ADMIN_READER_ROLE)
  86. def basic_admin_auth():
  87. """Ensure basic admin authorization."""
  88. if (
  89. current_user.has_role(ADMIN_READER_ROLE)
  90. and (request.method != "GET")
  91. and ("requeue" not in request.path)
  92. ):
  93. raise Forbidden(
  94. f"User with `{ADMIN_READER_ROLE}` role is only allowed to list/inspect tasks, queues and workers. Edition or deletion operations are forbidden."
  95. )
  96. return
  97. # Logged-in users can view queues on the demo server, but only admins can view them on other servers
  98. if app.config.get("FLEXMEASURES_ENV") != "documentation":
  99. if app.config.get("FLEXMEASURES_MODE", "") == "demo":
  100. rq_dashboard.blueprint.before_request(basic_auth)
  101. else:
  102. rq_dashboard.blueprint.before_request(basic_admin_auth)
  103. # To set template variables, use set_global_template_variables in app.py
  104. app.register_blueprint(rq_dashboard.blueprint, url_prefix="/tasks")
  105. def add_jinja_filters(app):
  106. from flexmeasures.ui.utils.view_utils import asset_icon_name, username, accountname
  107. app.jinja_env.filters["zip"] = zip # Allow zip function in templates
  108. app.jinja_env.add_extension(
  109. "jinja2.ext.do"
  110. ) # Allow expression statements (e.g. for modifying lists)
  111. app.jinja_env.filters["localized_datetime"] = localized_datetime_str
  112. app.jinja_env.filters["naturalized_datetime"] = naturalized_datetime_str
  113. app.jinja_env.filters["to_utc_timestamp"] = to_utc_timestamp
  114. app.jinja_env.filters["naturalized_timedelta"] = naturaldelta
  115. app.jinja_env.filters["capitalize"] = capitalize
  116. app.jinja_env.filters["pluralize"] = pluralize
  117. app.jinja_env.filters["parameterize"] = parameterize
  118. app.jinja_env.filters["isnull"] = pd.isnull
  119. app.jinja_env.filters["hide_nan_if_desired"] = lambda x: (
  120. ""
  121. if x in ("nan", "nan%", "NAN")
  122. and current_app.config.get("FLEXMEASURES_HIDE_NAN_IN_UI", False)
  123. else x
  124. )
  125. app.jinja_env.filters["asset_icon"] = asset_icon_name
  126. app.jinja_env.filters["username"] = username
  127. app.jinja_env.filters["accountname"] = accountname
  128. app.jinja_env.filters["parse_config_entry_by_account_roles"] = (
  129. parse_config_entry_by_account_roles
  130. )
  131. app.jinja_env.filters["find_first_applicable_config_entry"] = (
  132. find_first_applicable_config_entry
  133. )
  134. def add_jinja_variables(app):
  135. # Set variables for Jinja template context
  136. for v, d in (
  137. ("FLEXMEASURES_MODE", ""),
  138. ("FLEXMEASURES_PLATFORM_NAME", ""),
  139. ("FLEXMEASURES_MENU_LISTED_VIEWS", []),
  140. ("FLEXMEASURES_MENU_LISTED_VIEW_ICONS", {}),
  141. ("FLEXMEASURES_MENU_LISTED_VIEW_TITLES", {}),
  142. ("FLEXMEASURES_PUBLIC_DEMO_CREDENTIALS", ""),
  143. ):
  144. app.jinja_env.globals[v] = app.config.get(v, d)
  145. app.jinja_env.globals["documentation_exists"] = (
  146. True
  147. if os.path.exists(
  148. "%s/static/documentation/html/index.html" % flexmeasures_ui.root_path
  149. )
  150. else False
  151. )
  152. for role_name in (
  153. "ADMIN_ROLE",
  154. "ADMIN_READER_ROLE",
  155. "ACCOUNT_ADMIN_ROLE",
  156. "CONSULTANT_ROLE",
  157. ):
  158. app.jinja_env.globals[role_name] = auth_policy.__dict__[role_name]