From 6cee752f2b80645201129402be0b8923dcdac0e7 Mon Sep 17 00:00:00 2001 From: esensar Date: Thu, 20 Sep 2018 01:42:37 +0200 Subject: [PATCH] Add dashboards for users --- app/__init__.py | 6 +- app/api/__init__.py | 4 + app/api/resources/dashboard.py | 55 ++++++++++++ .../swagger/create_dashboard_spec.yaml | 19 +++++ .../resources/swagger/get_dashboard_spec.yaml | 21 +++++ .../swagger/get_dashboards_spec.yaml | 16 ++++ .../swagger/update_dashboard_spec.yaml | 23 +++++ app/dashboard/__init__.py | 58 +++++++++++++ app/dashboard/models.py | 83 +++++++++++++++++++ app/swagger/template.yaml | 24 ++++++ migrations/versions/5aa58dcd7a2c_.py | 36 ++++++++ 11 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 app/api/resources/dashboard.py create mode 100644 app/api/resources/swagger/create_dashboard_spec.yaml create mode 100644 app/api/resources/swagger/get_dashboard_spec.yaml create mode 100644 app/api/resources/swagger/get_dashboards_spec.yaml create mode 100644 app/api/resources/swagger/update_dashboard_spec.yaml create mode 100644 app/dashboard/__init__.py create mode 100644 app/dashboard/models.py create mode 100644 migrations/versions/5aa58dcd7a2c_.py diff --git a/app/__init__.py b/app/__init__.py index 65e1cbb..4d744fc 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -25,13 +25,15 @@ def setup_blueprints(app): They are exposed as blueprints just for consistency, otherwise they are just simple python packages/modules """ - from .devices import devices_bp from .accounts import accounts_bp + from .devices import devices_bp + from .dashboard import dashboard_bp from .api import api_bp from .mqtt import mqtt_bp - app.register_blueprint(devices_bp) app.register_blueprint(accounts_bp) + app.register_blueprint(devices_bp) + app.register_blueprint(dashboard_bp) app.register_blueprint(mqtt_bp) app.register_blueprint(api_bp, url_prefix='/api') diff --git a/app/api/__init__.py b/app/api/__init__.py index 86c857a..499e2db 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -46,6 +46,7 @@ def add_resources(): DeviceTypeResource, DeviceTypeListResource, DeviceConfigurationResource) + from .resources.dashboard import DashboardResource, DashboardListResource api.add_resource(AccountResource, '/v1/accounts/') api.add_resource(AccountListResource, '/v1/accounts') @@ -60,6 +61,9 @@ def add_resources(): api.add_resource(DeviceTypeListResource, '/v1/devices/types') api.add_resource(DeviceConfigurationResource, '/v1/devices//configuration') + api.add_resource(DashboardListResource, '/v1/dashboards') + api.add_resource(DashboardResource, + '/v1/dashboards/') add_resources() diff --git a/app/api/resources/dashboard.py b/app/api/resources/dashboard.py new file mode 100644 index 0000000..e3d295f --- /dev/null +++ b/app/api/resources/dashboard.py @@ -0,0 +1,55 @@ +from flask import g +from marshmallow import Schema, fields +from webargs.flaskparser import use_args +from flasgger import swag_from +import app.dashboard as dashboard +from app.api import ProtectedResource + + +class DashboardSchema(Schema): + id = fields.Integer(dump_only=True) + dashboard_data = fields.Raw() + + +class DashboardWrapperSchema(Schema): + dashboard = fields.Nested(DashboardSchema, required=True, location='json') + + +class DashboardsSchema(Schema): + dashboards = fields.Nested(DashboardSchema, required=True, location='json', + many=True) + + +class DashboardResource(ProtectedResource): + @swag_from('swagger/get_dashboard_spec.yaml') + def get(self, dashboard_id): + return DashboardWrapperSchema().dump( + {'dashboard': dashboard.get_dashboard(dashboard_id)}), 200 + + @use_args(DashboardWrapperSchema()) + @swag_from('swagger/update_dashboard_spec.yaml') + def put(self, dashboard_id, args): + args = args['dashboard'] + success = dashboard.update_dashboard( + dashboard_id, + args['dashboard_data']) + if success: + return '', 204 + + +class DashboardListResource(ProtectedResource): + @use_args(DashboardWrapperSchema()) + @swag_from('swagger/create_dashboard_spec.yaml') + def post(self, args): + args = args['dashboard'] + success = dashboard.create_dashboard( + args['dashboard_data'], + g.current_account.id) + if success: + return '', 201 + + @swag_from('swagger/get_dashboards_spec.yaml') + def get(self): + return DashboardsSchema().dump( + {'dashboards': + dashboard.get_dashboards(g.current_account.id)}), 200 diff --git a/app/api/resources/swagger/create_dashboard_spec.yaml b/app/api/resources/swagger/create_dashboard_spec.yaml new file mode 100644 index 0000000..65abc44 --- /dev/null +++ b/app/api/resources/swagger/create_dashboard_spec.yaml @@ -0,0 +1,19 @@ +Creates new dashboard +Requires Dashboard object and creates dashboard +--- +tags: + - Dashboard +parameters: + - in: body + name: body + required: true + schema: + type: object + required: + - dashboard + properties: + device: + $ref: '#/definitions/DashboardCreation' +responses: + 201: + description: Successful creation diff --git a/app/api/resources/swagger/get_dashboard_spec.yaml b/app/api/resources/swagger/get_dashboard_spec.yaml new file mode 100644 index 0000000..29dce88 --- /dev/null +++ b/app/api/resources/swagger/get_dashboard_spec.yaml @@ -0,0 +1,21 @@ +Gets 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: + - dashboard + properties: + device: + $ref: '#/definitions/Dashboard' + diff --git a/app/api/resources/swagger/get_dashboards_spec.yaml b/app/api/resources/swagger/get_dashboards_spec.yaml new file mode 100644 index 0000000..253c61e --- /dev/null +++ b/app/api/resources/swagger/get_dashboards_spec.yaml @@ -0,0 +1,16 @@ +Gets all associated dashboards +--- +tags: + - Dashboard +responses: + 200: + description: Success + schema: + type: object + required: + - dashboards + properties: + devices: + type: array + items: + $ref: '#/definitions/Dashboard' diff --git a/app/api/resources/swagger/update_dashboard_spec.yaml b/app/api/resources/swagger/update_dashboard_spec.yaml new file mode 100644 index 0000000..a5ea00e --- /dev/null +++ b/app/api/resources/swagger/update_dashboard_spec.yaml @@ -0,0 +1,23 @@ +Updates a dashboard +--- +tags: + - Dashboard +parameters: + - in: path + name: dashboard_id + required: true + type: integer + description: Id of the device + - in: body + name: body + required: true + schema: + type: object + required: + - dashboard + properties: + device: + $ref: '#/definitions/DashboardCreation' +responses: + 204: + description: Success diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py new file mode 100644 index 0000000..384578e --- /dev/null +++ b/app/dashboard/__init__.py @@ -0,0 +1,58 @@ +from flask import Blueprint +from .models import Dashboard + +dashboard_bp = Blueprint('dashboard', __name__) + + +# Public interface +def create_dashboard(dashboard_data, account_id): + """ + Tries to create dashboard with given parameters + + :param dashboard_data: JSON dashboard data + :param account_id: Id of owner of this dashboard + :type name: JSON + :type account_id: int + :returns: True if dashboard is successfully created + :rtype: Boolean + """ + dashboard = Dashboard(account_id, dashboard_data) + dashboard.save() + + +def get_dashboard(dashboard_id): + """ + Tries to fetch dashboard with given id + + :param dashboard_id: Id of requested dashboard + :type name: int + :returns: Dashboard object + :rtype: Dashboard + """ + return Dashboard.get(id=dashboard_id) + + +def update_dashboard(dashboard_id, dashboard_data): + """ + Tries to update dashboard with given parameters + + :param dashboard_data: JSON dashboard data + :param dashboard_id: Id of the dashboard + :type name: JSON + :type dashboard_id: int + """ + dashboard = Dashboard.get(id=dashboard_id) + dashboard.dashboard_data = dashboard_data + dashboard_data.save() + + +def get_dashboards(account_id): + """ + Tries to fetch dashboards owned by account with given id + + :param account_id: Id of owner account + :type name: int + :returns: Dashboard list + :rtype: List of Dashboard + """ + return Dashboard.get_many(account_id=account_id) diff --git a/app/dashboard/models.py b/app/dashboard/models.py new file mode 100644 index 0000000..fc7beb3 --- /dev/null +++ b/app/dashboard/models.py @@ -0,0 +1,83 @@ +from app import db +from sqlalchemy.dialects.postgresql import JSON + + +class Dashboard(db.Model): + __tablename__ = 'dashboards' + + id = db.Column(db.Integer, primary_key=True) + dashboard_data = db.Column(JSON, nullable=False) + account_id = db.Column(db.Integer, db.ForeignKey('accounts.id'), + primary_key=True) + 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()) + + def __init__(self, account_id, dashboard_data): + self.account_id = account_id + self.dashboard_data = dashboard_data + + def save(self): + """ + Stores this dashboard to database + This may raise errors + """ + db.session.add(self) + db.session.commit() + + @staticmethod + def exists_with_any_of(**kwargs): + """ + Checks if dashboard with any of the given arguments exists + """ + for key, value in kwargs.items(): + args = {key: value} + if Dashboard.query.filter_by(**args).first(): + return True + return False + + @staticmethod + def exists(**kwargs): + """ + Checks if dashboard with all of the given arguments exists + """ + if Dashboard.query.filter_by(**kwargs).first(): + return True + return False + + @staticmethod + def get_all(): + """ + Get all stored dashboards + """ + return Dashboard.query.all() + + @staticmethod + def get_many(**kwargs): + """ + Get dashboards with given filters + + Available filters: + * id + * account_id + """ + return Dashboard.query.filter_by(**kwargs).all() + + @staticmethod + def get(**kwargs): + """ + Get dashboard with given filters + + Available filters: + * id + * account_id + """ + return Dashboard.query.filter_by(**kwargs).first_or_404() + + def __repr__(self): + return '' % ( + self.dashboard_data, self.account_id) diff --git a/app/swagger/template.yaml b/app/swagger/template.yaml index e22dd95..b2827a5 100644 --- a/app/swagger/template.yaml +++ b/app/swagger/template.yaml @@ -61,6 +61,11 @@ definitions: description: Configuration default: {} + dashboarddata: + type: string + description: Dashboard data + default: {} + Credentials: type: object required: @@ -155,6 +160,25 @@ definitions: device_type_id: $ref: '#/definitions/id' + Dashboard: + type: object + required: + - id + - dashboard_data + properties: + id: + $ref: '#/definitions/id' + dashboard_data: + $ref: '#/definitions/dashboarddata' + + DashboardCreation: + type: object + required: + - dashboard_data + properties: + dashboard_data: + $ref: '#/definitions/dashboarddata' + UnauthorizedError: type: object required: diff --git a/migrations/versions/5aa58dcd7a2c_.py b/migrations/versions/5aa58dcd7a2c_.py new file mode 100644 index 0000000..d64f6f0 --- /dev/null +++ b/migrations/versions/5aa58dcd7a2c_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: 5aa58dcd7a2c +Revises: efbd47fca2fd +Create Date: 2018-09-20 01:29:06.797906 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '5aa58dcd7a2c' +down_revision = 'efbd47fca2fd' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('dashboards', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('dashboard_data', postgresql.JSON(astext_type=sa.Text()), nullable=False), + sa.Column('account_id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('modified_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['account_id'], ['accounts.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('dashboards') + # ### end Alembic commands ###