Add initial schema migration, with basic tempmon table clones

plus expose new tables in the admin
This commit is contained in:
Lance Edgar 2017-04-01 18:34:01 -05:00
parent e036abd313
commit e46b18f674
4 changed files with 329 additions and 2 deletions

View file

@ -0,0 +1,78 @@
"""initial tempmon tables
Revision ID: 9a0317b74dee
Revises: e33a25dbaf07
Create Date: 2017-04-01 15:58:27.513593
"""
# revision identifiers, used by Alembic.
revision = '9a0317b74dee'
down_revision = 'e33a25dbaf07'
branch_labels = None
depends_on = None
import datetime
import websauna.system.model.columns
from sqlalchemy.types import Text # Needed from proper creation of JSON fields as Alembic inserts astext_type=Text() row
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('client',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', postgresql.UUID(as_uuid=True), nullable=True),
sa.Column('config_key', sa.String(length=50), nullable=False),
sa.Column('hostname', sa.String(length=255), nullable=False),
sa.Column('location', sa.String(length=255), nullable=True),
sa.Column('delay', sa.Integer(), nullable=True),
sa.Column('enabled', sa.Boolean(), nullable=False),
sa.Column('online', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_client')),
sa.UniqueConstraint('config_key', name=op.f('uq_client_config_key'))
)
op.create_table('probe',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', postgresql.UUID(as_uuid=True), nullable=True),
sa.Column('client_id', sa.Integer(), nullable=True),
sa.Column('config_key', sa.String(length=50), nullable=False),
sa.Column('appliance_type', sa.Integer(), nullable=False),
sa.Column('description', sa.String(length=255), nullable=False),
sa.Column('device_path', sa.String(length=255), nullable=True),
sa.Column('enabled', sa.Boolean(), nullable=False),
sa.Column('good_temp_min', sa.Integer(), nullable=False),
sa.Column('good_temp_max', sa.Integer(), nullable=False),
sa.Column('critical_temp_min', sa.Integer(), nullable=False),
sa.Column('critical_temp_max', sa.Integer(), nullable=False),
sa.Column('therm_status_timeout', sa.Integer(), nullable=False),
sa.Column('status_alert_timeout', sa.Integer(), nullable=False),
sa.Column('status', sa.Integer(), nullable=True),
sa.Column('status_changed', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('status_alert_sent', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.ForeignKeyConstraint(['client_id'], ['client.id'], name=op.f('fk_probe_client_id_client')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_probe')),
sa.UniqueConstraint('config_key', name=op.f('uq_probe_config_key'))
)
op.create_table('reading',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', postgresql.UUID(as_uuid=True), nullable=True),
sa.Column('client_id', sa.Integer(), nullable=True),
sa.Column('probe_id', sa.Integer(), nullable=True),
sa.Column('taken', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('degrees_f', sa.Numeric(precision=7, scale=4), nullable=False),
sa.ForeignKeyConstraint(['client_id'], ['client.id'], name=op.f('fk_reading_client_id_client')),
sa.ForeignKeyConstraint(['probe_id'], ['probe.id'], name=op.f('fk_reading_probe_id_probe')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_reading'))
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('reading')
op.drop_table('probe')
op.drop_table('client')
# ### end Alembic commands ###

View file

@ -0,0 +1,82 @@
"""initial schema
Revision ID: e33a25dbaf07
Revises:
Create Date: 2017-04-01 15:27:22.495907
"""
# revision identifiers, used by Alembic.
revision = 'e33a25dbaf07'
down_revision = None
branch_labels = None
depends_on = None
import datetime
import websauna.system.model.columns
from sqlalchemy.types import Text # Needed from proper creation of JSON fields as Alembic inserts astext_type=Text() row
from alembic import op
import sqlalchemy as sa
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('group',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', websauna.system.model.columns.UUID(), nullable=True),
sa.Column('name', sa.String(length=64), nullable=True),
sa.Column('description', sa.String(length=256), nullable=True),
sa.Column('created_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('updated_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('group_data', websauna.system.model.columns.JSONB(), nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('pk_group')),
sa.UniqueConstraint('name', name=op.f('uq_group_name'))
)
op.create_table('user_activation',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('updated_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('expires_at', websauna.system.model.columns.UTCDateTime(), nullable=False),
sa.Column('code', sa.String(length=32), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_user_activation')),
sa.UniqueConstraint('code', name=op.f('uq_user_activation_code'))
)
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', websauna.system.model.columns.UUID(), nullable=True),
sa.Column('username', sa.String(length=256), nullable=True),
sa.Column('email', sa.String(length=256), nullable=True),
sa.Column('password', sa.String(length=256), nullable=True),
sa.Column('created_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('updated_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('activated_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('enabled', sa.Boolean(name='user_enabled_binary'), nullable=True),
sa.Column('last_login_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('last_login_ip', websauna.system.model.columns.INET(length=50), nullable=True),
sa.Column('user_data', websauna.system.model.columns.JSONB(), nullable=True),
sa.Column('last_auth_sensitive_operation_at', websauna.system.model.columns.UTCDateTime(), nullable=True),
sa.Column('activation_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['activation_id'], ['user_activation.id'], name=op.f('fk_users_activation_id_user_activation')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_users')),
sa.UniqueConstraint('email', name=op.f('uq_users_email')),
sa.UniqueConstraint('username', name=op.f('uq_users_username'))
)
op.create_table('usergroup',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('group_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['group_id'], ['group.id'], name=op.f('fk_usergroup_group_id_group')),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_usergroup_user_id_users')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_usergroup'))
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('usergroup')
op.drop_table('users')
op.drop_table('user_activation')
op.drop_table('group')
# ### end Alembic commands ###

View file

@ -1 +1,69 @@
"""Place your admin resources in this file."""
"""
Admin resources for hotcooler
"""
from websauna.system.admin.modeladmin import model_admin
from websauna.system.admin.modeladmin import ModelAdmin
# Import our models
from . import models
@model_admin(traverse_id='client')
class ClientAdmin(ModelAdmin):
"""Admin resource for tempmon client model.
This class declares a resource for client model admin root folder with listing and add views.
"""
#: Label as shown in admin
title = "Clients"
#: Used in admin listings etc. user visible messages
#: TODO: This mechanism will be phased out in the future versions with gettext or similar replacement for languages that have plulars one, two, many
singular_name = "client"
plural_name = "clients"
#: Which models this model admin controls
model = models.Client
class Resource(ModelAdmin.Resource):
"""Declare resource for each individual client.
View, edit and delete views are registered against this resource.
"""
def get_title(self):
return str(self.get_object())
@model_admin(traverse_id='probe')
class ProbeAdmin(ModelAdmin):
"""Admin resource for probe model."""
title = "Probes"
singular_name = "probe"
plural_name = "probes"
model = models.Probe
class Resource(ModelAdmin.Resource):
def get_title(self):
return str(self.get_object())
@model_admin(traverse_id='reading')
class ReadingAdmin(ModelAdmin):
"""Admin resource for reading model."""
title = "Readings"
singular_name = "reading"
plural_name = "readings"
model = models.Reading
class Resource(ModelAdmin.Resource):
def get_title(self):
return str(self.get_object())

View file

@ -1 +1,100 @@
"""Place your SQLAlchemy models in this file."""
"""
SQLAlchemy models for hotcooler
"""
from uuid import uuid4
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.dialects.postgresql import UUID
from websauna.system.model.meta import Base
from websauna.system.model.columns import UTCDateTime
class Client(Base):
"""
Represents a tempmon client
"""
__tablename__ = 'client'
id = sa.Column(sa.Integer(), autoincrement=True, primary_key=True)
uuid = sa.Column(UUID(as_uuid=True), default=uuid4)
config_key = sa.Column(sa.String(50), nullable=False, unique=True)
hostname = sa.Column(sa.String(255), nullable=False)
location = sa.Column(sa.String(255), nullable=True)
delay = sa.Column(sa.Integer(), nullable=True, doc="""
Number of seconds to delay between reading / recording temperatures. If
not set, a default of 60 seconds will be assumed.
""")
enabled = sa.Column(sa.Boolean(), nullable=False, default=False)
online = sa.Column(sa.Boolean(), nullable=False, default=False)
probes = orm.relationship('Probe',
back_populates='client',
cascade='all, delete-orphan',
single_parent=True)
def __str__(self):
return '{} ({})'.format(self.config_key, self.hostname)
def enabled_probes(self):
return [probe for probe in self.probes if probe.enabled]
class Probe(Base):
"""
Represents a probe connected to a tempmon client
"""
__tablename__ = 'probe'
id = sa.Column(sa.Integer(), autoincrement=True, primary_key=True)
uuid = sa.Column(UUID(as_uuid=True), default=uuid4)
client_id = sa.Column(sa.Integer(), sa.ForeignKey('client.id'))
client = orm.relationship(Client, back_populates='probes')
config_key = sa.Column(sa.String(length=50), nullable=False, unique=True)
appliance_type = sa.Column(sa.Integer(), nullable=False)
description = sa.Column(sa.String(length=255), nullable=False)
device_path = sa.Column(sa.String(length=255), nullable=True)
enabled = sa.Column(sa.Boolean(), nullable=False, default=True)
good_temp_min = sa.Column(sa.Integer(), nullable=False)
good_temp_max = sa.Column(sa.Integer(), nullable=False)
critical_temp_min = sa.Column(sa.Integer(), nullable=False)
critical_temp_max = sa.Column(sa.Integer(), nullable=False)
therm_status_timeout = sa.Column(sa.Integer(), nullable=False)
status_alert_timeout = sa.Column(sa.Integer(), nullable=False)
status = sa.Column(sa.Integer(), nullable=True)
status_changed = sa.Column(UTCDateTime, nullable=True)
status_alert_sent = sa.Column(UTCDateTime, nullable=True)
def __str__(self):
return self.description
class Reading(Base):
"""
Represents a single temperature reading from a tempmon probe
"""
__tablename__ = 'reading'
id = sa.Column(sa.Integer(), autoincrement=True, primary_key=True)
uuid = sa.Column(UUID(as_uuid=True), default=uuid4)
client_id = sa.Column(sa.Integer(), sa.ForeignKey('client.id'))
client = orm.relationship(Client)
probe_id = sa.Column(sa.Integer(), sa.ForeignKey('probe.id'))
probe = orm.relationship(Probe)
taken = sa.Column(UTCDateTime, default=None)
degrees_f = sa.Column(sa.Numeric(precision=7, scale=4), nullable=False)
def __str__(self):
return str(self.degrees_f)