From 6c6c2328c430e627cc6263ce91151a08169e3d8d Mon Sep 17 00:00:00 2001 From: "create-issue-branch[bot]" <53036503+create-issue-branch[bot]@users.noreply.github.com> Date: Fri, 9 Apr 2021 13:43:12 +0200 Subject: [PATCH 01/24] PostMeterData endpoint broken in API v2.0 (#95) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix v2_0.postMeterData endpoint Co-authored-by: nhoening Co-authored-by: Nicolas Höning --- documentation/changelog.rst | 8 ++++++++ flexmeasures/api/v2_0/routes.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 6fba835e8..742051b87 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -16,6 +16,14 @@ Infrastructure / Support * Integration with `timely beliefs `_ lib: Sensor data as TimedBeliefs [see `PR #79 `_] +v0.3.1 | April 9, 2021 +=========================== + +Bugfixes +-------- +* PostMeterData endpoint was broken in API v2.0 [see `PR #95 `_] + + v0.3.0 | April 2, 2021 =========================== diff --git a/flexmeasures/api/v2_0/routes.py b/flexmeasures/api/v2_0/routes.py index f3e4c3bc1..60cc27f07 100644 --- a/flexmeasures/api/v2_0/routes.py +++ b/flexmeasures/api/v2_0/routes.py @@ -761,7 +761,7 @@ def post_meter_data(): :status 403: INVALID_SENDER :status 405: INVALID_METHOD """ - return v2_0_implementations.post_meter_data_response() + return v2_0_implementations.sensors.post_meter_data_response() @flexmeasures_api_v2_0.route("/postPrognosis", methods=["POST"]) From ad6fa3f6210ef7ef4e2b1c01c79f9759f4197b3d Mon Sep 17 00:00:00 2001 From: "create-issue-branch[bot]" <53036503+create-issue-branch[bot]@users.noreply.github.com> Date: Sun, 11 Apr 2021 13:24:25 +0200 Subject: [PATCH 02/24] Using wrong format in asset editing leads to unhelpful error message (#93) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit asset edit form behaves better on form validation errors Co-authored-by: nhoening Co-authored-by: Nicolas Höning --- documentation/changelog.rst | 6 +++++- flexmeasures/ui/crud/assets.py | 15 +++++++++++++-- flexmeasures/ui/templates/crud/asset.html | 12 ++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 742051b87..f1dcdefd6 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -3,13 +3,17 @@ FlexMeasures Changelog ********************** -v0.2.5 | April XX, 2021 +v0.4.0 | April XX, 2021 =========================== New features ----------- * Add sensors with CLI command [see `PR #83 `_] +Bugfixes +----------- +* Asset edit form displayed wrong error message. Also enabled the asset edit form to display the invalid user input back to the user [see `PR #93 `_] + Infrastructure / Support ---------------------- * Updated dependencies, including Flask-Security-Too [see `PR #82 `_] diff --git a/flexmeasures/ui/crud/assets.py b/flexmeasures/ui/crud/assets.py index 609c95321..b779a8965 100644 --- a/flexmeasures/ui/crud/assets.py +++ b/flexmeasures/ui/crud/assets.py @@ -278,11 +278,22 @@ def post(self, id: str): else: asset_form = with_options(AssetForm()) if not asset_form.validate_on_submit(): + asset = Asset.query.get(id) + latest_measurement_time_str, asset_plot_html = get_latest_power_as_plot( + asset + ) + # Display the form data, but set some extra data which the page wants to show. + asset_info = asset_form.data.copy() + asset_info["id"] = id + asset_info["owner_id"] = asset.owner_id + asset_info["entity_address"] = asset.entity_address return render_flexmeasures_template( - "crud/asset_new.html", + "crud/asset.html", asset_form=asset_form, + asset=asset_info, msg="Cannot edit asset.", - map_center=get_center_location(db, user=current_user), + latest_measurement_time_str=latest_measurement_time_str, + asset_plot_html=asset_plot_html, mapboxAccessToken=current_app.config.get("MAPBOX_ACCESS_TOKEN", ""), ) patch_asset_response = InternalApi().patch( diff --git a/flexmeasures/ui/templates/crud/asset.html b/flexmeasures/ui/templates/crud/asset.html index 3e461fd16..0762524eb 100644 --- a/flexmeasures/ui/templates/crud/asset.html +++ b/flexmeasures/ui/templates/crud/asset.html @@ -49,7 +49,7 @@

Edit asset {{ asset.display_name }}

(Owned by {{ asset.owner_id | username }}) - +
{{ asset_form.display_name.label(class="col-sm-6 control-label") }}
@@ -198,7 +198,7 @@

Location

// create map var assetMap = L - .map('mapid', { center: [{{ asset.latitude }}, {{ asset.longitude }}], zoom: 10}) + .map('mapid', { center: [{{ asset.latitude | replace("None", 10) }}, {{ asset.longitude | replace("None", 10) }}], zoom: 10}) .on('popupopen', function () { $(function () { $('[data-toggle="tooltip"]').tooltip(); @@ -207,17 +207,17 @@

Location

addTileLayer(assetMap, '{{ mapboxAccessToken }}'); // create marker - var {{ asset.asset_type_name | parameterize }}_icon = new L.DivIcon({ + var asset_icon = new L.DivIcon({ className: 'map-icon', - html: '', + html: '', iconSize: [100, 100], // size of the icon iconAnchor: [50, 50], // point of the icon which will correspond to marker's location popupAnchor: [0, -50] // point from which the popup should open relative to the iconAnchor }); var marker = L .marker( - [{{ asset.latitude }}, {{ asset.longitude }}], - { icon: {{ asset.asset_type_name | parameterize }}_icon } + [{{ asset.latitude | replace("None", 10)}}, {{ asset.longitude | replace("None", 10) }}], + { icon: asset_icon } ).addTo(assetMap); assetMap.on('click', function (e) { From 134fb696587af2e38dd915f9aa18a0a934729cb7 Mon Sep 17 00:00:00 2001 From: "create-issue-branch[bot]" <53036503+create-issue-branch[bot]@users.noreply.github.com> Date: Mon, 12 Apr 2021 13:25:34 +0200 Subject: [PATCH 03/24] Plugin-ability for views and CLI (#91) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * no need to speak of Jeju island anymore in the base template * clean up CLI Readme * introduce FLEXMEASURES_LISTED_VIEWS, retire FLEXMEASURES_SHOW_CONTROL_UI * Analytics page: fix metrics table header for weather sensor * import plugins as Blueprints * enable listing configured plugin view names in menu * add general documentation for creating a plugin Co-authored-by: Nicolas Höning --- documentation/changelog.rst | 2 + documentation/configuration.rst | 43 +++++++--- documentation/dev/plugins.rst | 79 +++++++++++++++++++ documentation/index.rst | 2 +- flexmeasures/app.py | 4 + flexmeasures/data/scripts/cli_tasks/Readme.md | 17 +--- flexmeasures/ui/__init__.py | 2 +- flexmeasures/ui/templates/base.html | 5 +- flexmeasures/ui/templates/defaults.jinja | 41 ++++++---- .../ui/templates/views/analytics.html | 2 +- flexmeasures/utils/app_utils.py | 40 +++++++++- flexmeasures/utils/config_defaults.py | 9 ++- 12 files changed, 200 insertions(+), 46 deletions(-) create mode 100644 documentation/dev/plugins.rst diff --git a/documentation/changelog.rst b/documentation/changelog.rst index f1dcdefd6..e02b4c351 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -9,6 +9,8 @@ v0.4.0 | April XX, 2021 New features ----------- * Add sensors with CLI command [see `PR #83 `_] +* Configure views with ``FLEXMEASURES_LISTED_VIEWS`` [see `PR #91 `_] +* Allow for views and CLI functions to come from plugins [see also `PR #91 `_] Bugfixes ----------- diff --git a/documentation/configuration.rst b/documentation/configuration.rst index e737e0780..2262aaacc 100644 --- a/documentation/configuration.rst +++ b/documentation/configuration.rst @@ -6,7 +6,7 @@ Configuration The following configurations are used by FlexMeasures. Required settings (e.g. postgres db) are marked with a double star (**). -To enable easier quickstart tutorials, these settings can be set by env vars. +To enable easier quickstart tutorials, these settings can be set by environment variables. Recommended settings (e.g. mail, redis) are marked by one star (*). .. note:: FlexMeasures is best configured via a config file. The config file for FlexMeasures can be placed in one of two locations: @@ -15,6 +15,7 @@ Recommended settings (e.g. mail, redis) are marked by one star (*). * in the user's home directory (e.g. ``~/.flexmeasures.cfg`` on Unix). In this case, note the dot at the beginning of the filename! * in the app's instance directory (e.g. ``/path/to/your/flexmeasures/code/instance/flexmeasures.cfg``\ ). The path to that instance directory is shown to you by running flexmeasures (e.g. ``flexmeasures run``\ ) with required settings missing or otherwise by running ``flexmeasures shell``. + Basic functionality ------------------- @@ -51,6 +52,19 @@ and the first month when the domain was under the current owner's administration Default: ``{"flexmeasures.io": "2021-01"}`` + +.. _plugin-config: + +FLEXMEASURES_PLUGIN_PATHS +^^^^^^^^^^^^^^^^^^^^^^^^^ + +A list of absolute paths to Blueprint-based plugins for FlexMeasures (e.g. for custom views or CLI functions). +Each plugin path points to a folder, which should contain an ``__init__.py`` file where the Blueprint is defined. +See :ref:`plugins` on what is expected for content. + +Default: ``[]`` + + FLEXMEASURES_DB_BACKUP_PATH ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -65,6 +79,7 @@ Whether to turn on a feature which times requests made through FlexMeasures. Int Default: ``False`` + UI -- @@ -89,6 +104,7 @@ Interval in which viewing the queues dashboard refreshes itself, in milliseconds Default: ``3000`` (3 seconds) + Timing ------ @@ -113,6 +129,7 @@ The horizon to use when making schedules. Default: ``timedelta(hours=2 * 24)`` + Tokens ------ @@ -133,7 +150,7 @@ Default: ``None`` MAPBOX_ACCESS_TOKEN ^^^^^^^^^^^^^^^^^^^ -Token for accessing the mapbox API (for displaying maps on the dashboard and asset pages). You can learn how to obtain one `here `_ +Token for accessing the MapBox API (for displaying maps on the dashboard and asset pages). You can learn how to obtain one `here `_ Default: ``None`` @@ -144,6 +161,7 @@ Token which external services can use to check on the status of recurring tasks Default: ``None`` + SQLAlchemy ---------- @@ -172,6 +190,7 @@ Default: "connect_args": {"options": "-c timezone=utc"}, } + Security -------- @@ -215,7 +234,7 @@ Default: ``60 * 60 * 6`` (six hours) SECURITY_TRACKABLE ^^^^^^^^^^^^^^^^^^ -Wether to track user statistics. Turning this on requires certain user fields. +Whether to track user statistics. Turning this on requires certain user fields. We do not use this feature, but we do track number of logins. Default: ``False`` @@ -223,14 +242,14 @@ Default: ``False`` CORS_ORIGINS ^^^^^^^^^^^^ -Allowed cross-origins. Set to "*" to allow all. For development (e.g. javascript on localhost) you might use "null" in this list. +Allowed cross-origins. Set to "*" to allow all. For development (e.g. JavaScript on localhost) you might use "null" in this list. Default: ``[]`` CORS_RESOURCES: ^^^^^^^^^^^^^^^ -FlexMeasures resources which get cors protection. This can be a regex, a list of them or dict with all possible options. +FlexMeasures resources which get cors protection. This can be a regex, a list of them or a dictionary with all possible options. Default: ``[r"/api/*"]`` @@ -244,6 +263,7 @@ Allows users to make authenticated requests. If true, injects the Access-Control Default: ``True`` + .. _mail-config: Mail @@ -335,7 +355,7 @@ Default: ``6379`` FLEXMEASURES_REDIS_DB_NR (*) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Number of the redis database to use (Redis per default has 16 databases, nubered 0-15) +Number of the redis database to use (Redis per default has 16 databases, numbered 0-15) Default: ``0`` @@ -364,9 +384,14 @@ so that old imported data can be demoed as if it were current Default: ``None`` -FLEXMEASURES_SHOW_CONTROL_UI + +.. _menu-config: + +FLEXMEASURES_LISTED_VIEWS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The control page is still mocked, so this setting controls if it is to be shown. +A list of the views which are listed in the menu. -Default: ``False`` +.. note:: This setting is likely to be deprecated soon, as we might want to control it per account (once we implemented a multi-tenant data model per FlexMeasures server). + +Default: ``["dashboard", "analytics", "portfolio", "assets", "users"]`` diff --git a/documentation/dev/plugins.rst b/documentation/dev/plugins.rst new file mode 100644 index 000000000..c422b37a0 --- /dev/null +++ b/documentation/dev/plugins.rst @@ -0,0 +1,79 @@ +.. _plugins: + +Writing Plugins +==================== + +You can extend FlexMeasures with functionality like UI pages or CLI functions. + +A FlexMeasures plugin works as a `Flask Blueprint `_. + +.. todo:: We'll use this to allow for custom forecasting and scheduling algorithms, as well. + + +How it works +^^^^^^^^^^^^^^ + +Use the config setting :ref:`plugin-config` to point to your plugin(s). + +Here are the assumptions FlexMeasures makes to be able to import your Blueprint: + +- The plugin folder contains an __init__.py file. +- In this init, you define a Blueprint object called ``_bp``. + +We'll refer to the plugin with the name of your plugin folder. + + +Showcase +^^^^^^^^^ + +Here is a showcase file which constitutes a FlexMeasures plugin. We imagine that we made a plugin to implement some custom logic for a client. + +We created the file ``/our_client/__init__.py``. So, ``our_client`` is the plugin folder and becomes the plugin name. +All else that is needed for this showcase (not shown here) is ``/our_client/templates/metrics.html``, which works just as other FlexMeasures templates (they are Jinja2 templates and you can start them with ``{% extends "base.html" %}`` for integration into the FlexMeasures structure). + + +* We demonstrate adding a view which can be rendered via the FlexMeasures base templates. +* We also showcase a CLI function which has access to the FlexMeasures `app` object. It can be called via ``flexmeasures our_client test``. + +.. code-block:: python + + from flask import Blueprint, render_template, abort + + from flask_security import login_required + from flexmeasures.ui.utils.view_utils import render_flexmeasures_template + + + our_client_bp = Blueprint('our_client', 'our_client', + template_folder='templates') + + + # Showcase: Adding a view + + @our_client_bp.route('/metrics') + @login_required + def metrics(): + msg = "I am part of FM !" + # Note that we render via the in-built FlexMeasures way + return render_flexmeasures_template( + "metrics.html", + message=msg, + ) + + + # Showcase: Adding a CLI command + + import click + from flask import current_app + from flask.cli import with_appcontext + + + our_client_bp.cli.help = "Our client commands" + + @our_client_bp.cli.command("test") + @with_appcontext + def oc_test(): + print(f"I am a CLI command, part of FlexMeasures: {current_app}") + + + +.. note:: Plugin views can also be added to the FlexMeasures UI menu ― just name them in the config setting :ref:`menu-config`. \ No newline at end of file diff --git a/documentation/index.rst b/documentation/index.rst index 13c671cd4..d5af9d4f2 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -84,7 +84,7 @@ The platform operator of FlexMeasures can be an Aggregator. dev/data dev/api dev/ci - + dev/plugins .. toctree:: :caption: Integrations diff --git a/flexmeasures/app.py b/flexmeasures/app.py index e152b0517..c2ef442f2 100644 --- a/flexmeasures/app.py +++ b/flexmeasures/app.py @@ -104,6 +104,10 @@ def create(env: Optional[str] = None, path_to_config: Optional[str] = None) -> F register_ui_at(app) + from flexmeasures.utils.app_utils import register_plugins + + register_plugins(app) + # Profile endpoints (if needed, e.g. during development) @app.before_request def before_request(): diff --git a/flexmeasures/data/scripts/cli_tasks/Readme.md b/flexmeasures/data/scripts/cli_tasks/Readme.md index ee4a56beb..afa76806b 100644 --- a/flexmeasures/data/scripts/cli_tasks/Readme.md +++ b/flexmeasures/data/scripts/cli_tasks/Readme.md @@ -5,20 +5,9 @@ These scripts are made available as cli tasks. To view the available commands, run: - flask --help + flexmeasures --help -For help on individual commands, for example on the saving and loading functionality, type `flask db-save --help` or `flask db-load --help`. -These help messages are generated from the code (see the file db_pop.py in the cli_tasks directory). -Structural data refers to database tables with relatively little entries (they describe things like assets, markets and weather sensors). -Time series data refers to database tables with many entries (like power, price and temperature values). -The default location for storing database backups is within the top-level `migrations` directory. -The contents of this folder are not part of the code repository, and database backups will be lost when deleted. - -The load functionality is also made available as an API endpoint called _restoreData_, and described as such in the user documentation for the play server. -The relevant API endpoint is set up in the `flexmeasures/api/play` directory. -The file `routes.py` contains its registration and documentation, while the file `implementations.py` contains the functional logic that connects the API endpoint to the same scripts that are accessible through the command line interface. - -The save functionality is currently not available as an API endpoint. -This script cannot be executed within the lifetime of an https request, and would require processing within a separate thread, similar to how forecasting jobs are handled by FlexMeasures. +For help on individual commands, type `flexmesaures --help`. +Structural data refers to database tables which do not contain time series data. To create new commands, be sure to register any new file (containing the corresponding script) with the flask cli in `flexmeasures/data/__init__.py`. diff --git a/flexmeasures/ui/__init__.py b/flexmeasures/ui/__init__.py index c3df28dff..7e4d2681c 100644 --- a/flexmeasures/ui/__init__.py +++ b/flexmeasures/ui/__init__.py @@ -138,7 +138,7 @@ def add_jinja_variables(app): for v in ( "FLEXMEASURES_MODE", "FLEXMEASURES_PLATFORM_NAME", - "FLEXMEASURES_SHOW_CONTROL_UI", + "FLEXMEASURES_LISTED_VIEWS", "FLEXMEASURES_PUBLIC_DEMO_CREDENTIALS", ): app.jinja_env.globals[v] = app.config.get(v, "") diff --git a/flexmeasures/ui/templates/base.html b/flexmeasures/ui/templates/base.html index 666643572..2bfd0be51 100644 --- a/flexmeasures/ui/templates/base.html +++ b/flexmeasures/ui/templates/base.html @@ -64,8 +64,7 @@ {{ FLEXMEASURES_PLATFORM_NAME }} - {{ self.title() }} {% if user_is_logged_in and not "Error" in self.title() %} for {{ user_name }} on - Jeju island {% endif %} + {{ self.title() }} {% if user_is_logged_in and not "Error" in self.title() %} for {{ user_name }} {% endif %}
+ {% block teaser %}

The FlexMeasures Platform

+ {% endblock teaser %}
From 52a8f417adc0dd25062d0c37fe5a6f1434ae2a7c Mon Sep 17 00:00:00 2001 From: "create-issue-branch[bot]" <53036503+create-issue-branch[bot]@users.noreply.github.com> Date: Sat, 1 May 2021 23:51:17 +0200 Subject: [PATCH 12/24] Bug: logging in after clearing session redirects to clearing session (#112) Prevent user from being logged out when clearing the session. * Create draft PR for #28 * More session keys to avoid clearing * Changelog entry Co-authored-by: Flix6x Co-authored-by: F.N. Claessen Co-authored-by: Felix Claessen <30658763+Flix6x@users.noreply.github.com> --- documentation/changelog.rst | 4 ++++ flexmeasures/ui/utils/view_utils.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index d5a24a9a5..5fef02082 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -10,6 +10,10 @@ New features ----------- * Allow plugins to overwrite UI routes and customise the teaser on the login form [see `PR #106 `_] +Bugfixes +----------- +* Prevent logging out user when clearing the session [see `PR #112 `_] + Infrastructure / Support ---------------------- * Make assets use MW as their default unit and enforce that in CLI, as well (API already did) [see `PR #108 `_] diff --git a/flexmeasures/ui/utils/view_utils.py b/flexmeasures/ui/utils/view_utils.py index 50f9674f2..8e6c83cc3 100644 --- a/flexmeasures/ui/utils/view_utils.py +++ b/flexmeasures/ui/utils/view_utils.py @@ -91,7 +91,9 @@ def render_flexmeasures_template(html_filename: str, **variables): def clear_session(): for skey in [ - k for k in session.keys() if k not in ("_id", "user_id", "csrf_token") + k + for k in session.keys() + if k not in ("_fresh", "_id", "_user_id", "csrf_token", "fs_cc", "fs_paa") ]: current_app.logger.info( "Removing %s:%s from session ... " % (skey, session[skey]) From 760ea2ab7a6143612738c9abe6d961ab9f1bdfd4 Mon Sep 17 00:00:00 2001 From: Felix Claessen <30658763+Flix6x@users.noreply.github.com> Date: Thu, 6 May 2021 12:07:23 +0200 Subject: [PATCH 13/24] Add footer blocks (copyright notice and credits) (#123) Allows plugins to customise some elements of the footer. * Add footer blocks (copyright notice and credits) * Changelog entry Co-authored-by: F.N. Claessen --- documentation/changelog.rst | 1 + flexmeasures/ui/templates/base.html | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 5fef02082..31b31b839 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -9,6 +9,7 @@ v0.5.0 | May XX, 2021 New features ----------- * Allow plugins to overwrite UI routes and customise the teaser on the login form [see `PR #106 `_] +* Allow plugins to customise the copyright notice and credits in the UI footer [see `PR #123 `_] Bugfixes ----------- diff --git a/flexmeasures/ui/templates/base.html b/flexmeasures/ui/templates/base.html index 328f35830..b0fcc770f 100644 --- a/flexmeasures/ui/templates/base.html +++ b/flexmeasures/ui/templates/base.html @@ -230,11 +230,14 @@