Skip to content
Open
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
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
"""
49 changes: 49 additions & 0 deletions seed/backends/timescale_postgis/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
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.