now scraping rooms from capmus mgmt

This commit is contained in:
Tobias Kurze
2019-08-13 15:29:37 +02:00
parent 48505b76ea
commit f70cbdc463
17 changed files with 364 additions and 61 deletions

View File

@@ -23,6 +23,8 @@ flask-jwt-extended = "*"
ssh2-python = "*" ssh2-python = "*"
update = "*" update = "*"
flask-cors = "*" flask-cors = "*"
html5lib = "*"
beautifulsoup4 = "*"
[dev-packages] [dev-packages]

46
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "79518b33814e3afda968f02d6de17856076a2d7a81183eb7ee1c219eccc114cc" "sha256": "10fe25a82139020ea4ed6a7615b6f77965ebb4a96824b6f0d8989da081db892f"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -63,6 +63,15 @@
], ],
"version": "==1.10.1" "version": "==1.10.1"
}, },
"beautifulsoup4": {
"hashes": [
"sha256:05668158c7b85b791c5abde53e50265e16f98ad601c402ba44d70f96c4159612",
"sha256:25288c9e176f354bf277c0a10aa96c782a6a18a17122dba2e8cec4a97e03343b",
"sha256:f040590be10520f2ea4c2ae8c3dae441c7cfff5308ec9d58a0ec0c1b8f81d469"
],
"index": "pypi",
"version": "==4.8.0"
},
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
@@ -252,10 +261,10 @@
}, },
"flask-restplus": { "flask-restplus": {
"hashes": [ "hashes": [
"sha256:3fad697e1d91dfc13c078abcb86003f438a751c5a4ff41b84c9050199d2eab62", "sha256:a15d251923a8feb09a5d805c2f4d188555910a42c64d58f7dd281b8cac095f1b",
"sha256:cdc27b5be63f12968a7f762eaa355e68228b0c904b4c96040a314ba7dc6d0e69" "sha256:a66e442d0bca08f389fc3d07b4d808fc89961285d12fb8013f7cf15516fa9f5c"
], ],
"version": "==0.12.1" "version": "==0.13.0"
}, },
"flask-restplus-patched": { "flask-restplus-patched": {
"hashes": [ "hashes": [
@@ -292,6 +301,14 @@
], ],
"version": "==0.17.1" "version": "==0.17.1"
}, },
"html5lib": {
"hashes": [
"sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3",
"sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"
],
"index": "pypi",
"version": "==1.0.1"
},
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
@@ -361,10 +378,10 @@
}, },
"marshmallow": { "marshmallow": {
"hashes": [ "hashes": [
"sha256:9cedfc5b6f568d57e8a2cf3d293fbd81b05e5ef557854008d03e25660a39ccfd", "sha256:43bef4d33b7adb1f66eba8074095208b38dec96bed51f7a9bf2e687750f226d8",
"sha256:a4d99922116a76e5abd8f997ec0519086e24814b7e1e1344bebe2a312ba50235" "sha256:b240f5e14bc641c257f4b7bda3951d7e71963ebf66bd519078267f1f961cbd15"
], ],
"version": "==2.19.5" "version": "==2.20.1"
}, },
"oic": { "oic": {
"hashes": [ "hashes": [
@@ -396,7 +413,6 @@
}, },
"pycparser": { "pycparser": {
"hashes": [ "hashes": [
"sha256:50f84b9531cf541192a39303722ea4d69cab456fc1104ca47f66b9b8dd7be740",
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
], ],
"version": "==2.19" "version": "==2.19"
@@ -512,6 +528,13 @@
], ],
"version": "==1.12.0" "version": "==1.12.0"
}, },
"soupsieve": {
"hashes": [
"sha256:72b5f1aea9101cf720a36bb2327ede866fd6f1a07b1e87c92a1cc18113cbc946",
"sha256:e4e9c053d59795e440163733a7fec6c5972210e1790c507e4c7b051d6c5259de"
],
"version": "==1.9.2"
},
"sqlalchemy": { "sqlalchemy": {
"hashes": [ "hashes": [
"sha256:217e7fc52199a05851eee9b6a0883190743c4fb9c8ac4313ccfceaffd852b0ff" "sha256:217e7fc52199a05851eee9b6a0883190743c4fb9c8ac4313ccfceaffd852b0ff"
@@ -595,6 +618,13 @@
], ],
"version": "==5.4.0" "version": "==5.4.0"
}, },
"webencodings": {
"hashes": [
"sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
"sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
],
"version": "==0.5.1"
},
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4", "sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4",

View File

@@ -8,6 +8,8 @@ import ssl
from jinja2.exceptions import TemplateNotFound from jinja2.exceptions import TemplateNotFound
from backend import app, db from backend import app, db
from backend.models import pre_fill_table
from backend.tools.model_updater import update_recorder_models_database
def main(): def main():
@@ -28,8 +30,11 @@ def main():
except Exception as e: except Exception as e:
logging.CRITICAL(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__': if __name__ == '__main__':

View File

@@ -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_group = Namespace('group', description="Group management namespace", authorizations=api_authorizations)
api_room = Namespace('room', description="Room 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_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_user)
api_v1.add_namespace(api_group) api_v1.add_namespace(api_group)
api_v1.add_namespace(api_room) api_v1.add_namespace(api_room)
api_v1.add_namespace(api_recorder) 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') auth_api_bp = Blueprint('auth_api', __name__, url_prefix='/api/auth')
# user_api_bp = Blueprint('user_api', __name__, url_prefix='/api/user') # user_api_bp = Blueprint('user_api', __name__, url_prefix='/api/user')
@@ -45,7 +51,9 @@ from .user_api import *
from .group_api import * from .group_api import *
from .room_api import * from .room_api import *
from .recorder_api import * from .recorder_api import *
#from .group_api import *
# from .group_api import *
@api_bp.route('/<path:path>') @api_bp.route('/<path:path>')

View File

@@ -1,3 +1,4 @@
import datetime import datetime
import ipaddress import ipaddress
import json import json

View File

@@ -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. Login through API does not start a new session, but instead returns JWT.
""" """
import inspect from datetime import datetime
import pkgutil
from pprint import pprint from pprint import pprint
from flask_jwt_extended import jwt_required 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 import db, app
from backend.api import api_recorder from backend.api import api_recorder
@@ -21,17 +20,23 @@ import backend.recorder_adapters as r_a
recorder_model = api_recorder.model('Recorder', { recorder_model = api_recorder.model('Recorder', {
'id': fields.String(required=False, description='The recorder\'s identifier'), 'id': fields.String(required=False, description='The recorder\'s identifier'),
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'), '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'), 'name': fields.String(min_length=3, required=True, description='The recorder\'s name'),
'description': fields.String(required=False, description='The recorder\'s description'), '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'), 'ip': fields.String(required=False, description='The recorder\'s IP address'),
'network_name': fields.String(required=False, description='The recorder\'s network name'), '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'), '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'), '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, # 'use_telnet_instead_ssh': fields.Boolean(required=False, default=False,
description='If this is set, telnet will be used instead of ssh. ' # description='If this is set, telnet will be used instead of ssh. '
'This might require specific commands.'), # 'This might require specific commands.'),
'recorder_model': fields.Nested(api_recorder.model('recorder_model', '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, required=False,
allow_null=True, allow_null=True,
skip_none=False, skip_none=False,
@@ -39,30 +44,41 @@ recorder_model = api_recorder.model('Recorder', {
'room': fields.Nested(api_recorder.model('recorder_room', 'room': fields.Nested(api_recorder.model('recorder_room',
{'id': fields.Integer(), 'name': fields.String(), {'id': fields.Integer(), 'name': fields.String(),
'number': fields.String(), 'alternate_name': fields.String()}), 'number': fields.String(), 'alternate_name': fields.String()}),
required=False, r0equired=False,
allow_null=True, allow_null=True,
skip_none=False, 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', { recorder_command_model = api_recorder.model('Recorder Command', {
'id': fields.String(required=False, description='The recorder command\'s identifier'), 'id': fields.String(required=False, description='The recorder command\'s identifier'),
'name': fields.String(required=True, description='The recorder command\'s name'), '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), 'last_time_modified': fields.DateTime(required=False),
'description': fields.String(required=False, description='The recorder command\'s description'), 'description': fields.String(required=False, description='The recorder command\'s description'),
'parameters': fields.Raw(required=True, description='The recorder parameters'), 'parameters': fields.Raw(required=True, description='The recorder parameters'),
'recorder_model': fields.Nested(api_recorder.model('recorder_command_models', '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', { recorder_model_model = api_recorder.model('Recorder Model', {
'id': fields.String(required=False, description='The recorder model\'s identifier'), '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'), '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), 'last_time_modified': fields.DateTime(required=False),
'notes': fields.String(required=False, description='The recorder model\'s notes'), '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', '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(), 'network_name': fields.String(),
'ip': fields.String()})), required=False, 'ip': fields.String()})), required=False,
description='Model of the recorder.'), description='Model of the recorder.'),
@@ -98,12 +114,36 @@ class RecorderResource(Resource):
return '', 204 return '', 204
api_recorder.abort(404) 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 @jwt_required
@api_recorder.doc('update_recorder') @api_recorder.doc('update_recorder')
@api_recorder.expect(recorder_model) @api_recorder.expect(recorder_model)
def put(self, id): def put(self, id):
"""Update a recorder given its identifier""" """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: if num_rows_matched < 1:
api_recorder.abort(404) api_recorder.abort(404)
db.session.commit() db.session.commit()
@@ -205,21 +245,13 @@ class RecorderCommandResource(Resource):
return recorder_command return recorder_command
api_recorder.abort(404) api_recorder.abort(404)
@jwt_required recorder_command_model_parser = api_recorder.parser()
@api_recorder.doc('delete_recorder_command') recorder_command_model_parser.add_argument('description', type=str, required=False)
@api_recorder.response(204, 'Recorder_command deleted') recorder_command_model_parser.add_argument('alternative_name', type=str, required=False)
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)
@jwt_required @jwt_required
@api_recorder.doc('update_recorder_command') @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) @api_recorder.marshal_with(recorder_command_model)
def put(self, id): def put(self, id):
"""Update a recorder command given its identifier""" """Update a recorder command given its identifier"""
@@ -241,13 +273,3 @@ class RecorderCommandList(Resource):
:return: recorder commands :return: recorder commands
""" """
return RecorderCommand.get_all() 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

View File

@@ -21,6 +21,8 @@ room_model = api_room.model('Room', {
'alternate_name': fields.String(required=False, description='The room\'s alternate name'), 'alternate_name': fields.String(required=False, description='The room\'s alternate name'),
'comment': fields.String(required=False, description='The room\'s comment'), 'comment': fields.String(required=False, description='The room\'s comment'),
'number': fields.String(required=True, description='The room\'s number'), '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', 'recorder': fields.Nested(api_room.model('room_recorder',
{'id': fields.Integer(), 'name': fields.String(), {'id': fields.Integer(), 'name': fields.String(),
'ip': fields.String(), 'network_name': fields.String()}), 'ip': fields.String(), 'network_name': fields.String()}),

127
api/virtual_command_api.py Normal file
View File

@@ -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('/<int:id>')
@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

BIN
app.db

Binary file not shown.

View File

@@ -1,14 +1,17 @@
#!/usr/bin/env python #!/usr/bin/env python
import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir))
import os import os
import unittest import unittest
import coverage import coverage
from backend import app, db
from flask_script import Manager from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand from flask_migrate import Migrate, MigrateCommand
from backend import app, db
COV = coverage.coverage( COV = coverage.coverage(
branch=True, branch=True,
include='app/*', include='app/*',

View File

@@ -4,3 +4,6 @@ Import all models...
from backend.models.example_model import * from backend.models.example_model import *
from backend.models.user_model import * from backend.models.user_model import *
from backend.models.post_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 *

View File

@@ -6,6 +6,7 @@ import json
from sqlalchemy import MetaData from sqlalchemy import MetaData
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import validates
from backend import db, app, login_manager from backend import db, app, login_manager
import re import re
@@ -19,6 +20,7 @@ metadata = MetaData()
class RecorderModel(db.Model): class RecorderModel(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True) 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) last_time_modified = db.Column(db.DateTime, nullable=True, default=None)
record_adapter_id = db.Column(db.Unicode(63), unique=True, nullable=False) record_adapter_id = db.Column(db.Unicode(63), unique=True, nullable=False)
model_name = 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') recorder_commands = db.relationship('RecorderCommand', back_populates='recorder_model')
recorders = db.relationship('Recorder', back_populates='recorder_model') recorders = db.relationship('Recorder', back_populates='recorder_model')
checksum = db.Column(db.String(63), unique=True, nullable=False) checksum = db.Column(db.String(63), unique=True, nullable=False)
requires_user = db.Column(db.Boolean)
requires_password = db.Column(db.Boolean)
@staticmethod @staticmethod
def get_all(): def get_all():
@@ -49,13 +53,19 @@ class Recorder(db.Model):
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow()) created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
last_time_modified = 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) 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="") description = db.Column(db.Unicode(255), unique=False, nullable=True, default="")
room_id = db.Column(db.Integer, db.ForeignKey('room.id')) 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) 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) 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) 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) telnet_port = db.Column(db.Integer, unique=False, nullable=False, default=23)
ssh_port = db.Column(db.Integer, unique=False, nullable=False, default=22) 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_id = db.Column(db.Integer, db.ForeignKey('recorder_model.id'))
recorder_model = db.relationship('RecorderModel', back_populates='recorders') recorder_model = db.relationship('RecorderModel', back_populates='recorders')
virtual_commands = db.relationship('VirtualCommand', secondary=virtual_command_recorder_table, 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(): def get_all():
return Recorder.query.all() return Recorder.query.all()
@validates('name')
def validate_name(self, key, value):
assert len(value) > 2
return value
def __str__(self): def __str__(self):
return self.name return self.name
@@ -85,8 +100,11 @@ class Recorder(db.Model):
class RecorderCommand(db.Model): class RecorderCommand(db.Model):
"""Table containing permissions associated with groups.""" """Table containing permissions associated with groups."""
id = db.Column(db.Integer, autoincrement=True, primary_key=True) 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) last_time_modified = db.Column(db.DateTime, nullable=True, default=None)
name = db.Column(db.Unicode(63), unique=True, nullable=False) 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) description = db.Column(db.Unicode(511), nullable=True, default=None)
parameters_string = db.Column(db.String(2047), nullable=True) parameters_string = db.Column(db.String(2047), nullable=True)
recorder_model = db.relationship('RecorderModel', back_populates='recorder_commands') recorder_model = db.relationship('RecorderModel', back_populates='recorder_commands')

View File

@@ -5,13 +5,12 @@ Models for lecture recorder
import json import json
from sqlalchemy import MetaData, CheckConstraint from sqlalchemy import MetaData, CheckConstraint
from sqlalchemy.exc import IntegrityError
from datetime import datetime, timedelta
from backend import db, app, login_manager from backend import db, app, login_manager
import re from backend.tools.scrape_rooms import scrape_rooms
from sqlalchemy import or_
from datetime import datetime, timedelta
from backend.models.recorder_model import Recorder
metadata = MetaData() metadata = MetaData()
@@ -19,10 +18,12 @@ metadata = MetaData()
class Room(db.Model): class Room(db.Model):
id = db.Column(db.Integer, autoincrement=True, primary_key=True) id = db.Column(db.Integer, autoincrement=True, primary_key=True)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow()) created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow())
name = db.Column(db.Unicode(127), unique=True, nullable=False) name = db.Column(db.Unicode(127), unique=False, nullable=False)
alternate_name = db.Column(db.Unicode(127), unique=True, nullable=True, default=None) alternate_name = db.Column(db.Unicode(127), unique=False, nullable=True, default=None)
comment = db.Column(db.Unicode(2047), unique=False, nullable=True, default="") 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) 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): def __init__(self, **kwargs):
super(Room, self).__init__(**kwargs) super(Room, self).__init__(**kwargs)
@staticmethod @staticmethod
def get_by_name(name): def get_by_name(name):
""" """
@@ -47,7 +47,7 @@ class Room(db.Model):
@staticmethod @staticmethod
def get_all(): def get_all():
""" """
Return all groups Return all rooms
:return: :return:
""" """
return Room.query.all() return Room.query.all()
@@ -61,3 +61,16 @@ class Room(db.Model):
def toJSON(self): def toJSON(self):
return json.dumps(self.to_dict(), default=lambda o: o.__dict__, return json.dumps(self.to_dict(), default=lambda o: o.__dict__,
sort_keys=True, indent=4) 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()

View File

@@ -110,6 +110,10 @@ def get_defined_recorder_adapters():
rec_model = {'id': f_p[1], 'name': f_p[1], 'commands': {}} rec_model = {'id': f_p[1], 'name': f_p[1], 'commands': {}}
if hasattr(rec_model_module, 'RECORDER_MODEL_NAME'): if hasattr(rec_model_module, 'RECORDER_MODEL_NAME'):
rec_model['name'] = 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): for name, obj in inspect.getmembers(rec_model_module, inspect.isclass):
if issubclass(obj, RecorderAdapter): if issubclass(obj, RecorderAdapter):
commands = {} commands = {}

View File

@@ -2,6 +2,8 @@ from backend.recorder_adapters import telnetlib, TelnetAdapter, RecorderAdapter
RECORDER_MODEL_NAME = "SMP 351 / 352" RECORDER_MODEL_NAME = "SMP 351 / 352"
VERSION = "0.9.0" VERSION = "0.9.0"
REQUIRES_USER = False
REQUIRES_PW = True
# HOST = "localhost" # HOST = "localhost"
# HOST = "129.13.51.102" # Audimax SMP 351 # HOST = "129.13.51.102" # Audimax SMP 351
@@ -13,9 +15,9 @@ PW = "123mzsmp"
class SMP(TelnetAdapter, RecorderAdapter): class SMP(TelnetAdapter, RecorderAdapter):
def __init__(self, address, admin_password): def __init__(self, address, password, **kwargs):
super().__init__(address) super().__init__(address)
self.admin_pw = admin_password self.admin_pw = password
def _login(self): def _login(self):
self.tn = telnetlib.Telnet(HOST) self.tn = telnetlib.Telnet(HOST)

View File

@@ -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): 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())), existing_recorder_commands = RecorderCommand.query.filter(
RecorderCommand.recorder_model == recorder_model) and_(RecorderCommand.name.in_(command_definitions.keys())),
RecorderCommand.recorder_model == recorder_model)
existing_commands = set() existing_commands = set()
for existing_command in existing_recorder_commands: for existing_command in existing_recorder_commands:
existing_commands.add(existing_command.name) 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"]) r_m = RecorderModel.get_by_adapter_id(r_a["id"])
model_checksum = calculate_md5_checksum(dumps(r_a["commands"])) model_checksum = calculate_md5_checksum(dumps(r_a["commands"]))
if r_m is None: 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.add(r_m)
db.session.flush() db.session.flush()
db.session.refresh(r_m) db.session.refresh(r_m)

60
tools/scrape_rooms.py Normal file
View File

@@ -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()