covid-formity/formity/admin.py

249 lines
11 KiB
Python
Raw Normal View History

2020-04-08 18:09:24 +00:00
from flask import redirect, flash, request, url_for
2020-03-29 18:34:18 +00:00
import flask_admin
from formity.extensions import admin, db, ModelView, ModelViewHighSecurity, AdminSecurityMixin
from wtforms import TextAreaField
from formity.models import FaceshieldRequest, RequestChange, Status, PostalCode, ExternalUser
2020-03-27 18:04:29 +00:00
from spaceauth import current_user
import enum
2020-04-07 18:04:46 +00:00
import decimal
2020-04-08 18:09:24 +00:00
from flask_weasyprint import HTML, render_pdf
2020-03-29 18:34:18 +00:00
from sqlalchemy import inspect, func
2020-03-27 18:04:29 +00:00
from sqlalchemy.orm import class_mapper
from sqlalchemy.orm.attributes import get_history
2020-04-13 13:15:45 +00:00
diff_sanitization_rules = [
(enum.Enum, lambda v: v.name),
(decimal.Decimal, float),
]
2020-03-27 18:04:29 +00:00
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)
2020-04-13 13:15:45 +00:00
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])
2020-03-27 18:04:29 +00:00
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
2020-03-26 09:09:44 +00:00
2020-03-29 18:34:18 +00:00
class IndexView(AdminSecurityMixin, flask_admin.AdminIndexView):
@flask_admin.expose('/')
def index(self):
stats = FaceshieldRequest.query.with_entities(
FaceshieldRequest.status.label('status'),
FaceshieldRequest.handling_orga.label('handling_orga'),
2020-03-29 18:34:18 +00:00
func.count().label('count'),
func.sum(FaceshieldRequest.faceshield_full_required).label('faceshield_full_required'),
func.sum(FaceshieldRequest.faceshield_full_delivered).label('faceshield_full_delivered'),
func.sum(FaceshieldRequest.faceshield_front_required).label('faceshield_front_required'),
func.sum(FaceshieldRequest.faceshield_front_delivered).label('faceshield_front_delivered'),
).filter(FaceshieldRequest.status != Status.rejected, FaceshieldRequest.status != Status.spam).group_by(FaceshieldRequest.status, FaceshieldRequest.handling_orga).order_by(FaceshieldRequest.status, FaceshieldRequest.handling_orga).all()
2020-03-29 18:34:18 +00:00
2020-03-30 21:31:21 +00:00
vstats = FaceshieldRequest.query.select_from(FaceshieldRequest).with_entities(
func.coalesce(PostalCode.voivodeship, 'unknown').label('voivodeship'),
2020-03-30 21:31:21 +00:00
func.count().label('count'),
func.sum(FaceshieldRequest.faceshield_full_required).label('faceshield_full_required'),
func.sum(FaceshieldRequest.faceshield_full_delivered).label('faceshield_full_delivered'),
func.sum(FaceshieldRequest.faceshield_front_required).label('faceshield_front_required'),
func.sum(FaceshieldRequest.faceshield_front_delivered).label('faceshield_front_delivered'),
).filter(
FaceshieldRequest.status != Status.rejected,
FaceshieldRequest.status != Status.spam,
).join(FaceshieldRequest.postalcode_info, isouter=True).group_by('voivodeship').order_by(func.count().desc()).all()
2020-03-29 18:34:18 +00:00
2020-03-30 21:36:35 +00:00
dstats = FaceshieldRequest.query.select_from(FaceshieldRequest).with_entities(
func.date_trunc('day', FaceshieldRequest.created).label('date'),
func.count().label('count'),
2020-03-31 14:10:02 +00:00
).filter(FaceshieldRequest.status != Status.rejected, FaceshieldRequest.status != Status.spam).group_by(func.date_trunc('day', FaceshieldRequest.created)).order_by(func.date_trunc('day', FaceshieldRequest.created)).all()
2020-03-30 21:36:35 +00:00
return self.render('admin_index.html', stats=stats, vstats=vstats, dstats=dstats)
2020-03-29 18:34:18 +00:00
2020-04-07 18:59:51 +00:00
class MapView(AdminSecurityMixin, flask_admin.BaseView):
@flask_admin.expose('/')
def index(self):
mapdata = [
{
key: getattr(request, key).name if isinstance(getattr(request, key), enum.Enum) else getattr(request, key)
2020-04-07 19:04:23 +00:00
for key in ['id', 'entity_info', 'shipping_latitude', 'shipping_longitude', 'status', 'handling_orga']
2020-04-07 18:59:51 +00:00
}
2020-04-07 19:04:23 +00:00
for request in FaceshieldRequest.query.filter(FaceshieldRequest.shipping_latitude != None, FaceshieldRequest.status != Status.spam, FaceshieldRequest.status != Status.rejected)
2020-04-07 18:59:51 +00:00
]
2020-04-08 16:25:55 +00:00
return self.render('admin_map.html', mapdata=mapdata, focus=request.args.get('id', None))
2020-04-07 18:59:51 +00:00
2020-03-27 16:51:10 +00:00
class FaceshieldRequestAdmin(ModelView):
2020-03-29 12:37:42 +00:00
column_default_sort = 'created'
2020-03-27 18:04:29 +00:00
details_modal_template = 'changelog_details_modal.html'
edit_template = 'changelog_edit.html'
2020-04-08 16:25:55 +00:00
list_template = 'faceshieldrequest_list.html'
2020-03-27 18:04:29 +00:00
2020-04-07 16:25:30 +00:00
column_searchable_list = ('id', 'email', 'remarks', 'extra', 'entity_info', 'full_name')
2020-03-27 16:51:10 +00:00
column_filters = (
2020-04-07 16:25:30 +00:00
'id',
2020-03-27 16:51:10 +00:00
'entity_info', 'full_name', 'phone_number', 'email', 'extra',
'faceshield_front_required', 'faceshield_model',
'faceshield_full_required',
'faceshield_full_delivered', 'faceshield_front_delivered', 'handling_orga',
2020-03-27 18:04:29 +00:00
'created', 'ua', 'ip', 'status', 'remarks',
'shipping_name', 'shipping_street', 'shipping_postalcode', 'shipping_city',
2020-04-09 08:39:01 +00:00
'shipping_latitude', 'shipping_longitude',
'postalcode_info',
2020-03-27 16:51:10 +00:00
)
2020-03-26 09:09:44 +00:00
form_overrides = {'entity_info': TextAreaField, 'extra': TextAreaField, 'remarks': TextAreaField}
form_excluded_columns = ('changelog', 'postalcode_info')
2020-04-09 08:39:01 +00:00
column_export_list = column_filters
2020-04-07 16:34:33 +00:00
column_labels = {
'faceshield_front_required': 'Front required',
'faceshield_full_required': 'Full required',
'faceshield_front_delivered': 'Front delivered',
'faceshield_full_delivered': 'Full delivered',
}
2020-03-28 22:01:03 +00:00
2020-04-07 16:34:33 +00:00
column_list = ('id', 'entity_info', 'full_name', 'faceshield_full_required', 'faceshield_full_delivered', 'faceshield_front_required', 'faceshield_front_delivered', 'handling_orga', 'created', 'status')
column_editable_list = ('status', 'remarks', 'handling_orga')
form_choices = {
'handling_orga': [
2020-04-07 16:34:58 +00:00
('hswaw', 'hswaw'),
('hskrk', 'hskrk'),
('hswro', 'hswro'),
],
}
2020-03-27 18:04:29 +00:00
can_delete = False
can_view_details = True
details_modal = True
2020-03-28 22:08:27 +00:00
can_set_page_size = True
2020-03-27 18:04:29 +00:00
@property
def can_export(self):
return not current_user.external
2020-03-27 18:04:29 +00:00
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,
))
2020-04-08 18:09:24 +00:00
@flask_admin.expose('/label/')
def label(self):
return render_pdf(HTML(string=self.label_html()))
@flask_admin.expose('/label/html')
def label_html(self):
2020-04-12 13:16:13 +00:00
model = self.get_one(request.args.get('id'))
if model.faceshield_full_delivered % 150 == 0 and model.faceshield_full_delivered // 150 > 1:
label_count = model.faceshield_full_delivered // 150
else:
label_count = 1
return self.render('label.html', model=model, label_count=label_count)
2020-04-08 18:09:24 +00:00
2020-04-13 13:15:45 +00:00
def bulk_status_change(self, ids, status):
try:
query = self.get_query().filter(FaceshieldRequest.id.in_(ids))
count = 0
for req in query.all():
if req.status != status:
req.status = status
count += 1
self.on_model_change(None, req, False)
db.session.commit()
flash('{} requests were successfully marked as {}.'.format(count, status.name))
except Exception as ex:
if not self.handle_view_exception(ex):
raise
flash('Failed to mark request as {}: {}'.format(status.name, str(ex)), 'error')
@flask_admin.actions.action('new', 'Mark as new', 'Are you sure you want to mark as new?')
def action_new(self, ids):
self.bulk_status_change(ids, Status.new)
@flask_admin.actions.action('fulfilled', 'Mark as fulfilled', 'Are you sure you want to mark as fulfilled?')
def action_fulfilled(self, ids):
self.bulk_status_change(ids, Status.fulfilled)
@flask_admin.actions.action('intransit', 'Mark as intransit', 'Are you sure you want to mark as intransit?')
def action_intransit(self, ids):
self.bulk_status_change(ids, Status.intransit)
@flask_admin.actions.action('shippingpending', 'Mark as shippingpending', 'Are you sure you want to mark as shippingpending?')
def action_shippingpending(self, ids):
self.bulk_status_change(ids, Status.shippingpending)
@flask_admin.actions.action('pickuppending', 'Mark as pickuppending', 'Are you sure you want to mark as pickuppending?')
def action_pickuppending(self, ids):
self.bulk_status_change(ids, Status.pickuppending)
2020-03-29 12:37:42 +00:00
class FilteredFaceshieldRequestAdmin(FaceshieldRequestAdmin):
def get_query(self):
return super(FilteredFaceshieldRequestAdmin, self).get_query().filter(~FaceshieldRequest.status.in_([Status.rejected, Status.spam, Status.fulfilled, Status.delegated]))
2020-03-30 21:13:25 +00:00
def get_count_query(self):
return super(FilteredFaceshieldRequestAdmin, self).get_count_query().filter(~FaceshieldRequest.status.in_([Status.rejected, Status.spam, Status.fulfilled, Status.delegated]))
2020-03-29 12:37:42 +00:00
2020-04-04 11:09:10 +00:00
class ShippingFaceshieldRequestAdmin(FilteredFaceshieldRequestAdmin):
2020-04-09 08:39:01 +00:00
column_editable_list = ('shipping_name', 'shipping_street', 'shipping_postalcode', 'shipping_city', 'shipping_latitude', 'shipping_longitude', 'status')
2020-03-29 12:37:42 +00:00
column_list = ['id', 'entity_info', 'full_name', *column_editable_list]
2020-03-27 16:51:10 +00:00
class ExternalUserAdmin(ModelViewHighSecurity):
column_default_sort = 'id'
column_list = ('id', 'email', 'password', 'remarks')
form_columns = ('email', 'password', 'remarks')
can_delete = True
2020-04-13 13:15:54 +00:00
class ChangelogAdmin(ModelView):
can_delete = False
can_edit = False
can_set_page_size = True
can_create = False
column_list = ('user_id', 'request', 'state_after', 'created')
column_filters = ('request_id', 'request', 'user_id', 'created')
2020-03-30 21:13:25 +00:00
admin.add_view(FilteredFaceshieldRequestAdmin(FaceshieldRequest, db.session))
admin.add_view(FaceshieldRequestAdmin(FaceshieldRequest, db.session, name='FaceshieldRequest (Unfiltered)', endpoint='request_unfiltered'))
2020-03-29 12:37:42 +00:00
admin.add_view(ShippingFaceshieldRequestAdmin(FaceshieldRequest, db.session, name='FaceshieldRequest (Shipping)', endpoint='request_shipping'))
admin.add_view(ExternalUserAdmin(ExternalUser, db.session, name='External Users', endpoint='external_user'))
2020-04-07 18:59:51 +00:00
admin.add_view(MapView(name='Map', endpoint='map'))
2020-04-13 13:15:54 +00:00
admin.add_view(ChangelogAdmin(RequestChange, db.session))