diff --git a/app/api/blueprint.py b/app/api/blueprint.py index 8b9065e..43bdbb8 100644 --- a/app/api/blueprint.py +++ b/app/api/blueprint.py @@ -24,6 +24,7 @@ def add_resources(): DeviceTypeResource, DeviceTypeListResource, DeviceConfigurationResource, + DeviceDocumentationResource, DeviceSecretResource, DeviceSecretResetResource, DeviceShareResource, @@ -56,6 +57,8 @@ def add_resources(): api.add_resource(DeviceTypeListResource, '/v1/devices/types') api.add_resource(DeviceConfigurationResource, '/v1/devices//configuration') + api.add_resource(DeviceDocumentationResource, + '/v1/devices//documentation') api.add_resource(DeviceSecretResource, '/v1/devices//secret') api.add_resource(DeviceSecretResetResource, diff --git a/app/api/resources/device.py b/app/api/resources/device.py index 3cad303..51a3eec 100644 --- a/app/api/resources/device.py +++ b/app/api/resources/device.py @@ -45,6 +45,11 @@ class RecordingsQuerySchema(Schema): orders = fields.Raw() +class DeviceDocumentationSchema(BaseResourceSchema): + device_id = fields.Integer(dump_only=True) + text = fields.String(required=True) + + class DeviceSecretSchema(BaseResourceSchema): device_secret = fields.String(dump_only=True) secret_algorithm = fields.String(required=True) @@ -167,6 +172,23 @@ class DeviceConfigurationResource(ProtectedResource): return devices.get_device_configuration(device_id), 200 +class DeviceDocumentationResource(ProtectedResource): + @use_args(DeviceDocumentationSchema(), locations=('json',)) + @swag_from('swagger/update_device_documentation_spec.yaml') + def put(self, args, device_id): + validate_device_ownership(device_id) + update_device_documentation = devices.update_device_documentation( + device_id, args['text']) + return DeviceDocumentationSchema().dump( + update_device_documentation), 200 + + @swag_from('swagger/get_device_documentation_spec.yaml') + def get(self, device_id): + validate_device_ownership(device_id) + return DeviceDocumentationSchema().dump( + devices.get_device_documentation(device_id)), 200 + + class DeviceSecretResource(ProtectedResource): @swag_from('swagger/get_device_secret_spec.yaml') def get(self, device_id): diff --git a/app/devices/api.py b/app/devices/api.py index f57f919..8a0530c 100644 --- a/app/devices/api.py +++ b/app/devices/api.py @@ -8,7 +8,8 @@ from .models import (Device, Recording, DeviceAssociation, DeviceType, - AccessLevel) + AccessLevel, + DeviceDocumentation) from itsdangerous import URLSafeSerializer from app.core import app from app.errors import NotPresentError @@ -238,6 +239,29 @@ def get_devices(account_id): return Device.get_many_for_user(account_id) +def get_device_documentation(device_id): + """ + Tries to get device documentation associated to given device. + + :returns: DeviceDocumentation + """ + if not Device.exists(id=device_id): + raise NotPresentError("Device with id %s does not exist" % device_id) + return DeviceDocumentation.get_for_device(device_id) + + +def update_device_documentation(device_id, new_text): + """ + Tries to update device documentation + """ + if not Device.exists(id=device_id): + raise NotPresentError("Device with id %s does not exist" % device_id) + device_documentation = DeviceDocumentation.get_for_device(device_id) + device_documentation.text = new_text + device_documentation.save() + return device_documentation + + def get_device_types(): """ Tries to get all device types. Raises error on failure diff --git a/app/devices/models.py b/app/devices/models.py index dac9c1a..e390983 100644 --- a/app/devices/models.py +++ b/app/devices/models.py @@ -137,6 +137,8 @@ class Device(db.Model): cascade="save-update, merge, delete") widgets = db.relationship("DashboardWidget", cascade="save-update, merge, delete") + documentations = db.relationship("DeviceDocumentation", + cascade="save-update, merge, delete") def __init__(self, name, configuration=None, device_type=1): self.name = name @@ -341,6 +343,54 @@ class DeviceType(db.Model): return '' % self.name +class DeviceDocumentation(db.Model): + __tablename__ = 'device_documentations' + + device_id = db.Column(db.Integer, db.ForeignKey('devices.id'), + primary_key=True) + text = db.Column(db.Text, 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()) + + def __init__(self, device_id, text): + self.device_id = device_id + self.text = text + + def save(self): + """ + Stores this device documentation to database + This may raise errors + """ + db.session.add(self) + db.session.commit() + + @staticmethod + def get(**kwargs): + """ + Get device documentation with given filters + + Available filters: + * device_id + """ + return DeviceDocumentation.query.filter_by(**kwargs).first() + + @staticmethod + def get_for_device(device_id): + """ + Get documentation for device with device id passed in + parameter + """ + return (DeviceDocumentation.get(device_id=device_id) or + DeviceDocumentation(device_id, "")) + + def __repr__(self): + return '' % self.device_id + class AccessLevel(db.Model): __tablename__ = 'access_levels' diff --git a/migrations/versions/9f71f5a68c53_.py b/migrations/versions/9f71f5a68c53_.py new file mode 100644 index 0000000..e73d13e --- /dev/null +++ b/migrations/versions/9f71f5a68c53_.py @@ -0,0 +1,35 @@ +"""empty message + +Revision ID: 9f71f5a68c53 +Revises: 3cf41808886b +Create Date: 2018-11-04 15:00:35.383875 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9f71f5a68c53' +down_revision = '3cf41808886b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('device_documentations', + sa.Column('device_id', sa.Integer(), nullable=False), + sa.Column('text', sa.Text(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('modified_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['device_id'], ['devices.id'], ), + sa.PrimaryKeyConstraint('device_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('device_documentations') + # ### end Alembic commands ###