Add flask-caching and kurjerzy shipment creation
parent
c0df7cd026
commit
e3c74b48cb
|
@ -8,6 +8,9 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=secret
|
- POSTGRES_PASSWORD=secret
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:5.0.8-alpine@sha256:cda5d02e4ea900a8d52e834bc3158e83b8a87a5b44ae081885aecf9b156dcff1
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
build: .
|
build: .
|
||||||
image: registry.k0.hswaw.net/informatic/covid-formity
|
image: registry.k0.hswaw.net/informatic/covid-formity
|
||||||
|
@ -18,6 +21,8 @@ services:
|
||||||
environment:
|
environment:
|
||||||
- SPACEAUTH_DISABLE=true
|
- SPACEAUTH_DISABLE=true
|
||||||
- TEMPLATES_AUTO_RELOAD=true
|
- TEMPLATES_AUTO_RELOAD=true
|
||||||
|
- SHIPPING_KURJERZY_EMAIL
|
||||||
|
- SHIPPING_KURJERZY_PASSWORD
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import flask
|
import flask
|
||||||
from formity.admin import IndexView
|
from formity.admin import IndexView
|
||||||
from formity.external_auth import ExternalSpaceAuth
|
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():
|
def create_app():
|
||||||
app = flask.Flask(
|
app = flask.Flask(
|
||||||
|
@ -18,6 +18,7 @@ def create_app():
|
||||||
babel.init_app(app)
|
babel.init_app(app)
|
||||||
ExternalSpaceAuth().init_app(app)
|
ExternalSpaceAuth().init_app(app)
|
||||||
metrics.init_app(app)
|
metrics.init_app(app)
|
||||||
|
cache.init_app(app)
|
||||||
|
|
||||||
import formity.views
|
import formity.views
|
||||||
import formity.models
|
import formity.models
|
||||||
|
|
156
formity/admin.py
156
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
|
import flask_admin
|
||||||
from formity.extensions import admin, db, ModelView, ModelViewHighSecurity, AdminSecurityMixin
|
from formity.extensions import admin, db, ModelView, ModelViewHighSecurity, AdminSecurityMixin
|
||||||
from wtforms import TextAreaField, validators
|
from wtforms import TextAreaField, validators
|
||||||
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
|
|
||||||
from flask_weasyprint import HTML, render_pdf
|
from flask_weasyprint import HTML, render_pdf
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
@ -178,6 +181,155 @@ class FaceshieldRequestAdmin(ModelView):
|
||||||
models = self.get_query().filter(FaceshieldRequest.id.in_(ids)).all()
|
models = self.get_query().filter(FaceshieldRequest.id.in_(ids)).all()
|
||||||
return render_pdf(HTML(string=self.render('label.html', models=models)))
|
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):
|
class FilteredFaceshieldRequestAdmin(FaceshieldRequestAdmin):
|
||||||
def get_query(self):
|
def get_query(self):
|
||||||
return super(FilteredFaceshieldRequestAdmin, self).get_query().filter(~FaceshieldRequest.status.in_([Status.rejected, Status.spam, Status.fulfilled, Status.delegated]))
|
return super(FilteredFaceshieldRequestAdmin, self).get_query().filter(~FaceshieldRequest.status.in_([Status.rejected, Status.spam, Status.fulfilled, Status.delegated]))
|
||||||
|
|
|
@ -4,6 +4,7 @@ import flask_sqlalchemy
|
||||||
import flask_migrate
|
import flask_migrate
|
||||||
import flask_admin
|
import flask_admin
|
||||||
import flask_babel
|
import flask_babel
|
||||||
|
import flask_caching
|
||||||
import prometheus_flask_exporter
|
import prometheus_flask_exporter
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,3 +42,4 @@ migrate = flask_migrate.Migrate()
|
||||||
admin = flask_admin.Admin(name='Covid-Formity CRM™', template_mode='bootstrap3')
|
admin = flask_admin.Admin(name='Covid-Formity CRM™', template_mode='bootstrap3')
|
||||||
babel = flask_babel.Babel()
|
babel = flask_babel.Babel()
|
||||||
metrics = prometheus_flask_exporter.PrometheusMetrics(None, group_by='url_rule')
|
metrics = prometheus_flask_exporter.PrometheusMetrics(None, group_by='url_rule')
|
||||||
|
cache = flask_caching.Cache()
|
||||||
|
|
|
@ -92,13 +92,17 @@ class FaceshieldRequest(db.Model):
|
||||||
return self.faceshield_full_delivered // 150
|
return self.faceshield_full_delivered // 150
|
||||||
return 1
|
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):
|
class RequestChange(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
request_id = db.Column(db.Integer, db.ForeignKey(FaceshieldRequest.id))
|
request_id = db.Column(db.Integer, db.ForeignKey(FaceshieldRequest.id))
|
||||||
request = db.relationship(FaceshieldRequest, backref='changelog')
|
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_before = db.Column(db.JSON)
|
||||||
state_after = 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'}
|
before, after = {'diff': 'failed'}, {'diff': 'failed'}
|
||||||
|
|
||||||
if before or after:
|
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)
|
@event.listens_for(db.session, "after_flush", once=True)
|
||||||
def receive_after_flush(session, context):
|
def receive_after_flush(session, context):
|
||||||
db.session.add(RequestChange(
|
db.session.add(RequestChange(
|
||||||
user_id=user_id,
|
|
||||||
request_id=target.id,
|
request_id=target.id,
|
||||||
state_before=before,
|
state_before=before,
|
||||||
state_after=after,
|
state_after=after,
|
||||||
|
|
|
@ -21,3 +21,19 @@ PROXYFIX_NUM_PROXIES = env.int('PROXYFIX_NUM_PROXIES', default=1)
|
||||||
|
|
||||||
BABEL_DEFAULT_LOCALE = 'pl'
|
BABEL_DEFAULT_LOCALE = 'pl'
|
||||||
TEMPLATES_AUTO_RELOAD = env.bool('TEMPLATES_AUTO_RELOAD', default=False)
|
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')
|
||||||
|
|
|
@ -13,6 +13,7 @@ environs==7.3.1
|
||||||
Flask==1.1.1
|
Flask==1.1.1
|
||||||
Flask-Admin==1.5.5
|
Flask-Admin==1.5.5
|
||||||
Flask-Babel==1.0.0
|
Flask-Babel==1.0.0
|
||||||
|
Flask-Caching==1.8.0
|
||||||
Flask-Login==0.5.0
|
Flask-Login==0.5.0
|
||||||
Flask-Migrate==2.5.3
|
Flask-Migrate==2.5.3
|
||||||
Flask-OAuthlib==0.9.5
|
Flask-OAuthlib==0.9.5
|
||||||
|
@ -37,6 +38,7 @@ python-dateutil==2.8.1
|
||||||
python-dotenv==0.12.0
|
python-dotenv==0.12.0
|
||||||
python-editor==1.0.4
|
python-editor==1.0.4
|
||||||
pytz==2019.3
|
pytz==2019.3
|
||||||
|
redis==3.4.1
|
||||||
requests==2.23.0
|
requests==2.23.0
|
||||||
requests-oauthlib==1.3.0
|
requests-oauthlib==1.3.0
|
||||||
six==1.14.0
|
six==1.14.0
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% macro render_changelog(model) %}
|
{% macro render_changelog(model) %}
|
||||||
{% for entry in model.changelog %}
|
{% for entry in model.changelog %}
|
||||||
<p>
|
<p>
|
||||||
<span class="text-muted">{{ entry.created.strftime('%Y/%m/%d %H:%M') }}</span> <b>{{ entry.user_id }}</b> changed
|
<span class="text-muted">{{ entry.created.strftime('%Y/%m/%d %H:%M') }}</span> <b>{{ entry.user_id }}</b> {% if entry.state_after %}changed
|
||||||
{% for key, value in entry.state_after.items() %}
|
{% for key, value in entry.state_after.items() %}
|
||||||
{%- if not loop.first -%}
|
{%- if not loop.first -%}
|
||||||
{%- if loop.last -%}
|
{%- if loop.last -%}
|
||||||
|
@ -9,7 +9,8 @@
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
,
|
,
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endif %} <b>{{ key }}</b> to <code title="{{ value }}">{{ value|string()|truncate(40) }}</code> {% endfor %}
|
{%- endif %} <b>{{ key }}</b> to <code title="{{ value }}">{{ value|string()|truncate(40) }}</code> {% endfor %}{% endif %}
|
||||||
|
{% if entry.remarks %}<i>{{ entry.remarks }}</i>{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
Loading…
Reference in New Issue