commit
5647fe2318
|
@ -23,7 +23,9 @@ def add_resources():
|
|||
DeviceTypeListResource,
|
||||
DeviceConfigurationResource)
|
||||
from .resources.dashboard import (DashboardResource,
|
||||
DashboardListResource)
|
||||
DashboardListResource,
|
||||
DashboardWidgetResource,
|
||||
DashboardWidgetListResource)
|
||||
|
||||
api.add_resource(AccountResource, '/v1/accounts/<int:account_id>')
|
||||
api.add_resource(AccountListResource, '/v1/accounts')
|
||||
|
@ -48,6 +50,11 @@ def add_resources():
|
|||
api.add_resource(DashboardResource,
|
||||
'/v1/dashboards/<int:dashboard_id>')
|
||||
api.add_resource(DashboardListResource, '/v1/dashboards')
|
||||
api.add_resource(
|
||||
DashboardWidgetResource,
|
||||
'/v1/dashboards/<int:dashboard_id>/widgets/<int:widget_id>')
|
||||
api.add_resource(DashboardWidgetListResource,
|
||||
'/v1/dashboards/<int:dashboard_id>/widgets')
|
||||
|
||||
|
||||
add_resources()
|
||||
|
|
|
@ -1,36 +1,62 @@
|
|||
from flask import g, request
|
||||
from flask_restful import abort
|
||||
from marshmallow import fields
|
||||
from marshmallow import fields, Schema
|
||||
from webargs.flaskparser import use_args
|
||||
from flasgger import swag_from
|
||||
import app.dashboards.api as dashboard
|
||||
import app.devices.api as device
|
||||
from app.api.auth_protection import ProtectedResource
|
||||
from app.api.schemas import BaseResourceSchema
|
||||
|
||||
|
||||
class BasicDashboardWidgetSchema(Schema):
|
||||
id = fields.Integer(dump_only=True)
|
||||
device_id = fields.Integer()
|
||||
height = fields.Integer()
|
||||
width = fields.Integer()
|
||||
x = fields.Integer()
|
||||
y = fields.Integer()
|
||||
chart_type = fields.String()
|
||||
filters = fields.Raw()
|
||||
|
||||
|
||||
class DashboardWidgetSchema(BaseResourceSchema, BasicDashboardWidgetSchema):
|
||||
pass
|
||||
|
||||
|
||||
class DashboardSchema(BaseResourceSchema):
|
||||
id = fields.Integer(dump_only=True)
|
||||
active = fields.Boolean(required=False)
|
||||
dashboard_data = fields.Raw()
|
||||
name = fields.String()
|
||||
widgets = fields.Nested(BasicDashboardWidgetSchema, dump_only=True,
|
||||
many=True)
|
||||
|
||||
|
||||
def validate_dashboard_ownership(dashboard_id):
|
||||
requested_dashboard = dashboard.get_dashboard(dashboard_id)
|
||||
if requested_dashboard.account_id != g.current_account.id:
|
||||
abort(403, message='You are not allowed to access this dashboard',
|
||||
status='error')
|
||||
return requested_dashboard
|
||||
|
||||
|
||||
def validate_device_ownership(device_id):
|
||||
if not device.can_user_access_device(g.current_account.id, device_id):
|
||||
abort(403, message='You are not allowed to access this device',
|
||||
status='error')
|
||||
|
||||
|
||||
class DashboardResource(ProtectedResource):
|
||||
@swag_from('swagger/get_dashboard_spec.yaml')
|
||||
def get(self, dashboard_id):
|
||||
requested_dashboard = dashboard.get_dashboard(dashboard_id)
|
||||
if requested_dashboard.account_id != g.current_account.id:
|
||||
abort(403, message='You are not allowed to access this dashboard',
|
||||
status='error')
|
||||
requested_dashboard = validate_dashboard_ownership(dashboard_id)
|
||||
return DashboardSchema().dump(requested_dashboard), 200
|
||||
|
||||
@use_args(DashboardSchema(), locations=('json',))
|
||||
@swag_from('swagger/update_dashboard_spec.yaml')
|
||||
def put(self, args, dashboard_id):
|
||||
requested_dashboard = dashboard.get_dashboard(dashboard_id)
|
||||
if requested_dashboard.account_id != g.current_account.id:
|
||||
abort(403, message='You are not allowed to access this dashboard',
|
||||
status='error')
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
success = dashboard.patch_dashboard(
|
||||
g.current_account.id,
|
||||
dashboard_id,
|
||||
|
@ -43,10 +69,7 @@ class DashboardResource(ProtectedResource):
|
|||
@use_args(DashboardSchema(partial=True), locations=('json',))
|
||||
@swag_from('swagger/update_dashboard_spec.yaml')
|
||||
def patch(self, args, dashboard_id):
|
||||
requested_dashboard = dashboard.get_dashboard(dashboard_id)
|
||||
if requested_dashboard.account_id != g.current_account.id:
|
||||
abort(403, message='You are not allowed to access this dashboard',
|
||||
status='error')
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
success = dashboard.patch_dashboard(
|
||||
g.current_account.id,
|
||||
dashboard_id,
|
||||
|
@ -58,10 +81,7 @@ class DashboardResource(ProtectedResource):
|
|||
|
||||
@swag_from('swagger/delete_dashboard_spec.yaml')
|
||||
def delete(self, dashboard_id):
|
||||
requested_dashboard = dashboard.get_dashboard(dashboard_id)
|
||||
if requested_dashboard.account_id != g.current_account.id:
|
||||
abort(403, message='You are not allowed to access this dashboard',
|
||||
status='error')
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
dashboard.delete_dashboard(dashboard_id)
|
||||
return '', 204
|
||||
|
||||
|
@ -84,3 +104,77 @@ class DashboardListResource(ProtectedResource):
|
|||
dashboard.get_dashboards(
|
||||
g.current_account.id,
|
||||
request_args.get('active')), many=True), 200
|
||||
|
||||
|
||||
class DashboardWidgetListResource(ProtectedResource):
|
||||
@use_args(DashboardWidgetSchema(), locations=('json',))
|
||||
@swag_from('swagger/create_dashboard_widget_spec.yaml')
|
||||
def post(self, args, dashboard_id):
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
validate_dashboard_ownership(args['device_id'])
|
||||
success = dashboard.create_widget(
|
||||
dashboard_id,
|
||||
args['device_id'],
|
||||
args['height'],
|
||||
args['width'],
|
||||
args['x'],
|
||||
args['y'],
|
||||
args['chart_type'],
|
||||
args['filters'])
|
||||
if success:
|
||||
return '', 201
|
||||
|
||||
@swag_from('swagger/get_dashboard_widgets_spec.yaml')
|
||||
def get(self, dashboard_id):
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
return DashboardWidgetSchema().dump(
|
||||
dashboard.get_widgets(dashboard_id), many=True), 200
|
||||
|
||||
|
||||
class DashboardWidgetResource(ProtectedResource):
|
||||
@swag_from('swagger/get_dashboard_widget_spec.yaml')
|
||||
def get(self, dashboard_id, widget_id):
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
requested_widget = dashboard.get_widget(widget_id)
|
||||
return DashboardWidgetSchema().dump(requested_widget), 200
|
||||
|
||||
@use_args(DashboardWidgetSchema(), locations=('json',))
|
||||
@swag_from('swagger/update_dashboard_widget_spec.yaml')
|
||||
def put(self, args, dashboard_id, widget_id):
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
validate_dashboard_ownership(args['device_id'])
|
||||
success = dashboard.patch_widget(
|
||||
widget_id,
|
||||
args['device_id'],
|
||||
args['height'],
|
||||
args['width'],
|
||||
args['x'],
|
||||
args['y'],
|
||||
args['chart_type'],
|
||||
args['filters'])
|
||||
if success:
|
||||
return '', 204
|
||||
|
||||
@use_args(DashboardWidgetSchema(partial=True), locations=('json',))
|
||||
@swag_from('swagger/update_dashboard_widget_spec.yaml')
|
||||
def patch(self, args, dashboard_id, widget_id):
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
if args.get('device_id') is not None:
|
||||
validate_dashboard_ownership(args['device_id'])
|
||||
success = dashboard.patch_widget(
|
||||
widget_id,
|
||||
args.get('device_id'),
|
||||
args.get('height'),
|
||||
args.get('width'),
|
||||
args.get('x'),
|
||||
args.get('y'),
|
||||
args.get('chart_type'),
|
||||
args.get('filters'))
|
||||
if success:
|
||||
return '', 204
|
||||
|
||||
@swag_from('swagger/delete_dashboard_widget_spec.yaml')
|
||||
def delete(self, dashboard_id, widget_id):
|
||||
validate_dashboard_ownership(dashboard_id)
|
||||
dashboard.delete_widget(widget_id)
|
||||
return '', 204
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
Creates new dashboard widget
|
||||
Requires Widget object and creates dashboard
|
||||
---
|
||||
tags:
|
||||
- Dashboard
|
||||
parameters:
|
||||
- in: path
|
||||
name: dashboard_id
|
||||
required: true
|
||||
type: integer
|
||||
description: Id of the owning dashboard
|
||||
- in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
$ref: '#/definitions/WidgetCreation'
|
||||
responses:
|
||||
201:
|
||||
description: Successful creation
|
|
@ -1,4 +1,4 @@
|
|||
Deletes a dashboard
|
||||
Deletes a widget
|
||||
---
|
||||
tags:
|
||||
- Dashboard
|
||||
|
@ -8,6 +8,11 @@ parameters:
|
|||
required: true
|
||||
type: integer
|
||||
description: Id of the dashboard
|
||||
- in: path
|
||||
name: widget_id
|
||||
required: true
|
||||
type: integer
|
||||
description: Id of the widget
|
||||
responses:
|
||||
204:
|
||||
description: Success
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
Deletes a dashboard
|
||||
---
|
||||
tags:
|
||||
- Dashboard
|
||||
parameters:
|
||||
- in: path
|
||||
name: dashboard_id
|
||||
required: true
|
||||
type: integer
|
||||
description: Id of the dashboard
|
||||
responses:
|
||||
204:
|
||||
description: Success
|
|
@ -0,0 +1,26 @@
|
|||
Gets a widget
|
||||
---
|
||||
tags:
|
||||
- Dashboard
|
||||
parameters:
|
||||
- in: path
|
||||
name: dashboard_id
|
||||
required: true
|
||||
type: integer
|
||||
description: Id of the dashboard
|
||||
- in: path
|
||||
name: widget_id
|
||||
required: true
|
||||
type: integer
|
||||
description: Id of the widget
|
||||
responses:
|
||||
200:
|
||||
description: Success
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- content
|
||||
properties:
|
||||
content:
|
||||
$ref: '#/definitions/Widget'
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
Gets all widgets of a dashboard
|
||||
---
|
||||
tags:
|
||||
- Dashboard
|
||||
parameters:
|
||||
- in: path
|
||||
name: dashboard_id
|
||||
required: true
|
||||
type: integer
|
||||
description: Id of the dashboard
|
||||
responses:
|
||||
200:
|
||||
description: Success
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- content
|
||||
properties:
|
||||
content:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Widget'
|
|
@ -14,10 +14,6 @@ parameters:
|
|||
required: true
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- dashboard
|
||||
properties:
|
||||
device:
|
||||
$ref: '#/definitions/DashboardCreation'
|
||||
responses:
|
||||
204:
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
Updates a widget
|
||||
---
|
||||
tags:
|
||||
- Dashboard
|
||||
parameters:
|
||||
- in: path
|
||||
name: dashboard_id
|
||||
required: true
|
||||
type: integer
|
||||
description: Id of the dashboard
|
||||
- in: path
|
||||
name: widget_id
|
||||
required: true
|
||||
type: integer
|
||||
description: Id of the widget
|
||||
- in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
$ref: '#/definitions/WidgetCreation'
|
||||
responses:
|
||||
204:
|
||||
description: Success
|
|
@ -1,4 +1,4 @@
|
|||
from .models import Dashboard
|
||||
from .models import Dashboard, DashboardWidget
|
||||
|
||||
|
||||
# Public interface
|
||||
|
@ -101,3 +101,80 @@ def get_dashboards(account_id, active):
|
|||
:rtype: List of Dashboard
|
||||
"""
|
||||
return Dashboard.get_many_filtered(account_id=account_id, active=active)
|
||||
|
||||
|
||||
def create_widget(dashboard_id, device_id, height, width, x, y,
|
||||
chart_type, filters):
|
||||
"""
|
||||
Tries to create a dashboard widget
|
||||
"""
|
||||
widget = DashboardWidget(dashboard_id, device_id, height, width, x, y,
|
||||
chart_type, filters)
|
||||
widget.save()
|
||||
|
||||
|
||||
def delete_widget(widget_id):
|
||||
"""
|
||||
Tries to delete widget with given id
|
||||
|
||||
:param widget_id: Id of requested widget
|
||||
:type name: int
|
||||
"""
|
||||
widget = DashboardWidget.get(id=widget_id)
|
||||
widget.delete()
|
||||
|
||||
|
||||
def get_widgets(dashboard_id):
|
||||
"""
|
||||
Tries to fetch widgets of a dashboard with dashboard_id
|
||||
|
||||
:param dashboard_id: Id of owner dashboard
|
||||
:type name: int
|
||||
:returns: Widget list
|
||||
:rtype: List of Widgets
|
||||
"""
|
||||
return DashboardWidget.get_many_for_dashboard(dashboard_id)
|
||||
|
||||
|
||||
def get_widget(widget_id):
|
||||
"""
|
||||
Tries to fetch widget with given id
|
||||
|
||||
:param widget_id: Id of requested dashboard
|
||||
:type name: int
|
||||
:returns: Widget object
|
||||
:rtype: Widget
|
||||
"""
|
||||
return DashboardWidget.get(id=widget_id)
|
||||
|
||||
|
||||
def patch_widget(widget_id, device_id=None, height=None, width=None,
|
||||
x=None, y=None, chart_type=None, filters=None):
|
||||
"""
|
||||
Tries to update widget with given parameters
|
||||
"""
|
||||
widget = DashboardWidget.get(id=widget_id)
|
||||
|
||||
if device_id is not None:
|
||||
widget.device_id = device_id
|
||||
|
||||
if height is not None:
|
||||
widget.height = height
|
||||
|
||||
if width is not None:
|
||||
widget.width = width
|
||||
|
||||
if x is not None:
|
||||
widget.x = x
|
||||
|
||||
if y is not None:
|
||||
widget.y = y
|
||||
|
||||
if chart_type is not None:
|
||||
widget.chart_type = chart_type
|
||||
|
||||
if filters is not None:
|
||||
widget.filters = filters
|
||||
|
||||
widget.save()
|
||||
print("Saved widget")
|
||||
|
|
|
@ -19,6 +19,9 @@ class Dashboard(db.Model):
|
|||
default=db.func.current_timestamp(),
|
||||
onupdate=db.func.current_timestamp())
|
||||
|
||||
widgets = db.relationship("DashboardWidget",
|
||||
cascade="save-update, merge, delete")
|
||||
|
||||
def __init__(self, account_id, dashboard_data, name):
|
||||
self.account_id = account_id
|
||||
self.dashboard_data = dashboard_data
|
||||
|
@ -113,3 +116,123 @@ class Dashboard(db.Model):
|
|||
def __repr__(self):
|
||||
return '<Dashboard (dashboard_data=%s, account_id=%s)>' % (
|
||||
self.dashboard_data, self.account_id)
|
||||
|
||||
|
||||
class DashboardWidget(db.Model):
|
||||
__tablename__ = 'dashboard_widgets'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
dashboard_id = db.Column(db.Integer, db.ForeignKey('dashboards.id'))
|
||||
device_id = db.Column(db.Integer, db.ForeignKey('devices.id'))
|
||||
height = db.Column(db.Integer, nullable=False)
|
||||
width = db.Column(db.Integer, nullable=False)
|
||||
x = db.Column(db.Integer, nullable=False)
|
||||
y = db.Column(db.Integer, nullable=False)
|
||||
chart_type = db.Column(db.String, nullable=False)
|
||||
filters = db.Column(JSON, nullable=False)
|
||||
created_at = db.Column(db.DateTime,
|
||||
nullable=False,
|
||||
default=db.func.current_timestamp())
|
||||
modified_at = db.Column(db.DateTime,
|
||||
nullable=False,
|
||||
default=db.func.current_timestamp(),
|
||||
onupdate=db.func.current_timestamp())
|
||||
|
||||
dashboard = db.relationship("Dashboard", foreign_keys=[dashboard_id])
|
||||
|
||||
def __init__(self, dashboard_id, device_id, height, width, x, y,
|
||||
chart_type, filters):
|
||||
self.dashboard_id = dashboard_id
|
||||
self.device_id = device_id
|
||||
self.height = height
|
||||
self.width = width
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.chart_type = chart_type
|
||||
self.filters = filters
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Stores this widget to database
|
||||
This may raise errors
|
||||
"""
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes this widget from database
|
||||
"""
|
||||
db.session.delete(self)
|
||||
db.session.commit()
|
||||
|
||||
@staticmethod
|
||||
def exists_with_any_of(**kwargs):
|
||||
"""
|
||||
Checks if widget with any of the given arguments exists
|
||||
"""
|
||||
for key, value in kwargs.items():
|
||||
args = {key: value}
|
||||
if DashboardWidget.query.filter_by(**args).first():
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def exists(**kwargs):
|
||||
"""
|
||||
Checks if widget with all of the given arguments exists
|
||||
"""
|
||||
if DashboardWidget.query.filter_by(**kwargs).first():
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_all():
|
||||
"""
|
||||
Get all stored widgets
|
||||
"""
|
||||
return DashboardWidget.query.paginate(None, None, False).items
|
||||
|
||||
@staticmethod
|
||||
def get_many(**kwargs):
|
||||
"""
|
||||
Get widgets with given filters
|
||||
|
||||
Available filters:
|
||||
* id
|
||||
* dashboard_id
|
||||
* device_id
|
||||
* dimensions and positions, but useless
|
||||
* chart_type
|
||||
* filters, but useless
|
||||
"""
|
||||
return DashboardWidget.query.filter_by(**kwargs).paginate(
|
||||
None, None, False).items
|
||||
|
||||
@staticmethod
|
||||
def get_many_for_dashboard(dashboard_id):
|
||||
"""
|
||||
Get widgets for given dashboard
|
||||
"""
|
||||
query = DashboardWidget.query.filter(
|
||||
DashboardWidget.dashboard_id == dashboard_id)
|
||||
return query.paginate(None, None, False).items
|
||||
|
||||
@staticmethod
|
||||
def get(**kwargs):
|
||||
"""
|
||||
Get widget with given filters
|
||||
|
||||
Available filters:
|
||||
* id
|
||||
* dashboard_id
|
||||
* device_id
|
||||
* dimensions and positions, but useless
|
||||
* chart_type
|
||||
* filters, but useless
|
||||
"""
|
||||
return DashboardWidget.query.filter_by(**kwargs).first_or_404()
|
||||
|
||||
def __repr__(self):
|
||||
return '<DashboardWidget (id=%s, dashboard_id=%s)>' % (
|
||||
self.dashboard_data, self.account_id)
|
||||
|
|
|
@ -84,6 +84,16 @@ definitions:
|
|||
description: Dashboard data
|
||||
example: {}
|
||||
|
||||
filters:
|
||||
type: object
|
||||
description: Dashboard data
|
||||
example: {}
|
||||
|
||||
charttype:
|
||||
type: string
|
||||
description: Type of chart
|
||||
example: line
|
||||
|
||||
Credentials:
|
||||
type: object
|
||||
required:
|
||||
|
@ -222,6 +232,10 @@ definitions:
|
|||
$ref: '#/definitions/id'
|
||||
dashboard_data:
|
||||
$ref: '#/definitions/dashboarddata'
|
||||
active:
|
||||
type: boolean
|
||||
name:
|
||||
type: '#definitions/devicename'
|
||||
|
||||
DashboardCreation:
|
||||
type: object
|
||||
|
@ -234,6 +248,66 @@ definitions:
|
|||
$ref: '#/definitions/dashboarddata'
|
||||
active:
|
||||
type: boolean
|
||||
name:
|
||||
ref: '#definitions/genericname'
|
||||
|
||||
Widget:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- dashboard_id
|
||||
- device_id
|
||||
- width
|
||||
- height
|
||||
- x
|
||||
- y
|
||||
- chart_type
|
||||
- filters
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/definitions/id'
|
||||
dashboard_id:
|
||||
$ref: '#/definitions/id'
|
||||
device_id:
|
||||
$ref: '#/definitions/id'
|
||||
width:
|
||||
$ref: '#/definitions/id'
|
||||
height:
|
||||
$ref: '#/definitions/id'
|
||||
x:
|
||||
$ref: '#/definitions/id'
|
||||
y:
|
||||
$ref: '#/definitions/id'
|
||||
chart_type:
|
||||
$ref: '#/definitions/charttype'
|
||||
filters:
|
||||
$ref: '#/definitions/filters'
|
||||
|
||||
WidgetCreation:
|
||||
type: object
|
||||
required:
|
||||
- device_id
|
||||
- width
|
||||
- height
|
||||
- x
|
||||
- y
|
||||
- chart_type
|
||||
- filters
|
||||
properties:
|
||||
device_id:
|
||||
$ref: '#/definitions/id'
|
||||
width:
|
||||
$ref: '#/definitions/id'
|
||||
height:
|
||||
$ref: '#/definitions/id'
|
||||
x:
|
||||
$ref: '#/definitions/id'
|
||||
y:
|
||||
$ref: '#/definitions/id'
|
||||
chart_type:
|
||||
$ref: '#/definitions/charttype'
|
||||
filters:
|
||||
$ref: '#/definitions/filters'
|
||||
|
||||
UnauthorizedError:
|
||||
type: object
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: b5eb4a04c77e
|
||||
Revises: 5cce35244087
|
||||
Create Date: 2018-10-22 23:11:50.584996
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b5eb4a04c77e'
|
||||
down_revision = '5cce35244087'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('dashboard_widgets',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('dashboard_id', sa.Integer(), nullable=False),
|
||||
sa.Column('device_id', sa.Integer(), nullable=True),
|
||||
sa.Column('height', sa.Integer(), nullable=False),
|
||||
sa.Column('width', sa.Integer(), nullable=False),
|
||||
sa.Column('x', sa.Integer(), nullable=False),
|
||||
sa.Column('y', sa.Integer(), nullable=False),
|
||||
sa.Column('chart_type', sa.String(), nullable=False),
|
||||
sa.Column('filters', postgresql.JSON(astext_type=sa.Text()), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('modified_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['dashboard_id'], ['dashboards.id'], ),
|
||||
sa.ForeignKeyConstraint(['device_id'], ['devices.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('dashboard_widgets')
|
||||
# ### end Alembic commands ###
|
Loading…
Reference in New Issue