Skip to content
Permalink
Browse files

Merge branch 'membership-termination' into develop

  • Loading branch information...
ibot3 committed Sep 18, 2019
2 parents ad779d1 + 097ede2 commit 30fe0db8b538276f2aec593e4e333395591573cb
@@ -52,8 +52,7 @@ def error_handler_redirection(e):
if e.code == 401:
message = gettext("Bitte melde Dich an, um die Seite zu sehen.")
elif e.code == 403:
message = gettext("Diese Funktion wird in deinem Wohnheim "
"nicht unterstützt.")
message = gettext("Diese Funktion steht dir derzeit nicht zur Verfügung.")
elif e.code == 404:
message = gettext("Das von Dir angeforderte Dokument gibt es nicht.")
else:
@@ -2,30 +2,39 @@

"""Blueprint for Usersuite components
"""

from collections import OrderedDict
import logging
from datetime import datetime

from babel.numbers import format_currency
from flask import Blueprint, render_template, url_for, redirect, flash, abort
from flask import Blueprint, render_template, url_for, redirect, flash, abort, request
from flask_babel import format_date, gettext
from flask_login import current_user, login_required

from sipa.config.default import MEMBERSHIP_CONTRIBUTION
from sipa.forms import ContactForm, ChangeMACForm, ChangeMailForm, \
ChangePasswordForm, flash_formerrors, HostingForm, DeleteMailForm, \
ChangeUseCacheForm, PaymentForm, ActivateNetworkAccessForm
ChangeUseCacheForm, PaymentForm, ActivateNetworkAccessForm, TerminateMembershipForm, \
TerminateMembershipConfirmForm, ContinueMembershipForm
from sipa.mail import send_usersuite_contact_mail
from sipa.model.fancy_property import ActiveProperty
from sipa.utils import password_changeable
from sipa.model.exceptions import DBQueryEmpty, LDAPConnectionError, \
PasswordInvalid, UserNotFound, MacAlreadyExists
PasswordInvalid, UserNotFound, MacAlreadyExists, TerminationNotPossible, UnknownError, \
ContinuationNotPossible
from sipa.model.misc import PaymentDetails

logger = logging.getLogger(__name__)

bp_usersuite = Blueprint('usersuite', __name__, url_prefix='/usersuite')


def capability_or_403(active_property, capability):
prop: ActiveProperty = getattr(current_user, active_property)
if not getattr(prop.capabilities, capability):
abort(403)


@bp_usersuite.route("/", methods=['GET', 'POST'])
@login_required
def index():
@@ -44,7 +53,7 @@ def index():
('id', gettext("Nutzer-ID")),
('realname', gettext("Voller Name")),
('login', gettext("Accountname")),
('status', gettext("Mitgliedsschaftsstatus")),
('status', gettext("Mitgliedschaftsstatus")),
('address', gettext("Aktuelles Zimmer")),
('ips', gettext("Aktuelle IP-Adresse")),
('mac', gettext("Aktuelle MAC-Adresse")),
@@ -75,6 +84,8 @@ def index():
datasource = current_user.datasource
context = dict(rows=rows,
webmailer_url=datasource.webmailer_url,
terminate_membership_url=url_for('.terminate_membership'),
continue_membership_url=url_for('.continue_membership'),
payment_details=render_payment_details(current_user.payment_details(),
months),
girocode=generate_epc_qr_code(current_user.payment_details(), months))
@@ -218,6 +229,8 @@ def change_password():
def change_mail():
"""Frontend page to change the user's mail address"""

capability_or_403('mail', 'edit')

form = ChangeMailForm()

if form.validate_on_submit():
@@ -248,6 +261,9 @@ def delete_mail():
"""Resets the users forwarding mail attribute
in his LDAP entry.
"""

capability_or_403('mail', 'delete')

form = DeleteMailForm()

if form.validate_on_submit():
@@ -276,6 +292,9 @@ def delete_mail():
def change_mac():
"""As user, change the MAC address of your device.
"""

capability_or_403('mac', 'edit')

form = ChangeMACForm()

if form.validate_on_submit():
@@ -314,6 +333,9 @@ def change_mac():
def activate_network_access():
"""As user, activate your network access
"""

capability_or_403('network_access_active', 'edit')

form = ActivateNetworkAccessForm()

if form.validate_on_submit():
@@ -351,6 +373,9 @@ def activate_network_access():
def change_use_cache():
"""As user, change your usage of the cache.
"""

capability_or_403('use_cache', 'edit')

form = ChangeUseCacheForm()

if form.validate_on_submit():
@@ -408,3 +433,136 @@ def hosting(action=None):
@login_required
def finance_logs():
return redirect(url_for('usersuite.index', _anchor='transaction-log'))


@bp_usersuite.route("/terminate-membership", methods=['GET', 'POST'])
@login_required
def terminate_membership():
"""
As member, cancel your membership to a given date
:return:
"""

capability_or_403('membership_end_date', 'edit')

if current_user.membership_end_date.raw_value is not None:
abort(403)

form = TerminateMembershipForm()

if form.validate_on_submit():
end_date = form.end_date.data

return redirect(url_for('.terminate_membership_confirm',
end_date=end_date))
elif form.is_submitted():
flash_formerrors(form)

form_args = {
'form': form,
'cancel_to': url_for('.index'),
'submit_text': gettext('Weiter')
}

return render_template('generic_form.html',
page_title=gettext("Mitgliedschaft beenden"),
form_args=form_args)


@bp_usersuite.route("/terminate-membership/confirm", methods=['GET', 'POST'])
@login_required
def terminate_membership_confirm():
"""
As member, cancel your membership to a given date
:return:
"""

capability_or_403('membership_end_date', 'edit')

if current_user.membership_end_date.raw_value is not None:
abort(403)

end_date = request.args.get("end_date", None, lambda x: datetime.strptime(x, '%Y-%m-%d').date())

form = TerminateMembershipConfirmForm()

if end_date is not None:
try:
form.estimated_balance.default = current_user.estimate_balance(
end_date)

except UnknownError:
flash(gettext("Unbekannter Fehler!"), "error")
else:
form.end_date.default = end_date
else:
return redirect(url_for('.terminate_membership'))

if form.validate_on_submit():
try:
current_user.terminate_membership(form.end_date.data)
except TerminationNotPossible:
flash(gettext("Beendigung der Mitgliedschaft nicht möglich!"), "error")
except MacAlreadyExists:
flash(gettext("Unbekannter Fehler!"), "error")
else:
logger.info('Successfully scheduled membership termination',
extra={'data': {'end_date': form.end_date.data},
'tags': {'rate_critical': True}})

flash(gettext("Deine Mitgliedschaft wird zum angegebenen Datum beendet."), 'success')

return redirect(url_for('.index'))
elif form.is_submitted():
flash_formerrors(form)

form_args = {
'form': form,
'cancel_to': url_for('.terminate_membership')
}

return render_template('generic_form.html',
page_title=gettext("Mitgliedschaft beenden - Bestätigen"),
form_args=form_args)


@bp_usersuite.route("/continue-membership", methods=['GET', 'POST'])
@login_required
def continue_membership():
"""
Cancel termination of membership
:return:
"""

capability_or_403('membership_end_date', 'edit')

if current_user.membership_end_date.raw_value is None:
abort(403)

form = ContinueMembershipForm()

if form.validate_on_submit():
try:
current_user.continue_membership()
except ContinuationNotPossible:
flash(gettext("Fortsetzung der Mitgliedschaft nicht möglich!"), "error")
except UnknownError:
flash(gettext("Unbekannter Fehler!"), "error")
else:
logger.info('Successfully cancelled membership termination',
extra={'tags': {'rate_critical': True}})

flash(gettext("Deine Mitgliedschaft wird fortgesetzt."), 'success')

return redirect(url_for('.index'))
elif form.is_submitted():
flash_formerrors(form)

form_args = {
'form': form,
'cancel_to': url_for('.index')
}

return render_template('generic_form.html',
page_title=gettext("Mitgliedschaft fortsetzen"),
form_args=form_args)
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
import re
from datetime import date
from operator import itemgetter

from flask_babel import gettext, lazy_gettext
from flask import flash
from flask_wtf import FlaskForm
from werkzeug.local import LocalProxy
from wtforms import (BooleanField, HiddenField, PasswordField, SelectField,
StringField, TextAreaField, RadioField, IntegerField, DateField)
StringField, TextAreaField, RadioField, IntegerField, DateField, FloatField)
from wtforms.validators import (AnyOf, DataRequired, Email, EqualTo,
MacAddress, Regexp, ValidationError, NumberRange, Optional, Length)

@@ -230,6 +231,50 @@ class ActivateNetworkAccessForm(FlaskForm):
)


class TerminateMembershipForm(FlaskForm):
end_date = DateField(label=lazy_gettext("Austrittsdatum"),
validators=[DataRequired(lazy_gettext("Austrittsdatum nicht angegeben!"))],
description=lazy_gettext("YYYY-MM-DD (z.B. 2018-10-01)"))

def validate_end_date(form, field):
if field.data < date.today():
raise ValidationError(lazy_gettext("Das Austrittsdatum darf nicht in der Vergangenheit "
"liegen!"))


class TerminateMembershipConfirmForm(FlaskForm):
end_date = DateField(label=lazy_gettext("Austrittsdatum"),
render_kw={'readonly': True},
validators=[DataRequired("invalid end date")])

estimated_balance = FloatField(label=lazy_gettext("Geschätzter Kontostand (in EUR) zum Ende der Mitgliedschaft"),
render_kw={'readonly': True},
validators=[DataRequired("invalid balance")])

confirm_termination = BooleanField(default=lazy_gettext(
"Ich bestätige, dass ich meine Mitgliedschaft zum obenstehenden Datum beenden möchte"),
validators=[
DataRequired(lazy_gettext("Bitte bestätige die Beendigung der Mitgliedschaft"))])

confirm_settlement = BooleanField(default=lazy_gettext(
"Ich bestätige, dass ich ggf. ausstehende Beiträge baldmöglichst bezahle"),
validators=[
DataRequired(lazy_gettext("Bitte bestätige die baldmöglichste Bezahlung von ausstehenden Beiträgen."))])

confirm_donation = BooleanField(default=lazy_gettext(
"Ich bestätige, dass ich zu viel gezahltes Guthaben spende, wenn ich nicht innerhalb "
"von 31 Tagen nach Mitgliedschaftsende einen Rückerüberweisungsantrag stelle"),
validators=[
DataRequired(lazy_gettext("Bitte bestätige die Spendeneinwilligung."))])


class ContinueMembershipForm(FlaskForm):
confirm_continuation = BooleanField(default=lazy_gettext(
"Ich bestätige, dass ich die Kündigung meiner Mitgliedschaft zurückziehe"),
validators=[
DataRequired(lazy_gettext("Bitte bestätige die Aufhebung der Kündigung"))])


class ChangeUseCacheForm(FlaskForm):
use_cache = RadioField(
label=lazy_gettext("Cache-Nutzung"),
@@ -27,9 +27,21 @@ class NetworkAccessAlreadyActive(Exception):
pass


class TerminationNotPossible(Exception):
pass


class ContinuationNotPossible(Exception):
pass


class DBQueryEmpty(Exception):
pass


class LDAPConnectionError(Exception):
pass


class UnknownError(Exception):
pass
@@ -166,17 +166,6 @@ def realname(self):
def login(self):
return self._pg_account.account.strip()

@unsupported_prop
def network_access_active(self):
raise NotImplementedError

@network_access_active.setter
def network_access_active(self, value):
raise NotImplementedError

def activate_network_access(self, password, mac, birthdate, host_name):
raise NotImplementedError

@active_prop
def mac(self):
return {'value': ", ".join(mac.mac.lower() for mac in self._pg_account.macs),

0 comments on commit 30fe0db

Please sign in to comment.
You can’t perform that action at this time.