Skip to content
Closed
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
9 changes: 9 additions & 0 deletions admin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
SurfaceWaterPhotosAdmin,
ThingAdmin,
TransducerObservationAdmin,
WaterLevelsContinuousPressureDailyAdmin,
WeatherPhotosAdmin,
WeatherDataAdmin,
FieldParametersAdmin,
Expand Down Expand Up @@ -80,6 +81,7 @@
NMA_Soil_Rock_Results,
NMA_Stratigraphy,
NMA_SurfaceWaterData,
NMA_WaterLevelsContinuous_Pressure_Daily,
NMA_WeatherPhotos,
NMA_SurfaceWaterPhotos,
NMA_WeatherData,
Expand Down Expand Up @@ -192,6 +194,13 @@ def create_admin(app):
# Transducer observations
admin.add_view(TransducerObservationAdmin(TransducerObservation))

# Water Levels - Continuous (legacy)
admin.add_view(
WaterLevelsContinuousPressureDailyAdmin(
NMA_WaterLevelsContinuous_Pressure_Daily
)
)

# Weather
admin.add_view(WeatherPhotosAdmin(NMA_WeatherPhotos))

Expand Down
4 changes: 4 additions & 0 deletions admin/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
from admin.views.surface_water_photos import SurfaceWaterPhotosAdmin
from admin.views.thing import ThingAdmin
from admin.views.transducer_observation import TransducerObservationAdmin
from admin.views.waterlevelscontinuous_pressure_daily import (
WaterLevelsContinuousPressureDailyAdmin,
)
from admin.views.weather_photos import WeatherPhotosAdmin
from admin.views.weather_data import WeatherDataAdmin

Expand Down Expand Up @@ -88,6 +91,7 @@
"SurfaceWaterPhotosAdmin",
"ThingAdmin",
"TransducerObservationAdmin",
"WaterLevelsContinuousPressureDailyAdmin",
"WeatherPhotosAdmin",
"WeatherDataAdmin",
]
148 changes: 148 additions & 0 deletions admin/views/waterlevelscontinuous_pressure_daily.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# ===============================================================================
# Copyright 2026
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================
"""
WaterLevelsContinuousPressureDailyAdmin view for legacy NMA_WaterLevelsContinuous_Pressure_Daily.
"""

from starlette.requests import Request

from admin.views.base import OcotilloModelView


class WaterLevelsContinuousPressureDailyAdmin(OcotilloModelView):
"""
Admin view for NMA_WaterLevelsContinuous_Pressure_Daily model.
"""

# ========== Basic Configuration ==========
name = "NMA Water Levels Continuous Pressure Daily"
label = "NMA Water Levels Continuous Pressure Daily"
icon = "fa fa-tachometer-alt"

def can_create(self, request: Request) -> bool:
return False

def can_edit(self, request: Request) -> bool:
return False

def can_delete(self, request: Request) -> bool:
return False

# ========== List View ==========
list_fields = [
"global_id",
"object_id",
"well_id",
"point_id",
"date_measured",
"temperature_water",
"water_head",
"water_head_adjusted",
"depth_to_water_bgs",
"measurement_method",
"data_source",
"measuring_agency",
"qced",
"notes",
"created",
"updated",
"processed_by",
"checked_by",
"cond_dl_ms_cm",
]

sortable_fields = [
"global_id",
"object_id",
"well_id",
"point_id",
"date_measured",
"water_head",
"depth_to_water_bgs",
"measurement_method",
"data_source",
"measuring_agency",
"qced",
"created",
"updated",
"processed_by",
"checked_by",
"cond_dl_ms_cm",
]

fields_default_sort = [("date_measured", True)]

searchable_fields = [
"global_id",
"well_id",
"point_id",
"date_measured",
"measurement_method",
"data_source",
"measuring_agency",
"notes",
]

page_size = 50
page_size_options = [25, 50, 100, 200]

# ========== Detail View ==========
fields = [
"global_id",
"object_id",
"well_id",
"point_id",
"date_measured",
"temperature_water",
"water_head",
"water_head_adjusted",
"depth_to_water_bgs",
"measurement_method",
"data_source",
"measuring_agency",
"qced",
"notes",
"created",
"updated",
"processed_by",
"checked_by",
"cond_dl_ms_cm",
]

field_labels = {
"global_id": "GlobalID",
"object_id": "OBJECTID",
"well_id": "WellID",
"point_id": "PointID",
"date_measured": "Date Measured",
"temperature_water": "Temperature Water",
"water_head": "Water Head",
"water_head_adjusted": "Water Head Adjusted",
"depth_to_water_bgs": "Depth To Water (BGS)",
"measurement_method": "Measurement Method",
"data_source": "Data Source",
"measuring_agency": "Measuring Agency",
"qced": "QCed",
"notes": "Notes",
"created": "Created",
"updated": "Updated",
"processed_by": "Processed By",
"checked_by": "Checked By",
"cond_dl_ms_cm": "CONDDL (mS/cm)",
}


# ============= EOF =============================================
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Add thing_id FK to NMA_WaterLevelsContinuous_Pressure_Daily.

Revision ID: e8a7c6b5d4f3
Revises: b12e3919077e
Create Date: 2026-01-29 12:45:00.000000
"""

from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op
from sqlalchemy import inspect

# revision identifiers, used by Alembic.
revision: str = "e8a7c6b5d4f3"
down_revision: Union[str, Sequence[str], None] = "b12e3919077e"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Add thing_id and FK to legacy pressure daily table."""
bind = op.get_bind()
inspector = inspect(bind)
if not inspector.has_table("NMA_WaterLevelsContinuous_Pressure_Daily"):
return

columns = {
col["name"]
for col in inspector.get_columns("NMA_WaterLevelsContinuous_Pressure_Daily")
}
if "thing_id" not in columns:
op.add_column(
"NMA_WaterLevelsContinuous_Pressure_Daily",
sa.Column("thing_id", sa.Integer(), nullable=True),
)

existing_fks = {
fk["name"]
for fk in inspector.get_foreign_keys("NMA_WaterLevelsContinuous_Pressure_Daily")
if fk.get("name")
}
if "fk_pressure_daily_thing" not in existing_fks:
op.create_foreign_key(
"fk_pressure_daily_thing",
"NMA_WaterLevelsContinuous_Pressure_Daily",
"thing",
["thing_id"],
["id"],
ondelete="CASCADE",
)

null_count = bind.execute(
sa.text(
'SELECT COUNT(*) FROM "NMA_WaterLevelsContinuous_Pressure_Daily" '
'WHERE "thing_id" IS NULL'
)
).scalar()
if null_count == 0:
op.alter_column(
"NMA_WaterLevelsContinuous_Pressure_Daily",
"thing_id",
existing_type=sa.Integer(),
nullable=False,
)


def downgrade() -> None:
"""Remove thing_id FK from legacy pressure daily table."""
bind = op.get_bind()
inspector = inspect(bind)
if not inspector.has_table("NMA_WaterLevelsContinuous_Pressure_Daily"):
return

existing_fks = {
fk["name"]
for fk in inspector.get_foreign_keys("NMA_WaterLevelsContinuous_Pressure_Daily")
if fk.get("name")
}
if "fk_pressure_daily_thing" in existing_fks:
op.drop_constraint(
"fk_pressure_daily_thing",
"NMA_WaterLevelsContinuous_Pressure_Daily",
type_="foreignkey",
)

columns = {
col["name"]
for col in inspector.get_columns("NMA_WaterLevelsContinuous_Pressure_Daily")
}
if "thing_id" in columns:
op.drop_column("NMA_WaterLevelsContinuous_Pressure_Daily", "thing_id")
85 changes: 85 additions & 0 deletions alembic/versions/f0c9d8e7b6a5_align_pressure_daily_uuid_columns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Align UUID column types on NMA_WaterLevelsContinuous_Pressure_Daily.

Revision ID: f0c9d8e7b6a5
Revises: e8a7c6b5d4f3
Create Date: 2026-01-29 12:55:00.000000
"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision: str = "f0c9d8e7b6a5"
down_revision: Union[str, Sequence[str], None] = "e8a7c6b5d4f3"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def _column_is_uuid(col) -> bool:
return isinstance(col.get("type"), postgresql.UUID)


def upgrade() -> None:
"""Alter UUID columns to proper UUID types."""
bind = op.get_bind()
inspector = inspect(bind)
if not inspector.has_table("NMA_WaterLevelsContinuous_Pressure_Daily"):
return

columns = {
col["name"]: col
for col in inspector.get_columns("NMA_WaterLevelsContinuous_Pressure_Daily")
}

global_id_col = columns.get("GlobalID")
if global_id_col is not None and not _column_is_uuid(global_id_col):
op.alter_column(
"NMA_WaterLevelsContinuous_Pressure_Daily",
"GlobalID",
type_=postgresql.UUID(as_uuid=True),
postgresql_using='"GlobalID"::uuid',
)

well_id_col = columns.get("WellID")
if well_id_col is not None and not _column_is_uuid(well_id_col):
op.alter_column(
"NMA_WaterLevelsContinuous_Pressure_Daily",
"WellID",
type_=postgresql.UUID(as_uuid=True),
postgresql_using='"WellID"::uuid',
)


def downgrade() -> None:
"""Revert UUID columns back to strings."""
bind = op.get_bind()
inspector = inspect(bind)
if not inspector.has_table("NMA_WaterLevelsContinuous_Pressure_Daily"):
return

columns = {
col["name"]: col
for col in inspector.get_columns("NMA_WaterLevelsContinuous_Pressure_Daily")
}

global_id_col = columns.get("GlobalID")
if global_id_col is not None and _column_is_uuid(global_id_col):
op.alter_column(
"NMA_WaterLevelsContinuous_Pressure_Daily",
"GlobalID",
type_=sa.String(length=40),
postgresql_using='"GlobalID"::text',
)

well_id_col = columns.get("WellID")
if well_id_col is not None and _column_is_uuid(well_id_col):
op.alter_column(
"NMA_WaterLevelsContinuous_Pressure_Daily",
"WellID",
type_=sa.String(length=40),
postgresql_using='"WellID"::text',
)
Loading
Loading