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 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()
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
))
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue