diff --git a/flaschengeist/app.py b/flaschengeist/app.py index 4aa8b88..f992005 100644 --- a/flaschengeist/app.py +++ b/flaschengeist/app.py @@ -9,6 +9,7 @@ from sqlalchemy.exc import OperationalError from werkzeug.exceptions import HTTPException from flaschengeist import logger +from flaschengeist.models import Plugin from flaschengeist.utils.hook import Hook from flaschengeist.plugins import AuthPlugin from flaschengeist.config import config, configure_app diff --git a/flaschengeist/cli/plugin_cmd.py b/flaschengeist/cli/plugin_cmd.py index 97bd1bc..d8b90c4 100644 --- a/flaschengeist/cli/plugin_cmd.py +++ b/flaschengeist/cli/plugin_cmd.py @@ -6,7 +6,7 @@ from importlib.metadata import EntryPoint, entry_points from flaschengeist.database import db from flaschengeist.config import config -from flaschengeist.models.user import Permission +from flaschengeist.models import Permission @click.group() diff --git a/flaschengeist/controller/imageController.py b/flaschengeist/controller/imageController.py index 26bfd2d..3915bce 100644 --- a/flaschengeist/controller/imageController.py +++ b/flaschengeist/controller/imageController.py @@ -1,15 +1,14 @@ from datetime import date -from flask import send_file from pathlib import Path +from flask import send_file from PIL import Image as PImage - -from werkzeug.exceptions import NotFound, UnprocessableEntity -from werkzeug.datastructures import FileStorage from werkzeug.utils import secure_filename +from werkzeug.datastructures import FileStorage +from werkzeug.exceptions import NotFound, UnprocessableEntity -from flaschengeist.models.image import Image -from flaschengeist.database import db -from flaschengeist.config import config +from ..models import Image +from ..database import db +from ..config import config def check_mimetype(mime: str): diff --git a/flaschengeist/controller/messageController.py b/flaschengeist/controller/messageController.py index f43afc8..573a149 100644 --- a/flaschengeist/controller/messageController.py +++ b/flaschengeist/controller/messageController.py @@ -1,5 +1,5 @@ from flaschengeist.utils.hook import Hook -from flaschengeist.models.user import User, Role +from flaschengeist.models import User, Role class Message: diff --git a/flaschengeist/controller/pluginController.py b/flaschengeist/controller/pluginController.py index 7dbe678..8313935 100644 --- a/flaschengeist/controller/pluginController.py +++ b/flaschengeist/controller/pluginController.py @@ -4,9 +4,16 @@ Used by plugins for setting and notification functionality. """ import sqlalchemy + +from typing import Union +from flask import current_app +from werkzeug.exceptions import NotFound +from importlib.metadata import entry_points + +from .. import logger from ..database import db -from ..models.setting import _PluginSetting -from ..models.notification import Notification +from ..utils import Hook +from ..models import Plugin, PluginSetting, Notification def get_setting(plugin_id: str, name: str, **kwargs): @@ -23,7 +30,7 @@ def get_setting(plugin_id: str, name: str, **kwargs): """ try: setting = ( - _PluginSetting.query.filter(_PluginSetting.plugin == plugin_id).filter(_PluginSetting.name == name).one() + PluginSetting.query.filter(PluginSetting.plugin == plugin_id).filter(PluginSetting.name == name).one() ) return setting.value except sqlalchemy.orm.exc.NoResultFound: @@ -42,8 +49,8 @@ def set_setting(plugin_id: str, name: str, value): value: Value to be stored """ setting = ( - _PluginSetting.query.filter(_PluginSetting.plugin == plugin_id) - .filter(_PluginSetting.name == name) + PluginSetting.query.filter(PluginSetting.plugin == plugin_id) + .filter(PluginSetting.name == name) .one_or_none() ) if setting is not None: @@ -52,7 +59,7 @@ def set_setting(plugin_id: str, name: str, value): else: setting.value = value else: - db.session.add(_PluginSetting(plugin=plugin_id, name=name, value=value)) + db.session.add(PluginSetting(plugin=plugin_id, name=name, value=value)) db.session.commit() diff --git a/flaschengeist/controller/roleController.py b/flaschengeist/controller/roleController.py index 23528ad..eedb7c7 100644 --- a/flaschengeist/controller/roleController.py +++ b/flaschengeist/controller/roleController.py @@ -2,10 +2,10 @@ from typing import Union from sqlalchemy.exc import IntegrityError from werkzeug.exceptions import BadRequest, Conflict, NotFound -from flaschengeist import logger -from flaschengeist.models.user import Role, Permission -from flaschengeist.database import db, case_sensitive -from flaschengeist.utils.hook import Hook +from .. import logger +from ..models import Role, Permission +from ..database import db, case_sensitive +from ..utils.hook import Hook def get_all(): diff --git a/flaschengeist/controller/sessionController.py b/flaschengeist/controller/sessionController.py index 4cae005..5d5ceae 100644 --- a/flaschengeist/controller/sessionController.py +++ b/flaschengeist/controller/sessionController.py @@ -1,9 +1,12 @@ import secrets -from flaschengeist.models.session import Session -from flaschengeist.database import db -from flaschengeist import logger -from werkzeug.exceptions import Forbidden, Unauthorized + from datetime import datetime, timezone +from werkzeug.exceptions import Forbidden, Unauthorized + +from .. import logger +from ..models import Session +from ..database import db + lifetime = 1800 diff --git a/flaschengeist/controller/userController.py b/flaschengeist/controller/userController.py index bd6e4b8..610db39 100644 --- a/flaschengeist/controller/userController.py +++ b/flaschengeist/controller/userController.py @@ -1,5 +1,6 @@ -import secrets import re +import secrets + from io import BytesIO from sqlalchemy import exc from flask import current_app @@ -7,15 +8,15 @@ from datetime import datetime, timedelta, timezone from flask.helpers import send_file from werkzeug.exceptions import NotFound, BadRequest, Forbidden -from flaschengeist import logger -from flaschengeist.config import config -from flaschengeist.database import db -from flaschengeist.models.notification import Notification -from flaschengeist.utils.hook import Hook -from flaschengeist.utils.datetime import from_iso_format -from flaschengeist.utils.foreign_keys import merge_references -from flaschengeist.models.user import User, Role, _PasswordReset -from flaschengeist.controller import imageController, messageController, sessionController +from .. import logger +from ..config import config +from ..database import db +from ..models import Notification, User, Role +from ..models.user import _PasswordReset +from ..utils.hook import Hook +from ..utils.datetime import from_iso_format +from ..utils.foreign_keys import merge_references +from ..controller import imageController, messageController, sessionController def __active_users(): diff --git a/flaschengeist/database.py b/flaschengeist/database/__init__.py similarity index 100% rename from flaschengeist/database.py rename to flaschengeist/database/__init__.py diff --git a/flaschengeist/database/types.py b/flaschengeist/database/types.py new file mode 100644 index 0000000..369bb30 --- /dev/null +++ b/flaschengeist/database/types.py @@ -0,0 +1,95 @@ +import sys +import datetime + +from sqlalchemy import BigInteger, util +from sqlalchemy.dialects import mysql, sqlite +from sqlalchemy.types import DateTime, TypeDecorator + + +class ModelSerializeMixin: + """Mixin class used for models to serialize them automatically + Ignores private and protected members as well as members marked as not to publish (name ends with _) + """ + + def __is_optional(self, param): + if sys.version_info < (3, 8): + return False + + import typing + + hint = typing.get_type_hints(self.__class__)[param] + if ( + typing.get_origin(hint) is typing.Union + and len(typing.get_args(hint)) == 2 + and typing.get_args(hint)[1] is type(None) + ): + return getattr(self, param) is None + + def serialize(self): + """Serialize class to dict + Returns: + Dict of all not private or protected annotated member variables. + """ + d = { + param: getattr(self, param) + for param in self.__class__.__annotations__ + if not param.startswith("_") and not param.endswith("_") and not self.__is_optional(param) + } + if len(d) == 1: + key, value = d.popitem() + return value + return d + + def __str__(self) -> str: + return self.serialize().__str__() + + +class Serial(TypeDecorator): + """Same as MariaDB Serial used for IDs""" + + cache_ok = True + impl = BigInteger().with_variant(mysql.BIGINT(unsigned=True), "mysql").with_variant(sqlite.INTEGER(), "sqlite") + + # https://alembic.sqlalchemy.org/en/latest/autogenerate.html?highlight=custom%20column#affecting-the-rendering-of-types-themselves + def __repr__(self) -> str: + return util.generic_repr(self) + + +class UtcDateTime(TypeDecorator): + """Almost equivalent to `sqlalchemy.types.DateTime` with + ``timezone=True`` option, but it differs from that by: + + - Never silently take naive :class:`datetime.datetime`, instead it + always raise :exc:`ValueError` unless time zone aware value. + - :class:`datetime.datetime` value's :attr:`datetime.datetime.tzinfo` + is always converted to UTC. + - Unlike SQLAlchemy's built-in :class:`sqlalchemy.types.DateTime`, + it never return naive :class:`datetime.datetime`, but time zone + aware value, even with SQLite or MySQL. + """ + + cache_ok = True + impl = DateTime(timezone=True) + + @staticmethod + def current_utc(): + return datetime.datetime.now(tz=datetime.timezone.utc) + + def process_bind_param(self, value, dialect): + if value is not None: + if not isinstance(value, datetime.datetime): + raise TypeError("expected datetime.datetime, not " + repr(value)) + elif value.tzinfo is None: + raise ValueError("naive datetime is disallowed") + return value.astimezone(datetime.timezone.utc) + + def process_result_value(self, value, dialect): + if value is not None: + if value.tzinfo is not None: + value = value.astimezone(datetime.timezone.utc) + value = value.replace(tzinfo=datetime.timezone.utc) + return value + + # https://alembic.sqlalchemy.org/en/latest/autogenerate.html?highlight=custom%20column#affecting-the-rendering-of-types-themselves + def __repr__(self) -> str: + return util.generic_repr(self) diff --git a/flaschengeist/models/__init__.py b/flaschengeist/models/__init__.py index 369bb30..99a5e8d 100644 --- a/flaschengeist/models/__init__.py +++ b/flaschengeist/models/__init__.py @@ -1,95 +1,5 @@ -import sys -import datetime - -from sqlalchemy import BigInteger, util -from sqlalchemy.dialects import mysql, sqlite -from sqlalchemy.types import DateTime, TypeDecorator - - -class ModelSerializeMixin: - """Mixin class used for models to serialize them automatically - Ignores private and protected members as well as members marked as not to publish (name ends with _) - """ - - def __is_optional(self, param): - if sys.version_info < (3, 8): - return False - - import typing - - hint = typing.get_type_hints(self.__class__)[param] - if ( - typing.get_origin(hint) is typing.Union - and len(typing.get_args(hint)) == 2 - and typing.get_args(hint)[1] is type(None) - ): - return getattr(self, param) is None - - def serialize(self): - """Serialize class to dict - Returns: - Dict of all not private or protected annotated member variables. - """ - d = { - param: getattr(self, param) - for param in self.__class__.__annotations__ - if not param.startswith("_") and not param.endswith("_") and not self.__is_optional(param) - } - if len(d) == 1: - key, value = d.popitem() - return value - return d - - def __str__(self) -> str: - return self.serialize().__str__() - - -class Serial(TypeDecorator): - """Same as MariaDB Serial used for IDs""" - - cache_ok = True - impl = BigInteger().with_variant(mysql.BIGINT(unsigned=True), "mysql").with_variant(sqlite.INTEGER(), "sqlite") - - # https://alembic.sqlalchemy.org/en/latest/autogenerate.html?highlight=custom%20column#affecting-the-rendering-of-types-themselves - def __repr__(self) -> str: - return util.generic_repr(self) - - -class UtcDateTime(TypeDecorator): - """Almost equivalent to `sqlalchemy.types.DateTime` with - ``timezone=True`` option, but it differs from that by: - - - Never silently take naive :class:`datetime.datetime`, instead it - always raise :exc:`ValueError` unless time zone aware value. - - :class:`datetime.datetime` value's :attr:`datetime.datetime.tzinfo` - is always converted to UTC. - - Unlike SQLAlchemy's built-in :class:`sqlalchemy.types.DateTime`, - it never return naive :class:`datetime.datetime`, but time zone - aware value, even with SQLite or MySQL. - """ - - cache_ok = True - impl = DateTime(timezone=True) - - @staticmethod - def current_utc(): - return datetime.datetime.now(tz=datetime.timezone.utc) - - def process_bind_param(self, value, dialect): - if value is not None: - if not isinstance(value, datetime.datetime): - raise TypeError("expected datetime.datetime, not " + repr(value)) - elif value.tzinfo is None: - raise ValueError("naive datetime is disallowed") - return value.astimezone(datetime.timezone.utc) - - def process_result_value(self, value, dialect): - if value is not None: - if value.tzinfo is not None: - value = value.astimezone(datetime.timezone.utc) - value = value.replace(tzinfo=datetime.timezone.utc) - return value - - # https://alembic.sqlalchemy.org/en/latest/autogenerate.html?highlight=custom%20column#affecting-the-rendering-of-types-themselves - def __repr__(self) -> str: - return util.generic_repr(self) +from .session import * +from .user import * +from .plugin import * +from .notification import * +from .image import * \ No newline at end of file diff --git a/flaschengeist/models/image.py b/flaschengeist/models/image.py index d87af8a..406fefe 100644 --- a/flaschengeist/models/image.py +++ b/flaschengeist/models/image.py @@ -1,8 +1,8 @@ from sqlalchemy import event from pathlib import Path -from . import ModelSerializeMixin, Serial from ..database import db +from ..database.types import ModelSerializeMixin, Serial class Image(db.Model, ModelSerializeMixin): diff --git a/flaschengeist/models/notification.py b/flaschengeist/models/notification.py index 07320c7..a25efd4 100644 --- a/flaschengeist/models/notification.py +++ b/flaschengeist/models/notification.py @@ -1,9 +1,8 @@ from datetime import datetime from typing import Any -from . import Serial, UtcDateTime, ModelSerializeMixin from ..database import db -from .user import User +from ..database.types import Serial, UtcDateTime, ModelSerializeMixin class Notification(db.Model, ModelSerializeMixin): diff --git a/flaschengeist/models/plugin.py b/flaschengeist/models/plugin.py new file mode 100644 index 0000000..d2af46c --- /dev/null +++ b/flaschengeist/models/plugin.py @@ -0,0 +1,24 @@ +from typing import Any + +from ..database import db +from ..database.types import Serial + + +class Plugin(db.Model): + __tablename__ = "plugin" + id: int = db.Column("id", Serial, primary_key=True) + name: str = db.Column(db.String(127), nullable=False) + version: str = db.Column(db.String(30), nullable=False) + """The latest installed version""" + enabled: bool = db.Column(db.Boolean, default=False) + + settings_ = db.relationship("PluginSetting", cascade="all, delete") + permissions_ = db.relationship("Permission", cascade="all, delete") + + +class PluginSetting(db.Model): + __tablename__ = "plugin_setting" + id = db.Column("id", Serial, primary_key=True) + plugin_id: int = db.Column("plugin", Serial, db.ForeignKey("plugin.id")) + name: str = db.Column(db.String(127), nullable=False) + value: Any = db.Column(db.PickleType(protocol=4)) diff --git a/flaschengeist/models/session.py b/flaschengeist/models/session.py index 7dc6df8..dea7c62 100644 --- a/flaschengeist/models/session.py +++ b/flaschengeist/models/session.py @@ -1,5 +1,9 @@ from datetime import datetime, timedelta, timezone from secrets import compare_digest + +from ..database import db +from ..database.types import ModelSerializeMixin, UtcDateTime, Serial + from flaschengeist import logger @@ -22,7 +26,7 @@ class Session(db.Model, ModelSerializeMixin): _id = db.Column("id", Serial, primary_key=True) _user_id = db.Column("user_id", Serial, db.ForeignKey("user.id")) - user_: User = db.relationship("User", back_populates="sessions_") + user_: "User" = db.relationship("User", back_populates="sessions_") @property def userid(self): diff --git a/flaschengeist/models/setting.py b/flaschengeist/models/setting.py deleted file mode 100644 index b090c3e..0000000 --- a/flaschengeist/models/setting.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import annotations # TODO: Remove if python requirement is >= 3.10 -from typing import Any - -from . import Serial -from ..database import db - - -class _PluginSetting(db.Model): - __tablename__ = "plugin_setting" - id = db.Column("id", Serial, primary_key=True) - plugin: str = db.Column(db.String(127)) - name: str = db.Column(db.String(127), nullable=False) - value: Any = db.Column(db.PickleType(protocol=4)) diff --git a/flaschengeist/models/user.py b/flaschengeist/models/user.py index 2889eeb..c468ebf 100644 --- a/flaschengeist/models/user.py +++ b/flaschengeist/models/user.py @@ -3,9 +3,7 @@ from datetime import date, datetime from sqlalchemy.orm.collections import attribute_mapped_collection from ..database import db -from . import ModelSerializeMixin, UtcDateTime, Serial -from .image import Image - +from ..database.types import ModelSerializeMixin, UtcDateTime, Serial association_table = db.Table( "user_x_role", @@ -19,7 +17,6 @@ role_permission_association_table = db.Table( db.Column("permission_id", Serial, db.ForeignKey("permission.id")), ) - class Permission(db.Model, ModelSerializeMixin): __tablename__ = "permission" name: str = db.Column(db.String(30), unique=True) @@ -66,7 +63,7 @@ class User(db.Model, ModelSerializeMixin): sessions_: list["Session"] = db.relationship( "Session", back_populates="user_", cascade="all, delete, delete-orphan" ) - avatar_: Optional[Image] = db.relationship("Image", cascade="all, delete, delete-orphan", single_parent=True) + avatar_: Optional["Image"] = db.relationship("Image", cascade="all, delete, delete-orphan", single_parent=True) reset_requests_: list["_PasswordReset"] = db.relationship("_PasswordReset", cascade="all, delete, delete-orphan") # Private stuff for internal use diff --git a/flaschengeist/plugins/__init__.py b/flaschengeist/plugins/__init__.py index fadecff..76a35e3 100644 --- a/flaschengeist/plugins/__init__.py +++ b/flaschengeist/plugins/__init__.py @@ -9,7 +9,8 @@ from importlib.metadata import Distribution, EntryPoint from werkzeug.exceptions import MethodNotAllowed, NotFound from werkzeug.datastructures import FileStorage -from flaschengeist.models.user import _Avatar, User +from flaschengeist.models import User +from flaschengeist.models.user import _Avatar from flaschengeist.utils.hook import HookBefore, HookAfter __all__ = [ @@ -19,7 +20,7 @@ __all__ = [ "before_role_updated", "before_update_user", "after_role_updated", - "Plugin", + "BasePlugin", "AuthPlugin", ] @@ -70,7 +71,7 @@ Passed args: """ -class Plugin: +class BasePlugin: """Base class for all Plugins All plugins must derived from this class. @@ -193,10 +194,10 @@ class Plugin: return {"version": self.version, "permissions": self.permissions} -class AuthPlugin(Plugin): +class AuthPlugin(BasePlugin): """Base class for all authentification plugins - See also `Plugin` + See also `BasePlugin` """ def login(self, user, pw): diff --git a/flaschengeist/plugins/auth/__init__.py b/flaschengeist/plugins/auth/__init__.py index afcc854..439b2a6 100644 --- a/flaschengeist/plugins/auth/__init__.py +++ b/flaschengeist/plugins/auth/__init__.py @@ -6,13 +6,13 @@ from flask import Blueprint, request, jsonify from werkzeug.exceptions import Forbidden, BadRequest, Unauthorized from flaschengeist import logger -from flaschengeist.plugins import Plugin +from flaschengeist.plugins import BasePlugin from flaschengeist.utils.HTTP import no_content, created from flaschengeist.utils.decorators import login_required from flaschengeist.controller import sessionController, userController -class AuthRoutePlugin(Plugin): +class AuthRoutePlugin(BasePlugin): blueprint = Blueprint("auth", __name__) diff --git a/flaschengeist/plugins/auth_ldap/__init__.py b/flaschengeist/plugins/auth_ldap/__init__.py index ef8ecb1..bf2fb52 100644 --- a/flaschengeist/plugins/auth_ldap/__init__.py +++ b/flaschengeist/plugins/auth_ldap/__init__.py @@ -12,7 +12,8 @@ from werkzeug.datastructures import FileStorage from flaschengeist import logger from flaschengeist.controller import userController -from flaschengeist.models.user import User, Role, _Avatar +from flaschengeist.models import User, Role +from flaschengeist.models.user import _Avatar from flaschengeist.plugins import AuthPlugin, before_role_updated diff --git a/flaschengeist/plugins/auth_plain/__init__.py b/flaschengeist/plugins/auth_plain/__init__.py index 44c27f7..50ab4af 100644 --- a/flaschengeist/plugins/auth_plain/__init__.py +++ b/flaschengeist/plugins/auth_plain/__init__.py @@ -8,7 +8,7 @@ import hashlib import binascii from werkzeug.exceptions import BadRequest from flaschengeist.plugins import AuthPlugin, plugins_installed -from flaschengeist.models.user import User, Role, Permission +from flaschengeist.models import User, Role, Permission from flaschengeist.database import db from flaschengeist import logger diff --git a/flaschengeist/plugins/message_mail.py b/flaschengeist/plugins/message_mail.py index 2fe6a76..59d82d1 100644 --- a/flaschengeist/plugins/message_mail.py +++ b/flaschengeist/plugins/message_mail.py @@ -3,15 +3,14 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from flaschengeist import logger -from flaschengeist.models.user import User +from flaschengeist.models import User +from flaschengeist.plugins import BasePlugin from flaschengeist.utils.hook import HookAfter from flaschengeist.controller import userController from flaschengeist.controller.messageController import Message -from . import Plugin - -class MailMessagePlugin(Plugin): +class MailMessagePlugin(BasePlugin): def __init__(self, entry_point, config): super().__init__(entry_point, config) self.server = config["SERVER"] diff --git a/flaschengeist/plugins/pricelist/models.py b/flaschengeist/plugins/pricelist/models.py index 543fee0..1d8dc23 100644 --- a/flaschengeist/plugins/pricelist/models.py +++ b/flaschengeist/plugins/pricelist/models.py @@ -1,6 +1,6 @@ from flaschengeist.database import db -from flaschengeist.models import ModelSerializeMixin, Serial -from flaschengeist.models.image import Image +from flaschengeist.database.types import ModelSerializeMixin, Serial +from flaschengeist.models import Image from typing import Optional diff --git a/flaschengeist/plugins/roles/__init__.py b/flaschengeist/plugins/roles/__init__.py index 4e3c92b..cd2fae4 100644 --- a/flaschengeist/plugins/roles/__init__.py +++ b/flaschengeist/plugins/roles/__init__.py @@ -5,17 +5,16 @@ Provides routes used to configure roles and permissions of users / roles. from werkzeug.exceptions import BadRequest from flask import Blueprint, request, jsonify -from http.client import NO_CONTENT -from flaschengeist.plugins import Plugin -from flaschengeist.utils.decorators import login_required +from flaschengeist.plugins import BasePlugin from flaschengeist.controller import roleController from flaschengeist.utils.HTTP import created, no_content +from flaschengeist.utils.decorators import login_required from . import permissions -class RolesPlugin(Plugin): +class RolesPlugin(BasePlugin): blueprint = Blueprint("roles", __name__) permissions = permissions.permissions diff --git a/flaschengeist/plugins/scheduler.py b/flaschengeist/plugins/scheduler.py index 7d15b69..a5e6eef 100644 --- a/flaschengeist/plugins/scheduler.py +++ b/flaschengeist/plugins/scheduler.py @@ -2,10 +2,9 @@ from flask import Blueprint from datetime import datetime, timedelta from flaschengeist import logger +from flaschengeist.plugins import BasePlugin from flaschengeist.utils.HTTP import no_content -from . import Plugin - class __Task: def __init__(self, function, **kwags): @@ -38,7 +37,7 @@ def scheduled(id: str, replace=False, **kwargs): return real_decorator -class SchedulerPlugin(Plugin): +class SchedulerPlugin(BasePlugin): def __init__(self, entry_point, config=None): super().__init__(entry_point, config) self.blueprint = Blueprint(self.name, __name__) diff --git a/flaschengeist/plugins/users/__init__.py b/flaschengeist/plugins/users/__init__.py index 778c819..3cd44df 100644 --- a/flaschengeist/plugins/users/__init__.py +++ b/flaschengeist/plugins/users/__init__.py @@ -2,14 +2,14 @@ Provides routes used to manage users """ -from http.client import NO_CONTENT, CREATED +from http.client import CREATED from flask import Blueprint, request, jsonify, make_response -from werkzeug.exceptions import BadRequest, Forbidden, MethodNotAllowed, NotFound +from werkzeug.exceptions import BadRequest, Forbidden, MethodNotAllowed from . import permissions from flaschengeist import logger from flaschengeist.config import config -from flaschengeist.plugins import Plugin +from flaschengeist.plugins import BasePlugin from flaschengeist.models.user import User from flaschengeist.utils.decorators import login_required, extract_session, headers from flaschengeist.controller import userController @@ -17,7 +17,7 @@ from flaschengeist.utils.HTTP import created, no_content from flaschengeist.utils.datetime import from_iso_format -class UsersPlugin(Plugin): +class UsersPlugin(BasePlugin): blueprint = Blueprint("users", __name__) permissions = permissions.permissions