From 0a1b890929d4b37a1a2bbf1fb52fa6791ffb3e0e Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Mon, 26 Feb 2024 13:41:47 +0100 Subject: [PATCH 01/11] Testing doing 1 single request instead of one request per account. This relies on the fact that the index of the internal API already take care of auth. Running agains the simulation database, it takes 52s to load the asset page on average. Signed-off-by: Victor Garcia Reolid --- flexmeasures/api/v3_0/users.py | 10 +++++++-- flexmeasures/ui/crud/assets.py | 16 +++++++++------ flexmeasures/ui/crud/users.py | 26 +++++++++++++++--------- flexmeasures/ui/tests/test_asset_crud.py | 11 +++------- flexmeasures/ui/tests/test_user_crud.py | 2 +- flexmeasures/ui/tests/test_views.py | 4 ++-- 6 files changed, 40 insertions(+), 29 deletions(-) diff --git a/flexmeasures/api/v3_0/users.py b/flexmeasures/api/v3_0/users.py index 7c753f705..48465d656 100644 --- a/flexmeasures/api/v3_0/users.py +++ b/flexmeasures/api/v3_0/users.py @@ -48,12 +48,15 @@ class UserAPI(FlaskView): @permission_required_for_context("read", ctx_arg_name="account") @as_json def index(self, account: Account, include_inactive: bool = False): - """API endpoint to list all users of an account. + """API endpoint to list all users. + .. :quickref: User; Download user list This endpoint returns all accessible users. By default, only active users are returned. + The `account_id` query parameter can be used to filter the users of + a given account. The `include_inactive` query parameter can be used to also fetch inactive users. Accessible users are users in the same account as the current user. @@ -86,7 +89,10 @@ def index(self, account: Account, include_inactive: bool = False): :status 403: INVALID_SENDER :status 422: UNPROCESSABLE_ENTITY """ - users = get_users(account_name=account.name, only_active=not include_inactive) + users = get_users( + account_name=account.name if account is not None else None, + only_active=not include_inactive, + ) return users_schema.dump(users), 200 @route("/") diff --git a/flexmeasures/ui/crud/assets.py b/flexmeasures/ui/crud/assets.py index 7679bd3f6..1579438e5 100644 --- a/flexmeasures/ui/crud/assets.py +++ b/flexmeasures/ui/crud/assets.py @@ -207,6 +207,15 @@ def get_assets_by_account(account_id: int | str | None) -> list[GenericAsset]: ] +def get_all_assets() -> list[GenericAsset]: + get_assets_response = InternalApi().get(url_for("AssetAPI:index")) + + return [ + process_internal_api_response(ad, make_obj=True) + for ad in get_assets_response.json() + ] + + class AssetCrudUI(FlaskView): """ These views help us offer a Jinja2-based UI. @@ -225,12 +234,7 @@ def index(self, msg=""): """ assets = [] - if user_has_admin_access(current_user, "read"): - for account in db.session.scalars(select(Account)).all(): - assets += get_assets_by_account(account.id) - assets += get_assets_by_account(account_id=None) - else: - assets = get_assets_by_account(current_user.account_id) + assets = get_all_assets() return render_flexmeasures_template( "crud/assets.html", diff --git a/flexmeasures/ui/crud/users.py b/flexmeasures/ui/crud/users.py index 643bde9a4..6c595fc46 100644 --- a/flexmeasures/ui/crud/users.py +++ b/flexmeasures/ui/crud/users.py @@ -4,7 +4,6 @@ from flask import request, url_for from flask_classful import FlaskView -from flask_login import current_user from flask_security import login_required from flask_wtf import FlaskForm from wtforms import StringField, FloatField, DateTimeField, BooleanField @@ -92,6 +91,20 @@ def get_users_by_account( return users +def get_all_users(include_inactive: bool = False) -> list[User]: + get_users_response = InternalApi().get( + url_for( + "UserAPI:index", + include_inactive=include_inactive, + ) + ) + users = [ + process_internal_api_response(user, make_obj=True) + for user in get_users_response.json() + ] + return users + + class UserCrudUI(FlaskView): route_base = "/users" trailing_slash = False @@ -100,15 +113,8 @@ class UserCrudUI(FlaskView): def index(self): """/users""" include_inactive = request.args.get("include_inactive", "0") != "0" - users = [] - if current_user.has_role(ADMIN_ROLE) or current_user.has_role( - ADMIN_READER_ROLE - ): - accounts = db.session.scalars(select(Account)).all() - else: - accounts = [current_user.account] - for account in accounts: - users += get_users_by_account(account.id, include_inactive) + users = get_all_users(include_inactive) + return render_flexmeasures_template( "crud/users.html", users=users, include_inactive=include_inactive ) diff --git a/flexmeasures/ui/tests/test_asset_crud.py b/flexmeasures/ui/tests/test_asset_crud.py index ae316910a..e857148d7 100644 --- a/flexmeasures/ui/tests/test_asset_crud.py +++ b/flexmeasures/ui/tests/test_asset_crud.py @@ -16,17 +16,14 @@ def test_assets_page_empty(db, client, requests_mock, as_prosumer_user1): - requests_mock.get(f"{api_path_assets}?account_id=1", status_code=200, json={}) - requests_mock.get(f"{api_path_assets}/public", status_code=200, json={}) + requests_mock.get(f"{api_path_assets}", status_code=200, json=[]) asset_index = client.get(url_for("AssetCrudUI:index"), follow_redirects=True) assert asset_index.status_code == 200 def test_get_assets_by_account(db, client, requests_mock, as_prosumer_user1): mock_assets = mock_asset_response(multiple=True) - requests_mock.get( - f"{api_path_assets}?account_id=1", status_code=200, json=mock_assets - ) + requests_mock.get(f"{api_path_assets}", status_code=200, json=mock_assets) assert get_assets_by_account(1)[1].name == "TestAsset2" @@ -35,9 +32,7 @@ def test_assets_page_nonempty( db, client, requests_mock, as_prosumer_user1, use_owned_by ): mock_assets = mock_asset_response(multiple=True) - requests_mock.get( - f"{api_path_assets}?account_id=1", status_code=200, json=mock_assets - ) + requests_mock.get(f"{api_path_assets}", status_code=200, json=mock_assets) if use_owned_by: asset_index = client.get( url_for("AssetCrudUI:owned_by", account_id=mock_assets[0]["account_id"]) diff --git a/flexmeasures/ui/tests/test_user_crud.py b/flexmeasures/ui/tests/test_user_crud.py index 62e075c5c..e3823581c 100644 --- a/flexmeasures/ui/tests/test_user_crud.py +++ b/flexmeasures/ui/tests/test_user_crud.py @@ -15,7 +15,7 @@ def test_get_users_by_account(client, requests_mock, as_prosumer_user1): requests_mock.get( - f"http://localhost//api/v3_0/users?account_id={current_user.account.id}", + "http://localhost//api/v3_0/users", status_code=200, json=mock_user_response(multiple=True), ) diff --git a/flexmeasures/ui/tests/test_views.py b/flexmeasures/ui/tests/test_views.py index c2d13536b..898c3f3f6 100644 --- a/flexmeasures/ui/tests/test_views.py +++ b/flexmeasures/ui/tests/test_views.py @@ -21,9 +21,9 @@ def test_dashboard_responds_only_for_logged_in_users(client, as_prosumer_user1): def test_assets_responds(client, requests_mock, as_prosumer_user1): requests_mock.get( - "http://localhost//api/v3_0/assets?account_id=1", + "http://localhost//api/v3_0/assets", status_code=200, - json={}, + json=[], ) assets_page = client.get(url_for("AssetCrudUI:index"), follow_redirects=True) assert assets_page.status_code == 200 From 7f43acf6915969fe043c7566cc252497afdab949 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Wed, 28 Feb 2024 19:15:15 +0100 Subject: [PATCH 02/11] check permissions Signed-off-by: Victor Garcia Reolid --- flexmeasures/api/v3_0/users.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/flexmeasures/api/v3_0/users.py b/flexmeasures/api/v3_0/users.py index 48465d656..46e7d0d96 100644 --- a/flexmeasures/api/v3_0/users.py +++ b/flexmeasures/api/v3_0/users.py @@ -1,11 +1,13 @@ from flask_classful import FlaskView, route from marshmallow import fields from sqlalchemy.exc import IntegrityError +from sqlalchemy import select from webargs.flaskparser import use_kwargs from flask_security import current_user, auth_required from flask_security.recoverable import send_reset_password_instructions from flask_json import as_json -from werkzeug.exceptions import Forbidden +from werkzeug.exceptions import Forbidden, Unauthorized +from flexmeasures.auth.policy import check_access from flexmeasures.data.models.user import User as UserModel, Account from flexmeasures.api.common.schemas.users import AccountIdField, UserIdField @@ -38,14 +40,11 @@ class UserAPI(FlaskView): @route("", methods=["GET"]) @use_kwargs( { - "account": AccountIdField( - data_key="account_id", load_default=AccountIdField.load_current - ), + "account": AccountIdField(data_key="account_id", load_default=None), "include_inactive": fields.Bool(load_default=False), }, location="query", ) - @permission_required_for_context("read", ctx_arg_name="account") @as_json def index(self, account: Account, include_inactive: bool = False): """API endpoint to list all users. @@ -89,10 +88,25 @@ def index(self, account: Account, include_inactive: bool = False): :status 403: INVALID_SENDER :status 422: UNPROCESSABLE_ENTITY """ - users = get_users( - account_name=account.name if account is not None else None, - only_active=not include_inactive, - ) + + if account is not None: + check_access(account, "read") + accounts = [account] + else: + accounts = [] + for account in db.session.scalars(select(Account)).all(): + try: + check_access(account, "read") + accounts.append(account) + except (Forbidden, Unauthorized): + pass + + users = [] + for account in accounts: + users += get_users( + account_name=account.name, + only_active=not include_inactive, + ) return users_schema.dump(users), 200 @route("/") From fa54b6c5a5933fcf34e5b91b9c5c86e41d9411cf Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 18 Mar 2024 13:46:04 +0100 Subject: [PATCH 03/11] fix: harmless, but redundant plus Signed-off-by: F.N. Claessen --- flexmeasures/data/models/reporting/pandas_reporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/data/models/reporting/pandas_reporter.py b/flexmeasures/data/models/reporting/pandas_reporter.py index fbbd5e7c3..69c0c5a67 100644 --- a/flexmeasures/data/models/reporting/pandas_reporter.py +++ b/flexmeasures/data/models/reporting/pandas_reporter.py @@ -169,7 +169,7 @@ def _clean_belief_dataframe( if "belief_time" not in bdf.index.names: if belief_horizon is not None: belief_time = ( - bdf["event_start"] + +bdf.event_resolution - belief_horizon + bdf["event_start"] + bdf.event_resolution - belief_horizon ) else: belief_time = [belief_time] * len(bdf) From 6c84bd39e1ea263ea0fee9aefe6b4b1f93ca9951 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 18 Mar 2024 13:46:04 +0100 Subject: [PATCH 04/11] fix: no dumb children Signed-off-by: F.N. Claessen --- flexmeasures/data/schemas/generic_assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/data/schemas/generic_assets.py b/flexmeasures/data/schemas/generic_assets.py index 745020556..8306bb30d 100644 --- a/flexmeasures/data/schemas/generic_assets.py +++ b/flexmeasures/data/schemas/generic_assets.py @@ -44,7 +44,7 @@ class GenericAssetSchema(ma.SQLAlchemySchema): generic_asset_type_id = fields.Integer(required=True) attributes = JSON(required=False) parent_asset_id = fields.Int(required=False, allow_none=True) - child_assets = ma.Nested("GenericAssetSchema", many=True, dumb_only=True) + child_assets = ma.Nested("GenericAssetSchema", many=True, dump_only=True) class Meta: model = GenericAsset From 88086a68d804c064fa623710dabc1dbe8f0fdded Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 18 Mar 2024 15:59:58 +0100 Subject: [PATCH 05/11] feature: create Asset objects faster by selecting from db, while still using internal API Signed-off-by: F.N. Claessen --- flexmeasures/ui/crud/assets.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/flexmeasures/ui/crud/assets.py b/flexmeasures/ui/crud/assets.py index 1579438e5..22183a2d2 100644 --- a/flexmeasures/ui/crud/assets.py +++ b/flexmeasures/ui/crud/assets.py @@ -209,11 +209,8 @@ def get_assets_by_account(account_id: int | str | None) -> list[GenericAsset]: def get_all_assets() -> list[GenericAsset]: get_assets_response = InternalApi().get(url_for("AssetAPI:index")) - - return [ - process_internal_api_response(ad, make_obj=True) - for ad in get_assets_response.json() - ] + asset_ids = [GenericAsset.id.in_(ad["id"] for ad in get_assets_response.json())] + return db.session.scalars(select(GenericAsset).where(*asset_ids)).all() class AssetCrudUI(FlaskView): @@ -232,8 +229,6 @@ def index(self, msg=""): List the user's assets. For admins, list across all accounts. """ - assets = [] - assets = get_all_assets() return render_flexmeasures_template( From 021bd792746d0f5ce62a61a00d476e3317802725 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 18 Mar 2024 16:11:05 +0100 Subject: [PATCH 06/11] style: black Signed-off-by: F.N. Claessen --- flexmeasures/data/models/reporting/pandas_reporter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/flexmeasures/data/models/reporting/pandas_reporter.py b/flexmeasures/data/models/reporting/pandas_reporter.py index 69c0c5a67..9d838d2f0 100644 --- a/flexmeasures/data/models/reporting/pandas_reporter.py +++ b/flexmeasures/data/models/reporting/pandas_reporter.py @@ -168,9 +168,7 @@ def _clean_belief_dataframe( # filing the missing indexes with default values: if "belief_time" not in bdf.index.names: if belief_horizon is not None: - belief_time = ( - bdf["event_start"] + bdf.event_resolution - belief_horizon - ) + belief_time = bdf["event_start"] + bdf.event_resolution - belief_horizon else: belief_time = [belief_time] * len(bdf) bdf["belief_time"] = belief_time From 096921a9d2a4178c4f672f9c84cbbdf3dc48baab Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Wed, 27 Mar 2024 23:24:22 +0100 Subject: [PATCH 07/11] add public assets to the call and fix tests Signed-off-by: Victor Garcia Reolid --- flexmeasures/api/v3_0/assets.py | 28 +++++++++++++++++++----- flexmeasures/ui/crud/assets.py | 14 ++++++++++-- flexmeasures/ui/tests/conftest.py | 19 ++++++++++++++++ flexmeasures/ui/tests/test_asset_crud.py | 4 +++- flexmeasures/ui/tests/test_views.py | 5 +++++ flexmeasures/ui/tests/utils.py | 3 ++- 6 files changed, 63 insertions(+), 10 deletions(-) diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py index 14c81edab..9cc957754 100644 --- a/flexmeasures/api/v3_0/assets.py +++ b/flexmeasures/api/v3_0/assets.py @@ -18,6 +18,8 @@ from flexmeasures.api.common.schemas.users import AccountIdField from flexmeasures.utils.coding_utils import flatten_unique from flexmeasures.ui.utils.view_utils import set_session_variables +from flexmeasures.auth.policy import check_access +from werkzeug.exceptions import Forbidden, Unauthorized asset_schema = AssetSchema() @@ -38,15 +40,12 @@ class AssetAPI(FlaskView): @route("", methods=["GET"]) @use_kwargs( { - "account": AccountIdField( - data_key="account_id", load_default=AccountIdField.load_current - ), + "account": AccountIdField(data_key="account_id", load_default=None), }, location="query", ) - @permission_required_for_context("read", ctx_arg_name="account") @as_json - def index(self, account: Account): + def index(self, account: Account | None): """List all assets owned by a certain account. .. :quickref: Asset; Download asset list @@ -80,7 +79,24 @@ def index(self, account: Account): :status 403: INVALID_SENDER :status 422: UNPROCESSABLE_ENTITY """ - return assets_schema.dump(account.generic_assets), 200 + + if account is not None: + check_access(account, "read") + accounts = [account] + else: + accounts = [] + for account in db.session.scalars(select(Account)).all(): + try: + check_access(account, "read") + accounts.append(account) + except (Forbidden, Unauthorized): + pass + + assets = [] + for account in accounts: + assets.extend(account.generic_assets) + + return assets_schema.dump(assets), 200 @route("/public", methods=["GET"]) @as_json diff --git a/flexmeasures/ui/crud/assets.py b/flexmeasures/ui/crud/assets.py index 549d33f31..5a174dddb 100644 --- a/flexmeasures/ui/crud/assets.py +++ b/flexmeasures/ui/crud/assets.py @@ -208,8 +208,18 @@ def get_assets_by_account(account_id: int | str | None) -> list[GenericAsset]: def get_all_assets() -> list[GenericAsset]: - get_assets_response = InternalApi().get(url_for("AssetAPI:index")) - asset_ids = [GenericAsset.id.in_(ad["id"] for ad in get_assets_response.json())] + get_assets_response = InternalApi().get(url_for("AssetAPI:index")).json() + get_assets_response_public = InternalApi().get(url_for("AssetAPI:public")).json() + + assets = [] + if isinstance(get_assets_response, list): + assets.extend(get_assets_response) + + if isinstance(get_assets_response_public, list): + assets.extend(get_assets_response_public) + + asset_ids = [GenericAsset.id.in_(ad["id"] for ad in assets)] + return db.session.scalars(select(GenericAsset).where(*asset_ids)).all() diff --git a/flexmeasures/ui/tests/conftest.py b/flexmeasures/ui/tests/conftest.py index d2b78f811..0a3f080e6 100644 --- a/flexmeasures/ui/tests/conftest.py +++ b/flexmeasures/ui/tests/conftest.py @@ -2,6 +2,7 @@ from flexmeasures.data.services.users import create_user from flexmeasures.ui.tests.utils import login, logout +from flexmeasures import Asset @pytest.fixture(scope="function") @@ -41,3 +42,21 @@ def setup_ui_test_data( account_name=setup_accounts["Prosumer"].name, user_roles=dict(name="admin", description="A site admin."), ) + + +@pytest.fixture +def assets_prosumer(db, setup_accounts, setup_generic_asset_types): + assets = [] + for name in ["TestAsset", "TestAsset2"]: + asset = Asset( + name=name, + generic_asset_type=setup_generic_asset_types["battery"], + owner=setup_accounts["Prosumer"], + latitude=70.4, + longitude=30.9, + ) + assets.append(asset) + + db.session.add_all(assets) + + return assets diff --git a/flexmeasures/ui/tests/test_asset_crud.py b/flexmeasures/ui/tests/test_asset_crud.py index e857148d7..601af543d 100644 --- a/flexmeasures/ui/tests/test_asset_crud.py +++ b/flexmeasures/ui/tests/test_asset_crud.py @@ -17,6 +17,7 @@ def test_assets_page_empty(db, client, requests_mock, as_prosumer_user1): requests_mock.get(f"{api_path_assets}", status_code=200, json=[]) + requests_mock.get(f"{api_path_assets}/public", status_code=200, json=[]) asset_index = client.get(url_for("AssetCrudUI:index"), follow_redirects=True) assert asset_index.status_code == 200 @@ -29,10 +30,11 @@ def test_get_assets_by_account(db, client, requests_mock, as_prosumer_user1): @pytest.mark.parametrize("use_owned_by", [False, True]) def test_assets_page_nonempty( - db, client, requests_mock, as_prosumer_user1, use_owned_by + db, client, requests_mock, as_prosumer_user1, use_owned_by, assets_prosumer ): mock_assets = mock_asset_response(multiple=True) requests_mock.get(f"{api_path_assets}", status_code=200, json=mock_assets) + requests_mock.get(f"{api_path_assets}/public", status_code=200, json=[]) if use_owned_by: asset_index = client.get( url_for("AssetCrudUI:owned_by", account_id=mock_assets[0]["account_id"]) diff --git a/flexmeasures/ui/tests/test_views.py b/flexmeasures/ui/tests/test_views.py index 898c3f3f6..78d218915 100644 --- a/flexmeasures/ui/tests/test_views.py +++ b/flexmeasures/ui/tests/test_views.py @@ -25,6 +25,11 @@ def test_assets_responds(client, requests_mock, as_prosumer_user1): status_code=200, json=[], ) + requests_mock.get( + "http://localhost//api/v3_0/assets/public", + status_code=200, + json=[], + ) assets_page = client.get(url_for("AssetCrudUI:index"), follow_redirects=True) assert assets_page.status_code == 200 assert b"Asset overview" in assets_page.data diff --git a/flexmeasures/ui/tests/utils.py b/flexmeasures/ui/tests/utils.py index c5658198d..6783da896 100644 --- a/flexmeasures/ui/tests/utils.py +++ b/flexmeasures/ui/tests/utils.py @@ -20,7 +20,7 @@ def logout(client): def mock_asset_response( - asset_id: int = 1, + asset_id: int = 2, account_id: int = 1, as_list: bool = True, multiple: bool = False, @@ -38,6 +38,7 @@ def mock_asset_response( if multiple: asset2 = copy.deepcopy(asset) asset2["name"] = "TestAsset2" + asset2["id"] += 1 asset_list.append(asset2) return asset_list return asset From 02ac27e1023a411b7adef3d3c3bcd2781a68f680 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Wed, 27 Mar 2024 23:27:34 +0100 Subject: [PATCH 08/11] add future Signed-off-by: Victor Garcia Reolid --- flexmeasures/api/v3_0/assets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py index 9cc957754..9466f427e 100644 --- a/flexmeasures/api/v3_0/assets.py +++ b/flexmeasures/api/v3_0/assets.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from flask import current_app From f1f5ac056aedd30da8c51d2e443fba91fbe24d1c Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Tue, 2 Apr 2024 17:40:56 +0200 Subject: [PATCH 09/11] add `all_accessible` to get all the accessible assets by a certain account Signed-off-by: Victor Garcia Reolid --- flexmeasures/api/v3_0/assets.py | 24 +++++++++++++++++------- flexmeasures/ui/crud/assets.py | 10 +++++++--- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py index 9466f427e..14c6409b0 100644 --- a/flexmeasures/api/v3_0/assets.py +++ b/flexmeasures/api/v3_0/assets.py @@ -42,13 +42,23 @@ class AssetAPI(FlaskView): @route("", methods=["GET"]) @use_kwargs( { - "account": AccountIdField(data_key="account_id", load_default=None), + "account": AccountIdField( + data_key="account_id", load_default=AccountIdField.load_current + ), + }, + location="query", + ) + @use_kwargs( + { + "all_accessible": fields.Bool( + data_key="all_accessible", load_default=False + ), }, location="query", ) @as_json - def index(self, account: Account | None): - """List all assets owned by a certain account. + def index(self, account: Account | None, all_accessible: bool): + """List all assets owned or accesible by a certain account. .. :quickref: Asset; Download asset list @@ -82,10 +92,7 @@ def index(self, account: Account | None): :status 422: UNPROCESSABLE_ENTITY """ - if account is not None: - check_access(account, "read") - accounts = [account] - else: + if all_accessible: accounts = [] for account in db.session.scalars(select(Account)).all(): try: @@ -93,6 +100,9 @@ def index(self, account: Account | None): accounts.append(account) except (Forbidden, Unauthorized): pass + else: + check_access(account, "read") + accounts = [account] assets = [] for account in accounts: diff --git a/flexmeasures/ui/crud/assets.py b/flexmeasures/ui/crud/assets.py index 5a174dddb..1cf9e0157 100644 --- a/flexmeasures/ui/crud/assets.py +++ b/flexmeasures/ui/crud/assets.py @@ -208,7 +208,11 @@ def get_assets_by_account(account_id: int | str | None) -> list[GenericAsset]: def get_all_assets() -> list[GenericAsset]: - get_assets_response = InternalApi().get(url_for("AssetAPI:index")).json() + get_assets_response = ( + InternalApi() + .get(url_for("AssetAPI:index"), query={"all_accessible": True}) + .json() + ) get_assets_response_public = InternalApi().get(url_for("AssetAPI:public")).json() assets = [] @@ -218,9 +222,9 @@ def get_all_assets() -> list[GenericAsset]: if isinstance(get_assets_response_public, list): assets.extend(get_assets_response_public) - asset_ids = [GenericAsset.id.in_(ad["id"] for ad in assets)] + asset_ids_filter = [GenericAsset.id.in_(ad["id"] for ad in assets)] - return db.session.scalars(select(GenericAsset).where(*asset_ids)).all() + return db.session.scalars(select(GenericAsset).where(*asset_ids_filter)).all() class AssetCrudUI(FlaskView): From eec4ad475900a10067af0fcb08902ec69b8822da Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Tue, 2 Apr 2024 17:45:08 +0200 Subject: [PATCH 10/11] Improve docstring. Signed-off-by: Victor Garcia Reolid --- flexmeasures/api/v3_0/assets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py index 14c6409b0..0cc3d18d6 100644 --- a/flexmeasures/api/v3_0/assets.py +++ b/flexmeasures/api/v3_0/assets.py @@ -58,12 +58,13 @@ class AssetAPI(FlaskView): ) @as_json def index(self, account: Account | None, all_accessible: bool): - """List all assets owned or accesible by a certain account. + """List all assets owned or accessible by a certain account. .. :quickref: Asset; Download asset list This endpoint returns all accessible assets for the account of the user. The `account_id` query parameter can be used to list assets from a different account. + The `all_accessible` query parameter can be used to list all the assets accessible by the requesting user. Defaults to `false`. **Example response** From e98dc72335c97157eebaca1669019bd22db2cd74 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Wed, 3 Apr 2024 21:54:45 +0200 Subject: [PATCH 11/11] raise if the account is provided and all_accessible=True Signed-off-by: Victor Garcia Reolid --- flexmeasures/api/v3_0/assets.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py index 0cc3d18d6..abd7a5e53 100644 --- a/flexmeasures/api/v3_0/assets.py +++ b/flexmeasures/api/v3_0/assets.py @@ -4,6 +4,7 @@ from flask import current_app from flask_classful import FlaskView, route +from flask_login import current_user from flask_security import auth_required from flask_json import as_json from marshmallow import fields @@ -42,9 +43,7 @@ class AssetAPI(FlaskView): @route("", methods=["GET"]) @use_kwargs( { - "account": AccountIdField( - data_key="account_id", load_default=AccountIdField.load_current - ), + "account": AccountIdField(data_key="account_id", load_default=None), }, location="query", ) @@ -95,13 +94,18 @@ def index(self, account: Account | None, all_accessible: bool): if all_accessible: accounts = [] - for account in db.session.scalars(select(Account)).all(): + for _account in db.session.scalars(select(Account)).all(): try: - check_access(account, "read") - accounts.append(account) + check_access(_account, "read") + accounts.append(_account) except (Forbidden, Unauthorized): - pass + # re-raise exception if the account is provided + # but the requesting user has no read access to it. + if _account == account: + raise else: + if account is None: + account = current_user.account check_access(account, "read") accounts = [account]