Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion config/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# override this in local_untracked.py
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"ENGINE": "seed.backends.timescale_postgis",
"NAME": "seed",
"USER": "postgres",
"PASSWORD": "postgres",
Expand Down
2 changes: 1 addition & 1 deletion config/settings/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
# PostgreSQL DB config
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"ENGINE": "seed.backends.timescale_postgis",
"NAME": env_var("POSTGRES_DB"),
"USER": env_var("POSTGRES_USER"),
"PASSWORD": env_var("POSTGRES_PASSWORD"),
Expand Down
2 changes: 1 addition & 1 deletion config/settings/docker_dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
# PostgreSQL DB config
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"ENGINE": "seed.backends.timescale_postgis",
"NAME": env_var("POSTGRES_DB"),
"USER": env_var("POSTGRES_USER"),
"PASSWORD": env_var("POSTGRES_PASSWORD"),
Expand Down
3 changes: 2 additions & 1 deletion config/settings/docker_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

TESTING_MAPQUEST_API_KEY = env_var("TESTING_MAPQUEST_API_KEY", "<your_key_here>")

DATABASES["default"]["ENGINE"] = "seed.backends.postgis_parallel_tests"
TIMESCALE_DB_BACKEND_BASE = "seed.backends.postgis_parallel_tests"
DATABASES["default"]["ENGINE"] = "seed.backends.timescale_postgis"
DATABASES["default"]["CONN_MAX_AGE"] = 0

CACHES = {
Expand Down
7 changes: 5 additions & 2 deletions config/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@
from config.settings.local_untracked import * # noqa: F403

# Use a test-only PostGIS backend that pauses Timescale background workers
# while Django clones the template test database for parallel runs.
DATABASES["default"]["ENGINE"] = "seed.backends.postgis_parallel_tests"
# while Django clones the template test database for parallel runs. The
# Timescale schema editor layers on top of it via TIMESCALE_DB_BACKEND_BASE so
# hypertables are still created during test database setup.
TIMESCALE_DB_BACKEND_BASE = "seed.backends.postgis_parallel_tests"
DATABASES["default"]["ENGINE"] = "seed.backends.timescale_postgis"
DATABASES["default"]["CONN_MAX_AGE"] = 0
CACHES = {
"default": {
Expand Down
2 changes: 1 addition & 1 deletion config/settings/test_local_untracked.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
# postgres DB config
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"ENGINE": "seed.backends.timescale_postgis",
"NAME": "seeddb",
"USER": "postgres",
"PASSWORD": "postgres",
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies = [
"django~=6.0.6",
# Persistence (database)
"psycopg[binary]~=3.3.4",
"django-timescaledb==0.2.13",
# Background jobs / scheduling / caching
"celery==5.6.3",
"django-celery-beat==2.9.0",
Expand Down
4 changes: 4 additions & 0 deletions seed/backends/timescale_postgis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""
SEED Platform (TM), Copyright (c) Alliance for Energy Innovation, LLC, and other contributors.
See also https://github.com/SEED-platform/seed/blob/main/LICENSE.md
"""
51 changes: 51 additions & 0 deletions seed/backends/timescale_postgis/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
SEED Platform (TM), Copyright (c) Alliance for Energy Innovation, LLC, and other contributors.
See also https://github.com/SEED-platform/seed/blob/main/LICENSE.md

TimescaleDB + PostGIS database backend for SEED.

This wraps ``timescale.db.backends.postgis`` and swaps in a schema editor that
fixes an upstream bug in the released ``django-timescaledb`` (0.2.13): its
PostGIS ``schema.py`` references ``settings`` inside ``_create_hypertable``
without importing it. That method is executed whenever an existing table is
migrated into a hypertable (our ``SensorReading.timestamp`` AlterField), so the
released package raises ``NameError: name 'settings' is not defined`` and the
migration fails. The fix exists on the project's ``master`` branch but has not
been released.

The base backend is still configurable through ``TIMESCALE_DB_BACKEND_BASE``
(defaults to PostGIS), so this composes with the parallel test backend used by
the test settings.

This workaround can be removed once a ``django-timescaledb`` release that
includes the upstream fix is available.
"""

from django.conf import settings
from timescale.db.backends.postgis.base import DatabaseWrapper as TimescaleDatabaseWrapper
from timescale.db.backends.postgis.schema import TimescaleSchemaEditor


class SeedTimescaleSchemaEditor(TimescaleSchemaEditor):
def _create_hypertable(self, model, field, should_migrate=False):
# Reimplemented from TimescaleSchemaEditor to reference ``settings`` from
# this module's namespace, which the released package fails to import.
self._assert_is_not_hypertable(model)
self._drop_primary_key(model)

partition_column = self.quote_value(field.column)
interval = self.quote_value(field.interval)
table = self.quote_value(model._meta.db_table)
migrate = "true" if should_migrate else "false"

if should_migrate and getattr(settings, "TIMESCALE_MIGRATE_HYPERTABLE_WITH_FRESH_TABLE", False):
raise NotImplementedError()
Comment on lines +41 to +42

sql = self.sql_add_hypertable.format(
table=table, partition_column=partition_column, interval=interval, migrate=migrate
)
self.execute(sql)


class DatabaseWrapper(TimescaleDatabaseWrapper):
SchemaEditorClass = SeedTimescaleSchemaEditor
16 changes: 16 additions & 0 deletions seed/migrations/0255_alter_sensorreading_timestamp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import timescale.db.models.fields
from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("seed", "0254_repair_missing_primary_keys"),
]

operations = [
migrations.AlterField(
model_name="sensorreading",
name="timestamp",
field=timescale.db.models.fields.TimescaleDateTimeField(interval="7 days"),
),
]
3 changes: 2 additions & 1 deletion seed/models/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from django.db import models
from timescale.db.models.fields import TimescaleDateTimeField

from seed.models import Property

Expand Down Expand Up @@ -49,7 +50,7 @@ class Meta:

class SensorReading(models.Model):
reading = models.FloatField(null=True)
timestamp = models.DateTimeField()
timestamp = TimescaleDateTimeField(interval="7 days")
sensor = models.ForeignKey(
Sensor,
on_delete=models.CASCADE,
Expand Down
8 changes: 8 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading