From 6be6064741a0def99b6e88e7f6aaeb5fd3b9c759 Mon Sep 17 00:00:00 2001 From: Benjamin Bertrand <benjamin.bertrand@esss.se> Date: Fri, 25 Aug 2017 11:56:34 +0200 Subject: [PATCH] Add items versioning with SQLAlchemy-Continuum --- app/models.py | 13 ++++++++++++ app/plugins.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ environment.yml | 2 ++ 3 files changed, 69 insertions(+) create mode 100644 app/plugins.py diff --git a/app/models.py b/app/models.py index e988370..8e74079 100644 --- a/app/models.py +++ b/app/models.py @@ -11,16 +11,20 @@ This module implements the models used in the app. """ import re import qrcode +import sqlalchemy as sa from sqlalchemy.orm import validates from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy_continuum import make_versioned from citext import CIText from flask import current_app from flask_login import UserMixin from .extensions import db, login_manager, ldap_manager, jwt +from .plugins import FlaskUserPlugin from . import utils ICS_ID_RE = re.compile('[A-Z]{3}[0-9]{3}') +make_versioned(plugins=[FlaskUserPlugin()]) @login_manager.user_loader @@ -183,6 +187,10 @@ class Status(QRCodeMixin, db.Model): class Item(db.Model): + __versioned__ = { + 'exclude': ['_created'] + } + id = db.Column(db.Integer, primary_key=True) _created = db.Column(db.DateTime, default=db.func.now()) _updated = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now()) @@ -231,3 +239,8 @@ class Item(db.Model): 'updated': utils.format_field(self._updated), 'created': utils.format_field(self._created), } + + +# call configure_mappers after defining all the models +# required by sqlalchemy_continuum +sa.orm.configure_mappers() diff --git a/app/plugins.py b/app/plugins.py new file mode 100644 index 0000000..d51cd96 --- /dev/null +++ b/app/plugins.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +""" +app.plugins +~~~~~~~~~~~ + +This module implements SQLAlchemy-Continuum plugins. + +FlaskUserPlugin offers way of integrating Flask framework with +SQLAlchemy-Continuum. FlaskUser-Plugin adds a `user_id` column for Transaction model. + +This plugin is based on the official FlaskPlugin with the following modifications: + - no remote_addr column + - the user_id is taken from flask_jwt_extended (API) or flask_login (web UI) + +The `user_id` column is automatically populated when the transaction object is created. + +:copyright: original (c) 2012, Konsta Vesterinen +:copyright: modified (c) 2017 European Spallation Source ERIC +:license: BSD 2-Clause, see LICENSE for more details. + +""" + +from flask.globals import _app_ctx_stack, _request_ctx_stack +from sqlalchemy_continuum.plugins import Plugin +from flask_login import current_user +from flask_jwt_extended import get_current_user + + +def fetch_current_user_id(): + # Return None if we are outside of request context. + if _app_ctx_stack.top is None or _request_ctx_stack.top is None: + return None + # Try to get the user from both flask_jwt_extended and flask_login + user = get_current_user() or current_user + try: + return user.id + except AttributeError: + return None + + +class FlaskUserPlugin(Plugin): + def __init__( + self, + current_user_id_factory=None, + ): + self.current_user_id_factory = ( + fetch_current_user_id if current_user_id_factory is None + else current_user_id_factory + ) + + def transaction_args(self, uow, session): + return { + 'user_id': self.current_user_id_factory(), + } diff --git a/environment.yml b/environment.yml index ff90d44..cd652c5 100644 --- a/environment.yml +++ b/environment.yml @@ -60,4 +60,6 @@ dependencies: - pyasn1==0.2.3 - pyjwt==1.4.2 - sqlalchemy-citext==1.3.post0 + - sqlalchemy-continuum==1.3.1 + - sqlalchemy-utils==0.32.14 - visitor==0.1.3 -- GitLab