covid-formity/formity/models.py

269 lines
9.3 KiB
Python
Raw Normal View History

2020-03-26 09:09:44 +00:00
from datetime import datetime
2020-03-27 18:04:29 +00:00
import enum
import hashlib
import os
2020-04-19 20:21:32 +00:00
from flask import current_app
from jinja2 import Markup
2020-04-19 20:21:32 +00:00
from formity.extensions import db, cache
from sqlalchemy import event
from sqlalchemy.ext.hybrid import hybrid_property
from formity.utils import get_diff
2020-03-27 18:04:29 +00:00
class Status(enum.Enum):
new = 1
confirmed = 2
allocated = 3
fulfilled = 4
rejected = 5
2020-03-30 18:41:07 +00:00
spam = 6
2020-03-30 21:09:52 +00:00
delegated = 7
2020-04-07 16:58:35 +00:00
intransit = 8
shippingpending = 9
pickuppending = 10
2020-03-26 09:09:44 +00:00
2020-04-24 12:23:03 +00:00
class ShipmentStatus(enum.Enum):
unknown = 0
new = 1
booked = 2
collected = 3
inDelivery = 4
2020-04-24 12:41:29 +00:00
returned = 5
delivered = 6
2020-04-24 12:23:03 +00:00
def __ge__(self, other):
if self.__class__ is other.__class__:
return self.value >= other.value
return NotImplemented
def __gt__(self, other):
if self.__class__ is other.__class__:
return self.value > other.value
return NotImplemented
def __le__(self, other):
if self.__class__ is other.__class__:
return self.value <= other.value
return NotImplemented
def __lt__(self, other):
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
def get_shipment_status(shipment_info):
tracking = shipment_info.get('tracking', {})
if tracking.get('returned'):
return ShipmentStatus.returned
if tracking.get('delivered'):
return ShipmentStatus.delivered
if tracking.get('inDelivery'):
return ShipmentStatus.inDelivery
if tracking.get('collected'):
return ShipmentStatus.collected
if tracking.get('booked'):
return ShipmentStatus.booked
if tracking:
return ShipmentStatus.new
return ShipmentStatus.unknown
class PostalCode(db.Model):
postalcode = db.Column(db.String, primary_key=True)
address = db.Column(db.String, nullable=False)
city = db.Column(db.String, nullable=False)
voivodeship = db.Column(db.String, nullable=False)
county = db.Column(db.String, nullable=False)
2020-04-19 18:34:57 +00:00
def __str__(self):
return '{}/{}/{}/{}'.format(self.postalcode, self.city, self.county, self.voivodeship)
2020-03-26 09:09:44 +00:00
class FaceshieldRequest(db.Model):
id = db.Column(db.Integer, primary_key=True)
entity_info = db.Column(db.String)
full_name = db.Column(db.String)
phone_number = db.Column(db.String)
email = db.Column(db.String)
extra = db.Column(db.String)
2020-03-29 08:19:21 +00:00
faceshield_front_required = db.Column(db.Integer, default=0, server_default='0')
2020-03-26 09:09:44 +00:00
faceshield_model = db.Column(db.String)
2020-03-29 08:19:21 +00:00
faceshield_full_required = db.Column(db.Integer, default=0, server_default='0')
2020-03-26 09:09:44 +00:00
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated = db.Column(db.DateTime, default=datetime.utcnow, nullable=False,
onupdate=datetime.utcnow)
2020-03-27 11:41:08 +00:00
ua = db.Column(db.String)
ip = db.Column(db.String)
2020-03-27 18:04:29 +00:00
status = db.Column(db.Enum(Status), default=Status.new, server_default='new', nullable=False)
remarks = db.Column(db.String)
2020-03-29 08:19:21 +00:00
faceshield_front_delivered = db.Column(db.Integer, default=0, server_default='0')
faceshield_full_delivered = db.Column(db.Integer, default=0, server_default='0')
2020-03-27 18:04:29 +00:00
2020-04-15 20:26:48 +00:00
adapter_3m_dar_required = db.Column(db.Integer, default=0, server_default='0')
adapter_easybreath_dar_required = db.Column(db.Integer, default=0, server_default='0')
adapter_rd40_dar_required = db.Column(db.Integer, default=0, server_default='0')
adapter_secura_dar_required = db.Column(db.Integer, default=0, server_default='0')
adapter_3m_dar_delivered = db.Column(db.Integer, default=0, server_default='0')
adapter_easybreath_dar_delivered = db.Column(db.Integer, default=0, server_default='0')
adapter_rd40_dar_delivered = db.Column(db.Integer, default=0, server_default='0')
adapter_secura_dar_delivered = db.Column(db.Integer, default=0, server_default='0')
2020-04-21 06:39:16 +00:00
delivery_method = db.Column(db.String, default='shipping')
2020-03-29 12:37:42 +00:00
shipping_name = db.Column(db.String)
shipping_street = db.Column(db.String)
shipping_postalcode = db.Column(db.String)
shipping_city = db.Column(db.String)
2020-04-17 15:18:44 +00:00
2020-04-07 17:15:30 +00:00
shipping_latitude = db.Column(db.Float)
shipping_longitude = db.Column(db.Float)
2020-03-29 12:37:42 +00:00
2020-04-17 15:18:44 +00:00
shipping_provider = db.Column(db.String)
shipping_id = db.Column(db.String)
postalcode_info = db.relationship(PostalCode, primaryjoin='remote(PostalCode.postalcode) == foreign(FaceshieldRequest.shipping_postalcode)')
2020-04-16 21:27:25 +00:00
parent_id = db.Column(db.Integer, db.ForeignKey(id))
parent_shipment = db.relationship('FaceshieldRequest', remote_side=id, backref='children')
2020-03-29 08:19:44 +00:00
handling_orga = db.Column(db.String, default='hswaw', server_default='hswaw', nullable=False)
2020-04-24 06:41:06 +00:00
handling_orga_info = db.relationship('Orga', primaryjoin='remote(Orga.id) == foreign(FaceshieldRequest.handling_orga)')
2020-04-13 13:15:54 +00:00
def __str__(self):
2020-04-16 21:27:25 +00:00
return Markup('<a href="/admin/request_unfiltered/edit/?id={}">#{} {}</a> ({})').format(self.id, self.id, self.entity_info, self.status.name)
2020-04-13 13:15:54 +00:00
2020-04-22 16:47:44 +00:00
def __repr__(self):
return '<FaceshieldRequest {} ({})>'.format(self.id, self.status.name)
2020-04-14 16:18:07 +00:00
@property
def label_count(self):
if self.faceshield_full_delivered % 150 == 0 and self.faceshield_full_delivered // 150 > 1:
return self.faceshield_full_delivered // 150
return 1
2020-04-19 20:21:32 +00:00
def fetch_shipping_info(self):
if self.shipping_provider == 'kurjerzy':
from shipping.kurjerzy import Kurjerzy
k = Kurjerzy(current_app)
k.authenticate()
2020-04-24 08:50:14 +00:00
return [self.cache_shipment_info(k.shipment_info, i) for i in self.shipping_id.split(',')]
elif self.shipping_provider == 'xbs':
from shipping.dpd import DPD
d = DPD(current_app)
return [self.cache_shipment_info(d.shipment_info, i) for i in self.shipping_id.split(',')]
2020-04-19 20:21:32 +00:00
return None
2020-04-24 08:50:14 +00:00
def cache_shipment_info(self, fetch, id):
key = 'shipment_info_%d_%s' % (self.id, id)
res = cache.get(key)
2020-04-24 12:23:03 +00:00
if res is None:
2020-04-24 08:50:14 +00:00
try:
2020-04-24 12:23:03 +00:00
res = fetch(id) or {'error': 'No shipment found'}
2020-04-24 08:50:14 +00:00
except Exception as exc:
res = {'error': str(exc)}
if res.get('tracking', {}).get('delivered'):
cache.set(key, res, timeout=60 * 60 * 24 * 7)
else:
cache.set(key, res, timeout=60 * 10)
return res
2020-04-19 20:21:32 +00:00
def refresh_shipping_info(self):
2020-04-24 08:50:14 +00:00
for i in self.shipping_id.split(','):
2020-04-24 12:36:18 +00:00
cache.delete('shipment_info_%d_%s' % (self.id, i))
2020-04-19 20:21:32 +00:00
@property
def shipping_info(self):
return self.fetch_shipping_info()
2020-04-24 12:23:03 +00:00
@property
def shipment_status(self):
try:
if self.shipping_info:
return min([get_shipment_status(s) for s in self.shipping_info], default=ShipmentStatus.unknown)
except Exception as exc:
print(exc)
return ShipmentStatus.unknown
2020-04-19 20:21:32 +00:00
def changelog_default_user_id():
from flask_login import current_user
return current_user.get_id() if current_user and current_user.get_id() else 'nobody'
2020-03-29 08:19:44 +00:00
2020-03-27 18:04:29 +00:00
class RequestChange(db.Model):
id = db.Column(db.Integer, primary_key=True)
request_id = db.Column(db.Integer, db.ForeignKey(FaceshieldRequest.id))
request = db.relationship(FaceshieldRequest, backref='changelog')
user_id = db.Column(db.String, nullable=False, default=changelog_default_user_id)
2020-03-27 18:04:29 +00:00
state_before = db.Column(db.JSON)
state_after = db.Column(db.JSON)
remarks = db.Column(db.String)
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
2020-04-24 06:41:06 +00:00
class Orga(db.Model):
id = db.Column(db.String, primary_key=True)
name = db.Column(db.String)
shipping_name = db.Column(db.String)
shipping_full_name = db.Column(db.String)
shipping_street = db.Column(db.String)
shipping_number = db.Column(db.String)
shipping_postalcode = db.Column(db.String)
shipping_city = db.Column(db.String)
shipping_phone_number = db.Column(db.String)
shipping_email = db.Column(db.String)
class ExternalUser(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String, nullable=False)
_password = db.Column('password', db.String, nullable=False)
remarks = db.Column(db.String)
2020-04-24 06:41:06 +00:00
orga = db.Column(db.String, db.ForeignKey(Orga.id))
def _hash(self, salt, password):
return hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 213700).hex()
@hybrid_property
def password(self):
return self._password
@password.setter
def password(self, new_pass):
# hack: do not double hash
if self._password == new_pass:
return
salt = hashlib.sha256(os.urandom(16)).hexdigest()
self._password = salt + ':' + self._hash(salt, new_pass)
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:
@event.listens_for(db.session, "after_flush", once=True)
def receive_after_flush(session, context):
db.session.add(RequestChange(
request_id=target.id,
state_before=before,
state_after=after,
))