Extract changelog into global sqlalchemy event handler

master
informatic 2020-04-16 23:26:47 +02:00
parent df88c56678
commit 0cf4531bbc
3 changed files with 68 additions and 59 deletions

View File

@ -5,41 +5,8 @@ from wtforms import TextAreaField
from formity.models import FaceshieldRequest, RequestChange, Status, PostalCode, ExternalUser from formity.models import FaceshieldRequest, RequestChange, Status, PostalCode, ExternalUser
from spaceauth import current_user from spaceauth import current_user
import enum import enum
import decimal
from flask_weasyprint import HTML, render_pdf from flask_weasyprint import HTML, render_pdf
from sqlalchemy import inspect, func from sqlalchemy import func
from sqlalchemy.orm import class_mapper
from sqlalchemy.orm.attributes import get_history
diff_sanitization_rules = [
(enum.Enum, lambda v: v.name),
(decimal.Decimal, float),
]
def get_diff(target, blacklist=['created', 'updated']):
state_before = {}
state_after = {}
inspr = inspect(target)
attrs = class_mapper(target.__class__).column_attrs
for attr in attrs:
if attr.key in blacklist:
continue
hist = getattr(inspr.attrs, attr.key).history
if hist.has_changes():
state_before[attr.key] = get_history(target, attr.key)[2].pop()
state_after[attr.key] = getattr(target, attr.key)
for data in [state_before, state_after]:
for t, c in diff_sanitization_rules:
if isinstance(data[attr.key], t):
data[attr.key] = c(data[attr.key])
if state_after[attr.key] == state_before[attr.key] or (state_after[attr.key] in ['', None] and state_before[attr.key] in ['', None]):
state_after.pop(attr.key)
state_before.pop(attr.key)
return state_before, state_after
class IndexView(AdminSecurityMixin, flask_admin.AdminIndexView): class IndexView(AdminSecurityMixin, flask_admin.AdminIndexView):
@ -106,8 +73,13 @@ class FaceshieldRequestAdmin(ModelView):
'id', 'id',
'entity_info', 'full_name', 'phone_number', 'email', 'extra', 'entity_info', 'full_name', 'phone_number', 'email', 'extra',
'faceshield_front_required', 'faceshield_model', 'faceshield_front_required', 'faceshield_model',
'faceshield_full_required', 'faceshield_front_delivered',
'faceshield_full_delivered', 'faceshield_front_delivered', 'handling_orga', 'faceshield_full_required', 'faceshield_full_delivered',
'adapter_3m_dar_required', 'adapter_3m_dar_delivered',
'adapter_easybreath_dar_required', 'adapter_easybreath_dar_delivered',
'adapter_rd40_dar_required', 'adapter_rd40_dar_delivered',
'adapter_secura_dar_required', 'adapter_secura_dar_delivered',
'handling_orga',
'created', 'ua', 'ip', 'status', 'remarks', 'created', 'ua', 'ip', 'status', 'remarks',
'shipping_name', 'shipping_street', 'shipping_postalcode', 'shipping_city', 'shipping_name', 'shipping_street', 'shipping_postalcode', 'shipping_city',
'shipping_latitude', 'shipping_longitude', 'shipping_latitude', 'shipping_longitude',
@ -144,24 +116,6 @@ class FaceshieldRequestAdmin(ModelView):
def can_export(self): def can_export(self):
return not current_user.external return not current_user.external
def on_model_change(self, form, model, is_created):
if is_created:
return
try:
before, after = get_diff(model)
except Exception:
# xD
before, after = {'diff': 'failed'}, {'diff': 'failed'}
if before or after:
db.session.add(RequestChange(
user_id=current_user.get_id() or 'nobody',
request_id=model.id,
state_before=before,
state_after=after,
))
@flask_admin.expose('/label/') @flask_admin.expose('/label/')
def label(self): def label(self):
return render_pdf(HTML(string=self.label_html())) return render_pdf(HTML(string=self.label_html()))
@ -180,7 +134,6 @@ class FaceshieldRequestAdmin(ModelView):
if req.status != status: if req.status != status:
req.status = status req.status = status
count += 1 count += 1
self.on_model_change(None, req, False)
db.session.commit() db.session.commit()

View File

@ -2,11 +2,11 @@ from datetime import datetime
import enum import enum
import hashlib import hashlib
import os import os
from jinja2 import Markup
from formity.extensions import db from formity.extensions import db
from sqlalchemy import event
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from formity.utils import get_diff
class Status(enum.Enum): class Status(enum.Enum):
new = 1 new = 1
@ -124,3 +124,23 @@ class ExternalUser(db.Model):
def check_password(self, password): def check_password(self, password):
salt, h = self.password.split(':') salt, h = self.password.split(':')
return self._hash(salt, password) == h return self._hash(salt, password) == h
@event.listens_for(FaceshieldRequest, 'after_update')
def on_request_change(mapper, connection, target):
try:
before, after = get_diff(target)
except Exception as exc:
# xD
before, after = {'diff': 'failed'}, {'diff': 'failed'}
if before or after:
from flask_login import current_user
user_id = current_user.get_id() if current_user and current_user.get_id() else 'nobody'
@event.listens_for(db.session, "after_flush", once=True)
def receive_after_flush(session, context):
db.session.add(RequestChange(
user_id=user_id,
request_id=target.id,
state_before=before,
state_after=after,
))

View File

@ -1,6 +1,10 @@
import re import re
import enum
import decimal
import unicodedata import unicodedata
from sqlalchemy.orm import exc from sqlalchemy import inspect
from sqlalchemy.orm import exc, class_mapper
from sqlalchemy.orm.attributes import get_history
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
@ -47,3 +51,35 @@ def text_to_id(text):
text = re.sub('[ ]+', '_', text) text = re.sub('[ ]+', '_', text)
text = re.sub('[^0-9a-zA-Z_-]', '', text) text = re.sub('[^0-9a-zA-Z_-]', '', text)
return text return text
diff_sanitization_rules = [
(enum.Enum, lambda v: v.name),
(decimal.Decimal, float),
]
def get_diff(target, blacklist=['created', 'updated']):
state_before = {}
state_after = {}
inspr = inspect(target)
attrs = class_mapper(target.__class__).column_attrs
for attr in attrs:
if attr.key in blacklist:
continue
hist = getattr(inspr.attrs, attr.key).history
if hist.has_changes():
state_before[attr.key] = get_history(target, attr.key)[2].pop()
state_after[attr.key] = getattr(target, attr.key)
for data in [state_before, state_after]:
for t, c in diff_sanitization_rules:
if isinstance(data[attr.key], t):
data[attr.key] = c(data[attr.key])
if state_after[attr.key] == state_before[attr.key] or (state_after[attr.key] in ['', None] and state_before[attr.key] in ['', None]):
state_after.pop(attr.key)
state_before.pop(attr.key)
return state_before, state_after