Extract changelog into global sqlalchemy event handler
parent
df88c56678
commit
0cf4531bbc
|
@ -5,41 +5,8 @@ from wtforms import TextAreaField
|
|||
from formity.models import FaceshieldRequest, RequestChange, Status, PostalCode, ExternalUser
|
||||
from spaceauth import current_user
|
||||
import enum
|
||||
import decimal
|
||||
from flask_weasyprint import HTML, render_pdf
|
||||
from sqlalchemy import inspect, 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
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
class IndexView(AdminSecurityMixin, flask_admin.AdminIndexView):
|
||||
|
@ -106,8 +73,13 @@ class FaceshieldRequestAdmin(ModelView):
|
|||
'id',
|
||||
'entity_info', 'full_name', 'phone_number', 'email', 'extra',
|
||||
'faceshield_front_required', 'faceshield_model',
|
||||
'faceshield_full_required',
|
||||
'faceshield_full_delivered', 'faceshield_front_delivered', 'handling_orga',
|
||||
'faceshield_front_delivered',
|
||||
'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',
|
||||
'shipping_name', 'shipping_street', 'shipping_postalcode', 'shipping_city',
|
||||
'shipping_latitude', 'shipping_longitude',
|
||||
|
@ -144,24 +116,6 @@ class FaceshieldRequestAdmin(ModelView):
|
|||
def can_export(self):
|
||||
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/')
|
||||
def label(self):
|
||||
return render_pdf(HTML(string=self.label_html()))
|
||||
|
@ -180,7 +134,6 @@ class FaceshieldRequestAdmin(ModelView):
|
|||
if req.status != status:
|
||||
req.status = status
|
||||
count += 1
|
||||
self.on_model_change(None, req, False)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ from datetime import datetime
|
|||
import enum
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
from jinja2 import Markup
|
||||
from formity.extensions import db
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
|
||||
from formity.utils import get_diff
|
||||
|
||||
class Status(enum.Enum):
|
||||
new = 1
|
||||
|
@ -124,3 +124,23 @@ class ExternalUser(db.Model):
|
|||
def check_password(self, password):
|
||||
salt, h = self.password.split(':')
|
||||
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,
|
||||
))
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import re
|
||||
import enum
|
||||
import decimal
|
||||
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
|
||||
|
||||
|
||||
|
@ -47,3 +51,35 @@ def text_to_id(text):
|
|||
text = re.sub('[ ]+', '_', text)
|
||||
text = re.sub('[^0-9a-zA-Z_-]', '', 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
|
||||
|
|
Loading…
Reference in New Issue