Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
## [0.2.x] - xxxx-xx-xx
## [0.2.1] - 2025-07-01
### Added
### Changed
- New fields `q_raw`, `v_av`, `v_bulk` in `api.models.time_series.py` for storing raw
optical discharge, average surface velocity and bulk velocity.
- Added new time series fields to admin and API views.
### Deprecated
### Removed
### Fixed
- Recipe serialization now includes "id"
- fixed test assertions by using `assertEqual` instead of deprecated `assertEquals`
- fixed wrongly preferred HTML serializer for API POST/GET request to time series objects. This now
is JSON by default.
- storage port mapping for internal storage set to correct port.
### Security


Expand Down
2 changes: 1 addition & 1 deletion LiveORC/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import boto3

# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
VERSION = "0.2.0"
VERSION = "0.2.1"
# Build paths inside the project like this: BASE_DIR / 'subdir'.
# try to get BASE_DIR from env variable
BASE_DIR = Path(__file__).resolve().parent.parent
Expand Down
52 changes: 47 additions & 5 deletions api/admin/time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class TimeSeriesInline(admin.TabularInline):
class TimeSeriesAdmin(ExportActionModelAdmin, BaseAdmin):
resource_classes = [TimeSeriesResource]
export_form_class = CustomExportForm
list_display = ["get_site_name", "timestamp", "str_h", "str_fraction_velocimetry", "str_q_50", 'thumbnail_preview']
list_display = ["get_site_name", "timestamp", "str_h", "str_fraction_velocimetry", "str_q_50", "str_q_raw", "str_v_bulk", "str_v_av", "thumbnail_preview"]
list_filter = ["site__name"]
readonly_fields = (
"image_preview",
Expand All @@ -74,6 +74,11 @@ class TimeSeriesAdmin(ExportActionModelAdmin, BaseAdmin):
"str_q_50",
"str_q_75",
"str_q_95",
"str_q_raw",
"str_v_av",
"str_v_bulk",
"str_wetted_surface",
"str_wetted_perimeter",
"str_fraction_velocimetry",
"link_video"
)
Expand All @@ -90,8 +95,11 @@ class TimeSeriesAdmin(ExportActionModelAdmin, BaseAdmin):
"str_q_50",
"str_q_75",
"str_q_95",
"wetted_surface",
"wetted_perimeter",
"str_q_raw",
"str_v_av",
"str_v_bulk",
"str_wetted_surface",
"str_wetted_perimeter",
"str_fraction_velocimetry"
]
}
Expand Down Expand Up @@ -142,8 +150,11 @@ def get_readonly_fields(self, request, obj=None):
"str_q_50",
"str_q_75",
"str_q_95",
"wetted_surface",
"wetted_perimeter",
"q_raw",
"str_v_av",
"str_v_bulk",
"str_wetted_surface",
"str_wetted_perimeter",
"str_fraction_velocimetry"
)
return self.readonly_fields
Expand Down Expand Up @@ -202,6 +213,37 @@ def str_q_95(self, obj):
str_q_95.short_description = 'Discharge 95% [m3/s]'
str_q_95.allow_tags = True

def str_q_raw(self, obj):
if obj.q_raw:
return round(obj.q_raw, 2)
str_q_raw.short_description = 'Optical discharge [m3/s]'
str_q_raw.allow_tags = True

def str_v_av(self, obj):
if obj.v_av:
return round(obj.v_av, 2)
str_v_av.short_description = 'Av. surface velocity [m/s]'
str_v_av.allow_tags = True


def str_wetted_surface(self, obj):
if obj.wetted_surface:
return round(obj.wetted_surface, 2)
str_wetted_surface.short_description = 'Wetted surface [m2]'
str_wetted_surface.allow_tags = True

def str_wetted_perimeter(self, obj):
if obj.wetted_perimeter:
return round(obj.wetted_perimeter, 2)
str_wetted_perimeter.short_description = 'Wetted perimeter [m]'
str_wetted_perimeter.allow_tags = True

def str_v_bulk(self, obj):
if obj.v_bulk:
return round(obj.v_bulk, 2)
str_v_bulk.short_description = 'Bulk velocity [m/s]'
str_v_bulk.allow_tags = True

def str_fraction_velocimetry(self, obj):
if obj.fraction_velocimetry:
return round(obj.fraction_velocimetry, 2)
Expand Down
6 changes: 6 additions & 0 deletions api/custom_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ def pijsonify(self, data):
"q_50": "q.50",
"q_75": "q.75",
"q_95": "q.95",
"q_raw": "q.raw",
"v_av": "v.av",
"v_bulk": "v.bulk",
"wetted_surface": "A.wet",
"wetted_perimeter": "P.wet",
"fraction_velocimetry": "v.frac"
Expand All @@ -71,6 +74,9 @@ def pijsonify(self, data):
"q_50": "m3/s",
"q_75": "m3/s",
"q_95": "m3/s",
"q_raw": "m3/s",
"v_av": "m/s",
"v_bulk": "m/s",
"wetted_surface": "m2",
"wetted_perimeter": "m",
"fraction_velocimetry": "%"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 5.1.1 on 2025-06-30 15:02

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0003_alter_cameraconfig_version_alter_recipe_version'),
]

operations = [
migrations.AddField(
model_name='timeseries',
name='q_raw',
field=models.FloatField(blank=True, help_text='River flow measured optically', null=True, verbose_name='Raw discharge'),
),
migrations.AddField(
model_name='timeseries',
name='v_av',
field=models.FloatField(blank=True, help_text='Average surface velocity [m/s]', null=True),
),
migrations.AddField(
model_name='timeseries',
name='v_bulk',
field=models.FloatField(blank=True, help_text='Bulk velocity [m/s]', null=True),
),
]
9 changes: 8 additions & 1 deletion api/models/time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ class TimeSeries(BaseModel):
q_50 = models.FloatField("Discharge median", help_text="Median river flow", null=True, blank=True)
q_75 = models.FloatField("Discharge 75%", help_text="River flow with probability of non-exceedance of 75% [m3/s]", null=True, blank=True)
q_95 = models.FloatField("Discharge 95%", help_text="River flow with probability of non-exceedance of 95% [m3/s]", null=True, blank=True)
q_raw = models.FloatField("Raw discharge", help_text="River flow measured optically", null=True, blank=True)
wetted_surface = models.FloatField(help_text="Wetted surface area with given water level [m2]", null=True, blank=True)
wetted_perimeter = models.FloatField(help_text="Wetted perimeter with given water level [m]", null=True, blank=True)
fraction_velocimetry = models.FloatField(help_text="Fraction of discharge resolved using velocimetry [-]", null=True, blank=True)
v_bulk = models.FloatField(help_text="Bulk velocity [m/s]", null=True, blank=True)
v_av = models.FloatField(help_text="Average surface velocity [m/s]", null=True, blank=True)

# TODO: create link with videos, filtered on site, to add water level to those videos.
def save(self, *args, **kwargs):
Expand All @@ -65,9 +68,13 @@ class Meta:
verbose_name_plural = "time series"

def __str__(self):
return "{:s} h [m]: {:s}, Q [m3/s]: {:s}, f [-]: {:s}".format(
return "{:s} h [m]: {:s}, Q [m3/s]: {:s}, Q_raw [m3/s]: {:s}, v_av [m/s]: {:s}, v_bulk [m/s]: {:s} f [-]: {:s}".format(
self.timestamp.strftime("%Y-%m-%dT%H:%M:%S"),
get_str(self.h, 3),
get_str(self.q_50, 2),
get_str(self.q_raw, 2),
get_str(self.v_av, 2),
get_str(self.v_bulk, 2),
get_str(self.fraction_velocimetry, 1)

)
3 changes: 3 additions & 0 deletions api/serializers/time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class Meta:
"q_50",
"q_75",
"q_95",
"q_raw",
"v_av",
"v_bulk",
"wetted_surface",
"wetted_perimeter",
"fraction_velocimetry",
Expand Down
4 changes: 2 additions & 2 deletions api/tests/test_api_timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ def test_add_longer_timeseries_and_query(self):
uri + "?startDateTime=2000-01-01T04:00:00:00Z" + "&endDateTime=2000-01-01T07:00:00:00Z" + "&format=pijson"
)
self.assertEqual(len(r.json()), 3) # test for number of fields in pijson (always 3)
self.assertEqual(len(r.json()["timeSeries"]), 9) # test for number of variables (currently 9)
self.assertEqual(len(r.json()["timeSeries"]), 12) # test for number of variables (currently 9)
self.assertEqual(len(r.json()["timeSeries"][0]["events"]), 4) # test for number of records
# query in csv format
r = client.get(
uri + "?startDateTime=2000-01-01T04:00:00:00Z" + "&endDateTime=2000-01-01T07:00:00:00Z" + "&format=csv"
)
self.assertEqual(len(r.content), 277)
self.assertEqual(len(r.content), 307)
5 changes: 3 additions & 2 deletions docker-compose.s3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ services:
storage:
image: minio/minio
ports:
- ${LORC_STORAGE_PORT}:9000
- ${LORC_STORAGE_PORT}:${LORC_STORAGE_PORT}
- 9001:9001
environment:
MINIO_ROOT_USER: "${LORC_STORAGE_ACCESS}"
MINIO_ROOT_PASSWORD: "${LORC_STORAGE_SECRET}"
MINIO_API_PORT: ${LORC_STORAGE_PORT}
MINIO_CONSOLE_PORT: 9001

volumes:
- ${LORC_STORAGE_DIR}:/data
command: server /data --console-address ":9001"
command: server /data --console-address ":9001" --address ":${LORC_STORAGE_PORT}"
2 changes: 1 addition & 1 deletion liveorc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ case $key in
shift # past value
;;
--storage-port)
LORC_STORAGE_PORT=$(realpath "$2")
LORC_STORAGE_PORT=$2
export LORC_STORAGE_PORT
shift # past argument
shift # past value
Expand Down