diff --git a/Pipfile b/Pipfile index 7632ec7..a505d45 100644 --- a/Pipfile +++ b/Pipfile @@ -22,6 +22,7 @@ python-jose = "*" flask-jwt-extended = "*" ssh2-python = "*" update = "*" +flask-cors = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index af75197..1583b6d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0b4fd0972054dfeecdd89492b1128706d5c26dc7dcdf85f35b9261ccc8b02c7a" + "sha256": "79518b33814e3afda968f02d6de17856076a2d7a81183eb7ee1c219eccc114cc" }, "pipfile-spec": 6, "requires": { @@ -38,10 +38,10 @@ }, "apispec": { "hashes": [ - "sha256:427293594699c77753b8fdc6d78d8dfe267c394df5186c236d57f6ef1af95027", - "sha256:edad7a67fae5576fd3e97888b05d228573bf75b2dc0d2714d7cea0480662ac19" + "sha256:11d1aaf620a80f67ded7688fcaf14fa4fd975d566876b5db69b067ffbfe4d1d9", + "sha256:2f4734203e53f6ae6499d1f2168d19e62f04f071d6e4b86c6d6014ebf2ebdf6b" ], - "version": "==2.0.1" + "version": "==2.0.2" }, "asn1crypto": { "hashes": [ @@ -119,40 +119,41 @@ }, "coverage": { "hashes": [ - "sha256:3684fabf6b87a369017756b551cef29e505cb155ddb892a7a29277b978da88b9", - "sha256:39e088da9b284f1bd17c750ac672103779f7954ce6125fd4382134ac8d152d74", - "sha256:3c205bc11cc4fcc57b761c2da73b9b72a59f8d5ca89979afb0c1c6f9e53c7390", - "sha256:465ce53a8c0f3a7950dfb836438442f833cf6663d407f37d8c52fe7b6e56d7e8", - "sha256:48020e343fc40f72a442c8a1334284620f81295256a6b6ca6d8aa1350c763bbe", - "sha256:5296fc86ab612ec12394565c500b412a43b328b3907c0d14358950d06fd83baf", - "sha256:5f61bed2f7d9b6a9ab935150a6b23d7f84b8055524e7be7715b6513f3328138e", - "sha256:68a43a9f9f83693ce0414d17e019daee7ab3f7113a70c79a3dd4c2f704e4d741", - "sha256:6b8033d47fe22506856fe450470ccb1d8ba1ffb8463494a15cfc96392a288c09", - "sha256:7ad7536066b28863e5835e8cfeaa794b7fe352d99a8cded9f43d1161be8e9fbd", - "sha256:7bacb89ccf4bedb30b277e96e4cc68cd1369ca6841bde7b005191b54d3dd1034", - "sha256:839dc7c36501254e14331bcb98b27002aa415e4af7ea039d9009409b9d2d5420", - "sha256:8f9a95b66969cdea53ec992ecea5406c5bd99c9221f539bca1e8406b200ae98c", - "sha256:932c03d2d565f75961ba1d3cec41ddde00e162c5b46d03f7423edcb807734eab", - "sha256:988529edadc49039d205e0aa6ce049c5ccda4acb2d6c3c5c550c17e8c02c05ba", - "sha256:998d7e73548fe395eeb294495a04d38942edb66d1fa61eb70418871bc621227e", - "sha256:9de60893fb447d1e797f6bf08fdf0dbcda0c1e34c1b06c92bd3a363c0ea8c609", - "sha256:9e80d45d0c7fcee54e22771db7f1b0b126fb4a6c0a2e5afa72f66827207ff2f2", - "sha256:a545a3dfe5082dc8e8c3eb7f8a2cf4f2870902ff1860bd99b6198cfd1f9d1f49", - "sha256:a5d8f29e5ec661143621a8f4de51adfb300d7a476224156a39a392254f70687b", - "sha256:aca06bfba4759bbdb09bf52ebb15ae20268ee1f6747417837926fae990ebc41d", - "sha256:bb23b7a6fd666e551a3094ab896a57809e010059540ad20acbeec03a154224ce", - "sha256:bfd1d0ae7e292105f29d7deaa9d8f2916ed8553ab9d5f39ec65bcf5deadff3f9", - "sha256:c62ca0a38958f541a73cf86acdab020c2091631c137bd359c4f5bddde7b75fd4", - "sha256:c709d8bda72cf4cd348ccec2a4881f2c5848fd72903c185f363d361b2737f773", - "sha256:c968a6aa7e0b56ecbd28531ddf439c2ec103610d3e2bf3b75b813304f8cb7723", - "sha256:df785d8cb80539d0b55fd47183264b7002077859028dfe3070cf6359bf8b2d9c", - "sha256:f406628ca51e0ae90ae76ea8398677a921b36f0bd71aab2099dfed08abd0322f", - "sha256:f46087bbd95ebae244a0eda01a618aff11ec7a069b15a3ef8f6b520db523dcf1", - "sha256:f8019c5279eb32360ca03e9fac40a12667715546eed5c5eb59eb381f2f501260", - "sha256:fc5f4d209733750afd2714e9109816a29500718b32dd9a5db01c0cb3a019b96a" + "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", + "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", + "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", + "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", + "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", + "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", + "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", + "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", + "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", + "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", + "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", + "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", + "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", + "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", + "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", + "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", + "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", + "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", + "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", + "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", + "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", + "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", + "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", + "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", + "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", + "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", + "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", + "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", + "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", + "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", + "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", + "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025" ], "index": "pypi", - "version": "==4.5.3" + "version": "==4.5.4" }, "cryptography": { "hashes": [ @@ -191,11 +192,19 @@ }, "flask": { "hashes": [ - "sha256:ad7c6d841e64296b962296c2c2dabc6543752985727af86a975072dea984b6f3", - "sha256:e7d32475d1de5facaa55e3958bc4ec66d3762076b074296aa50ef8fdc5b9df61" + "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", + "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6" ], "index": "pypi", - "version": "==1.0.3" + "version": "==1.1.1" + }, + "flask-cors": { + "hashes": [ + "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", + "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a" + ], + "index": "pypi", + "version": "==3.0.8" }, "flask-httpauth": { "hashes": [ @@ -207,10 +216,10 @@ }, "flask-jwt-extended": { "hashes": [ - "sha256:583a6e363ea51434c4189e35d3832a23949cfa5203d60a8287b6be137a75528d" + "sha256:d671b24981a1d09d8f21c7b2681ce7b4bde33e2dfcb98be50ae412fc5908ec79" ], "index": "pypi", - "version": "==3.19.0" + "version": "==3.21.0" }, "flask-login": { "hashes": [ @@ -236,10 +245,10 @@ }, "flask-pyoidc": { "hashes": [ - "sha256:6a8fac4459ae5c65f710ec77877391d55b338068405b89f1f4a4394770ef1114" + "sha256:fb95c452c62de7ec6290fbb5ab8590753541c399e7b817d7d8badd0e9ad6fd52" ], "index": "pypi", - "version": "==2.0.0" + "version": "==2.2.0" }, "flask-restplus": { "hashes": [ @@ -306,16 +315,16 @@ }, "jsonschema": { "hashes": [ - "sha256:0c0a81564f181de3212efa2d17de1910f8732fa1b71c42266d983cd74304e20d", - "sha256:a5f6559964a3851f59040d3b961de5e68e70971afb88ba519d27e6a039efff1a" + "sha256:5f9c0a719ca2ce14c5de2fd350a64fd2d13e8539db29836a86adc990bb1a068f", + "sha256:8d4a2b7b6c2237e0199c8ea1a6d3e05bf118e289ae2b9d7ba444182a2959560d" ], - "version": "==3.0.1" + "version": "==3.0.2" }, "mako": { "hashes": [ - "sha256:95ee720cc3453063788515d55bd7ce4a2a77b7b209e4ac70ec5c86091eb02541" + "sha256:a36919599a9b7dc5d86a7a8988f23a9a3a3d083070023bab23d64f7f1d1e0a4b" ], - "version": "==1.0.13" + "version": "==1.1.0" }, "markupsafe": { "hashes": [ @@ -373,20 +382,21 @@ }, "pbr": { "hashes": [ - "sha256:9181e2a34d80f07a359ff1d0504fad3a47e00e1cf2c475b0aa7dcb030af54c40", - "sha256:94bdc84da376b3dd5061aa0c3b6faffe943ee2e56fa4ff9bd63e1643932f34fc" + "sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc", + "sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf" ], - "version": "==5.3.1" + "version": "==5.4.2" }, "pyasn1": { "hashes": [ - "sha256:da2420fe13a9452d8ae97a0e478adde1dee153b11ba832a95b223a2ba01c10f7", - "sha256:da6b43a8c9ae93bc80e2739efb38cc776ba74a886e3e9318d65fe81a8b8a2c6e" + "sha256:3bb81821d47b17146049e7574ab4bf1e315eb7aead30efe5d6a9ca422c9710be", + "sha256:b773d5c9196ffbc3a1e13bdf909d446cad80a039aa3340bcad72f395b76ebc86" ], - "version": "==0.4.5" + "version": "==0.4.6" }, "pycparser": { "hashes": [ + "sha256:50f84b9531cf541192a39303722ea4d69cab456fc1104ca47f66b9b8dd7be740", "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], "version": "==2.19" @@ -447,9 +457,9 @@ }, "pyrsistent": { "hashes": [ - "sha256:16692ee739d42cf5e39cef8d27649a8c1fdb7aa99887098f1460057c5eb75c3a" + "sha256:34b47fa169d6006b32e99d4b3c4031f155e6e68ebcc107d6454852e8e0ee6533" ], - "version": "==0.15.2" + "version": "==0.15.4" }, "python-dateutil": { "hashes": [ @@ -476,10 +486,10 @@ }, "pytz": { "hashes": [ - "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", - "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32", + "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7" ], - "version": "==2019.1" + "version": "==2019.2" }, "requests": { "hashes": [ @@ -504,10 +514,10 @@ }, "sqlalchemy": { "hashes": [ - "sha256:c30925d60af95443458ebd7525daf791f55762b106049ae71e18f8dd58084c2f" + "sha256:217e7fc52199a05851eee9b6a0883190743c4fb9c8ac4313ccfceaffd852b0ff" ], "index": "pypi", - "version": "==1.3.5" + "version": "==1.3.6" }, "sqlalchemy-migrate": { "hashes": [ @@ -579,18 +589,18 @@ }, "webargs": { "hashes": [ - "sha256:6b81ce44572d4f345104aa41c734fdc01165f054a061a8ebb1b46e89851e1170", - "sha256:713bd63440ee078ce48ca953d254d51e5f1a6fa0c76fb521fc596306c78d95a5", - "sha256:e2394ea7e422c1e795681cee5e8b1c6083bab7db6d7a380841130cbbae173d29" + "sha256:132216236980316da205a4cfb571913109a07a2e014bcc2313b72d0b83dce507", + "sha256:538c9f333f1f7ce06a1eb14b3daf640351057907be71b58d2b5a23c7d6d026be", + "sha256:63cecd4dc79f504c31c33a8470624f79f54c1b35a23141cc52c5a3fa37dc674b" ], - "version": "==5.3.2" + "version": "==5.4.0" }, "werkzeug": { "hashes": [ - "sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c", - "sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6" + "sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4", + "sha256:a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6" ], - "version": "==0.15.4" + "version": "==0.15.5" } }, "develop": {} diff --git a/__init__.py b/__init__.py index 57011f9..bdb5afc 100644 --- a/__init__.py +++ b/__init__.py @@ -10,6 +10,7 @@ from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth from flask_jwt_extended import JWTManager, decode_token from flask_login import LoginManager from flask_sqlalchemy import SQLAlchemy +from flask_cors import CORS app = Flask(__name__) app.config.from_object('backend.config.Config') @@ -62,6 +63,9 @@ app.register_blueprint(auth_api_bp) app.register_blueprint(api_bp) app.register_blueprint(fe_bp) +CORS(app) +CORS(api_bp) + # Fix flask-restplus by duck typing error handlers jwt_extended._set_error_handler_callbacks(api_v1) diff --git a/__main__.py b/__main__.py index 483318f..0b67d80 100644 --- a/__main__.py +++ b/__main__.py @@ -7,7 +7,7 @@ import ssl from jinja2.exceptions import TemplateNotFound -from backend import app +from backend import app, db def main(): @@ -23,6 +23,12 @@ def main(): except FileNotFoundError: app.run(debug=True, threaded=True) + try: + db.create_all() + except Exception as e: + logging.CRITICAL(e) + + app.run(debug=True) diff --git a/api/recorder_api.py b/api/recorder_api.py index e5da45f..f9574aa 100644 --- a/api/recorder_api.py +++ b/api/recorder_api.py @@ -7,6 +7,7 @@ Login through API does not start a new session, but instead returns JWT. """ import inspect import pkgutil +from pprint import pprint from flask_jwt_extended import jwt_required from flask_restplus import fields, Resource @@ -44,6 +45,30 @@ recorder_model = api_recorder.model('Recorder', { description='Room in which the recorder is located.') }) +recorder_command_model = api_recorder.model('Recorder Command', { + 'id': fields.String(required=False, description='The recorder command\'s identifier'), + 'name': fields.String(required=True, description='The recorder command\'s name'), + 'last_time_modified': fields.DateTime(required=False), + 'description': fields.String(required=False, description='The recorder command\'s description'), + 'parameters': fields.String(required=True, description='The recorder parameters'), + 'recorder_model': fields.Nested(api_recorder.model('recorder_command_models', + {'id': fields.Integer(), 'name': fields.String()})), +}) + +recorder_model_model = api_recorder.model('Recorder Model', { + 'id': fields.String(required=False, description='The recorder model\'s identifier'), + 'name': fields.String(attribute="model_name", required=True, description='The recorder model\'s name'), + 'last_time_modified': fields.DateTime(required=False), + 'notes': fields.String(required=False, description='The recorder model\'s notes'), + + 'recorders': fields.List(fields.Nested(api_recorder.model('recorder_model', + {'id': fields.Integer(), 'name': fields.String(), + 'network_name': fields.String(), + 'ip': fields.String()})), required=False, + description='Model of the recorder.'), + 'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands") +}) + # == @@ -119,30 +144,14 @@ class RecorderList(Resource): if rec_model is not None: api_recorder.payload["recorder_model"] = rec_model else: - return "specified recorder model (id: {}) does not exist!".format(api_recorder.payload["recorder_model_id"]), 404 + return "specified recorder model (id: {}) does not exist!".format( + api_recorder.payload["recorder_model_id"]), 404 recorder = Recorder(**api_recorder.payload) db.session.add(recorder) db.session.commit() return recorder -recorder_model_model = api_recorder.model('Recorder Model', { - 'id': fields.String(required=False, description='The recorder model\'s identifier'), - 'name': fields.String(required=True, description='The recorder model\'s name'), - 'notes': fields.String(required=False, description='The recorder model\'s notes'), - - 'recorders': fields.List(fields.Nested(api_recorder.model('recorder_model', - {'id': fields.Integer(), 'name': fields.String(), - 'network_name': fields.String(), - 'ip': fields.String()})), required=False, - description='Model of the recorder.'), - 'commands': fields.List(fields.Nested(api_recorder.model('recorder_model_commands', - {'id': fields.Integer(), 'name': fields.String()})), - required=False, - description='Room in which the recorder is located.') -}) - - @api_recorder.route('/model/') @api_recorder.response(404, 'Recorder Model not found') @api_recorder.param('id', 'The recorder model identifier') @@ -157,10 +166,12 @@ class RecorderModelResource(Resource): return recorder_model api_recorder.abort(404) + recorder_model_parser = api_recorder.parser() + recorder_model_parser.add_argument('notes', type=str, required=True) @jwt_required @api_recorder.doc('update_recorder_model') - @api_recorder.expect(recorder_model_model) + @api_recorder.expect(recorder_model_parser) @api_recorder.marshal_with(recorder_model_model) def put(self, id): """Update a recorder_model given its identifier""" @@ -173,32 +184,11 @@ class RecorderModelResource(Resource): @api_recorder.route('/model') class RecorderModelList(Resource): - #@jwt_required + @jwt_required @api_recorder.doc('recorders') @api_recorder.marshal_list_with(recorder_model_model) def get(self): - models = r_a.get_defined_recorder_adapters() - return models - """ - List all recorder models - :return: recorder models - """ - return Recorder.get_all() - - -# == - - -recorder_command_model = api_recorder.model('Recorder Command', { - 'id': fields.String(required=False, description='The recorder command\'s identifier'), - 'name': fields.String(required=True, description='The recorder command\'s name'), - 'description': fields.String(required=False, description='The recorder command\'s description'), - 'command': fields.String(required=True, description='The recorder command\'s name'), - 'recorder_models': fields.List(fields.Nested(api_recorder.model('recorder_command_models', - {'id': fields.Integer(), 'name': fields.String()})), - required=False, - description='Recorder models associated with the command.'), -}) + return RecorderModel.get_all() @api_recorder.route('/command/') diff --git a/app.db b/app.db index 7e71e48..6c62f62 100644 Binary files a/app.db and b/app.db differ diff --git a/models/recorder_model.py b/models/recorder_model.py index a60b926..3a4bfce 100644 --- a/models/recorder_model.py +++ b/models/recorder_model.py @@ -5,44 +5,49 @@ Models for lecture recorder import json from sqlalchemy import MetaData +from sqlalchemy.ext.hybrid import hybrid_property from backend import db, app, login_manager import re from sqlalchemy import or_ from datetime import datetime, timedelta -from backend.models.virtual_command_model import virtual_command_recorder_command_table +from backend.models.virtual_command_model import virtual_command_recorder_command_table, virtual_command_recorder_table metadata = MetaData() -# This is the association table for the many-to-many relationship between -# groups and permissions. -recorder_model_recorder_command_table = db.Table('recorder_model_recorder_command', - db.Column('recorder_model_id', db.Integer, - db.ForeignKey('recorder_model.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True), - db.Column('recorder_command_id', db.Integer, - db.ForeignKey('recorder_command.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True)) - class RecorderModel(db.Model): id = db.Column(db.Integer, autoincrement=True, primary_key=True) + last_time_modified = db.Column(db.DateTime, nullable=True, default=None) + record_adapter_id = db.Column(db.Unicode(63), unique=True, nullable=False) model_name = db.Column(db.Unicode(63), unique=True, nullable=False) - model_checksum = db.Column(db.String(63), unique=True, nullable=True) notes = db.Column(db.Unicode(255), unique=False, nullable=True, default=None) - recorder_commands = db.relationship('RecorderCommand', secondary=recorder_model_recorder_command_table, - back_populates='recorder_models') + recorder_commands = db.relationship('RecorderCommand', back_populates='recorder_model') recorders = db.relationship('Recorder', back_populates='recorder_model') + checksum = db.Column(db.String(63), unique=True, nullable=False) + + @staticmethod + def get_all(): + return RecorderModel.query.all() + + @staticmethod + def get_by_name(name): + return RecorderModel.query.filter(RecorderModel.model_name == name).first() + + @staticmethod + def get_by_adapter_id(name): + return RecorderModel.query.filter(RecorderModel.record_adapter_id == name).first() + + @staticmethod + def get_by_checksum(md5_sum): + return RecorderModel.query.filter(RecorderModel.checksum == md5_sum).first() class Recorder(db.Model): id = db.Column(db.Integer, autoincrement=True, primary_key=True) created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow()) + last_time_modified = db.Column(db.DateTime, nullable=False, default=datetime.utcnow()) name = db.Column(db.Unicode(63), unique=True, nullable=False) description = db.Column(db.Unicode(255), unique=False, nullable=True, default="") room_id = db.Column(db.Integer, db.ForeignKey('room.id')) @@ -53,27 +58,17 @@ class Recorder(db.Model): ssh_port = db.Column(db.Integer, unique=False, nullable=False, default=22) recorder_model_id = db.Column(db.Integer, db.ForeignKey('recorder_model.id')) recorder_model = db.relationship('RecorderModel', back_populates='recorders') - virtual_commands = db.relationship('VirtualCommand', back_populates='recorders') + virtual_commands = db.relationship('VirtualCommand', secondary=virtual_command_recorder_table, back_populates='recorders') def __init__(self, **kwargs): super(Recorder, self).__init__(**kwargs) - @staticmethod def get_by_name(name): - """ - Find group by name - :param name: - :return: - """ return Recorder.query.filter(Recorder.name == name).first() @staticmethod def get_all(): - """ - Return all groups - :return: - """ return Recorder.query.all() def __str__(self): @@ -90,11 +85,26 @@ class Recorder(db.Model): class RecorderCommand(db.Model): """Table containing permissions associated with groups.""" id = db.Column(db.Integer, autoincrement=True, primary_key=True) + last_time_modified = db.Column(db.DateTime, nullable=True, default=None) name = db.Column(db.Unicode(63), unique=True, nullable=False) description = db.Column(db.Unicode(511), nullable=True, default=None) - parameters = db.Column(db.String(2047), nullable=True) - recorder_models = db.relationship('RecorderModel', secondary=recorder_model_recorder_command_table, - back_populates='recorder_commands') - + parameters_string = db.Column(db.String(2047), nullable=True) + recorder_model = db.relationship('RecorderModel', back_populates='recorder_commands') + recorder_model_id = db.Column(db.Integer, db.ForeignKey('recorder_model.id')) virtual_commands = db.relationship('VirtualCommand', secondary=virtual_command_recorder_command_table, back_populates='recorder_commands') + + @staticmethod + def get_all(): + return RecorderCommand.query.all() + + @hybrid_property + def parameters(self): + if self.parameters_string is None: + return [] + print(self.parameters_string) + return json.loads(self.parameters_string) + + @parameters.setter + def parameters(self, parameters_dict: dict): + self.parameters_string = json.dumps(parameters_dict) diff --git a/models/user_model.py b/models/user_model.py index f52d3fb..81cce10 100644 --- a/models/user_model.py +++ b/models/user_model.py @@ -460,4 +460,5 @@ class Permission(db.Model): @event.listens_for(Permission.__table__, 'after_create') def insert_initial_permissions(*args, **kwargs): for p in app.config.get("PERMISSIONS", []): - db.session.add(p) + db.session.add(Permission(name=p)) + db.session.commit() diff --git a/models/virtual_command_model.py b/models/virtual_command_model.py index b416eac..6b64944 100644 --- a/models/virtual_command_model.py +++ b/models/virtual_command_model.py @@ -2,37 +2,37 @@ import json from datetime import datetime from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import backref from backend import db # This is the association table for the many-to-many relationship between # virtual commands and recorder commands. virtual_command_recorder_command_table = db.Table('virtual_command_recorder_command', - db.Column('virtual_command_id', db.Integer, - db.ForeignKey('virtual_command.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True), - db.Column('recorder_command_id', db.Integer, - db.ForeignKey('recorder_command.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True)) - + db.Column('virtual_command_id', db.Integer, + db.ForeignKey('virtual_command.id', + onupdate="CASCADE", + ondelete="CASCADE"), + primary_key=True), + db.Column('recorder_command_id', db.Integer, + db.ForeignKey('recorder_command.id', + onupdate="CASCADE", + ondelete="CASCADE"), + primary_key=True)) # This is the association table for the many-to-many relationship between # virtual commands and recorder commands. virtual_command_recorder_table = db.Table('virtual_command_recorder', - db.Column('virtual_command_id', db.Integer, - db.ForeignKey('virtual_command.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True), - db.Column('recorder_id', db.Integer, - db.ForeignKey('recorder.id', - onupdate="CASCADE", - ondelete="CASCADE"), - primary_key=True)) + db.Column('virtual_command_id', db.Integer, + db.ForeignKey('virtual_command.id', + onupdate="CASCADE", + ondelete="CASCADE"), + primary_key=True), + db.Column('recorder_id', db.Integer, + db.ForeignKey('recorder.id', + onupdate="CASCADE", + ondelete="CASCADE"), + primary_key=True)) class VirtualCommand(db.Model): @@ -47,9 +47,10 @@ class VirtualCommand(db.Model): recorder_commands = db.relationship('RecorderCommand', secondary=virtual_command_recorder_command_table, back_populates='virtual_commands') - parent_virtual_command = db.relationship('VirtualCommand', back_populates='child_virtual_commands', - nullable=True, default=None) - child_virtual_commands = db.relationship('VirtualCommand', back_populates='parent_virtual_command') + # parent_virtual_command = db.relationship('VirtualCommand', back_populates='child_virtual_commands') + parent_virtual_command_id = db.Column(db.Integer, db.ForeignKey('virtual_command.id')) + child_virtual_commands = db.relationship('VirtualCommand', + backref=backref('parent_virtual_command', remote_side=[id])) command_order_string = db.Column(db.String) diff --git a/recorder_adapters/__init__.py b/recorder_adapters/__init__.py index 8df01cf..c297c35 100644 --- a/recorder_adapters/__init__.py +++ b/recorder_adapters/__init__.py @@ -5,8 +5,6 @@ import telnetlib from abc import ABC, abstractmethod # monkey patching of telnet lib -from pprint import pprint - original_read_until = telnetlib.Telnet.read_until original_write = telnetlib.Telnet.write @@ -131,8 +129,4 @@ def get_defined_recorder_adapters(): commands[method_name] = parameters rec_model["commands"] = commands models.append(rec_model) - pprint(models) return models - - -get_defined_recorder_adapters() diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..3dbb81b --- /dev/null +++ b/tools/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2019. Tobias Kurze, KIT + diff --git a/tools/model_updater.py b/tools/model_updater.py new file mode 100644 index 0000000..2cc0fe6 --- /dev/null +++ b/tools/model_updater.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2019. Tobias Kurze + +import hashlib +import logging +from datetime import datetime +from json import dumps +from pprint import pprint + +from sqlalchemy import and_ + +from backend import db +from backend.models.recorder_model import RecorderModel, RecorderCommand +from backend.recorder_adapters import get_defined_recorder_adapters + + +def calculate_md5_checksum(string_to_md5_sum: str): + return hashlib.md5(string_to_md5_sum.encode('utf-8')).hexdigest() + + +def create_recorder_commands_for_recorder_adapter(command_definitions: dict, recorder_model: RecorderModel): + existing_recorder_commands = RecorderCommand.query.filter(and_(RecorderCommand.name.in_(command_definitions.keys())), + RecorderCommand.recorder_model == recorder_model) + existing_commands = set() + for existing_command in existing_recorder_commands: + existing_commands.add(existing_command.name) + args = command_definitions.get(existing_command.name) + if dumps(existing_command.parameters) != dumps(args): + logging.warning( + "The function definition {} collides with an existing definition of the same name " + "but different parameters!".format( + existing_command.name)) + existing_command.last_time_modified = datetime.utcnow() + existing_command.parameters = args + db.session.commit() + + for c_d in set(command_definitions.keys()) - existing_commands: + # print(c_d) + args = command_definitions.get(c_d) + # create new recorder command(s) + r_c = RecorderCommand(name=c_d, parameters=args, recorder_model=recorder_model) + db.session.add(r_c) + db.session.commit() + + +def update_recorder_models_database(): + r_as = get_defined_recorder_adapters() + for r_a in r_as: + r_m = RecorderModel.get_by_adapter_id(r_a["id"]) + model_checksum = calculate_md5_checksum(dumps(r_a["commands"])) + if r_m is None: + r_m = RecorderModel(record_adapter_id=r_a["id"], model_name=r_a["name"], checksum=model_checksum) + db.session.add(r_m) + db.session.flush() + db.session.refresh(r_m) + else: + if not model_checksum == r_m.checksum: + r_m.model_name = r_a["name"] + r_m.model_name = r_a["name"] + r_m.checksum = model_checksum + create_recorder_commands_for_recorder_adapter(r_a["commands"], r_m) + db.session.commit() + + +if __name__ == '__main__': + db.create_all() + update_recorder_models_database()