From 5d3a14b8b27ee5ca0532308b8d47584a2d76dbb2 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Fri, 5 May 2017 23:17:22 +0200 Subject: [PATCH] Member spamming implementation --- web/webapp/__init__.py | 12 ++++++ web/webapp/forms.py | 4 ++ web/webapp/models.py | 9 ++++ web/webapp/templates/admin_spam.html | 19 +++++++++ web/webapp/templates/mailing/due.txt | 35 +++++++++++++++ web/webapp/views.py | 64 ++++++++++++++++++++++------ 6 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 web/webapp/templates/admin_spam.html create mode 100644 web/webapp/templates/mailing/due.txt diff --git a/web/webapp/__init__.py b/web/webapp/__init__.py index e008463..835dc13 100644 --- a/web/webapp/__init__.py +++ b/web/webapp/__init__.py @@ -104,6 +104,18 @@ import webapp.api def unauthorized(): return redirect('/login') +@app.template_filter('inflect') +def inflect(v, one, two, five): + num = abs(v) + + if num == 0: + return '%d %s' % (v, five) + elif num == 1: + return '%d %s' % (v, one) + elif num <= 4: + return '%d %s' % (v, two) + else: + return '%d %s' % (v, five) def init(): pass diff --git a/web/webapp/forms.py b/web/webapp/forms.py index 90a1111..533877c 100644 --- a/web/webapp/forms.py +++ b/web/webapp/forms.py @@ -23,6 +23,7 @@ # POSSIBILITY OF SUCH DAMAGE. from wtforms import Form, BooleanField, TextField, PasswordField, SelectMultipleField, FormField, validators, widgets +from flask_wtf import Form as FlaskForm class MultiCheckboxField(SelectMultipleField): widget = widgets.ListWidget(prefix_label=False) @@ -43,3 +44,6 @@ class LDAPSyncForm(Form): starving_to_add = MultiCheckboxField("Starving to add", choices=[]) starving_to_remove = MultiCheckboxField("Starving to remove", choices=[]) +class SpamForm(FlaskForm): + dry_run = BooleanField("Dry run") + members = MultiCheckboxField("Members to spam", coerce=int) diff --git a/web/webapp/models.py b/web/webapp/models.py index 34483a0..8148288 100644 --- a/web/webapp/models.py +++ b/web/webapp/models.py @@ -226,7 +226,13 @@ class Member(db.Model): previous_scalar = self._yearmonth_scalar(previous_transfer) unpaid_months += (now - previous_scalar) + fees = { + 'starving': 50, + 'fatty': 100, + } + status['months_due'] = unpaid_months + status['money_due'] = fees.get(self.type, 0) * unpaid_months status['payment_status'] = PaymentStatus.okay.value if unpaid_months < 4 else PaymentStatus.unpaid.value status['last_paid'] = most_recent_transfer status['left'] = not active_payment @@ -333,6 +339,9 @@ class Member(db.Model): self.join_month = now_date.month self.payment_policy = PaymentPolicy.normal.value + def __unicode__(self): + return self.username + class Transfer(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/web/webapp/templates/admin_spam.html b/web/webapp/templates/admin_spam.html new file mode 100644 index 0000000..bf04e36 --- /dev/null +++ b/web/webapp/templates/admin_spam.html @@ -0,0 +1,19 @@ +{% extends "root.html" %} +{% set active_page = "admin" %} +{% block title %}Admin LDAP Sync{% endblock %} +{% block content %} + +
+
+
+
+ + {{ form.dry_run() }} {{ form.dry_run.label }} + + {{ form.hidden_tag() }} + {{ form.members() }} +
+
+
+
+{% endblock %} diff --git a/web/webapp/templates/mailing/due.txt b/web/webapp/templates/mailing/due.txt new file mode 100644 index 0000000..4c2524a --- /dev/null +++ b/web/webapp/templates/mailing/due.txt @@ -0,0 +1,35 @@ +Siemasz {{ member.username }}, + +automatycznie wygenerowałem raport ze stanu składek dla Twojego konta. +Oto stan na dzień {{ now.strftime('%d/%m/%Y') }}: + +{% if status['months_due'] > 0 -%} +Jesteś {{ status['months_due']|inflect('składkę', 'składki', 'składek') }} +{%- if status['money_due'] %} ({{ status['money_due'] / 100 }} PLN){% endif %} do tyłu. Kiepsko. +{% elif status['months_due'] == 0 -%} +Jesteś na bieżąco ze składkami. Hura! +{% else -%} +Jesteś do przodu ze składkami. Świetnie! +{% endif -%} + +{% if status['months_due'] > 2 %} + +Zgodnie z regulaminem HS, trzymiesięczna zaległość w składkach oznacza +automatyczne wykreślenie z listy członków i usunięcie karty z zamka. +Masz tydzień na uregulowanie składki od daty wysłania tego emaila. +{% endif %} +Oto szczegółowe informacje o Twoich ostatnich wpłatach: +{% for t in transfers %} + - opłata za {{ t.month }}/{{ t.year }}, pokryta przelewem za {{ + '%.02f'|format(t.transfer.amount/100) }} PLN w dniu {{ t.transfer.date.strftime('%d/%m/%Y') }} +{% endfor %} + +Jeśli coś się nie zgadza, odpisz na tego mejla z pretensjami - wiadomość trafi +do naszego białkowego skarbnika który postara się ustalić, co poszło źle. +Jednocześnie przypominam, że trzymiesięczna zaległość w płaceniu oznacza +wykreślenie z listy członków - automatyczną! + +xoxoxoxo, +Hackerspace'owy Kasownik +-- +„100 linii pythona!” - enki o skrypcie do składek diff --git a/web/webapp/views.py b/web/webapp/views.py index a34f545..68e716f 100644 --- a/web/webapp/views.py +++ b/web/webapp/views.py @@ -104,20 +104,6 @@ def admin_index(): inactive_members=inactive_members) -@app.route("/admin/spam", methods=["POST", "GET"]) -@admin_required -@login_required -def admin_spam(): - #members = [m.get_status() for m in models.Member.get_members(True)] - - form = forms.SPAMForm(request.form) - - form.process(request.form) - if request.method == "POST" and form.validate(): - pass - - return render_template("admin_spam.html", form=form) - @app.route("/admin/ldapsync", methods=["POST", "GET"]) @admin_required @login_required @@ -304,6 +290,56 @@ def match_user_transfer(): return render_template("match_user_transfer.html", member=member, transfer=transfer) +@app.route("/admin/spam/", methods=["GET", "POST"]) +@login_required +@admin_required +def sendspam(): + now = datetime.datetime.now() + members = models.Member.query.filter_by( + active=True, payment_policy=models.PaymentPolicy.normal.value).all() + + form = forms.SpamForm() + form.members.choices = [(member.id, member) for member in members] + form.members.default = [member.id for member in members] + + form.process(request.form) + + if request.method == 'POST' and form.validate(): + spam = [] + for member in members: + if member.id not in form.members.data: + continue + + content = render_template( + 'mailing/due.txt', + member=member, + status=member.get_status(), + transfers=member.transfers[:5], + now=now) + + # Just ignore empty messages + if not content.strip(): + continue + + msg = MIMEText(content, "plain", "utf-8") + msg["From"] = "Faszysta Hackerspace'owy " + msg["Subject"] = "Stan składek na dzień %s" % now.strftime("%d/%m/%Y") + msg["To"] = member.get_contact_email() + spam.append(msg) + + if form.dry_run.data: + readable = [ + msg.as_string().split('\n\n')[0] + '\n\n' + msg.get_payload(decode=True) for msg in spam] + return Response('\n====\n'.join(readable), mimetype='text/text') + + for msg in spam: + p = Popen(["/usr/sbin/sendmail", "-t"], stdin=PIPE) + p.communicate(msg.as_string()) + + flash('%d messages sent!' % len(spam)) + return redirect(url_for('admin_index')) + return render_template('admin_spam.html', form=form) + @app.route("/login", methods=["POST", "GET"]) def login(): form = forms.LoginForm(request.form)