from datetime import datetime import enum import hashlib import os from flask import current_app from jinja2 import Markup from formity.extensions import db, cache from sqlalchemy import event from sqlalchemy.ext.hybrid import hybrid_property from formity.utils import get_diff class Status(enum.Enum): new = 1 confirmed = 2 allocated = 3 fulfilled = 4 rejected = 5 spam = 6 delegated = 7 intransit = 8 shippingpending = 9 pickuppending = 10 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) def __str__(self): return '{}/{}/{}/{}'.format(self.postalcode, self.city, self.county, self.voivodeship) 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) faceshield_front_required = db.Column(db.Integer, default=0, server_default='0') faceshield_model = db.Column(db.String) faceshield_full_required = db.Column(db.Integer, default=0, server_default='0') created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) updated = db.Column(db.DateTime, default=datetime.utcnow, nullable=False, onupdate=datetime.utcnow) ua = db.Column(db.String) ip = db.Column(db.String) status = db.Column(db.Enum(Status), default=Status.new, server_default='new', nullable=False) remarks = db.Column(db.String) faceshield_front_delivered = db.Column(db.Integer, default=0, server_default='0') faceshield_full_delivered = db.Column(db.Integer, default=0, server_default='0') 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') shipping_name = db.Column(db.String) shipping_street = db.Column(db.String) shipping_postalcode = db.Column(db.String) shipping_city = db.Column(db.String) shipping_latitude = db.Column(db.Float) shipping_longitude = db.Column(db.Float) 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)') parent_id = db.Column(db.Integer, db.ForeignKey(id)) parent_shipment = db.relationship('FaceshieldRequest', remote_side=id, backref='children') handling_orga = db.Column(db.String, default='hswaw', server_default='hswaw', nullable=False) def __str__(self): return Markup('#{} {} ({})').format(self.id, self.id, self.entity_info, self.status.name) @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 @cache.memoize(timeout=10 * 60) def fetch_shipping_info(self): if self.shipping_provider == 'kurjerzy': from shipping.kurjerzy import Kurjerzy k = Kurjerzy(current_app) k.authenticate() return k.shipment_info(self.shipping_id) return None def refresh_shipping_info(self): cache.delete_memoized(self.fetch_shipping_info, self) @property def shipping_info(self): return self.fetch_shipping_info() 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' 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) 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) 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) 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, ))