From e3c74b48cbff01283f224112a702127f6397a05a Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sun, 19 Apr 2020 10:34:05 +0200 Subject: [PATCH] Add flask-caching and kurjerzy shipment creation --- docker-compose.yml | 5 ++ formity/__init__.py | 3 +- formity/admin.py | 156 ++++++++++++++++++++++++++++++++- formity/extensions.py | 2 + formity/models.py | 9 +- formity/settings/production.py | 16 ++++ requirements.txt | 2 + templates/_changelog.html | 5 +- 8 files changed, 189 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0c51de8..ac7f3a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,9 @@ services: environment: - POSTGRES_PASSWORD=secret + redis: + image: redis:5.0.8-alpine@sha256:cda5d02e4ea900a8d52e834bc3158e83b8a87a5b44ae081885aecf9b156dcff1 + backend: build: . image: registry.k0.hswaw.net/informatic/covid-formity @@ -18,6 +21,8 @@ services: environment: - SPACEAUTH_DISABLE=true - TEMPLATES_AUTO_RELOAD=true + - SHIPPING_KURJERZY_EMAIL + - SHIPPING_KURJERZY_PASSWORD volumes: pgdata: diff --git a/formity/__init__.py b/formity/__init__.py index a0de53a..77a1f6b 100644 --- a/formity/__init__.py +++ b/formity/__init__.py @@ -1,7 +1,7 @@ import flask from formity.admin import IndexView from formity.external_auth import ExternalSpaceAuth -from formity.extensions import db, migrate, admin, babel, metrics +from formity.extensions import db, migrate, admin, babel, metrics, cache def create_app(): app = flask.Flask( @@ -18,6 +18,7 @@ def create_app(): babel.init_app(app) ExternalSpaceAuth().init_app(app) metrics.init_app(app) + cache.init_app(app) import formity.views import formity.models diff --git a/formity/admin.py b/formity/admin.py index 0d8ae50..40ab880 100644 --- a/formity/admin.py +++ b/formity/admin.py @@ -1,10 +1,13 @@ -from flask import redirect, flash, request, url_for +import enum +import io +import datetime +import csv +from flask import redirect, flash, request, url_for, make_response, current_app import flask_admin from formity.extensions import admin, db, ModelView, ModelViewHighSecurity, AdminSecurityMixin from wtforms import TextAreaField, validators from formity.models import FaceshieldRequest, RequestChange, Status, PostalCode, ExternalUser from spaceauth import current_user -import enum from flask_weasyprint import HTML, render_pdf from sqlalchemy import func @@ -178,6 +181,155 @@ class FaceshieldRequestAdmin(ModelView): models = self.get_query().filter(FaceshieldRequest.id.in_(ids)).all() return render_pdf(HTML(string=self.render('label.html', models=models))) + @flask_admin.actions.action('csv_kurjerzy', 'Export Kurjerzy.pl CSV') + def action_csv_kurjerzy(self, ids): + models = self.get_query().filter(FaceshieldRequest.id.in_(ids)).all() + fields = [ + 'kurier', + 'nadawca_nazwa', 'nadawca_email', 'nadawca_ulica', + 'nadawca_nr_lok', 'nadawca_kod', 'nadawca_miasto', + 'nadawca_telefon', 'nadawca_kraj', 'nadawca_region', + 'nadawca_punkt', + + 'odbiorca_nazwa', 'odbiorca_email', 'odbiorca_ulica', + 'odbiorca_nr_lok', 'odbiorca_kod', 'odbiorca_miasto', + 'odbiorca_telefon', 'odbiorca_kraj', 'odbiorca_region', + 'odbiorca_punkt', + + 'przesylka_rodzaj', 'przesylka_opakowanie', 'przesylka_waga', + 'przesylka_szerokosc', 'przesylka_wysokosc', 'przesylka_dlugosc', + 'przesylka_wartosc', 'przesylka_zawartosc', + + 'pobranie_wartosc', 'pobranie_nr_bank', 'pobranie_1_dzien', + 'sms_nadawca', 'sms_odbiorca', 'dostawa_nast_dnia', + 'dokumenty_zwrotne', 'bez_odbioru', 'numer_ref', 'kod_promo' + ] + + fname = 'kurjerzy-export-%s.csv' % (datetime.datetime.now().strftime(r'%Y%m%d-%H%M%S'),) + + si = io.StringIO() + writer = csv.DictWriter(si, fields, delimiter=';') + writer.writeheader() + + config = current_app.config + for row in models: + writer.writerow({ + 'kurier': 'ups', + 'nadawca_nazwa': config['SHIPPING_SENDER_NAME'], + 'nadawca_email': config['SHIPPING_SENDER_EMAIL'], + 'nadawca_ulica': config['SHIPPING_SENDER_STREET'], + 'nadawca_nr_lok': config['SHIPPING_SENDER_NUMBER'], + 'nadawca_kod': config['SHIPPING_SENDER_POSTALCODE'], + 'nadawca_miasto': config['SHIPPING_SENDER_CITY'], + 'nadawca_telefon': config['SHIPPING_SENDER_PHONE_NUMBER'], + 'nadawca_kraj': 'PL', + 'odbiorca_nazwa': row.shipping_name, + 'odbiorca_email': row.email, + 'odbiorca_ulica': row.shipping_street, + 'odbiorca_nr_lok': '1/1', # FIXME + 'odbiorca_kod': row.shipping_postalcode, + 'odbiorca_miasto': row.shipping_city, + 'odbiorca_telefon': row.phone_number, + 'odbiorca_kraj': 'PL', + + # TODO + 'przesylka_rodzaj': 1, + 'przesylka_opakowanie': 1, + 'przesylka_waga': 4, + 'przesylka_szerokosc': 40, + 'przesylka_wysokosc': 30, + 'przesylka_dlugosc': 20, + 'przesylka_wartosc': 100, + + 'przesylka_zawartosc': 'przyłbice dla medyków %d' % (row.id,), + 'sms_odbiorca': 1, + 'dostawa_nast_dnia': 1, + }) + + row.changelog.append(RequestChange( + remarks='included on Kurjerzy export (%s)' % (fname,), + )) + + db.session.commit() + + output = make_response(si.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=%s" % (fname,) + output.headers["Content-type"] = "text/csv" + return output + + @flask_admin.actions.action('csv_xbs', 'Export XBS Group/DPD CSV') + def action_csv_xbs(self, ids): + models = self.get_query().filter(FaceshieldRequest.id.in_(ids)).all() + fields = [ + 'NAZWA NADAWCY', 'OSOBA KONTAKTOWA NADAWCA', + 'TELEFON KONTAKTOWY NADAWCA ', 'ULICA NADAWCA', + 'KOD POCZTOWY NADAWCA', 'MIASTO NADAWCA', + + 'NAZWA ODBIORCA', 'OSOBA KONTAKTOWA ODBIORCA', + 'TELEFON KONTAKTOWYODBIORCA', 'ULICA ODBIORCA', + 'KOD POCZTOWY ODBIORCA', 'MIASTO ODBIORCA', + + 'WAGA', 'ILOŚĆ PACZEK', 'NUMKAT', 'NEXT DAY', 'SOBOTA', + 'GWARANT 9:30', 'GWARANT 12:00', 'DZ', 'COD', 'WARTOŚĆ', + 'NR REFERENCYJNY 1', 'NR REFERENCYJNY 2', + 'ZAWARTOŚĆ', 'UWAGII', + ] + + fname = 'xbs-dpd-export-%s.csv' % (datetime.datetime.now().strftime(r'%Y%m%d-%H%M%S'),) + + si = io.StringIO() + writer = csv.DictWriter(si, fields) + writer.writeheader() + config = current_app.config + for row in models: + writer.writerow({ + 'NAZWA NADAWCY': config['SHIPPING_SENDER_NAME'], + 'OSOBA KONTAKTOWA NADAWCA': config['SHIPPING_SENDER_NAME2'], + 'ULICA NADAWCA': '%s %s' % (config['SHIPPING_SENDER_STREET'], config['SHIPPING_SENDER_NUMBER']), + 'KOD POCZTOWY NADAWCA': config['SHIPPING_SENDER_POSTALCODE'], + 'MIASTO NADAWCA': config['SHIPPING_SENDER_CITY'], + 'TELEFON KONTAKTOWY NADAWCA ': config['SHIPPING_SENDER_PHONE_NUMBER'], + + 'NAZWA ODBIORCA': row.shipping_name, + 'OSOBA KONTAKTOWA ODBIORCA': row.full_name, + 'TELEFON KONTAKTOWYODBIORCA': row.phone_number, + 'ULICA ODBIORCA': row.shipping_street, + 'KOD POCZTOWY ODBIORCA': row.shipping_postalcode, + 'MIASTO ODBIORCA': row.shipping_city, + + # TODO kartony? + 'WAGA': 12, + 'ILOŚĆ PACZEK': 1, + 'NR REFERENCYJNY 1': row.id, + 'ZAWARTOŚĆ': 'przyłbice dla medyków', + }) + + row.changelog.append(RequestChange( + remarks='included on XBS export (%s)' % (fname,), + )) + + db.session.commit() + + output = make_response(si.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=%s" % (fname,) + output.headers["Content-type"] = "text/csv" + return output + + @flask_admin.actions.action('create_shipment', 'Create Kurjerzy shipment') + def action_create_shipment(self, ids): + from shipping.kurjerzy import Kurjerzy + + models = self.get_query().filter(FaceshieldRequest.id.in_(ids)).all() + + k = Kurjerzy(current_app) + k.authenticate() + + for model in models: + k.create_shipment(model) + db.session.commit() + + flash('Shipments created') + 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])) diff --git a/formity/extensions.py b/formity/extensions.py index 0ad6053..65412af 100644 --- a/formity/extensions.py +++ b/formity/extensions.py @@ -4,6 +4,7 @@ import flask_sqlalchemy import flask_migrate import flask_admin import flask_babel +import flask_caching import prometheus_flask_exporter @@ -41,3 +42,4 @@ migrate = flask_migrate.Migrate() admin = flask_admin.Admin(name='Covid-Formity CRM™', template_mode='bootstrap3') babel = flask_babel.Babel() metrics = prometheus_flask_exporter.PrometheusMetrics(None, group_by='url_rule') +cache = flask_caching.Cache() diff --git a/formity/models.py b/formity/models.py index 94db728..8aafea1 100644 --- a/formity/models.py +++ b/formity/models.py @@ -92,13 +92,17 @@ class FaceshieldRequest(db.Model): return self.faceshield_full_delivered // 150 return 1 +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) + 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) @@ -140,12 +144,9 @@ def on_request_change(mapper, connection, target): 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, diff --git a/formity/settings/production.py b/formity/settings/production.py index 8a09bc4..5add654 100644 --- a/formity/settings/production.py +++ b/formity/settings/production.py @@ -21,3 +21,19 @@ PROXYFIX_NUM_PROXIES = env.int('PROXYFIX_NUM_PROXIES', default=1) BABEL_DEFAULT_LOCALE = 'pl' TEMPLATES_AUTO_RELOAD = env.bool('TEMPLATES_AUTO_RELOAD', default=False) + + +SHIPPING_KURJERZY_EMAIL = env.str('SHIPPING_KURJERZY_EMAIL', default='') +SHIPPING_KURJERZY_PASSWORD = env.str('SHIPPING_KURJERZY_PASSWORD', default='') + +SHIPPING_SENDER_NAME = env.str('SHIPPING_SENDER_NAME', default='Warszawski Hackerspace') +SHIPPING_SENDER_NAME2 = env.str('SHIPPING_SENDER_NAME2', default='') +SHIPPING_SENDER_EMAIL = env.str('SHIPPING_SENDER_EMAIL', default='covid-logistics@hackerspace.pl') +SHIPPING_SENDER_STREET = env.str('SHIPPING_SENDER_STREET', default='Wolność') +SHIPPING_SENDER_NUMBER = env.str('SHIPPING_SENDER_NUMBER', default='2A') +SHIPPING_SENDER_POSTALCODE = env.str('SHIPPING_SENDER_POSTALCODE', default='01-018') +SHIPPING_SENDER_CITY = env.str('SHIPPING_SENDER_CITY', default='Warszawa') +SHIPPING_SENDER_PHONE_NUMBER = env.str('SHIPPING_SENDER_PHONE_NUMBER', default='') + +CACHE_TYPE = env.str('CACHE_TYPE', default='redis') +CACHE_REDIS_URL = env.str('CACHE_REDIS_URL', default='redis://redis') diff --git a/requirements.txt b/requirements.txt index 1de827d..da18024 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ environs==7.3.1 Flask==1.1.1 Flask-Admin==1.5.5 Flask-Babel==1.0.0 +Flask-Caching==1.8.0 Flask-Login==0.5.0 Flask-Migrate==2.5.3 Flask-OAuthlib==0.9.5 @@ -37,6 +38,7 @@ python-dateutil==2.8.1 python-dotenv==0.12.0 python-editor==1.0.4 pytz==2019.3 +redis==3.4.1 requests==2.23.0 requests-oauthlib==1.3.0 six==1.14.0 diff --git a/templates/_changelog.html b/templates/_changelog.html index 0f5bb16..9172e4f 100644 --- a/templates/_changelog.html +++ b/templates/_changelog.html @@ -1,7 +1,7 @@ {% macro render_changelog(model) %} {% for entry in model.changelog %}

-{{ entry.created.strftime('%Y/%m/%d %H:%M') }} {{ entry.user_id }} changed +{{ entry.created.strftime('%Y/%m/%d %H:%M') }} {{ entry.user_id }} {% if entry.state_after %}changed {% for key, value in entry.state_after.items() %} {%- if not loop.first -%} {%- if loop.last -%} @@ -9,7 +9,8 @@ {%- else -%} , {%- endif -%} - {%- endif %} {{ key }} to {{ value|string()|truncate(40) }} {% endfor %} + {%- endif %} {{ key }} to {{ value|string()|truncate(40) }} {% endfor %}{% endif %} +{% if entry.remarks %}{{ entry.remarks }}{% endif %}

{% endfor %} {% endmacro %}