From ba14c5e9cc1fb88176106b654f2ee322f0c0ad82 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 13:01:16 +0000 Subject: [PATCH 01/11] Add Measurement model and fields --- measurement/models.py | 48 +++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/measurement/models.py b/measurement/models.py index 92ea31de..3cd28171 100755 --- a/measurement/models.py +++ b/measurement/models.py @@ -40,24 +40,9 @@ class MeasurementBase(TimescaleModel): variable = models.ForeignKey( Variable, on_delete=models.PROTECT, null=False, verbose_name="Variable" ) - value = models.DecimalField( - "value", - max_digits=14, - decimal_places=6, - null=False, - ) - maximum = models.DecimalField( - "maximum", - max_digits=14, - decimal_places=6, - null=True, - ) - minimum = models.DecimalField( - "minimum", - max_digits=14, - decimal_places=6, - null=True, - ) + value = models.DecimalField("value", max_digits=14, decimal_places=6, null=False) + maximum = models.DecimalField("maximum", max_digits=14, decimal_places=6, null=True) + minimum = models.DecimalField("minimum", max_digits=14, decimal_places=6, null=True) class Meta: default_permissions = () @@ -105,6 +90,33 @@ def clean(self) -> None: ) +class Measurement(MeasurementBase): + """_summary_""" + + depth = models.PositiveSmallIntegerField("depth", null=True) + direction = models.DecimalField( + "direction", max_digits=14, decimal_places=6, null=True + ) + raw_value = models.DecimalField( + "raw value", max_digits=14, decimal_places=6, null=True, editable=False + ) + raw_maximum = models.DecimalField( + "raw maximum", max_digits=14, decimal_places=6, null=True, editable=False + ) + raw_minimum = models.DecimalField( + "raw minimum", max_digits=14, decimal_places=6, null=True, editable=False + ) + raw_direction = models.DecimalField( + "raw direction", max_digits=14, decimal_places=6, null=True, editable=False + ) + raw_depth = models.PositiveSmallIntegerField("raw depth", null=True, editable=False) + used_for_hourly = models.BooleanField( + verbose_name="Has data been used already for an hourly report?", default=False + ) + is_validated = models.BooleanField("Has data been validated?", default=False) + is_active = models.BooleanField("Is data active?", default=True) + + ## Legacy models - to be removed From e2e446b9ce1b46d064939ba516ad0e385eb37349 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 13:29:29 +0000 Subject: [PATCH 02/11] Add Measurement.clean method and docstrings --- measurement/models.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/measurement/models.py b/measurement/models.py index 3cd28171..f69a1171 100755 --- a/measurement/models.py +++ b/measurement/models.py @@ -91,7 +91,16 @@ def clean(self) -> None: class Measurement(MeasurementBase): - """_summary_""" + """Class to store the measurements and their validation status. + + This class holds the value of a given variable and station at a specific time, as + well as auxiliary information such as maximum and minimum values, depth and + direction, for vector quantities. All of these hava a `raw` version where a backup + of the original data is kept, should this change at any point. + + Flgas to monitor its validation status, if the data is active (and therefore can be + used for reporting) and if it has actually been used for that is also included. + """ depth = models.PositiveSmallIntegerField("depth", null=True) direction = models.DecimalField( @@ -116,6 +125,25 @@ class Measurement(MeasurementBase): is_validated = models.BooleanField("Has data been validated?", default=False) is_active = models.BooleanField("Is data active?", default=True) + def clean(self) -> None: + """Check consistency of validation, reporting and backs-up values.""" + # Check consistency of validation + if not self.is_validated and not self.is_active: + raise ValueError("Only validated entries can be delcared as inactive.") + + # Check consistency of the reporting + if self.used_for_hourly and not (self.is_validated and self.is_active): + raise ValueError( + "Only validated, active data can be used for hourly reports." + ) + + # Backup values to raws, if needed + raws = (r for r in dir(self) if r.startswith("raw_")) + for r in raws: + value = getattr(self, r.removeprefix("raw_")) + if value and not getattr(self, r): + setattr(self, r, value) + ## Legacy models - to be removed From cf1c246d4bcb8f906dacc41d0be3bb6c6a535227 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 13:36:09 +0000 Subject: [PATCH 03/11] Adds Measurement.overwritten property --- measurement/models.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/measurement/models.py b/measurement/models.py index f69a1171..c39c6df2 100755 --- a/measurement/models.py +++ b/measurement/models.py @@ -144,6 +144,22 @@ def clean(self) -> None: if value and not getattr(self, r): setattr(self, r, value) + @property + def overwritten(self) -> bool: + """Indicates if any of the values associated to the entry have been overwritten. + + Returns: + bool: True if any raw field is different to the corresponding standard + field. + """ + raws = (r for r in dir(self) if r.startswith("raw_")) + for r in raws: + value = getattr(self, r.removeprefix("raw_")) + if value and value != getattr(self, r): + return True + + return False + ## Legacy models - to be removed From 885d18bebf54b0a16c32528e3d022316639b7340 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 13:42:29 +0000 Subject: [PATCH 04/11] Add measurement test_overwritten --- tests/measurement/test_models.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/measurement/test_models.py b/tests/measurement/test_models.py index 00b3766d..2a8681b1 100755 --- a/tests/measurement/test_models.py +++ b/tests/measurement/test_models.py @@ -117,3 +117,28 @@ def test_clean(self): self.model.report_type = ReportType.MONTLY with self.assertRaises(ValueError): self.model.clean() + + +class TestMeasurement(TestCase): + def setUp(self) -> None: + from measurement.models import Measurement + from station.models import Station + from variable.models import Variable + + station = baker.make(Station) + variable = baker.make(Variable) + self.model: Measurement = Measurement( + time=datetime(2018, 1, 9, 23, 55, 59, tzinfo=pytz.UTC), + station=station, + variable=variable, + value=42, + ) + + def test_overwritten(self): + # If we start fresh, it is not overwritten + self.model.clean() + self.assertFalse(self.model.overwritten) + + # But if we change it, it should be overwritten + self.model.value = 32 + self.assertTrue(self.model.overwritten) From 89193327de032b051df36a58b6c671625f8eabe7 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 13:45:59 +0000 Subject: [PATCH 05/11] Add measurement test_clean_backup_raws --- tests/measurement/test_models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/measurement/test_models.py b/tests/measurement/test_models.py index 2a8681b1..778962b2 100755 --- a/tests/measurement/test_models.py +++ b/tests/measurement/test_models.py @@ -134,6 +134,14 @@ def setUp(self) -> None: value=42, ) + def test_clean_backup_raws(self): + # Initially, there's no raw + self.assertIsNone(self.model.raw_value) + + # But after running clean, there is and is equal to value + self.model.clean() + self.assertEqual(self.model.value, self.model.raw_value) + def test_overwritten(self): # If we start fresh, it is not overwritten self.model.clean() From 26e482422fbb2e587a4955d6d91d3a9c17c20cbf Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 13:50:12 +0000 Subject: [PATCH 06/11] Add measurement test_clean_validation --- tests/measurement/test_models.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/measurement/test_models.py b/tests/measurement/test_models.py index 778962b2..cfffb3c3 100755 --- a/tests/measurement/test_models.py +++ b/tests/measurement/test_models.py @@ -142,6 +142,21 @@ def test_clean_backup_raws(self): self.model.clean() self.assertEqual(self.model.value, self.model.raw_value) + def test_clean_validation(self): + # When not validated and active, all should be ok + self.model.clean() + + # If becomes inactive but is not validated, there's an error + self.model.is_validated = False + self.model.is_active = False + with self.assertRaises(ValueError): + self.model.clean() + + # If it is inactive but has been validated, then it is ok again + self.model.is_validated = True + self.model.is_active = False + self.model.clean() + def test_overwritten(self): # If we start fresh, it is not overwritten self.model.clean() From b3292127c296af2486424b72afb97feda5cdcf85 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 13:54:09 +0000 Subject: [PATCH 07/11] Add measurement test_clean_reporting --- tests/measurement/test_models.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/measurement/test_models.py b/tests/measurement/test_models.py index cfffb3c3..f0a1e40d 100755 --- a/tests/measurement/test_models.py +++ b/tests/measurement/test_models.py @@ -157,6 +157,29 @@ def test_clean_validation(self): self.model.is_active = False self.model.clean() + def test_clean_reporting(self): + # If it is validated and active, then it can be used for hourly reporting + self.model.is_validated = True + self.model.is_active = True + self.model.used_for_hourly = True + self.model.clean() + + # If either is false, then it cannot be used + self.model.is_validated = True + self.model.is_active = False + with self.assertRaises(ValueError): + self.model.clean() + + self.model.is_validated = False + self.model.is_active = True + with self.assertRaises(ValueError): + self.model.clean() + + self.model.is_validated = False + self.model.is_active = False + with self.assertRaises(ValueError): + self.model.clean() + def test_overwritten(self): # If we start fresh, it is not overwritten self.model.clean() From 4e8221e35e733710f74bcba54a7e90782f71e653 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 15:29:42 +0000 Subject: [PATCH 08/11] Make fields optional in measurement --- measurement/migrations/0006_measurement.py | 160 ++++++++++++++++++ ...th_alter_measurement_direction_and_more.py | 154 +++++++++++++++++ measurement/models.py | 60 +++++-- 3 files changed, 356 insertions(+), 18 deletions(-) create mode 100644 measurement/migrations/0006_measurement.py create mode 100644 measurement/migrations/0007_alter_measurement_depth_alter_measurement_direction_and_more.py diff --git a/measurement/migrations/0006_measurement.py b/measurement/migrations/0006_measurement.py new file mode 100644 index 00000000..e20fa69b --- /dev/null +++ b/measurement/migrations/0006_measurement.py @@ -0,0 +1,160 @@ +# Generated by Django 4.2.7 on 2024-01-17 15:08 + +import django.db.models.deletion +import timescale.db.models.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("station", "0003_alter_station_timezone"), + ("variable", "0001_initial"), + ("measurement", "0005_report"), + ] + + operations = [ + migrations.CreateModel( + name="Measurement", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "time", + timescale.db.models.fields.TimescaleDateTimeField(interval="1 day"), + ), + ( + "value", + models.DecimalField( + decimal_places=6, max_digits=14, verbose_name="value" + ), + ), + ( + "maximum", + models.DecimalField( + decimal_places=6, + max_digits=14, + null=True, + verbose_name="maximum", + ), + ), + ( + "minimum", + models.DecimalField( + decimal_places=6, + max_digits=14, + null=True, + verbose_name="minimum", + ), + ), + ( + "depth", + models.PositiveSmallIntegerField(null=True, verbose_name="depth"), + ), + ( + "direction", + models.DecimalField( + decimal_places=6, + max_digits=14, + null=True, + verbose_name="direction", + ), + ), + ( + "raw_value", + models.DecimalField( + decimal_places=6, + editable=False, + max_digits=14, + null=True, + verbose_name="raw value", + ), + ), + ( + "raw_maximum", + models.DecimalField( + decimal_places=6, + editable=False, + max_digits=14, + null=True, + verbose_name="raw maximum", + ), + ), + ( + "raw_minimum", + models.DecimalField( + decimal_places=6, + editable=False, + max_digits=14, + null=True, + verbose_name="raw minimum", + ), + ), + ( + "raw_direction", + models.DecimalField( + decimal_places=6, + editable=False, + max_digits=14, + null=True, + verbose_name="raw direction", + ), + ), + ( + "raw_depth", + models.PositiveSmallIntegerField( + editable=False, null=True, verbose_name="raw depth" + ), + ), + ( + "used_for_hourly", + models.BooleanField( + default=False, + verbose_name="Has data been used already for an hourly report?", + ), + ), + ( + "is_validated", + models.BooleanField( + default=False, verbose_name="Has data been validated?" + ), + ), + ( + "is_active", + models.BooleanField(default=True, verbose_name="Is data active?"), + ), + ( + "station", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="station.station", + verbose_name="Station", + ), + ), + ( + "variable", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="variable.variable", + verbose_name="Variable", + ), + ), + ], + options={ + "abstract": False, + "default_permissions": (), + "indexes": [ + models.Index( + fields=["station", "time", "variable"], + name="measurement_station_322060_idx", + ) + ], + }, + ), + ] diff --git a/measurement/migrations/0007_alter_measurement_depth_alter_measurement_direction_and_more.py b/measurement/migrations/0007_alter_measurement_depth_alter_measurement_direction_and_more.py new file mode 100644 index 00000000..240bb2d2 --- /dev/null +++ b/measurement/migrations/0007_alter_measurement_depth_alter_measurement_direction_and_more.py @@ -0,0 +1,154 @@ +# Generated by Django 4.2.7 on 2024-01-17 15:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("measurement", "0006_measurement"), + ] + + operations = [ + migrations.AlterField( + model_name="measurement", + name="depth", + field=models.PositiveSmallIntegerField( + blank=True, null=True, verbose_name="depth" + ), + ), + migrations.AlterField( + model_name="measurement", + name="direction", + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=14, + null=True, + verbose_name="direction", + ), + ), + migrations.AlterField( + model_name="measurement", + name="is_active", + field=models.BooleanField(default=True, verbose_name="Active?"), + ), + migrations.AlterField( + model_name="measurement", + name="is_validated", + field=models.BooleanField(default=False, verbose_name="Validated?"), + ), + migrations.AlterField( + model_name="measurement", + name="maximum", + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=14, + null=True, + verbose_name="maximum", + ), + ), + migrations.AlterField( + model_name="measurement", + name="minimum", + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=14, + null=True, + verbose_name="minimum", + ), + ), + migrations.AlterField( + model_name="measurement", + name="raw_depth", + field=models.PositiveSmallIntegerField( + blank=True, editable=False, null=True, verbose_name="raw depth" + ), + ), + migrations.AlterField( + model_name="measurement", + name="raw_direction", + field=models.DecimalField( + blank=True, + decimal_places=6, + editable=False, + max_digits=14, + null=True, + verbose_name="raw direction", + ), + ), + migrations.AlterField( + model_name="measurement", + name="raw_maximum", + field=models.DecimalField( + blank=True, + decimal_places=6, + editable=False, + max_digits=14, + null=True, + verbose_name="raw maximum", + ), + ), + migrations.AlterField( + model_name="measurement", + name="raw_minimum", + field=models.DecimalField( + blank=True, + decimal_places=6, + editable=False, + max_digits=14, + null=True, + verbose_name="raw minimum", + ), + ), + migrations.AlterField( + model_name="measurement", + name="raw_value", + field=models.DecimalField( + blank=True, + decimal_places=6, + editable=False, + max_digits=14, + null=True, + verbose_name="raw value", + ), + ), + migrations.AlterField( + model_name="measurement", + name="used_for_hourly", + field=models.BooleanField(default=False, verbose_name="Used for hourly?"), + ), + migrations.AlterField( + model_name="report", + name="maximum", + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=14, + null=True, + verbose_name="maximum", + ), + ), + migrations.AlterField( + model_name="report", + name="minimum", + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=14, + null=True, + verbose_name="minimum", + ), + ), + migrations.AlterField( + model_name="report", + name="used_for_daily", + field=models.BooleanField(default=False, verbose_name="Used for daily?"), + ), + migrations.AlterField( + model_name="report", + name="used_for_monthly", + field=models.BooleanField(default=False, verbose_name="Used for monthly?"), + ), + ] diff --git a/measurement/models.py b/measurement/models.py index 29aa64f9..5b42da62 100755 --- a/measurement/models.py +++ b/measurement/models.py @@ -42,8 +42,12 @@ class MeasurementBase(TimescaleModel): Variable, on_delete=models.PROTECT, null=False, verbose_name="Variable" ) value = models.DecimalField("value", max_digits=14, decimal_places=6, null=False) - maximum = models.DecimalField("maximum", max_digits=14, decimal_places=6, null=True) - minimum = models.DecimalField("minimum", max_digits=14, decimal_places=6, null=True) + maximum = models.DecimalField( + "maximum", max_digits=14, decimal_places=6, null=True, blank=True + ) + minimum = models.DecimalField( + "minimum", max_digits=14, decimal_places=6, null=True, blank=True + ) class Meta: default_permissions = () @@ -66,11 +70,9 @@ class Report(MeasurementBase): """ report_type = models.CharField(max_length=7, choices=ReportType.choices, null=False) - used_for_daily = models.BooleanField( - verbose_name="Has data been used already for a daily report?", default=False - ) + used_for_daily = models.BooleanField(verbose_name="Used for daily?", default=False) used_for_monthly = models.BooleanField( - verbose_name="Has data been used already for a montly report?", default=False + verbose_name="Used for monthly?", default=False ) class Meta: @@ -103,38 +105,60 @@ class Measurement(MeasurementBase): used for reporting) and if it has actually been used for that is also included. """ - depth = models.PositiveSmallIntegerField("depth", null=True) + depth = models.PositiveSmallIntegerField("depth", null=True, blank=True) direction = models.DecimalField( - "direction", max_digits=14, decimal_places=6, null=True + "direction", max_digits=14, decimal_places=6, null=True, blank=True ) raw_value = models.DecimalField( - "raw value", max_digits=14, decimal_places=6, null=True, editable=False + "raw value", + max_digits=14, + decimal_places=6, + null=True, + blank=True, + editable=False, ) raw_maximum = models.DecimalField( - "raw maximum", max_digits=14, decimal_places=6, null=True, editable=False + "raw maximum", + max_digits=14, + decimal_places=6, + null=True, + blank=True, + editable=False, ) raw_minimum = models.DecimalField( - "raw minimum", max_digits=14, decimal_places=6, null=True, editable=False + "raw minimum", + max_digits=14, + decimal_places=6, + null=True, + blank=True, + editable=False, ) raw_direction = models.DecimalField( - "raw direction", max_digits=14, decimal_places=6, null=True, editable=False + "raw direction", + max_digits=14, + decimal_places=6, + null=True, + blank=True, + editable=False, + ) + raw_depth = models.PositiveSmallIntegerField( + "raw depth", null=True, blank=True, editable=False ) - raw_depth = models.PositiveSmallIntegerField("raw depth", null=True, editable=False) used_for_hourly = models.BooleanField( - verbose_name="Has data been used already for an hourly report?", default=False + verbose_name="Used for hourly?", default=False ) - is_validated = models.BooleanField("Has data been validated?", default=False) - is_active = models.BooleanField("Is data active?", default=True) + is_validated = models.BooleanField("Validated?", default=False) + is_active = models.BooleanField("Active?", default=True) def clean(self) -> None: """Check consistency of validation, reporting and backs-up values.""" # Check consistency of validation if not self.is_validated and not self.is_active: - raise ValueError("Only validated entries can be delcared as inactive.") + raise ValidationError("Only validated entries can be delcared as inactive.") # Check consistency of the reporting if self.used_for_hourly and not (self.is_validated and self.is_active): - raise ValueError( + raise ValidationError( "Only validated, active data can be used for hourly reports." ) From 9443b125d6d4da2b35d94e22818f94de4fa08b9a Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 15:30:00 +0000 Subject: [PATCH 09/11] Add measurement to admin --- measurement/admin.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/measurement/admin.py b/measurement/admin.py index 3297c29c..329d814e 100644 --- a/measurement/admin.py +++ b/measurement/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Report +from .models import Measurement, Report admin.site.site_header = "Paricia Administration - Measurements" @@ -12,5 +12,17 @@ class MeasurementBaseAdmin(admin.ModelAdmin): @admin.register(Report) class ReportAdmin(MeasurementBaseAdmin): - list_display = ["report_type"] + MeasurementBaseAdmin.list_display + list_display = ["id", "report_type"] + MeasurementBaseAdmin.list_display[1:] list_filter = ["report_type"] + MeasurementBaseAdmin.list_filter + + +@admin.register(Measurement) +class MeasurementAdmin(MeasurementBaseAdmin): + list_display = [ + "id", + "is_validated", + "is_active", + "overwritten", + ] + MeasurementBaseAdmin.list_display[1:] + list_filter = ["is_validated", "is_active"] + MeasurementBaseAdmin.list_filter + readonly_fields = [r for r in dir(Measurement) if r.startswith("raw_")] From b21da1c3a157450994c8ad8fc19795de50d0e68e Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Wed, 17 Jan 2024 15:31:19 +0000 Subject: [PATCH 10/11] Use ValidationError in tests --- tests/measurement/test_models.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/measurement/test_models.py b/tests/measurement/test_models.py index f0a1e40d..5e2e3be7 100755 --- a/tests/measurement/test_models.py +++ b/tests/measurement/test_models.py @@ -1,6 +1,7 @@ from datetime import datetime import pytz +from django.core.exceptions import ValidationError from django.test import TestCase from model_bakery import baker @@ -149,7 +150,7 @@ def test_clean_validation(self): # If becomes inactive but is not validated, there's an error self.model.is_validated = False self.model.is_active = False - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): self.model.clean() # If it is inactive but has been validated, then it is ok again @@ -167,17 +168,17 @@ def test_clean_reporting(self): # If either is false, then it cannot be used self.model.is_validated = True self.model.is_active = False - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): self.model.clean() self.model.is_validated = False self.model.is_active = True - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): self.model.clean() self.model.is_validated = False self.model.is_active = False - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): self.model.clean() def test_overwritten(self): From e6c32c771a1708c8489e2ef4b4c76f493c1473a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Alonso=20=C3=81lvarez?= <6095790+dalonsoa@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:29:37 +0000 Subject: [PATCH 11/11] Update measurement/models.py Co-authored-by: Tom Bland --- measurement/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/measurement/models.py b/measurement/models.py index 5b42da62..46a4a309 100755 --- a/measurement/models.py +++ b/measurement/models.py @@ -101,7 +101,7 @@ class Measurement(MeasurementBase): direction, for vector quantities. All of these hava a `raw` version where a backup of the original data is kept, should this change at any point. - Flgas to monitor its validation status, if the data is active (and therefore can be + Flags to monitor its validation status, if the data is active (and therefore can be used for reporting) and if it has actually been used for that is also included. """