Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
informatic | f04b635b68 | |
informatic | ce179f14d4 | |
informatic | f60e45df08 | |
informatic | 8afb17fe3e | |
informatic | 4694e06dbc | |
informatic | 478843186b | |
informatic | 6a245f6af6 | |
informatic | 2650349785 | |
informatic | 8738708eb2 | |
informatic | c83712b244 | |
informatic | 4e0680cbba |
|
@ -1,4 +1,5 @@
|
|||
Flask==0.10.1
|
||||
Flask-Caching==1.3.3
|
||||
Flask-Gravatar==0.4.1
|
||||
Flask-Login==0.2.11
|
||||
Flask-SQLAlchemy==2.0
|
|
@ -11,7 +11,7 @@
|
|||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
|
@ -29,94 +29,76 @@ import requests
|
|||
import sqltap.wsgi
|
||||
|
||||
from flask import Flask, redirect
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
from flask.ext.login import LoginManager, AnonymousUserMixin, login_required, current_user
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager, AnonymousUserMixin, login_required, current_user
|
||||
from flask_caching import Cache
|
||||
from flaskext.gravatar import Gravatar
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object("config.CurrentConfig")
|
||||
app.wsgi_app = sqltap.wsgi.SQLTapMiddleware(app.wsgi_app)
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
db = SQLAlchemy()
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
mc = memcache.Client(app.config['MEMCACHE_SERVERS'], debug=0)
|
||||
cache = Cache()
|
||||
gravatar = Gravatar(size=256, rating='g', default='retro',
|
||||
force_default=False, use_ssl=True, base_url=None)
|
||||
|
||||
# TODO unsubscribe me from life
|
||||
cache_enabled = False
|
||||
gravatar = Gravatar(app, size=256, rating='g', default='retro', force_default=False, use_ssl=True, base_url=None)
|
||||
mc = cache
|
||||
|
||||
|
||||
import webapp.models
|
||||
|
||||
|
||||
class AnonymousUser(AnonymousUserMixin):
|
||||
def is_admin(self):
|
||||
return False
|
||||
|
||||
import webapp.models # noqa
|
||||
from webapp.auth import AnonymousUser, User
|
||||
|
||||
login_manager.anonymous_user = AnonymousUser
|
||||
|
||||
|
||||
class User(object):
|
||||
def __init__(self, username):
|
||||
self.username = username.lower().strip()
|
||||
self._admin = None
|
||||
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
def is_anonymous(self):
|
||||
return False
|
||||
|
||||
def get_id(self):
|
||||
return self.username
|
||||
|
||||
def is_admin(self):
|
||||
if not self.is_authenticated():
|
||||
return False
|
||||
if self._admin is None:
|
||||
r = requests.get('https://capacifier.hackerspace.pl/kasownik_access/'+
|
||||
self.username)
|
||||
self._admin = r.status_code == 200
|
||||
return self._admin
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(username):
|
||||
return User(username)
|
||||
|
||||
|
||||
def admin_required(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not current_user.is_admin():
|
||||
return login_manager.unauthorized()
|
||||
return f(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
import webapp.views
|
||||
import webapp.api
|
||||
|
||||
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized():
|
||||
return redirect('/login')
|
||||
|
||||
@app.template_filter('inflect')
|
||||
def inflect(v, one, two, five):
|
||||
num = abs(v)
|
||||
def admin_required(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not current_user.is_admin():
|
||||
return login_manager.unauthorized()
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
import webapp.views
|
||||
import webapp.admin
|
||||
import webapp.api
|
||||
|
||||
|
||||
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
|
||||
db.init_app(app)
|
||||
login_manager.init_app(app)
|
||||
gravatar.init_app(app)
|
||||
cache.init_app(app)
|
||||
|
||||
# Initialize middleware
|
||||
app.wsgi_app = sqltap.wsgi.SQLTapMiddleware(app.wsgi_app)
|
||||
|
||||
# Register blueprints
|
||||
app.register_blueprint(webapp.admin.bp)
|
||||
app.register_blueprint(webapp.api.bp)
|
||||
|
||||
# Custom filters
|
||||
@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)
|
||||
|
||||
return '%d %s' % (v, five)
|
||||
|
|
|
@ -0,0 +1,302 @@
|
|||
# - * - coding=utf-8 - * -
|
||||
|
||||
import datetime
|
||||
from email.mime.text import MIMEText
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from flask import render_template, request, flash, g, Response, \
|
||||
redirect, url_for, abort, Blueprint
|
||||
from flask_login import login_required
|
||||
from webapp import forms, db, models, admin_required
|
||||
|
||||
import directory
|
||||
import logic
|
||||
|
||||
|
||||
bp = Blueprint('admin', __name__)
|
||||
|
||||
@bp.route("/admin")
|
||||
@admin_required
|
||||
@login_required
|
||||
def index():
|
||||
members = [m.get_status() for m in models.Member.get_members(True)]
|
||||
for member in members:
|
||||
due = member['months_due']
|
||||
if due < 1:
|
||||
member['color'] = "00FF00"
|
||||
elif due < 3:
|
||||
member['color'] = "E0941B"
|
||||
else:
|
||||
member['color'] = "FF0000"
|
||||
|
||||
active_members = filter(lambda m: m['judgement'], members)
|
||||
inactive_members = filter(lambda m: not m['judgement'], members)
|
||||
diff = directory.get_ldap_group_diff(members)
|
||||
if diff is not None:
|
||||
flash("LDAP sync required")
|
||||
return render_template("admin_index.html",
|
||||
active_members=active_members,
|
||||
inactive_members=inactive_members)
|
||||
|
||||
|
||||
@bp.route("/admin/ldapsync", methods=["POST", "GET"])
|
||||
@admin_required
|
||||
@login_required
|
||||
def admin_ldap_sync():
|
||||
members = [m.get_status() for m in models.Member.get_members(True)]
|
||||
diff = directory.get_ldap_group_diff(members)
|
||||
if diff is None:
|
||||
return render_template("admin_ldap_sync.html", form=False)
|
||||
|
||||
form = forms.LDAPSyncForm(request.form)
|
||||
|
||||
form.fatty_to_add.choices = zip(diff['fatty_to_add'], diff['fatty_to_add'])
|
||||
form.fatty_to_add.default = diff['fatty_to_add']
|
||||
|
||||
form.fatty_to_remove.choices = zip(diff['fatty_to_remove'], diff['fatty_to_remove'])
|
||||
form.fatty_to_remove.default = diff['fatty_to_remove']
|
||||
|
||||
form.starving_to_add.choices = zip(diff['starving_to_add'], diff['starving_to_add'])
|
||||
form.starving_to_add.default = diff['starving_to_add']
|
||||
|
||||
form.starving_to_remove.choices = zip(diff['starving_to_remove'], diff['starving_to_remove'])
|
||||
form.starving_to_remove.default = diff['starving_to_remove']
|
||||
|
||||
form.process(request.form)
|
||||
if form.validate():
|
||||
changes = {'fatty': {}, 'starving': {}}
|
||||
changes['fatty']['add'] = form.fatty_to_add.data
|
||||
changes['fatty']['remove'] = form.fatty_to_remove.data
|
||||
changes['starving']['add'] = form.starving_to_add.data
|
||||
changes['starving']['remove'] = form.starving_to_remove.data
|
||||
|
||||
directory.update_member_groups(g.ldap, changes)
|
||||
|
||||
return render_template("admin_ldap_sync.html", form=form)
|
||||
|
||||
|
||||
@bp.route("/admin/csv")
|
||||
@admin_required
|
||||
@login_required
|
||||
def admin_csv():
|
||||
members = []
|
||||
for m in models.Member.get_members(True):
|
||||
member = m.get_status()
|
||||
if member['type'] == 'supporting':
|
||||
continue
|
||||
member['contact_email'] = m.get_contact_email()
|
||||
member['cn'] = directory.get_member_fields(g.ldap, member['username'], 'cn')['cn']
|
||||
members.append(member)
|
||||
|
||||
active_members = filter(lambda m: m['judgement'], members)
|
||||
output = render_template("admin_csv.html", active_members=active_members)
|
||||
return Response(output)
|
||||
|
||||
@bp.route('/admin/member/<membername>')
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_member(membername):
|
||||
member = models.Member.get_members(True).filter_by(username=membername).first()
|
||||
if not member:
|
||||
abort(404)
|
||||
status = member.get_status()
|
||||
cn = directory.get_member_fields(g.ldap, member.username, 'cn')['cn']
|
||||
return render_template("admin_member.html", member=member, status=status,
|
||||
cn=cn, admin=True)
|
||||
|
||||
@bp.route("/admin/member/<membername>/policy:<policy>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_member_set_policy(membername, policy):
|
||||
member = models.Member.query.filter_by(username=membername).first()
|
||||
member.payment_policy = models.PaymentPolicy[policy].value
|
||||
db.session.add(member)
|
||||
db.session.commit()
|
||||
return redirect(request.referrer)
|
||||
|
||||
@bp.route("/admin/member/<membername>/membership:<membershiptype>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_member_set_membership(membername, membershiptype):
|
||||
member = models.Member.query.filter_by(username=membername).first()
|
||||
member.type = models.MembershipType[membershiptype].name
|
||||
db.session.add(member)
|
||||
db.session.commit()
|
||||
return redirect(request.referrer)
|
||||
|
||||
|
||||
@bp.route("/admin/member/add/<membershiptype>/<username>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def add_member(membershiptype, username):
|
||||
member = models.Member(None, username, models.MembershipType[membershiptype].name, True)
|
||||
db.session.add(member)
|
||||
db.session.commit()
|
||||
flash('Member created')
|
||||
return redirect(request.referrer)
|
||||
|
||||
@bp.route("/admin/match")
|
||||
@login_required
|
||||
@admin_required
|
||||
def match_index():
|
||||
transfers_unmatched = logic.get_unmatched_transfers()
|
||||
return render_template("match.html", transfers_unmatched=transfers_unmatched)
|
||||
|
||||
|
||||
@bp.route("/admin/match/auto", methods=["GET"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def match_auto():
|
||||
matched = 0
|
||||
left = 0
|
||||
transfers_unmatched = logic.get_unmatched_transfers()
|
||||
affected_members = []
|
||||
for transfer in transfers_unmatched:
|
||||
matchability, member, months = transfer.get_matchability()
|
||||
try:
|
||||
print "[i] Matching transfer {} for {:.2f}PLN by member {}, {} months".format(
|
||||
transfer.id, transfer.amount/100, member.username, months)
|
||||
except AttributeError:
|
||||
print "[e] Member data invalid, WTF - {}".format(repr(member))
|
||||
continue
|
||||
if matchability == models.Transfer.MATCH_OK:
|
||||
if member.transfers:
|
||||
year, month = member.get_next_unpaid()
|
||||
if None in (year, month):
|
||||
print "[w] next_unpaid borked, skipping"
|
||||
continue
|
||||
else:
|
||||
year, month = transfer.date.year, transfer.date.month
|
||||
for m in range(months):
|
||||
mt = models.MemberTransfer(None, year, month, transfer)
|
||||
member.transfers.append(mt)
|
||||
db.session.add(mt)
|
||||
flash("Matched transfer {} for {:.2f}PLN to member {} for month {}-{}".format(transfer.id, transfer.amount/100, member.username, year, month))
|
||||
year, month = member._yearmonth_increment((year,month))
|
||||
matched += 1
|
||||
affected_members.append(member)
|
||||
else:
|
||||
left += 1
|
||||
db.session.commit()
|
||||
for member in affected_members:
|
||||
member.get_status(force_refresh=True)
|
||||
flash("Matched %i, %i left" % (matched, left))
|
||||
return redirect(url_for(".match_index"))
|
||||
|
||||
@bp.route("/admin/match/manual", methods=["GET"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def match_manual():
|
||||
transfers_unmatched = logic.get_unmatched_transfers()
|
||||
return render_template("match_manual.html", transfers_unmatched=transfers_unmatched)
|
||||
|
||||
@bp.route("/admin/match/ignore/<path:uid>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def ignore(uid):
|
||||
transfer = models.Transfer.query.filter_by(uid=uid).first()
|
||||
if not transfer:
|
||||
flash('No transfer found', 'danger')
|
||||
return redirect(url_for(".match_manual"))
|
||||
transfer.ignore = True
|
||||
db.session.commit()
|
||||
|
||||
flash('Transfer %s ignored' % (transfer,))
|
||||
|
||||
return redirect(request.referrer)
|
||||
|
||||
@bp.route("/admin/match/<username>/<int:months>/<path:uid>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def match(username, uid, months):
|
||||
member = models.Member.query.filter_by(username=username).first()
|
||||
if not member:
|
||||
flash('No member found', 'danger')
|
||||
return redirect(url_for(".match_manual"))
|
||||
|
||||
transfer = models.Transfer.query.filter_by(uid=uid).first()
|
||||
if not transfer:
|
||||
flash('No transfer found', 'danger')
|
||||
return redirect(url_for(".match_manual"))
|
||||
|
||||
for _ in range(months):
|
||||
year, month = member.get_next_unpaid()
|
||||
mt = models.MemberTransfer(None, year, month, transfer)
|
||||
member.transfers.append(mt)
|
||||
db.session.add(mt)
|
||||
|
||||
db.session.commit()
|
||||
member.get_status(force_refresh=True)
|
||||
|
||||
flash('OK, %d get' % transfer.amount)
|
||||
return redirect(url_for(".match_manual"))
|
||||
|
||||
|
||||
@bp.route("/admin/match/", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def match_user_transfer():
|
||||
username = request.form["username"]
|
||||
uid = request.form["uid"]
|
||||
member = models.Member.query.filter_by(username=username).first()
|
||||
if not member:
|
||||
flash('No member found', 'danger')
|
||||
return redirect(url_for(".match_manual"))
|
||||
|
||||
transfer = models.Transfer.query.filter_by(uid=uid).first()
|
||||
if not transfer:
|
||||
flash('No transfer found', 'danger')
|
||||
return redirect(url_for(".match_manual"))
|
||||
|
||||
return render_template("match_user_transfer.html", member=member, transfer=transfer)
|
||||
|
||||
@bp.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 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"] = "Kasownik Hackerspace'owy <kasownik@hackerspace.pl>"
|
||||
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('.index'))
|
||||
return render_template('admin_spam.html', form=form)
|
|
@ -12,7 +12,7 @@
|
|||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
|
@ -26,12 +26,16 @@
|
|||
import hmac
|
||||
import json
|
||||
import datetime
|
||||
import logging
|
||||
from functools import wraps
|
||||
from sqlalchemy import and_
|
||||
|
||||
from flask import request, abort, Response
|
||||
from flask import request, abort, Response, Blueprint
|
||||
|
||||
from webapp import models, app, mc
|
||||
from webapp import models, app, cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
bp = Blueprint('api', __name__)
|
||||
|
||||
class APIError(Exception):
|
||||
def __init__(self, message, code=500):
|
||||
|
@ -50,27 +54,23 @@ def _public_api_method(path):
|
|||
content = original(*args, **kwargs)
|
||||
status = "ok"
|
||||
code = 200
|
||||
except APIError as e:
|
||||
content = e.message
|
||||
code = e.code
|
||||
except APIError as exc:
|
||||
content = exc.message
|
||||
code = exc.code
|
||||
status = "error"
|
||||
except Exception as e:
|
||||
raise
|
||||
content = "Internal server error."
|
||||
code = 500
|
||||
status = "error"
|
||||
|
||||
last_transfer = models.Transfer.query.order_by(models.Transfer.date.desc()).first()
|
||||
modified = str(last_transfer.date)
|
||||
|
||||
r = {}
|
||||
r["status"] = status
|
||||
r["content"] = content
|
||||
r["modified"] = modified
|
||||
return Response(json.dumps(r), mimetype="application/json"), code
|
||||
return app.route("/api/" + path + ".json", methods=["GET"])(wrapper_json)
|
||||
last_transfer = models.Transfer.query.order_by(models.Transfer.date.desc()).first()
|
||||
modified = str(last_transfer.date) if last_transfer else None
|
||||
|
||||
resp = {
|
||||
"status": status,
|
||||
"content": content,
|
||||
"modified": modified
|
||||
}
|
||||
return Response(json.dumps(resp), mimetype="application/json"), code
|
||||
return bp.route("/api/" + path + ".json", methods=["GET"])(wrapper_json)
|
||||
return decorator2
|
||||
|
||||
|
||||
|
||||
def _private_api_method(path):
|
||||
"""A decorator that adds a private, HMACed, POST based method at /api/path.
|
||||
|
@ -110,13 +110,12 @@ def _private_api_method(path):
|
|||
request.decoded = json.loads(request.data.decode("base64"))
|
||||
else:
|
||||
request.decoded = {}
|
||||
except Exception as e:
|
||||
print request.data
|
||||
print e
|
||||
except Exception:
|
||||
logger.exception('Request decode failed')
|
||||
abort(400)
|
||||
|
||||
return json.dumps(original(*args, **kwargs))
|
||||
return app.route("/api/" + path, methods=["POST"])(wrapper)
|
||||
return bp.route("/api/" + path, methods=["POST"])(wrapper)
|
||||
return decorator
|
||||
|
||||
@_private_api_method("list_members")
|
||||
|
@ -156,12 +155,8 @@ def api_member():
|
|||
|
||||
return response
|
||||
|
||||
@cache.memoize()
|
||||
def _stats_for_month(year, month):
|
||||
cache_key = 'kasownik-stats_for_month-{}-{}'.format(year, month)
|
||||
cache_data = mc.get(cache_key)
|
||||
if cache_data:
|
||||
cache_data = json.loads(cache_data)
|
||||
return cache_data[0], cache_data[1]
|
||||
# TODO: export this to the config
|
||||
money_required = 4217+615+615
|
||||
money_paid = 0
|
||||
|
@ -171,7 +166,6 @@ def _stats_for_month(year, month):
|
|||
amount_all = mt.transfer.amount
|
||||
amount = amount_all / len(mt.transfer.member_transfers)
|
||||
money_paid += amount
|
||||
mc.set(cache_key, json.dumps([money_required, money_paid/100]))
|
||||
return money_required, money_paid/100
|
||||
|
||||
@_public_api_method("month/<year>/<month>")
|
||||
|
@ -180,7 +174,7 @@ def api_month(year=None, month=None):
|
|||
return dict(required=money_required, paid=money_paid)
|
||||
|
||||
@_public_api_method("mana")
|
||||
def api_manamana(year=None, month=None):
|
||||
def api_manamana():
|
||||
"""To-odee doo-dee-doo!"""
|
||||
now = datetime.datetime.now()
|
||||
money_required, money_paid = _stats_for_month(now.year, now.month)
|
||||
|
@ -195,11 +189,8 @@ def api_judgement(membername):
|
|||
return judgement
|
||||
|
||||
@_public_api_method("months_due/<membername>")
|
||||
@cache.memoize()
|
||||
def api_months_due(membername):
|
||||
cache_key = 'kasownik-months_due-{}'.format(membername)
|
||||
cache_data = mc.get(cache_key)
|
||||
if cache_data:
|
||||
return cache_data
|
||||
member = models.Member.query.filter_by(username=membername).first()
|
||||
if not member:
|
||||
raise APIError("No such member.", 404)
|
||||
|
@ -211,26 +202,17 @@ def api_months_due(membername):
|
|||
if year and member.active == False:
|
||||
raise APIError("No longer a member.", 410)
|
||||
due = member.get_months_due()
|
||||
#now = datetime.datetime.now()
|
||||
#then_timestamp = year * 12 + (month-1)
|
||||
#now_timestamp = now.year * 12 + (now.month-1)
|
||||
mc.set(cache_key, due)
|
||||
return due
|
||||
|
||||
@_public_api_method("cashflow/<int:year>/<int:month>")
|
||||
@cache.memoize()
|
||||
def api_cashflow(year, month):
|
||||
cache_key = 'kasownik-cashflow-{}-{}'.format(year, month)
|
||||
cache_data = mc.get(cache_key)
|
||||
if cache_data:
|
||||
amount_in = cache_data
|
||||
else:
|
||||
start = datetime.date(year=year, month=month, day=1)
|
||||
month += 1
|
||||
if month > 12:
|
||||
month = 1
|
||||
year += 1
|
||||
end = datetime.date(year=year, month=month, day=1)
|
||||
transfers = models.Transfer.query.filter(and_(models.Transfer.date >= start, models.Transfer.date < end, models.Transfer.ignore == False)).all()
|
||||
amount_in = sum(t.amount for t in transfers)
|
||||
mc.set(cache_key, amount_in)
|
||||
start = datetime.date(year=year, month=month, day=1)
|
||||
month += 1
|
||||
if month > 12:
|
||||
month = 1
|
||||
year += 1
|
||||
end = datetime.date(year=year, month=month, day=1)
|
||||
transfers = models.Transfer.query.filter(and_(models.Transfer.date >= start, models.Transfer.date < end, models.Transfer.ignore == False)).all()
|
||||
amount_in = sum(t.amount for t in transfers)
|
||||
return {"in": amount_in/100, "out": -1}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import requests
|
||||
from flask_login import AnonymousUserMixin
|
||||
|
||||
from webapp import models
|
||||
|
||||
class AnonymousUser(AnonymousUserMixin):
|
||||
def is_admin(self):
|
||||
return False
|
||||
|
||||
class User(object):
|
||||
def __init__(self, username):
|
||||
self.username = username.lower().strip()
|
||||
self._admin = None
|
||||
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
def is_anonymous(self):
|
||||
return False
|
||||
|
||||
def get_id(self):
|
||||
return self.username
|
||||
|
||||
def is_admin(self):
|
||||
if not self.is_authenticated():
|
||||
return False
|
||||
if self._admin is None:
|
||||
req = requests.get(
|
||||
'https://capacifier.hackerspace.pl/kasownik_access/%s'
|
||||
% self.username)
|
||||
self._admin = req.status_code == 200
|
||||
return self._admin
|
||||
|
||||
def get_model(self, deep=True):
|
||||
return models.Member.get_members(deep) \
|
||||
.filter_by(username=self.username).first()
|
|
@ -38,16 +38,22 @@ def connect():
|
|||
app.config['LDAP_BIND_PASSWORD'])
|
||||
return c
|
||||
|
||||
if not app.config.get('DISABLE_LDAP'):
|
||||
@app.before_request
|
||||
def _setup_ldap():
|
||||
@app.before_request
|
||||
def _setup_ldap():
|
||||
if not app.config.get('DISABLE_LDAP'):
|
||||
g.ldap = connect()
|
||||
else:
|
||||
g.ldap = None
|
||||
|
||||
@app.teardown_request
|
||||
def _destroy_ldap(exception=None):
|
||||
@app.teardown_request
|
||||
def _destroy_ldap(exception=None):
|
||||
if g.ldap:
|
||||
g.ldap.unbind_s()
|
||||
|
||||
def get_ldap_group_diff(members):
|
||||
if app.config.get('DISABLE_LDAP'):
|
||||
return None
|
||||
|
||||
active_members = filter(lambda m: m['judgement'], members)
|
||||
fatty = set([member['username'] for member in active_members if member['type'] in ['fatty', 'supporting']])
|
||||
starving = set([member['username'] for member in active_members if member['type'] in ['starving']])
|
||||
|
@ -118,6 +124,10 @@ def get_group_members(c, group):
|
|||
return members
|
||||
|
||||
def get_member_fields(c, member, fields):
|
||||
if app.config.get('DISABLE_LDAP'):
|
||||
import collections
|
||||
return collections.defaultdict(str)
|
||||
|
||||
if isinstance(fields, str):
|
||||
fields = [fields,]
|
||||
fields_needed = set(fields)
|
||||
|
|
|
@ -29,16 +29,16 @@ class MultiCheckboxField(SelectMultipleField):
|
|||
widget = widgets.ListWidget(prefix_label=False)
|
||||
option_widget = widgets.CheckboxInput()
|
||||
|
||||
class LoginForm(Form):
|
||||
class LoginForm(FlaskForm):
|
||||
username = TextField('Username', [validators.Required()])
|
||||
password = PasswordField('Password', [validators.Required()])
|
||||
|
||||
class ContactEmailSettingsForm(Form):
|
||||
class ContactEmailSettingsForm(FlaskForm):
|
||||
local = BooleanField("")
|
||||
ldap = BooleanField("")
|
||||
custom = TextField("Custom address:")
|
||||
|
||||
class LDAPSyncForm(Form):
|
||||
class LDAPSyncForm(FlaskForm):
|
||||
fatty_to_add = MultiCheckboxField("Fatty to add", choices=[])
|
||||
fatty_to_remove = MultiCheckboxField("Fatty to remove", choices=[])
|
||||
starving_to_add = MultiCheckboxField("Starving to add", choices=[])
|
||||
|
|
|
@ -35,7 +35,7 @@ from sqlalchemy.orm import subqueryload_all
|
|||
from sqlalchemy.sql.expression import or_
|
||||
from flask import g
|
||||
|
||||
from webapp import app, db, mc, cache_enabled
|
||||
from webapp import app, db, cache, cache_enabled
|
||||
import directory
|
||||
|
||||
|
||||
|
@ -61,8 +61,10 @@ class MemberTransfer(db.Model):
|
|||
self.year = year
|
||||
self.month = month
|
||||
self.transfer = transfer
|
||||
mc.delete('kasownik-stats_for_month-{}-{}'.format(year, month))
|
||||
mc.delete('kasownik-cashflow-{}-{}'.format(year, month))
|
||||
|
||||
from webapp import api
|
||||
cache.delete_memoized(api._stats_for_month, year, month)
|
||||
cache.delete_memoized(api.api_cashflow, year, month)
|
||||
|
||||
|
||||
class PaymentStatus(enum.Enum):
|
||||
|
@ -85,7 +87,9 @@ class Member(db.Model):
|
|||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(64), unique=True)
|
||||
type = db.Column(db.Enum("starving", "fatty", "supporting", name="member_types"))
|
||||
transfers = db.relationship("MemberTransfer",order_by=[db.asc(MemberTransfer.year), db.asc(MemberTransfer.month)])
|
||||
transfers = db.relationship("MemberTransfer", order_by=[
|
||||
db.asc(MemberTransfer.year), db.asc(MemberTransfer.month)])
|
||||
|
||||
# old field
|
||||
active = db.Column(db.Boolean)
|
||||
api_keys = db.relationship("APIKey")
|
||||
|
@ -122,15 +126,15 @@ class Member(db.Model):
|
|||
return rowspan + 1
|
||||
|
||||
@classmethod
|
||||
def get_members(kls, deep=False):
|
||||
def get_members(cls, deep=False):
|
||||
"""Gets all members as an SQLAlchemy query.
|
||||
@param(deep) - whether to do a subqueryload_all and load all transfer data
|
||||
"""
|
||||
if deep:
|
||||
return kls.query.options(subqueryload_all(kls.transfers,
|
||||
MemberTransfer.transfer)).order_by(kls.username)
|
||||
return cls.query.options(subqueryload_all(
|
||||
cls.transfers, MemberTransfer.transfer)).order_by(cls.username)
|
||||
else:
|
||||
return kls.query.order_by(kls.username)
|
||||
return cls.query.order_by(cls.username)
|
||||
|
||||
|
||||
def _yearmonth_increment(self, ym):
|
||||
|
@ -220,7 +224,6 @@ class Member(db.Model):
|
|||
previous_transfer = this_transfer
|
||||
previous_uid = this_uid
|
||||
|
||||
|
||||
# Apply missing payments from now
|
||||
if active_payment:
|
||||
previous_scalar = self._yearmonth_scalar(previous_transfer)
|
||||
|
@ -284,17 +287,19 @@ class Member(db.Model):
|
|||
|
||||
return email
|
||||
|
||||
def get_status(self, force_refresh = False):
|
||||
def get_status(self, force_refresh=False):
|
||||
"""It's better to call this after doing a full select of data."""
|
||||
cache_key = 'kasownik-payment_status-{}'.format(self.username)
|
||||
cache_data = mc.get(cache_key)
|
||||
cache_data = cache.get(cache_key)
|
||||
if cache_data and cache_enabled and not force_refresh:
|
||||
data = json.loads(cache_data)
|
||||
return data
|
||||
else:
|
||||
cache_data = self._get_status_uncached()
|
||||
mc.delete('kasownik-months_due-{}'.format(self.username))
|
||||
mc.set(cache_key, json.dumps(cache_data))
|
||||
|
||||
from webapp import api
|
||||
cache.delete_memoized(api.api_months_due, self.username)
|
||||
cache.set(cache_key, json.dumps(cache_data))
|
||||
return cache_data
|
||||
|
||||
def _apply_judgement(self, status):
|
||||
|
@ -304,7 +309,8 @@ class Member(db.Model):
|
|||
return
|
||||
policy = status['payment_policy']
|
||||
if policy == 'Normal':
|
||||
if status['payment_status'] == PaymentStatus.okay.value and status['last_paid'][0] is not None:
|
||||
if status['payment_status'] == PaymentStatus.okay.value \
|
||||
and status['last_paid'][0] is not None:
|
||||
status['judgement'] = True
|
||||
else:
|
||||
status['judgement'] = False
|
||||
|
@ -384,7 +390,7 @@ class Transfer(db.Model):
|
|||
member = Member.query.filter(or_(Member.username==member_name, Member.alias==member_name)).first()
|
||||
if not member:
|
||||
return self.MATCH_NO_USER, member_name, 0
|
||||
|
||||
|
||||
if title[2]:
|
||||
return self.MATCH_WRONG_TYPE, member, 0
|
||||
|
||||
|
|
|
@ -5,112 +5,68 @@
|
|||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-2 operations">
|
||||
<h4>Active operations:</h4>
|
||||
<div class="col-md-3 operations">
|
||||
<h4>Available operations:</h4>
|
||||
<p>
|
||||
<a href="/admin/match"><b>Match transfers</b></a>
|
||||
<a href="/admin/ldapsync"><b>Synchronize LDAP groups</b></a>
|
||||
<a href="/admin/spam"><b>Spam members</b></a>
|
||||
</p>
|
||||
<ul class="list-group">
|
||||
<a href="/admin/match" class="list-group-item">Match transfers</a>
|
||||
<a href="/admin/ldapsync" class="list-group-item">Synchronize LDAP groups</a>
|
||||
<a href="/admin/spam" class="list-group-item">Spam members</a>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="col-md-9">
|
||||
{% block admin_content %}
|
||||
<div class="row">
|
||||
{% for group in active_members|groupby("type") %}
|
||||
<div class="col-md-6">
|
||||
<h2>Active members, {{ group.grouper }}:</h2>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>LDAP Username</th>
|
||||
<th>Months Due</th>
|
||||
<th>Payment Policy</th>
|
||||
</tr>
|
||||
{% for member in group.list %}
|
||||
<tr>
|
||||
<td>{{loop.index}}.</td>
|
||||
<td>
|
||||
<a href="/admin/member/{{member.username}}">
|
||||
<b>{{member.username}}</b>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" style="background-color: #{{member.color}}">
|
||||
{{member.months_due}}
|
||||
</span>
|
||||
{% if member.last_transfer_bank != 'IdeaBank' %}
|
||||
<span class="badge" style="background-color: red">
|
||||
{{member.last_transfer_bank}}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% include "button_payment_policy.html" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<h2>Active members, {{ group.grouper }}:</h2>
|
||||
{{ members_list(group.list) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="col-md-6">
|
||||
<h2>Inactive-wannabes:</h2>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>LDAP Username</th>
|
||||
<th>Months Due</th>
|
||||
<th>Payment Policy</th>
|
||||
</tr>
|
||||
{% for member in inactive_members|selectattr("months_due") %}
|
||||
<tr>
|
||||
<td>{{loop.index}}.</td>
|
||||
<td>
|
||||
<a href="/admin/member/{{member.username}}">
|
||||
<b>{{member.username}}</b>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" style="background-color: #{{member.color}}">
|
||||
{{member.months_due}}
|
||||
</span>
|
||||
{% if member.last_transfer_bank != 'IdeaBank' %}
|
||||
<span class="badge" style="background-color: red">
|
||||
{{member.last_transfer_bank}}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% include "button_payment_policy.html" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{{ members_list(inactive_members|selectattr("months_due")) }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h2>Inactive members:</h2>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>LDAP Username</th>
|
||||
<th>Months Due</th>
|
||||
<th>Payment Policy</th>
|
||||
</tr>
|
||||
{% for member in inactive_members|rejectattr("months_due") %}
|
||||
<tr>
|
||||
<td>{{loop.index}}.</td>
|
||||
<td>
|
||||
<a href="/admin/member/{{member.username}}">
|
||||
<b>{{member.username}}</b>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" style="background-color: #{{member.color}}">
|
||||
{{member.months_due}}
|
||||
</span>
|
||||
</td>
|
||||
<td>{% include "button_payment_policy.html" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Inactive members:</h2>
|
||||
{{ members_list(inactive_members|rejectattr("months_due")) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro members_list(members) %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>LDAP Username</th>
|
||||
<th>Months Due</th>
|
||||
<th>Payment Policy</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td>{{loop.index}}.</td>
|
||||
<td>
|
||||
<a href="/admin/member/{{member.username}}">
|
||||
<b>{{member.username}}</b>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" style="background-color: #{{member.color}}">
|
||||
{{member.months_due}}
|
||||
</span>
|
||||
{% if member.active and member.last_transfer_bank != 'IdeaBank' %}
|
||||
<span class="badge" style="background-color: red">
|
||||
{{member.last_transfer_bank}}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% include "button_payment_policy.html" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -1,52 +1,35 @@
|
|||
{% extends "root.html" %}
|
||||
{% extends "admin_index.html" %}
|
||||
{% set active_page = "admin" %}
|
||||
{% block title %}Admin LDAP Sync{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-2 operations">
|
||||
<h4>Active operations:</h4>
|
||||
<h4>Available operations:</h4>
|
||||
<p>
|
||||
<a href="/admin/match"><b>Match transfers</b></a>
|
||||
<a href="/admin/ldapsync"><b>Synchronize LDAP groups</b></a>
|
||||
</p>
|
||||
{% block admin_content %}
|
||||
{% if not form %}
|
||||
<h2>No sync required - groups are up to date.</h2>
|
||||
{% else %}
|
||||
<form method="post" action="/admin/ldapsync">
|
||||
{{ form.hidden_tag() }}
|
||||
<div class="col-md-12">
|
||||
<input type="submit" value="Sync" />
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="row">
|
||||
{% if not form %}
|
||||
<div class="col-md-12">
|
||||
<h2>No sync required - groups are up to date.</h2>
|
||||
</div>
|
||||
{% else %}
|
||||
<form method="post" action="/admin/ldapsync">
|
||||
<div class="col-md-12">
|
||||
<input type="submit" value="Sync" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Fatty to add:</h2>
|
||||
{{ form.fatty_to_add() }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Fatty to remove:</h2>
|
||||
{{ form.fatty_to_remove() }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Starving to add:</h2>
|
||||
{{ form.starving_to_add() }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Starving to remove:</h2>
|
||||
{{ form.starving_to_remove() }}
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<input type="submit" value="Sync" />
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Fatty to add:</h2>
|
||||
{{ form.fatty_to_add() }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Fatty to remove:</h2>
|
||||
{{ form.fatty_to_remove() }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Starving to add:</h2>
|
||||
{{ form.starving_to_add() }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h2>Starving to remove:</h2>
|
||||
{{ form.starving_to_remove() }}
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<input type="submit" value="Sync" />
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
</p>
|
||||
<h3>Transfers</h3>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>UID</th>
|
||||
<th>Amount</th>
|
||||
|
@ -79,6 +80,7 @@
|
|||
<th>Date</th>
|
||||
<th>Covers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% set rowspan_left = -1 %}
|
||||
{% for mt in member.transfers %}
|
||||
{% if mt.transfer.uid != "NOTAMEMBER" %}
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
{% extends "root.html" %}
|
||||
{% extends "admin_index.html" %}
|
||||
{% set active_page = "admin" %}
|
||||
{% block title %}Admin LDAP Sync{% endblock %}
|
||||
{% block content %}
|
||||
{% block title %}Admin Spam Mailing™{% endblock %}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form method="post" action="/admin/spam/">
|
||||
<button>Spam</button>
|
||||
{% block admin_content %}
|
||||
<form method="post" action="/admin/spam/" class="form-inline">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<button class="btn btn-warning">Spam</button>
|
||||
<div class="checkbox">
|
||||
{{ form.dry_run() }} {{ form.dry_run.label }}
|
||||
|
||||
{{ form.hidden_tag() }}
|
||||
{{ form.members() }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
{{ form.members() }}
|
||||
</p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
{% block title %}Sign in{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<form class="form-signin" method="post" action="/login">
|
||||
<label for="inputEmail" class="sr-only">Username</label>
|
||||
<input type="text" id="username" class="form-control" name="username" placeholder="Username" required autofocus>
|
||||
<label for="inputPassword" class="sr-only">Password</label>
|
||||
<input type="password" id="password" class="form-control" name="password" placeholder="Password" required>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
||||
<form class="form-signin" method="post" action="/login">
|
||||
{{ form.hidden_tag() }}
|
||||
<label for="inputEmail" class="sr-only">Username</label>
|
||||
<input type="text" id="username" class="form-control" name="username" placeholder="Username" required autofocus>
|
||||
<label for="inputPassword" class="sr-only">Password</label>
|
||||
<input type="password" id="password" class="form-control" name="password" placeholder="Password" required>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
{% extends "root.html" %}
|
||||
{% extends "admin_index.html" %}
|
||||
{% block title %}Match transfers{% endblock %}
|
||||
{% block content %}
|
||||
{% block admin_content %}
|
||||
|
||||
<div class="container">
|
||||
<h2>Match transfers</h2>
|
||||
<h2>Matching operations</h2>
|
||||
<a href="/admin/match/auto">Match all easily matchable transfers</a><br />
|
||||
<a href="/admin/match/manual">Match manually all unmatched transfers</a><br />
|
||||
<h2>Unmatched transfers - troublesome</h2>
|
||||
<ul>
|
||||
{% for t in transfers_unmatched %}
|
||||
{% if t.get_matchability()[0] > 0 %}
|
||||
<li>{{ t.title }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h2>Unmatched transfers - easily matchable</h2>
|
||||
<ul>
|
||||
{% for t in transfers_unmatched %}
|
||||
{% set details = t.get_matchability() %}
|
||||
{% if details[0] == 0 %}
|
||||
<li><em>{{ t.title }}</em>: {{ details[2] }} months from {{ details[1].get_next_unpaid() }} for {{ details[1].username }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
<h3>Matching operations</h3>
|
||||
<a href="/admin/match/auto">Match all easily matchable transfers</a><br />
|
||||
<a href="/admin/match/manual">Match manually all unmatched transfers</a><br />
|
||||
</p>
|
||||
<p>
|
||||
<h3>Unmatched transfers - troublesome</h3>
|
||||
<ul>
|
||||
{% for t in transfers_unmatched %}
|
||||
{% if t.get_matchability()[0] > 0 %}
|
||||
<li>{{ t.title }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<h3>Unmatched transfers - easily matchable</h3>
|
||||
<ul>
|
||||
{% for t in transfers_unmatched %}
|
||||
{% set details = t.get_matchability() %}
|
||||
{% if details[0] == 0 %}
|
||||
<li><em>{{ t.title }}</em>: {{ details[2] }} months from {{ details[1].get_next_unpaid() }} for {{ details[1].username }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{% extends "root.html" %}
|
||||
{% extends "admin_index.html" %}
|
||||
{% block title %}manual match{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
{% block admin_content %}
|
||||
<script>
|
||||
function payment(months, uid, username)
|
||||
{
|
||||
|
@ -18,48 +17,76 @@
|
|||
{% set extra = t.get_matchability()[1] %}
|
||||
|
||||
{% if matchability > 0 %}
|
||||
<div id="uid-{{t.uid}}">
|
||||
<h2>{{t.title}}</h2>
|
||||
Amount: {{t.amount/100}} <br />
|
||||
Title: {{t.title}} <br />
|
||||
|
||||
<div id="uid-{{t.uid}}" class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{{t.title}} <small>({{t.amount/100}} PLN)</small>
|
||||
<a href="{{ url_for("admin.ignore", uid=t.uid) }}" title="Ignore this transfer" class="label label-default pull-right" style="color: white; margin-left:5px">×</a>
|
||||
{% if matchability == 1 %}
|
||||
<span class="label label-danger pull-right">unknown type</span>
|
||||
{% elif matchability == 2 %}
|
||||
<span class="label label-danger pull-right">unknown member</span>
|
||||
{% elif matchability == 3 %}
|
||||
<span class="label label-danger pull-right">unparseable</span>
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
{% if matchability == 1 %}
|
||||
<h3>Previous transfers...</h3>
|
||||
<ul>
|
||||
{% for member_transfer in extra.transfers %}
|
||||
<li>{{ member_transfer.transfer.title }} for <b>{{ member_transfer.transfer.amount/100 }}</b> </li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!--<div class="panel-body">
|
||||
</div>
|
||||
-->
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr><th>Previous transfers</th></tr>
|
||||
</thead>
|
||||
{% for member_transfer in extra.transfers[:3] %}
|
||||
<tr><td>{{ member_transfer.transfer.title }} for <b>{{ member_transfer.transfer.amount/100 }}</b> </td></tr>
|
||||
{% else %}
|
||||
<tr><td>Nothing...</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% set fattycount = t.amount/10000 %}
|
||||
{% set starvingcount = t.amount/5000 %}
|
||||
<b>Unknown type... </b><br />
|
||||
<a href="javascript:payment(1, '{{t.uid}}', '{{ extra.username }}')">This is a one-time payment.</a><br />
|
||||
<a href="javascript:payment({{fattycount|int}}, '{{t.uid}}', '{{ extra.username }}')">This is a {{fattycount|int}}-time payment. (fatty)</a><br />
|
||||
<a href="javascript:payment({{starvingcount|int}}, '{{t.uid}}', '{{ extra.username }}')">This is a {{starvingcount|int}}-time payment. (starving)</a><br />
|
||||
<a href="javascript:payment(prompt('months?', '0'), '{{t.uid}}', '{{ extra.username }}')">This is a N-time payment. (superfatty?)</a><br />
|
||||
|
||||
<div class="list-group">
|
||||
<a href="javascript:payment(1, '{{t.uid}}', '{{ extra.username }}')" class="list-group-item">This is a one-time payment.</a>
|
||||
<a href="javascript:payment({{fattycount|int}}, '{{t.uid}}', '{{ extra.username }}')" class="list-group-item">This is a {{fattycount|int}}-time payment. (fatty)</a>
|
||||
<a href="javascript:payment({{starvingcount|int}}, '{{t.uid}}', '{{ extra.username }}')" class="list-group-item">This is a {{starvingcount|int}}-time payment. (starving)</a>
|
||||
<a href="javascript:payment(prompt('months?', '0'), '{{t.uid}}', '{{ extra.username }}')" class="list-group-item">This is a N-time payment. (superfatty?)</a>
|
||||
</div>
|
||||
{% elif matchability == 2 %}
|
||||
<b>Unknown member...</b><br />
|
||||
<a href="/admin/member/add/starving/{{extra}}">Add member {{extra}} - starving</a><br />
|
||||
<a href="/admin/member/add/fatty/{{extra}}">Add member {{extra}} - fatty</a><br />
|
||||
<a href="/admin/member/add/supporting/{{extra}}">Add member {{extra}} - supporting</a>
|
||||
<div class="list-group">
|
||||
<a href="/admin/member/add/starving/{{extra}}" class="list-group-item">Add member <b>{{extra}}</b> - starving</a>
|
||||
<a href="/admin/member/add/fatty/{{extra}}" class="list-group-item">Add member <b>{{extra}}</b> - fatty</a>
|
||||
<a href="/admin/member/add/supporting/{{extra}}" class="list-group-item">Add member <b>{{extra}}</b> - supporting</a>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="POST" action="/admin/match/">
|
||||
username: <input name="username" />
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Username" name="username" />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button">Match other username</button>
|
||||
</span>
|
||||
</div>
|
||||
<input type="hidden" name="uid" value="{{t.uid}}" />
|
||||
<input type="submit" value="match other username" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
{% elif matchability == 3 %}
|
||||
<b>Unparseable...</b>
|
||||
<div class="panel-body">
|
||||
<form method="POST" action="/admin/match/">
|
||||
username: <input name="username" />
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Username" name="username" />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button">Match</button>
|
||||
</span>
|
||||
</div>
|
||||
<input type="hidden" name="uid" value="{{t.uid}}" />
|
||||
<input type="submit" value="match" />
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
<script>
|
||||
function payment(months, uid, username)
|
||||
{
|
||||
window.location = "/admin/match/" + username + "/" + months + "/" + uid;
|
||||
return; // FIXME
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/admin/match/" + username + "/" + months + "/" + uid, true);
|
||||
xhr.send();
|
||||
|
|
|
@ -7,14 +7,17 @@
|
|||
<div class="col-md-4">
|
||||
<h1>Active Members</h1>
|
||||
<p>Membership fees in order, has full access to the space and private mailing lists.</p>
|
||||
<small>Total: {{ active_members|count }}</small>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Membership type</th>
|
||||
<th>Member since</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for member in active_members %}
|
||||
<tr>
|
||||
<td>{{member['username']}}</td>
|
||||
|
|
|
@ -63,11 +63,11 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flashes">
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-info alert-dismissible" role="alert">
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'info' if category == 'message' else category }} alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
{{message}}
|
||||
</div>
|
||||
|
@ -76,7 +76,7 @@
|
|||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
|
@ -24,20 +24,13 @@
|
|||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
import json
|
||||
import requests
|
||||
import re
|
||||
from email.mime.text import MIMEText
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from webapp import app, forms, User, db, models, mc, cache_enabled, admin_required
|
||||
from flask.ext.login import login_user, login_required, logout_user, current_user
|
||||
from flask import Response, request, redirect, flash, render_template, url_for, abort, g
|
||||
import logic
|
||||
from flask_login import login_user, login_required, logout_user, current_user
|
||||
|
||||
from webapp import app, forms, User, models, cache
|
||||
import directory
|
||||
import traceback
|
||||
|
||||
@app.route('/')
|
||||
def stats():
|
||||
|
@ -45,314 +38,45 @@ def stats():
|
|||
|
||||
@app.route('/memberlist')
|
||||
@login_required
|
||||
@cache.cached()
|
||||
def memberlist():
|
||||
cache_key = 'kasownik-view-memberlist'
|
||||
cache_data = mc.get(cache_key)
|
||||
if not cache_data or not cache_enabled:
|
||||
members = models.Member.get_members(True)
|
||||
cache_data = []
|
||||
for member in members:
|
||||
element = member.get_status()
|
||||
if not element['judgement']:
|
||||
continue
|
||||
cache_data.append(element)
|
||||
mc.set(cache_key, cache_data)
|
||||
return render_template('memberlist.html',
|
||||
active_members=cache_data)
|
||||
members = models.Member.get_members(True)
|
||||
result = []
|
||||
for member in members:
|
||||
element = member.get_status()
|
||||
if not element['judgement']:
|
||||
continue
|
||||
result.append(element)
|
||||
|
||||
return render_template('memberlist.html', active_members=result)
|
||||
|
||||
@app.route('/profile', methods=['POST', 'GET'])
|
||||
@login_required
|
||||
def self_profile():
|
||||
member = models.Member.get_members(True).filter_by(username=current_user.username).first()
|
||||
member = current_user.get_model()
|
||||
if not member:
|
||||
abort(404)
|
||||
status = member.get_status()
|
||||
cn = directory.get_member_fields(g.ldap, member.username, 'cn')['cn']
|
||||
|
||||
#cesform = forms.ContactEmailSettingsForm(request.form)
|
||||
|
||||
#if request.method == "POST" and cesform.validate():
|
||||
# pe = request.form['preferred_email']
|
||||
|
||||
# member.preferred_email = request.form['preferred_email']
|
||||
#db.session.add(member)
|
||||
#db.session.commit()
|
||||
return render_template("admin_member.html", member=member, status=status,
|
||||
cn=cn, admin=False)
|
||||
|
||||
@app.route("/admin")
|
||||
@admin_required
|
||||
@login_required
|
||||
def admin_index():
|
||||
members = [m.get_status() for m in models.Member.get_members(True)]
|
||||
for member in members:
|
||||
due = member['months_due']
|
||||
if due < 1:
|
||||
member['color'] = "00FF00"
|
||||
elif due < 3:
|
||||
member['color'] = "E0941B"
|
||||
else:
|
||||
member['color'] = "FF0000"
|
||||
|
||||
active_members = filter(lambda m: m['judgement'], members)
|
||||
inactive_members = filter(lambda m: not m['judgement'], members)
|
||||
diff = directory.get_ldap_group_diff(members)
|
||||
if diff is not None:
|
||||
flash("LDAP sync required")
|
||||
return render_template("admin_index.html",
|
||||
active_members=active_members,
|
||||
inactive_members=inactive_members)
|
||||
|
||||
|
||||
@app.route("/admin/ldapsync", methods=["POST", "GET"])
|
||||
@admin_required
|
||||
@login_required
|
||||
def admin_ldap_sync():
|
||||
members = [m.get_status() for m in models.Member.get_members(True)]
|
||||
diff = directory.get_ldap_group_diff(members)
|
||||
if diff is None:
|
||||
return render_template("admin_ldap_sync.html", form=False)
|
||||
|
||||
form = forms.LDAPSyncForm(request.form)
|
||||
|
||||
form.fatty_to_add.choices = zip(diff['fatty_to_add'],diff['fatty_to_add'])
|
||||
form.fatty_to_add.default = diff['fatty_to_add']
|
||||
|
||||
form.fatty_to_remove.choices = zip(diff['fatty_to_remove'],diff['fatty_to_remove'])
|
||||
form.fatty_to_remove.default = diff['fatty_to_remove']
|
||||
|
||||
form.starving_to_add.choices = zip(diff['starving_to_add'],diff['starving_to_add'])
|
||||
form.starving_to_add.default = diff['starving_to_add']
|
||||
|
||||
form.starving_to_remove.choices = zip(diff['starving_to_remove'],diff['starving_to_remove'])
|
||||
form.starving_to_remove.default = diff['starving_to_remove']
|
||||
|
||||
form.process(request.form)
|
||||
if request.method == "POST" and form.validate():
|
||||
changes = {'fatty': {}, 'starving': {}}
|
||||
changes['fatty']['add'] = form.fatty_to_add.data
|
||||
changes['fatty']['remove'] = form.fatty_to_remove.data
|
||||
changes['starving']['add'] = form.starving_to_add.data
|
||||
changes['starving']['remove'] = form.starving_to_remove.data
|
||||
|
||||
directory.update_member_groups(g.ldap, changes)
|
||||
|
||||
return render_template("admin_ldap_sync.html", form=form)
|
||||
|
||||
|
||||
@app.route("/admin/csv")
|
||||
@admin_required
|
||||
@login_required
|
||||
def admin_csv():
|
||||
members = []
|
||||
for m in models.Member.get_members(True):
|
||||
member = m.get_status()
|
||||
if member['type'] == 'supporting':
|
||||
continue
|
||||
member['contact_email'] = m.get_contact_email()
|
||||
member['cn'] = directory.get_member_fields(g.ldap, member['username'], 'cn')['cn']
|
||||
members.append(member)
|
||||
|
||||
active_members = filter(lambda m: m['judgement'], members)
|
||||
output = render_template("admin_csv.html", active_members=active_members)
|
||||
return Response(output)
|
||||
|
||||
@app.route('/admin/member/<membername>')
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_member(membername):
|
||||
member = models.Member.get_members(True).filter_by(username=membername).first()
|
||||
if not member:
|
||||
abort(404)
|
||||
status = member.get_status()
|
||||
cn = directory.get_member_fields(g.ldap, member.username, 'cn')['cn']
|
||||
return render_template("admin_member.html", member=member, status=status,
|
||||
cn=cn, admin=True)
|
||||
|
||||
@app.route("/admin/member/<membername>/policy:<policy>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_member_set_policy(membername,policy):
|
||||
member = models.Member.query.filter_by(username=membername).first()
|
||||
member.payment_policy = models.PaymentPolicy[policy].value
|
||||
db.session.add(member)
|
||||
db.session.commit()
|
||||
return redirect(request.referrer)
|
||||
|
||||
@app.route("/admin/member/<membername>/membership:<membershiptype>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_member_set_membership(membername,membershiptype):
|
||||
member = models.Member.query.filter_by(username=membername).first()
|
||||
member.type = models.MembershipType[membershiptype].name
|
||||
db.session.add(member)
|
||||
db.session.commit()
|
||||
return redirect(request.referrer)
|
||||
|
||||
|
||||
@app.route("/admin/member/add/<membershiptype>/<username>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def add_member(membershiptype, username):
|
||||
member = models.Member(None, username, models.MembershipType[membershiptype].name, True)
|
||||
db.session.add(member)
|
||||
db.session.commit()
|
||||
return "ok"
|
||||
|
||||
@app.route("/admin/match")
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_match():
|
||||
transfers_unmatched = logic.get_unmatched_transfers()
|
||||
return render_template("match.html", transfers_unmatched=transfers_unmatched)
|
||||
|
||||
|
||||
@app.route("/admin/match/auto", methods=["GET"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_match_auto():
|
||||
matched = 0
|
||||
left = 0
|
||||
transfers_unmatched = logic.get_unmatched_transfers()
|
||||
affected_members = []
|
||||
for transfer in transfers_unmatched:
|
||||
matchability, member, months = transfer.get_matchability()
|
||||
try:
|
||||
print "[i] Matching transfer {} for {:.2f}PLN by member {}, {} months".format(transfer.id, transfer.amount/100, member.username, months)
|
||||
except AttributeError:
|
||||
print "[e] Member data invalid, WTF - {}".format(repr(member))
|
||||
continue
|
||||
if matchability == models.Transfer.MATCH_OK:
|
||||
if len(member.transfers) > 0:
|
||||
year, month = member.get_next_unpaid()
|
||||
if None in (year, month):
|
||||
print "[w] next_unpaid borked, skipping"
|
||||
continue
|
||||
else:
|
||||
year, month = transfer.date.year, transfer.date.month
|
||||
for m in range(months):
|
||||
mt = models.MemberTransfer(None, year, month, transfer)
|
||||
member.transfers.append(mt)
|
||||
db.session.add(mt)
|
||||
flash("Matched transfer {} for {:.2f}PLN to member {} for month {}-{}".format(transfer.id, transfer.amount/100, member.username, year, month))
|
||||
year, month = member._yearmonth_increment((year,month))
|
||||
matched += 1
|
||||
affected_members.append(member)
|
||||
else:
|
||||
left += 1
|
||||
db.session.commit()
|
||||
for member in affected_members:
|
||||
member.get_status(force_refresh=True)
|
||||
flash("Matched %i, %i left" % (matched, left))
|
||||
return redirect(url_for("admin_match"))
|
||||
|
||||
@app.route("/admin/match/manual", methods=["GET"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def match_manual():
|
||||
transfers_unmatched = logic.get_unmatched_transfers()
|
||||
return render_template("match_manual.html", transfers_unmatched=transfers_unmatched)
|
||||
|
||||
@app.route("/admin/match/<username>/<int:months>/<path:uid>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def match(username, uid, months):
|
||||
member = models.Member.query.filter_by(username=username).first()
|
||||
if not member:
|
||||
return "no member"
|
||||
transfer = models.Transfer.query.filter_by(uid=uid).first()
|
||||
if not transfer:
|
||||
return "no transfer"
|
||||
|
||||
for _ in range(months):
|
||||
year, month = member.get_next_unpaid()
|
||||
mt = models.MemberTransfer(None, year, month, transfer)
|
||||
member.transfers.append(mt)
|
||||
db.session.add(mt)
|
||||
|
||||
db.session.commit()
|
||||
member.get_status(force_refresh=True)
|
||||
return "ok, %i PLN get!" % transfer.amount
|
||||
|
||||
|
||||
@app.route("/admin/match/", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def match_user_transfer():
|
||||
username = request.form["username"]
|
||||
uid = request.form["uid"]
|
||||
member = models.Member.query.filter_by(username=username).first()
|
||||
if not member:
|
||||
return "no such member! :("
|
||||
transfer = models.Transfer.query.filter_by(uid=uid).first()
|
||||
if not transfer:
|
||||
return "no 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 <fascist@hackerspace.pl>"
|
||||
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)
|
||||
if request.method == "POST" and form.validate():
|
||||
if requests.post("https://auth.hackerspace.pl/",
|
||||
dict(login=form.username.data, password=form.password.data)).status_code == 200:
|
||||
if requests.post("https://auth.hackerspace.pl/", {
|
||||
'login': form.username.data,
|
||||
'password': form.password.data}).status_code == 200:
|
||||
user = User(form.username.data)
|
||||
login_user(user)
|
||||
flash('Logged in succesfully')
|
||||
|
||||
if user.is_admin():
|
||||
return redirect(request.args.get("next") or url_for("admin_index"))
|
||||
else:
|
||||
return redirect(request.args.get("next") or url_for("self_profile"))
|
||||
return redirect(request.args.get("next") or url_for("admin.index"))
|
||||
|
||||
return redirect(request.args.get("next") or url_for("self_profile"))
|
||||
return render_template("login.html", form=form)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue