From f70cbdc463d225a1fcb43583d34015edec4ba0ce Mon Sep 17 00:00:00 2001 From: Tobias Kurze Date: Tue, 13 Aug 2019 15:29:37 +0200 Subject: [PATCH] now scraping rooms from capmus mgmt --- Pipfile | 2 + Pipfile.lock | 46 ++++++++++-- __main__.py | 7 +- api/__init__.py | 10 ++- api/example_api.py | 1 + api/recorder_api.py | 90 +++++++++++++--------- api/room_api.py | 2 + api/virtual_command_api.py | 127 ++++++++++++++++++++++++++++++++ app.db | Bin 200704 -> 0 bytes manage.py | 7 +- models/__init__.py | 3 + models/recorder_model.py | 18 +++++ models/room_model.py | 33 ++++++--- recorder_adapters/__init__.py | 4 + recorder_adapters/extron_smp.py | 6 +- tools/model_updater.py | 9 ++- tools/scrape_rooms.py | 60 +++++++++++++++ 17 files changed, 364 insertions(+), 61 deletions(-) create mode 100644 api/virtual_command_api.py delete mode 100644 app.db create mode 100644 tools/scrape_rooms.py diff --git a/Pipfile b/Pipfile index a505d45..15fca1e 100644 --- a/Pipfile +++ b/Pipfile @@ -23,6 +23,8 @@ flask-jwt-extended = "*" ssh2-python = "*" update = "*" flask-cors = "*" +html5lib = "*" +beautifulsoup4 = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 1583b6d..93e69c0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "79518b33814e3afda968f02d6de17856076a2d7a81183eb7ee1c219eccc114cc" + "sha256": "10fe25a82139020ea4ed6a7615b6f77965ebb4a96824b6f0d8989da081db892f" }, "pipfile-spec": 6, "requires": { @@ -63,6 +63,15 @@ ], "version": "==1.10.1" }, + "beautifulsoup4": { + "hashes": [ + "sha256:05668158c7b85b791c5abde53e50265e16f98ad601c402ba44d70f96c4159612", + "sha256:25288c9e176f354bf277c0a10aa96c782a6a18a17122dba2e8cec4a97e03343b", + "sha256:f040590be10520f2ea4c2ae8c3dae441c7cfff5308ec9d58a0ec0c1b8f81d469" + ], + "index": "pypi", + "version": "==4.8.0" + }, "certifi": { "hashes": [ "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", @@ -252,10 +261,10 @@ }, "flask-restplus": { "hashes": [ - "sha256:3fad697e1d91dfc13c078abcb86003f438a751c5a4ff41b84c9050199d2eab62", - "sha256:cdc27b5be63f12968a7f762eaa355e68228b0c904b4c96040a314ba7dc6d0e69" + "sha256:a15d251923a8feb09a5d805c2f4d188555910a42c64d58f7dd281b8cac095f1b", + "sha256:a66e442d0bca08f389fc3d07b4d808fc89961285d12fb8013f7cf15516fa9f5c" ], - "version": "==0.12.1" + "version": "==0.13.0" }, "flask-restplus-patched": { "hashes": [ @@ -292,6 +301,14 @@ ], "version": "==0.17.1" }, + "html5lib": { + "hashes": [ + "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", + "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" + ], + "index": "pypi", + "version": "==1.0.1" + }, "idna": { "hashes": [ "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", @@ -361,10 +378,10 @@ }, "marshmallow": { "hashes": [ - "sha256:9cedfc5b6f568d57e8a2cf3d293fbd81b05e5ef557854008d03e25660a39ccfd", - "sha256:a4d99922116a76e5abd8f997ec0519086e24814b7e1e1344bebe2a312ba50235" + "sha256:43bef4d33b7adb1f66eba8074095208b38dec96bed51f7a9bf2e687750f226d8", + "sha256:b240f5e14bc641c257f4b7bda3951d7e71963ebf66bd519078267f1f961cbd15" ], - "version": "==2.19.5" + "version": "==2.20.1" }, "oic": { "hashes": [ @@ -396,7 +413,6 @@ }, "pycparser": { "hashes": [ - "sha256:50f84b9531cf541192a39303722ea4d69cab456fc1104ca47f66b9b8dd7be740", "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], "version": "==2.19" @@ -512,6 +528,13 @@ ], "version": "==1.12.0" }, + "soupsieve": { + "hashes": [ + "sha256:72b5f1aea9101cf720a36bb2327ede866fd6f1a07b1e87c92a1cc18113cbc946", + "sha256:e4e9c053d59795e440163733a7fec6c5972210e1790c507e4c7b051d6c5259de" + ], + "version": "==1.9.2" + }, "sqlalchemy": { "hashes": [ "sha256:217e7fc52199a05851eee9b6a0883190743c4fb9c8ac4313ccfceaffd852b0ff" @@ -595,6 +618,13 @@ ], "version": "==5.4.0" }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, "werkzeug": { "hashes": [ "sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4", diff --git a/__main__.py b/__main__.py index 0b67d80..ca2c74e 100644 --- a/__main__.py +++ b/__main__.py @@ -8,6 +8,8 @@ import ssl from jinja2.exceptions import TemplateNotFound from backend import app, db +from backend.models import pre_fill_table +from backend.tools.model_updater import update_recorder_models_database def main(): @@ -28,8 +30,11 @@ def main(): except Exception as e: logging.CRITICAL(e) + pre_fill_table() + update_recorder_models_database() - app.run(debug=True) + + app.run(debug=True, host="0.0.0.0") if __name__ == '__main__': diff --git a/api/__init__.py b/api/__init__.py index c2ecde2..439d94e 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -29,11 +29,17 @@ api_user = Namespace('user', description="User management namespace", authorizat api_group = Namespace('group', description="Group management namespace", authorizations=api_authorizations) api_room = Namespace('room', description="Room management namespace", authorizations=api_authorizations) api_recorder = Namespace('recorder', description="Recorder management namespace", authorizations=api_authorizations) +api_virtual_command = Namespace('virtual_command', description="Virtual command namespace", + authorizations=api_authorizations) +api_cron_job = Namespace('cron_job', description="Cron job namespace", + authorizations=api_authorizations) api_v1.add_namespace(api_user) api_v1.add_namespace(api_group) api_v1.add_namespace(api_room) api_v1.add_namespace(api_recorder) +api_v1.add_namespace(api_virtual_command) +api_v1.add_namespace(api_cron_job) auth_api_bp = Blueprint('auth_api', __name__, url_prefix='/api/auth') # user_api_bp = Blueprint('user_api', __name__, url_prefix='/api/user') @@ -45,7 +51,9 @@ from .user_api import * from .group_api import * from .room_api import * from .recorder_api import * -#from .group_api import * + + +# from .group_api import * @api_bp.route('/') diff --git a/api/example_api.py b/api/example_api.py index 1e00900..17c0192 100644 --- a/api/example_api.py +++ b/api/example_api.py @@ -1,3 +1,4 @@ + import datetime import ipaddress import json diff --git a/api/recorder_api.py b/api/recorder_api.py index 695efe6..5970c03 100644 --- a/api/recorder_api.py +++ b/api/recorder_api.py @@ -5,12 +5,11 @@ For example: listing of available auth providers or registration of users. Login through API does not start a new session, but instead returns JWT. """ -import inspect -import pkgutil +from datetime import datetime from pprint import pprint from flask_jwt_extended import jwt_required -from flask_restplus import fields, Resource +from flask_restplus import fields, Resource, inputs from backend import db, app from backend.api import api_recorder @@ -21,17 +20,23 @@ import backend.recorder_adapters as r_a recorder_model = api_recorder.model('Recorder', { 'id': fields.String(required=False, description='The recorder\'s identifier'), 'created_at': fields.DateTime(required=False, description='Creation date of the recorder'), + 'last_time_modified': fields.DateTime(required=False, description='Creation date of the recorder'), 'name': fields.String(min_length=3, required=True, description='The recorder\'s name'), 'description': fields.String(required=False, description='The recorder\'s description'), + 'locked': fields.Boolean(required=False, description='Indicates whether the recorder settings can be altered'), + 'lock_message': fields.String(required=False, description='Optional: message explaining lock state'), + 'offline': fields.Boolean(required=False, + description='Should be set when recorder is disconnected for maintenance, etc.'), 'ip': fields.String(required=False, description='The recorder\'s IP address'), 'network_name': fields.String(required=False, description='The recorder\'s network name'), 'ssh_port': fields.Integer(required=True, default=22, description='The recorder\'s SSH port number'), 'telnet_port': fields.Integer(required=True, default=23, description='The recorder\'s telnet port number'), - 'use_telnet_instead_ssh': fields.Boolean(required=False, default=False, - description='If this is set, telnet will be used instead of ssh. ' - 'This might require specific commands.'), + # 'use_telnet_instead_ssh': fields.Boolean(required=False, default=False, + # description='If this is set, telnet will be used instead of ssh. ' + # 'This might require specific commands.'), 'recorder_model': fields.Nested(api_recorder.model('recorder_model', - {'id': fields.Integer(), 'name': fields.String()}), + {'id': fields.Integer(), + 'name': fields.String(attribute="model_name", )}), required=False, allow_null=True, skip_none=False, @@ -39,30 +44,41 @@ recorder_model = api_recorder.model('Recorder', { 'room': fields.Nested(api_recorder.model('recorder_room', {'id': fields.Integer(), 'name': fields.String(), 'number': fields.String(), 'alternate_name': fields.String()}), - required=False, + r0equired=False, allow_null=True, skip_none=False, - description='Room in which the recorder is located.') + description='Room in which the recorder is located.'), + 'virtual_commands': fields.List(fields.Nested(api_recorder.model('recorder_virtual_commands', + {'id': fields.Integer(), + 'name': fields.String()}))) }) 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'), + 'alternative_name': fields.String(required=False, description='The recorder command\'s alternative name'), + 'disabled': fields.Boolean(required=False, description='Indicates if the recorder command is disabled'), + 'created_at': fields.DateTime(required=False, description='Creation date of the recorder'), 'last_time_modified': fields.DateTime(required=False), 'description': fields.String(required=False, description='The recorder command\'s description'), 'parameters': fields.Raw(required=True, description='The recorder parameters'), 'recorder_model': fields.Nested(api_recorder.model('recorder_command_models', - {'id': fields.Integer(), 'name': fields.String()})), + {'id': fields.Integer(), + 'name': fields.String(attribute="model_name", )})), }) 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'), + 'created_at': fields.DateTime(required=False, description='Creation date of the recorder'), 'last_time_modified': fields.DateTime(required=False), 'notes': fields.String(required=False, description='The recorder model\'s notes'), + 'requires_username': fields.Boolean(), + 'requires_password': fields.Boolean(), 'recorders': fields.List(fields.Nested(api_recorder.model('recorder_model', - {'id': fields.Integer(), 'name': fields.String(), + {'id': fields.Integer(), + 'name': fields.String(attribute="model_name", ), 'network_name': fields.String(), 'ip': fields.String()})), required=False, description='Model of the recorder.'), @@ -98,12 +114,36 @@ class RecorderResource(Resource): return '', 204 api_recorder.abort(404) + recorder_update_parser = api_recorder.parser() + recorder_update_parser.add_argument('name', type=str, required=False, nullable=False, store_missing=False) + recorder_update_parser.add_argument('network_name', type=inputs.regex(inputs.netloc_regex), required=False, store_missing=False) + recorder_update_parser.add_argument('ip', type=inputs.ipv4, required=False, store_missing=False) + recorder_update_parser.add_argument('ip6', type=inputs.ipv6, required=False, store_missing=False) + recorder_update_parser.add_argument('ssh_port', type=inputs.int_range(0,65535), required=False, default=22, store_missing=False) + recorder_update_parser.add_argument('telnet_port', type=inputs.int_range(0,65535), required=False, default=23, store_missing=False) + recorder_update_parser.add_argument('room_id', type=int, required=False, store_missing=False) + recorder_update_parser.add_argument('offline', type=inputs.boolean, required=False, default=False, store_missing=False) + recorder_update_parser.add_argument('locked', type=inputs.boolean, required=False, default=False, store_missing=False) + recorder_update_parser.add_argument('lock_message', type=str, required=False, nullable=True, default=None, + store_missing=False) + recorder_update_parser.add_argument('model_id', type=int, required=False, store_missing=False) + recorder_update_parser.add_argument('description', type=str, required=False, nullable=True, default=None, + store_missing=False) + recorder_update_parser.add_argument('virtual_command_ids', action='split', nullable=True, default=[], + required=False, store_missing=False) + @jwt_required @api_recorder.doc('update_recorder') @api_recorder.expect(recorder_model) def put(self, id): """Update a recorder given its identifier""" - num_rows_matched = Recorder.query.filter_by(id=id).update(api_recorder.payload) + args = self.recorder_update_parser.parse_args(strict=True) + args['last_time_modified'] = datetime.utcnow() + pprint(args) + + num_rows_matched = Recorder.query.filter_by(id=id).update(args) + print(num_rows_matched) + if num_rows_matched < 1: api_recorder.abort(404) db.session.commit() @@ -205,21 +245,13 @@ class RecorderCommandResource(Resource): return recorder_command api_recorder.abort(404) - @jwt_required - @api_recorder.doc('delete_recorder_command') - @api_recorder.response(204, 'Recorder_command deleted') - def delete(self, id): - """Delete a recorder command given its identifier""" - recorder_command = RecorderCommand.query.get(id) - if recorder_command is not None: - db.session.delete(recorder_command) - db.session.commit() - return '', 204 - api_recorder.abort(404) + recorder_command_model_parser = api_recorder.parser() + recorder_command_model_parser.add_argument('description', type=str, required=False) + recorder_command_model_parser.add_argument('alternative_name', type=str, required=False) @jwt_required @api_recorder.doc('update_recorder_command') - @api_recorder.expect(recorder_command_model) + @api_recorder.expect(recorder_command_model_parser) @api_recorder.marshal_with(recorder_command_model) def put(self, id): """Update a recorder command given its identifier""" @@ -241,13 +273,3 @@ class RecorderCommandList(Resource): :return: recorder commands """ return RecorderCommand.get_all() - - @jwt_required - @api_recorder.doc('create_recorder_commands') - @api_recorder.expect(recorder_command_model) - @api_recorder.marshal_with(recorder_command_model, code=201) - def post(self): - recorder_command = RecorderCommand(**api_recorder.payload) - db.session.add(recorder_command) - db.session.commit() - return recorder_command diff --git a/api/room_api.py b/api/room_api.py index 9f29f11..ad93a45 100644 --- a/api/room_api.py +++ b/api/room_api.py @@ -21,6 +21,8 @@ room_model = api_room.model('Room', { 'alternate_name': fields.String(required=False, description='The room\'s alternate name'), 'comment': fields.String(required=False, description='The room\'s comment'), 'number': fields.String(required=True, description='The room\'s number'), + 'building_name': fields.String(required=False, description='The building\'s name'), + 'building_number': fields.String(required=False, description='The building\'s number'), 'recorder': fields.Nested(api_room.model('room_recorder', {'id': fields.Integer(), 'name': fields.String(), 'ip': fields.String(), 'network_name': fields.String()}), diff --git a/api/virtual_command_api.py b/api/virtual_command_api.py new file mode 100644 index 0000000..68f7698 --- /dev/null +++ b/api/virtual_command_api.py @@ -0,0 +1,127 @@ +# Copyright (c) 2019. Tobias Kurze +""" +This module provides functions related to authentication through the API. +For example: listing of available auth providers or registration of users. + +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 + +from backend import db, app +from backend.api import api_virtual_command +from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand +from backend.models.room_model import Room +import backend.recorder_adapters as r_a + +virtual_command_model = api_virtual_command.model('VirtualCommand', { + 'id': fields.String(required=False, description='The recorder\'s identifier'), + 'created_at': fields.DateTime(required=False, description='Creation date of the recorder'), + 'name': fields.String(min_length=3, required=True, description='The recorder\'s name'), + 'description': fields.String(required=False, description='The recorder\'s description'), + + 'parent_virtual_command': fields.Nested('virtual_command_model', + required=False, + allow_null=True, + skip_none=False, + description='Parent virtual command.'), + 'room': fields.Nested(api_virtual_command.model('recorder_room', + {'id': fields.Integer(), 'name': fields.String(), + 'number': fields.String(), 'alternate_name': fields.String()}), + r0equired=False, + allow_null=True, + skip_none=False, + description='Room in which the recorder is located.') +}) + + +# == + +@api_virtual_command.route('/') +@api_virtual_command.response(404, 'Recorder not found') +@api_virtual_command.param('id', 'The recorder identifier') +class VirtualCommandResource(Resource): + @jwt_required + @api_virtual_command.doc('get_recorder') + @api_virtual_command.marshal_with(virtual_command_model, skip_none=False) + def get(self, id): + """Fetch a recorder given its identifier""" + recorder = Recorder.query.get(id) + if recorder is not None: + return recorder + api_virtual_command.abort(404) + + @jwt_required + @api_virtual_command.doc('delete_todo') + @api_virtual_command.response(204, 'Todo deleted') + def delete(self, id): + """Delete a recorder given its identifier""" + recorder = Recorder.query.get(id) + if recorder is not None: + db.session.delete(recorder) + db.session.commit() + return '', 204 + api_virtual_command.abort(404) + + virtual_command_model_parser = api_virtual_command.parser() + virtual_command_model_parser.add_argument('notes', type=str, required=True) + + @jwt_required + @api_virtual_command.doc('update_recorder') + @api_virtual_command.expect(virtual_command_model_parser) + def put(self, id): + """Update a recorder given its identifier""" + num_rows_matched = Recorder.query.filter_by(id=id).update(api_virtual_command.payload) + if num_rows_matched < 1: + api_virtual_command.abort(404) + db.session.commit() + return "ok" + + +@api_virtual_command.route('') +class RecorderList(Resource): + @jwt_required + @api_virtual_command.doc('recorders') + @api_virtual_command.marshal_list_with(virtual_command_model, skip_none=False) + def get(self): + """ + List all recorders + :return: recorders + """ + return Recorder.get_all() + + virtual_command_model_parser = api_virtual_command.parser() + virtual_command_model_parser.add_argument('notes', type=str, required=True) + + @jwt_required + @api_virtual_command.doc('create_recorder') + @api_virtual_command.expect(virtual_command_model_parser) + @api_virtual_command.marshal_with(virtual_command_model, skip_none=False, code=201) + def post(self): + if "room_id" in api_virtual_command.payload: + if api_virtual_command.payload["room_id"] is None: + api_virtual_command.payload["room"] = None + else: + room = Room.query.get(api_virtual_command.payload["room_id"]) + if room is not None: + api_virtual_command.payload["room"] = room + else: + return "specified room (id: {}) does not exist!".format(api_virtual_command.payload["room_id"]), 404 + if "recorder_model_id" in api_virtual_command.payload: + if api_virtual_command.payload["recorder_model_id"] is None: + api_virtual_command.payload["recorder_model"] = None + else: + rec_model = RecorderModel.query.get(api_virtual_command.payload["recorder_model_id"]) + if rec_model is not None: + api_virtual_command.payload["recorder_model"] = rec_model + else: + return "specified recorder model (id: {}) does not exist!".format( + api_virtual_command.payload["recorder_model_id"]), 404 + recorder = Recorder(**api_virtual_command.payload) + db.session.add(recorder) + db.session.commit() + return recorder diff --git a/app.db b/app.db deleted file mode 100644 index bd2019acaa1f4092299179ae451af28b4621850b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 200704 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 diff --git a/manage.py b/manage.py index 1b36e0e..21ff429 100755 --- a/manage.py +++ b/manage.py @@ -1,14 +1,17 @@ #!/usr/bin/env python - +import os, sys +sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir)) import os import unittest import coverage -from backend import app, db + from flask_script import Manager from flask_migrate import Migrate, MigrateCommand +from backend import app, db + COV = coverage.coverage( branch=True, include='app/*', diff --git a/models/__init__.py b/models/__init__.py index 4bfbabc..93e79ca 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -4,3 +4,6 @@ Import all models... from backend.models.example_model import * from backend.models.user_model import * from backend.models.post_model import * +from backend.models.recorder_model import * +from backend.models.room_model import * +from backend.models.virtual_command_model import * diff --git a/models/recorder_model.py b/models/recorder_model.py index 876121b..4c0fe07 100644 --- a/models/recorder_model.py +++ b/models/recorder_model.py @@ -6,6 +6,7 @@ import json from sqlalchemy import MetaData from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.orm import validates from backend import db, app, login_manager import re @@ -19,6 +20,7 @@ metadata = MetaData() class RecorderModel(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=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) @@ -26,6 +28,8 @@ class RecorderModel(db.Model): 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) + requires_user = db.Column(db.Boolean) + requires_password = db.Column(db.Boolean) @staticmethod def get_all(): @@ -49,13 +53,19 @@ class Recorder(db.Model): 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) + locked = db.Column(db.Boolean, default=False) + lock_message = db.Column(db.String, nullable=True, default=None) + offline = db.Column(db.Boolean, default=False) description = db.Column(db.Unicode(255), unique=False, nullable=True, default="") room_id = db.Column(db.Integer, db.ForeignKey('room.id')) room = db.relationship('Room', uselist=False, back_populates='recorder') # one-to-one relation (uselist=False) ip = db.Column(db.String(15), unique=True, nullable=True, default=None) + ip6 = db.Column(db.String(46), unique=True, nullable=True, default=None) network_name = db.Column(db.String(127), unique=True, nullable=True, default=None) telnet_port = db.Column(db.Integer, unique=False, nullable=False, default=23) ssh_port = db.Column(db.Integer, unique=False, nullable=False, default=22) + username = db.column(db.String(127)) + password = db.column(db.String(127)) 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', secondary=virtual_command_recorder_table, back_populates='recorders') @@ -71,6 +81,11 @@ class Recorder(db.Model): def get_all(): return Recorder.query.all() + @validates('name') + def validate_name(self, key, value): + assert len(value) > 2 + return value + def __str__(self): return self.name @@ -85,8 +100,11 @@ class Recorder(db.Model): class RecorderCommand(db.Model): """Table containing permissions associated with groups.""" 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=True, default=None) name = db.Column(db.Unicode(63), unique=True, nullable=False) + alternative_name = db.Column(db.Unicode(63), unique=True, nullable=True, default=None) + disabled = db.Column(db.Boolean, default=False) description = db.Column(db.Unicode(511), nullable=True, default=None) parameters_string = db.Column(db.String(2047), nullable=True) recorder_model = db.relationship('RecorderModel', back_populates='recorder_commands') diff --git a/models/room_model.py b/models/room_model.py index f855e53..0469eeb 100644 --- a/models/room_model.py +++ b/models/room_model.py @@ -5,13 +5,12 @@ Models for lecture recorder import json from sqlalchemy import MetaData, CheckConstraint +from sqlalchemy.exc import IntegrityError + +from datetime import datetime, timedelta from backend import db, app, login_manager -import re -from sqlalchemy import or_ -from datetime import datetime, timedelta -from backend.models.recorder_model import Recorder - +from backend.tools.scrape_rooms import scrape_rooms metadata = MetaData() @@ -19,10 +18,12 @@ metadata = MetaData() class Room(db.Model): id = db.Column(db.Integer, autoincrement=True, primary_key=True) created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow()) - name = db.Column(db.Unicode(127), unique=True, nullable=False) - alternate_name = db.Column(db.Unicode(127), unique=True, nullable=True, default=None) + name = db.Column(db.Unicode(127), unique=False, nullable=False) + alternate_name = db.Column(db.Unicode(127), unique=False, nullable=True, default=None) comment = db.Column(db.Unicode(2047), unique=False, nullable=True, default="") - number = db.Column(db.Unicode(63), unique=True, nullable=True) + number = db.Column(db.Unicode(63), unique=False, nullable=True) + building_name = db.Column(db.Unicode(63), unique=False, nullable=True) + building_number = db.Column(db.Unicode(63), unique=False, nullable=True) recorder = db.relationship('Recorder', uselist=False, back_populates='room') # one-to-one relation (uselist=False) @@ -34,7 +35,6 @@ class Room(db.Model): def __init__(self, **kwargs): super(Room, self).__init__(**kwargs) - @staticmethod def get_by_name(name): """ @@ -47,7 +47,7 @@ class Room(db.Model): @staticmethod def get_all(): """ - Return all groups + Return all rooms :return: """ return Room.query.all() @@ -61,3 +61,16 @@ class Room(db.Model): def toJSON(self): return json.dumps(self.to_dict(), default=lambda o: o.__dict__, sort_keys=True, indent=4) + + +def pre_fill_table(): + rooms = scrape_rooms() + i_tunes_u_mappings = [Room(name=room['name'], number=room['room_number'], + building_name=room['building_name'], building_number=room['building_number']) for room in + rooms] + + try: + db.session.bulk_save_objects(i_tunes_u_mappings) + db.session.commit() + except IntegrityError as e: + db.session.rollback() diff --git a/recorder_adapters/__init__.py b/recorder_adapters/__init__.py index c297c35..769e8b3 100644 --- a/recorder_adapters/__init__.py +++ b/recorder_adapters/__init__.py @@ -110,6 +110,10 @@ def get_defined_recorder_adapters(): rec_model = {'id': f_p[1], 'name': f_p[1], 'commands': {}} if hasattr(rec_model_module, 'RECORDER_MODEL_NAME'): rec_model['name'] = rec_model_module.RECORDER_MODEL_NAME + if hasattr(rec_model_module, 'REQUIRES_USER'): + rec_model['requires_user'] = rec_model_module.REQUIRES_USER + if hasattr(rec_model_module, 'REQUIRES_PW'): + rec_model['requires_password'] = rec_model_module.REQUIRES_PW for name, obj in inspect.getmembers(rec_model_module, inspect.isclass): if issubclass(obj, RecorderAdapter): commands = {} diff --git a/recorder_adapters/extron_smp.py b/recorder_adapters/extron_smp.py index dd3bd95..1e44aab 100644 --- a/recorder_adapters/extron_smp.py +++ b/recorder_adapters/extron_smp.py @@ -2,6 +2,8 @@ from backend.recorder_adapters import telnetlib, TelnetAdapter, RecorderAdapter RECORDER_MODEL_NAME = "SMP 351 / 352" VERSION = "0.9.0" +REQUIRES_USER = False +REQUIRES_PW = True # HOST = "localhost" # HOST = "129.13.51.102" # Audimax SMP 351 @@ -13,9 +15,9 @@ PW = "123mzsmp" class SMP(TelnetAdapter, RecorderAdapter): - def __init__(self, address, admin_password): + def __init__(self, address, password, **kwargs): super().__init__(address) - self.admin_pw = admin_password + self.admin_pw = password def _login(self): self.tn = telnetlib.Telnet(HOST) diff --git a/tools/model_updater.py b/tools/model_updater.py index 2cc0fe6..e780f81 100644 --- a/tools/model_updater.py +++ b/tools/model_updater.py @@ -21,8 +21,9 @@ def calculate_md5_checksum(string_to_md5_sum: str): 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_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) @@ -51,7 +52,9 @@ def update_recorder_models_database(): 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) + r_m = RecorderModel(record_adapter_id=r_a["id"], model_name=r_a["name"], checksum=model_checksum, + requires_user=r_a.get('requires_user', None), + requires_password=r_a.get('requires_password', None)) db.session.add(r_m) db.session.flush() db.session.refresh(r_m) diff --git a/tools/scrape_rooms.py b/tools/scrape_rooms.py new file mode 100644 index 0000000..322dcc3 --- /dev/null +++ b/tools/scrape_rooms.py @@ -0,0 +1,60 @@ +from pprint import pprint + +import re +import requests +from bs4 import BeautifulSoup + + +def scrape_rooms(): + headers = { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'} + + room_url = "https://campus.kit.edu/live-stud/campus/all/roomgroup.asp?roomgroupcolumn1=H%F6r%2D%2FLehrsaal&tguid=0x1A35C3A1490748388EBEBA3943EFCDD5" + page = requests.get(room_url, headers=headers) + # soup = BeautifulSoup(page.content, 'html5lib') + soup = BeautifulSoup(page.content, 'html.parser') + + # pprint(page.content) + + # pprint(soup.prettify()) + + idx = 0 + + rooms = [] + + re_string = r"^(\d\d.\d\d)?\s(.*)" + re_exp = re.compile(re_string) + + for tr in soup.find_all('tr'): + idx += 1 + if idx == 1: # skip first row + continue + a_name = tr.find_all('a')[0].string + a_building = tr.find_all('a')[3].string + match = re_exp.match(a_name) + if match is not None: + building_number, name = re_exp.match(a_name).groups() + else: + name = a_name + building_number = None + + match = re_exp.match(a_building) + if match is not None: + building_number, building_name = re_exp.match(a_building).groups() + else: + building_name = a_name + building_number = None + + room = {'name': name, + 'room_number': tr.find_all('a')[1].string if tr.find_all('a')[0].string != "None" else tr.find_all('a')[ + 1].string, + 'building_name': building_name, + 'building_number': building_number} + + rooms.append(room) + + return rooms + + +if __name__ == '__main__': + scrape_rooms()