From 668425805ba10f208c498ece4a9a6020e7c533f2 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Tue, 6 Jun 2023 13:38:35 +0200 Subject: [PATCH 01/19] feat: revision to add `attributes` column to the `data_source` table Signed-off-by: Victor Garcia Reolid --- ...e0c_add_attribute_column_to_data_source.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py diff --git a/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py b/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py new file mode 100644 index 000000000..2dc59a6c1 --- /dev/null +++ b/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py @@ -0,0 +1,28 @@ +"""add attribute column to data source + +Revision ID: 2ac7fb39ce0c +Revises: d814c0688ae0 +Create Date: 2023-06-05 23:41:31.788961 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "2ac7fb39ce0c" +down_revision = "d814c0688ae0" +branch_labels = None +depends_on = None + + +def upgrade(): + # add the column `attributes`to the table `data_source` + op.add_column( + "data_source", + sa.Column("attributes", sa.JSON(), nullable=True, default={}), + ) + + +def downgrade(): + pass From abfe310870f60bf943774c7d33374bdc06235214 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Tue, 6 Jun 2023 13:42:59 +0200 Subject: [PATCH 02/19] feat: add `attributes` column to the DataSource model Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index e0524021d..080dd4be3 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from sqlalchemy.ext.mutable import MutableDict import timely_beliefs as tb @@ -68,6 +69,8 @@ class DataSource(db.Model, tb.BeliefSourceDBMixin): ) user = db.relationship("User", backref=db.backref("data_source", lazy=True)) + attributes = db.Column(MutableDict.as_mutable(db.JSON), nullable=False, default={}) + # The model and version of a script source model = db.Column(db.String(80), nullable=True) version = db.Column( From c5fddad053c246019d77c84deac2a8d138cf4d2d Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Tue, 6 Jun 2023 13:43:32 +0200 Subject: [PATCH 03/19] feat: add sensors relationship in DataSource Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 080dd4be3..9550dc5d8 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -78,6 +78,12 @@ class DataSource(db.Model, tb.BeliefSourceDBMixin): nullable=True, ) + sensors = db.relationship( + "Sensor", + secondary="timed_belief", + backref=db.backref("data_sources", lazy="dynamic"), + ) + def __init__( self, name: str | None = None, From c2de8fc0d62d84ef7b7360130d8746d38ba1a082 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Tue, 20 Jun 2023 14:02:29 +0200 Subject: [PATCH 04/19] fix: make sensors relationship viewonly Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 1 + 1 file changed, 1 insertion(+) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 9550dc5d8..baad820e9 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -82,6 +82,7 @@ class DataSource(db.Model, tb.BeliefSourceDBMixin): "Sensor", secondary="timed_belief", backref=db.backref("data_sources", lazy="dynamic"), + viewonly=True, ) def __init__( From 80284317f2d2f4a8546494e7da2da4721ab38f2d Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Fri, 23 Jun 2023 12:59:31 +0200 Subject: [PATCH 05/19] feat: add helper methods to DataSource Signed-off-by: Victor Garcia Reolid --- .vscode/settings.json | 7 ++++++- flexmeasures/data/models/data_sources.py | 25 +++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 618764c3f..9e744aa83 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,10 @@ "python.linting.pylintEnabled": false, "python.linting.flake8Enabled": true, "workbench.editor.wrapTabs": true, - "python.formatting.provider": "black" + "python.formatting.provider": "black", + "python.testing.pytestArgs": [ + "flexmeasures" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true } diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index baad820e9..f905dc86d 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from sqlalchemy.ext.mutable import MutableDict import timely_beliefs as tb @@ -90,6 +90,7 @@ def __init__( name: str | None = None, type: str | None = None, user: User | None = None, + attributes: dict | None = None, **kwargs, ): if user is not None: @@ -99,6 +100,10 @@ def __init__( elif user is None and type == "user": raise TypeError("A data source cannot have type 'user' but no user set.") self.type = type + + if attributes is not None: + kwargs["attributes"] = attributes + tb.BeliefSourceDBMixin.__init__(self, name=name) db.Model.__init__(self, **kwargs) @@ -154,3 +159,21 @@ def to_dict(self) -> dict: type=self.type if self.type in ("forecaster", "scheduler") else "other", description=self.description, ) + + def get_attribute(self, attribute: str, default: Any = None) -> Any: + """Looks for the attribute on the DataSource. + If not found, returns the default. + """ + if hasattr(self, attribute): + return getattr(self, attribute) + if attribute in self.attributes: + return self.attributes[attribute] + + return default + + def has_attribute(self, attribute: str) -> bool: + return attribute in self.attributes + + def set_attribute(self, attribute: str, value): + if self.has_attribute(attribute): + self.attributes[attribute] = value From 4567c23f8525f9675cab86017be5843dcd3e804b Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Mon, 26 Jun 2023 13:13:33 +0200 Subject: [PATCH 06/19] feat: add attributes hash Signed-off-by: Victor Garcia Reolid --- .../2ac7fb39ce0c_add_attribute_column_to_data_source.py | 8 +++++++- flexmeasures/data/models/data_sources.py | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py b/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py index 2dc59a6c1..4ed55d15d 100644 --- a/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py +++ b/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py @@ -17,12 +17,18 @@ def upgrade(): - # add the column `attributes`to the table `data_source` + # add the column `attributes` to the table `data_source` op.add_column( "data_source", sa.Column("attributes", sa.JSON(), nullable=True, default={}), ) + # add the column `attributes_hash` to the table `data_source` + op.add_column( + "data_source", + sa.Column("attributes_hash", sa.LargeBinary(length=256), nullable=True), + ) + def downgrade(): pass diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index f905dc86d..8350830e8 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from typing import TYPE_CHECKING, Any from sqlalchemy.ext.mutable import MutableDict @@ -7,6 +8,7 @@ from flexmeasures.data import db from flask import current_app +import hashlib if TYPE_CHECKING: @@ -71,6 +73,8 @@ class DataSource(db.Model, tb.BeliefSourceDBMixin): attributes = db.Column(MutableDict.as_mutable(db.JSON), nullable=False, default={}) + attributes_hash = db.Column(db.LargeBinary(length=256)) + # The model and version of a script source model = db.Column(db.String(80), nullable=True) version = db.Column( @@ -102,7 +106,10 @@ def __init__( self.type = type if attributes is not None: - kwargs["attributes"] = attributes + self.attributes = attributes + self.attributes_hash = hashlib.sha256( + json.dumps(attributes).encode("utf-8") + ).digest() tb.BeliefSourceDBMixin.__init__(self, name=name) db.Model.__init__(self, **kwargs) From 773dc9db31810050ab32adc1b390649e259b9190 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Mon, 26 Jun 2023 13:14:19 +0200 Subject: [PATCH 07/19] feat: add attributes to the function get_or_create_source Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/services/data_sources.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/flexmeasures/data/services/data_sources.py b/flexmeasures/data/services/data_sources.py index d9787f147..b19c37ca0 100644 --- a/flexmeasures/data/services/data_sources.py +++ b/flexmeasures/data/services/data_sources.py @@ -2,6 +2,9 @@ from flask import current_app +import json +import hashlib + from flexmeasures import User from flexmeasures.data import db from flexmeasures.data.models.data_sources import DataSource @@ -13,6 +16,7 @@ def get_or_create_source( source_type: str | None = None, model: str | None = None, version: str | None = None, + attributes: dict | None = None, flush: bool = True, ) -> DataSource: if is_user(source): @@ -22,6 +26,11 @@ def get_or_create_source( query = query.filter(DataSource.model == model) if version is not None: query = query.filter(DataSource.version == version) + if attributes is not None: + attributes_hash = hashlib.sha256( + json.dumps(attributes).encode("utf-8") + ).digest() + query = query.filter(DataSource.attributes_hash == attributes_hash) if is_user(source): query = query.filter(DataSource.user == source) elif isinstance(source, str): From 6d632b86a2f176d5cfb357177a33fa9ab3a0f4be Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Mon, 26 Jun 2023 18:13:09 +0200 Subject: [PATCH 08/19] feat: add attribute hash to get_or_create_source Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/services/data_sources.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/flexmeasures/data/services/data_sources.py b/flexmeasures/data/services/data_sources.py index b19c37ca0..70910df6d 100644 --- a/flexmeasures/data/services/data_sources.py +++ b/flexmeasures/data/services/data_sources.py @@ -19,6 +19,7 @@ def get_or_create_source( attributes: dict | None = None, flush: bool = True, ) -> DataSource: + attributes_hash = None if is_user(source): source_type = "user" query = DataSource.query.filter(DataSource.type == source_type) @@ -45,7 +46,12 @@ def get_or_create_source( if source_type is None: raise TypeError("Please specify a source type") _source = DataSource( - name=source, model=model, version=version, type=source_type + name=source, + model=model, + version=version, + type=source_type, + attributes=attributes, + attributes_hash=attributes_hash, ) current_app.logger.info(f"Setting up {_source} as new data source...") db.session.add(_source) From ef6306762c9ae2c4756d213b7bf1ff98a9dd00aa Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Thu, 29 Jun 2023 12:05:34 +0200 Subject: [PATCH 09/19] changing backref from "dynamic" to "select" Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 8350830e8..0fc4c314b 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -85,7 +85,7 @@ class DataSource(db.Model, tb.BeliefSourceDBMixin): sensors = db.relationship( "Sensor", secondary="timed_belief", - backref=db.backref("data_sources", lazy="dynamic"), + backref=db.backref("data_sources", lazy="select"), viewonly=True, ) From 8ea2702407abd6af307ebb84b42349b0938cbf95 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Thu, 29 Jun 2023 12:16:35 +0200 Subject: [PATCH 10/19] feat: add hash_attributes static method Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 0fc4c314b..6d03978e7 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -167,6 +167,10 @@ def to_dict(self) -> dict: description=self.description, ) + @staticmethod + def hash_attributes(attributes: dict) -> str: + return hashlib.sha256(json.dumps(attributes).encode("utf-8")).digest() + def get_attribute(self, attribute: str, default: Any = None) -> Any: """Looks for the attribute on the DataSource. If not found, returns the default. From e717a4e43549f9443f694ed60e47cb2f204fe7ff Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Thu, 29 Jun 2023 12:18:51 +0200 Subject: [PATCH 11/19] fix: use hash_attributes static method Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/services/data_sources.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/flexmeasures/data/services/data_sources.py b/flexmeasures/data/services/data_sources.py index 70910df6d..74eae3f56 100644 --- a/flexmeasures/data/services/data_sources.py +++ b/flexmeasures/data/services/data_sources.py @@ -2,9 +2,6 @@ from flask import current_app -import json -import hashlib - from flexmeasures import User from flexmeasures.data import db from flexmeasures.data.models.data_sources import DataSource @@ -19,7 +16,6 @@ def get_or_create_source( attributes: dict | None = None, flush: bool = True, ) -> DataSource: - attributes_hash = None if is_user(source): source_type = "user" query = DataSource.query.filter(DataSource.type == source_type) @@ -28,10 +24,9 @@ def get_or_create_source( if version is not None: query = query.filter(DataSource.version == version) if attributes is not None: - attributes_hash = hashlib.sha256( - json.dumps(attributes).encode("utf-8") - ).digest() - query = query.filter(DataSource.attributes_hash == attributes_hash) + query = query.filter( + DataSource.attributes_hash == DataSource.hash_attributes(attributes) + ) if is_user(source): query = query.filter(DataSource.user == source) elif isinstance(source, str): @@ -51,7 +46,6 @@ def get_or_create_source( version=version, type=source_type, attributes=attributes, - attributes_hash=attributes_hash, ) current_app.logger.info(f"Setting up {_source} as new data source...") db.session.add(_source) From ec3b370ebbb94fade62263ce05f1d0bcd4b9b452 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Thu, 29 Jun 2023 12:36:04 +0200 Subject: [PATCH 12/19] feat: adding attributes_hash to the DataSource unique constraint list Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 6d03978e7..528258ebf 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -60,7 +60,9 @@ class DataSource(db.Model, tb.BeliefSourceDBMixin): """Each data source is a data-providing entity.""" __tablename__ = "data_source" - __table_args__ = (db.UniqueConstraint("name", "user_id", "model", "version"),) + __table_args__ = ( + db.UniqueConstraint("name", "user_id", "model", "version", "attributes_hash"), + ) # The type of data source (e.g. user, forecaster or scheduler) type = db.Column(db.String(80), default="") From ab4b0ae956d135f1b1141221a5dbde000123c50a Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Thu, 29 Jun 2023 17:14:41 +0200 Subject: [PATCH 13/19] fix: add constraint to migration and downgrade Signed-off-by: Victor Garcia Reolid --- ...e0c_add_attribute_column_to_data_source.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py b/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py index 4ed55d15d..8698bc3a5 100644 --- a/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py +++ b/flexmeasures/data/migrations/versions/2ac7fb39ce0c_add_attribute_column_to_data_source.py @@ -29,6 +29,23 @@ def upgrade(): sa.Column("attributes_hash", sa.LargeBinary(length=256), nullable=True), ) + # remove previous uniqueness constraint and add a new that takes attributes_hash into account + op.drop_constraint(op.f("data_source_name_key"), "data_source", type_="unique") + op.create_unique_constraint( + "data_source_name_key", + "data_source", + ["name", "user_id", "model", "version", "attributes_hash"], + ) + def downgrade(): - pass + + op.drop_constraint("data_source_name_key", "data_source", type_="unique") + op.create_unique_constraint( + "data_source_name_key", + "data_source", + ["name", "user_id", "model", "version"], + ) + + op.drop_column("data_source", "attributes") + op.drop_column("data_source", "attributes_hash") From 6b3a585949ab52e0858c5fe73311bb0a003a8e5f Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Thu, 29 Jun 2023 17:24:49 +0200 Subject: [PATCH 14/19] fix: only returning keys from the attributes field Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 528258ebf..5f28a95ba 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -174,15 +174,8 @@ def hash_attributes(attributes: dict) -> str: return hashlib.sha256(json.dumps(attributes).encode("utf-8")).digest() def get_attribute(self, attribute: str, default: Any = None) -> Any: - """Looks for the attribute on the DataSource. - If not found, returns the default. - """ - if hasattr(self, attribute): - return getattr(self, attribute) - if attribute in self.attributes: - return self.attributes[attribute] - - return default + """Looks for the attribute on the DataSource.""" + return self.attributes.get(attribute) def has_attribute(self, attribute: str) -> bool: return attribute in self.attributes From 95518e015f69f4a210ac35f9422676975c6bb616 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Sun, 2 Jul 2023 22:40:09 +0200 Subject: [PATCH 15/19] docs: fix docstring Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 5f28a95ba..4a260d8f6 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -174,7 +174,7 @@ def hash_attributes(attributes: dict) -> str: return hashlib.sha256(json.dumps(attributes).encode("utf-8")).digest() def get_attribute(self, attribute: str, default: Any = None) -> Any: - """Looks for the attribute on the DataSource.""" + """Looks for the attribute in the DataSource's attributes column.""" return self.attributes.get(attribute) def has_attribute(self, attribute: str) -> bool: From 3cdf91b89f31a132050ec20b7cd75055161cc22f Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Sun, 2 Jul 2023 22:40:32 +0200 Subject: [PATCH 16/19] fix: use default value Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 4a260d8f6..9a49d6583 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -175,7 +175,7 @@ def hash_attributes(attributes: dict) -> str: def get_attribute(self, attribute: str, default: Any = None) -> Any: """Looks for the attribute in the DataSource's attributes column.""" - return self.attributes.get(attribute) + return self.attributes.get(attribute, default) def has_attribute(self, attribute: str) -> bool: return attribute in self.attributes From b347cb210406a3c56be0b584c471d48c82873bee Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Sun, 2 Jul 2023 22:41:28 +0200 Subject: [PATCH 17/19] fix: allow creating new attributes with the method `set_attributes` Signed-off-by: Victor Garcia Reolid --- flexmeasures/data/models/data_sources.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flexmeasures/data/models/data_sources.py b/flexmeasures/data/models/data_sources.py index 9a49d6583..307b008d0 100644 --- a/flexmeasures/data/models/data_sources.py +++ b/flexmeasures/data/models/data_sources.py @@ -181,5 +181,4 @@ def has_attribute(self, attribute: str) -> bool: return attribute in self.attributes def set_attribute(self, attribute: str, value): - if self.has_attribute(attribute): - self.attributes[attribute] = value + self.attributes[attribute] = value From 6c5589343d2b7792e21abc14f151b2ffda33a0a2 Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Sun, 2 Jul 2023 22:43:06 +0200 Subject: [PATCH 18/19] docs: add changelog entry Signed-off-by: Victor Garcia Reolid --- documentation/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 940ea1ccb..f1264f997 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -12,6 +12,7 @@ New features * Allow deleting multiple sensors with a single call to ``flexmeasures delete sensor`` by passing the ``--id`` option multiple times [see `PR #734 `_] * Make it a lot easier to read off the color legend on the asset page, especially when showing many sensors, as they will now be ordered from top to bottom in the same order as they appear in the chart (as defined in the ``sensors_to_show`` attribute), rather than alphabetically [see `PR #742 `_] * Having percentages within the [0, 100] domain is such a common use case that we now always include it in sensor charts with % units, making it easier to read off individual charts and also to compare across charts [see `PR #739 `_] +* DataSource table now allows storing arbitrary attributes as a JSON (without content validation), similar to the Sensor and GenericAsset tables [see `PR #750 `_] Bugfixes ----------- From 58a42d8ef33836ec33aa6aeca71b4369783e3f5a Mon Sep 17 00:00:00 2001 From: Victor Garcia Reolid Date: Thu, 6 Jul 2023 23:28:29 +0200 Subject: [PATCH 19/19] docs: add db upgrade warning Signed-off-by: Victor Garcia Reolid --- documentation/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index f1264f997..fb9f7db29 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -6,6 +6,8 @@ FlexMeasures Changelog v0.15.0 | July XX, 2023 ============================ +.. warning:: Upgrading to this version requires running ``flexmeasures db upgrade`` (you can create a backup first with ``flexmeasures db-ops dump``). + New features -------------