aboutsummaryrefslogtreecommitdiff
path: root/ihatemoney/web.py
diff options
context:
space:
mode:
authorAlexis M <alexis@notmyidea.org>2019-10-11 20:20:13 +0200
committerAlexis Metaireau <alexis@notmyidea.org>2019-10-14 21:20:38 +0200
commitf260a2c9e7b2f34d49ef4c2e50ce83a2361cf343 (patch)
treededf02275cb089fb2d954a668de6f8eff794036a /ihatemoney/web.py
parentf2a0b9f3f0e24617f698ce74943cdabdea01431e (diff)
downloadihatemoney-mirror-f260a2c9e7b2f34d49ef4c2e50ce83a2361cf343.zip
ihatemoney-mirror-f260a2c9e7b2f34d49ef4c2e50ce83a2361cf343.tar.gz
ihatemoney-mirror-f260a2c9e7b2f34d49ef4c2e50ce83a2361cf343.tar.bz2
Use black to refomat the files.
Diffstat (limited to 'ihatemoney/web.py')
-rw-r--r--ihatemoney/web.py329
1 files changed, 202 insertions, 127 deletions
diff --git a/ihatemoney/web.py b/ihatemoney/web.py
index 3e74362..fc12e9d 100644
--- a/ihatemoney/web.py
+++ b/ihatemoney/web.py
@@ -11,8 +11,18 @@ and `add_project_id` for a quick overview)
import os
from flask import (
- abort, Blueprint, current_app, flash, g, redirect, render_template, request,
- session, url_for, send_file, send_from_directory
+ abort,
+ Blueprint,
+ current_app,
+ flash,
+ g,
+ redirect,
+ render_template,
+ request,
+ session,
+ url_for,
+ send_file,
+ send_from_directory,
)
from flask_mail import Message
from flask_babel import get_locale, gettext as _
@@ -24,10 +34,22 @@ from functools import wraps
from ihatemoney.models import db, Project, Person, Bill
from ihatemoney.forms import (
- AdminAuthenticationForm, AuthenticationForm, EditProjectForm,
- InviteForm, MemberForm, PasswordReminder, ResetPasswordForm, ProjectForm, get_billform_for
+ AdminAuthenticationForm,
+ AuthenticationForm,
+ EditProjectForm,
+ InviteForm,
+ MemberForm,
+ PasswordReminder,
+ ResetPasswordForm,
+ ProjectForm,
+ get_billform_for,
+)
+from ihatemoney.utils import (
+ Redirect303,
+ list_of_dicts2json,
+ list_of_dicts2csv,
+ LoginThrottler,
)
-from ihatemoney.utils import Redirect303, list_of_dicts2json, list_of_dicts2csv, LoginThrottler
main = Blueprint("main", __name__)
@@ -46,17 +68,20 @@ def requires_admin(bypass=None):
Admin authentication will be bypassed when ALLOW_PUBLIC_PROJECT_CREATION is
set to True.
"""
+
def check_admin(f):
@wraps(f)
def admin_auth(*args, **kws):
is_admin_auth_bypassed = False
if bypass is not None and current_app.config.get(bypass[0]) == bypass[1]:
is_admin_auth_bypassed = True
- is_admin = session.get('is_admin')
+ is_admin = session.get("is_admin")
if is_admin or is_admin_auth_bypassed:
return f(*args, **kws)
- raise Redirect303(url_for('.admin', goto=request.path))
+ raise Redirect303(url_for(".admin", goto=request.path))
+
return admin_auth
+
return check_admin
@@ -66,10 +91,10 @@ def add_project_id(endpoint, values):
This is to not carry it everywhere in the templates.
"""
- if 'project_id' in values or not hasattr(g, 'project'):
+ if "project_id" in values or not hasattr(g, "project"):
return
- if current_app.url_map.is_endpoint_expecting(endpoint, 'project_id'):
- values['project_id'] = g.project.id
+ if current_app.url_map.is_endpoint_expecting(endpoint, "project_id"):
+ values["project_id"] = g.project.id
@main.url_value_preprocessor
@@ -97,21 +122,19 @@ def pull_project(endpoint, values):
return
if not values:
values = {}
- project_id = values.pop('project_id', None)
+ project_id = values.pop("project_id", None)
if project_id:
project = Project.query.get(project_id)
if not project:
- raise Redirect303(url_for(".create_project",
- project_id=project_id))
+ raise Redirect303(url_for(".create_project", project_id=project_id))
- is_admin = session.get('is_admin')
+ is_admin = session.get("is_admin")
if session.get(project.id) or is_admin:
# add project into kwargs and call the original function
g.project = project
else:
# redirect to authentication page
- raise Redirect303(
- url_for(".authenticate", project_id=project_id))
+ raise Redirect303(url_for(".authenticate", project_id=project_id))
@main.route("/admin", methods=["GET", "POST"])
@@ -121,30 +144,41 @@ def admin():
When ADMIN_PASSWORD is empty, admin authentication is deactivated.
"""
form = AdminAuthenticationForm()
- goto = request.args.get('goto', url_for('.home'))
- is_admin_auth_enabled = bool(current_app.config['ADMIN_PASSWORD'])
+ goto = request.args.get("goto", url_for(".home"))
+ is_admin_auth_enabled = bool(current_app.config["ADMIN_PASSWORD"])
if request.method == "POST":
client_ip = request.remote_addr
if not login_throttler.is_login_allowed(client_ip):
msg = _("Too many failed login attempts, please retry later.")
- form.errors['admin_password'] = [msg]
- return render_template("admin.html", form=form, admin_auth=True,
- is_admin_auth_enabled=is_admin_auth_enabled)
+ form.errors["admin_password"] = [msg]
+ return render_template(
+ "admin.html",
+ form=form,
+ admin_auth=True,
+ is_admin_auth_enabled=is_admin_auth_enabled,
+ )
if form.validate():
# Valid password
- if (check_password_hash(current_app.config['ADMIN_PASSWORD'],
- form.admin_password.data)):
- session['is_admin'] = True
+ if check_password_hash(
+ current_app.config["ADMIN_PASSWORD"], form.admin_password.data
+ ):
+ session["is_admin"] = True
session.update()
login_throttler.reset(client_ip)
return redirect(goto)
# Invalid password
login_throttler.increment_attempts_counter(client_ip)
- msg = _("This admin password is not the right one. Only %(num)d attempts left.",
- num=login_throttler.get_remaining_attempts(client_ip))
- form.errors['admin_password'] = [msg]
- return render_template("admin.html", form=form, admin_auth=True,
- is_admin_auth_enabled=is_admin_auth_enabled)
+ msg = _(
+ "This admin password is not the right one. Only %(num)d attempts left.",
+ num=login_throttler.get_remaining_attempts(client_ip),
+ )
+ form.errors["admin_password"] = [msg]
+ return render_template(
+ "admin.html",
+ form=form,
+ admin_auth=True,
+ is_admin_auth_enabled=is_admin_auth_enabled,
+ )
@main.route("/authenticate", methods=["GET", "POST"])
@@ -152,13 +186,13 @@ def authenticate(project_id=None):
"""Authentication form"""
form = AuthenticationForm()
# Try to get project_id from token first
- token = request.args.get('token')
+ token = request.args.get("token")
if token:
- project_id = Project.verify_token(token, token_type='non_timed_token')
+ project_id = Project.verify_token(token, token_type="non_timed_token")
token_auth = True
else:
- if not form.id.data and request.args.get('project_id'):
- form.id.data = request.args['project_id']
+ if not form.id.data and request.args.get("project_id"):
+ form.id.data = request.args["project_id"]
project_id = form.id.data
token_auth = False
if project_id is None:
@@ -172,16 +206,22 @@ def authenticate(project_id=None):
if not project:
# If the user try to connect to an unexisting project, we will
# propose him a link to the creation form.
- return render_template("authenticate.html", form=form, create_project=project_id)
+ return render_template(
+ "authenticate.html", form=form, create_project=project_id
+ )
# if credentials are already in session, redirect
if session.get(project_id):
- setattr(g, 'project', project)
+ setattr(g, "project", project)
return redirect(url_for(".list_bills"))
# else do form authentication or token authentication
is_post_auth = request.method == "POST" and form.validate()
- if is_post_auth and check_password_hash(project.password, form.password.data) or token_auth:
+ if (
+ is_post_auth
+ and check_password_hash(project.password, form.password.data)
+ or token_auth
+ ):
# maintain a list of visited projects
if "projects" not in session:
session["projects"] = []
@@ -189,11 +229,11 @@ def authenticate(project_id=None):
session["projects"].insert(0, (project_id, project.name))
session[project_id] = True
session.update()
- setattr(g, 'project', project)
+ setattr(g, "project", project)
return redirect(url_for(".list_bills"))
if is_post_auth and not check_password_hash(project.password, form.password.data):
msg = _("This private code is not the right one")
- form.errors['password'] = [msg]
+ form.errors["password"] = [msg]
return render_template("authenticate.html", form=form)
@@ -202,21 +242,27 @@ def authenticate(project_id=None):
def home():
project_form = ProjectForm()
auth_form = AuthenticationForm()
- is_demo_project_activated = current_app.config['ACTIVATE_DEMO_PROJECT']
- is_public_project_creation_allowed = current_app.config['ALLOW_PUBLIC_PROJECT_CREATION']
+ is_demo_project_activated = current_app.config["ACTIVATE_DEMO_PROJECT"]
+ is_public_project_creation_allowed = current_app.config[
+ "ALLOW_PUBLIC_PROJECT_CREATION"
+ ]
- return render_template("home.html", project_form=project_form,
- is_demo_project_activated=is_demo_project_activated,
- is_public_project_creation_allowed=is_public_project_creation_allowed,
- auth_form=auth_form, session=session)
+ return render_template(
+ "home.html",
+ project_form=project_form,
+ is_demo_project_activated=is_demo_project_activated,
+ is_public_project_creation_allowed=is_public_project_creation_allowed,
+ auth_form=auth_form,
+ session=session,
+ )
@main.route("/create", methods=["GET", "POST"])
@requires_admin(bypass=("ALLOW_PUBLIC_PROJECT_CREATION", True))
def create_project():
form = ProjectForm()
- if request.method == "GET" and 'project_id' in request.values:
- form.name.data = request.values['project_id']
+ if request.method == "GET" and "project_id" in request.values:
+ form.name.data = request.values["project_id"]
if request.method == "POST":
# At first, we don't want the user to bother with the identifier
@@ -239,26 +285,34 @@ def create_project():
# send reminder email
g.project = project
- message_title = _("You have just created '%(project)s' "
- "to share your expenses", project=g.project.name)
+ message_title = _(
+ "You have just created '%(project)s' " "to share your expenses",
+ project=g.project.name,
+ )
- message_body = render_template("reminder_mail.%s.j2" %
- get_locale().language)
+ message_body = render_template(
+ "reminder_mail.%s.j2" % get_locale().language
+ )
- msg = Message(message_title,
- body=message_body,
- recipients=[project.contact_email])
+ msg = Message(
+ message_title, body=message_body, recipients=[project.contact_email]
+ )
try:
current_app.mail.send(msg)
except SMTPRecipientsRefused:
- msg_compl = 'Problem sending mail. '
+ msg_compl = "Problem sending mail. "
# TODO: destroy the project and cancel instead?
else:
- msg_compl = ''
+ msg_compl = ""
# redirect the user to the next step (invite)
- flash(_("%(msg_compl)sThe project identifier is %(project)s",
- msg_compl=msg_compl, project=project.id))
+ flash(
+ _(
+ "%(msg_compl)sThe project identifier is %(project)s",
+ msg_compl=msg_compl,
+ project=project.id,
+ )
+ )
return redirect(url_for(".list_bills", project_id=project.id))
return render_template("create_project.html", form=form)
@@ -273,10 +327,13 @@ def remind_password():
project = Project.query.get(form.id.data)
# send a link to reset the password
password_reminder = "password_reminder.%s.j2" % get_locale().language
- current_app.mail.send(Message(
- "password recovery",
- body=render_template(password_reminder, project=project),
- recipients=[project.contact_email]))
+ current_app.mail.send(
+ Message(
+ "password recovery",
+ body=render_template(password_reminder, project=project),
+ recipients=[project.contact_email],
+ )
+ )
return redirect(url_for(".password_reminder_sent"))
return render_template("password_reminder.html", form=form)
@@ -287,18 +344,24 @@ def password_reminder_sent():
return render_template("password_reminder_sent.html")
-@main.route('/reset-password', methods=['GET', 'POST'])
+@main.route("/reset-password", methods=["GET", "POST"])
def reset_password():
form = ResetPasswordForm()
- token = request.args.get('token')
+ token = request.args.get("token")
if not token:
- return render_template('reset_password.html', form=form, error=_("No token provided"))
+ return render_template(
+ "reset_password.html", form=form, error=_("No token provided")
+ )
project_id = Project.verify_token(token)
if not project_id:
- return render_template('reset_password.html', form=form, error=_("Invalid token"))
+ return render_template(
+ "reset_password.html", form=form, error=_("Invalid token")
+ )
project = Project.query.get(project_id)
if not project:
- return render_template('reset_password.html', form=form, error=_("Unknown project"))
+ return render_template(
+ "reset_password.html", form=form, error=_("Unknown project")
+ )
if request.method == "POST" and form.validate():
project.password = generate_password_hash(form.password.data)
@@ -306,7 +369,7 @@ def reset_password():
db.session.commit()
flash(_("Password successfully reset."))
return redirect(url_for(".home"))
- return render_template('reset_password.html', form=form)
+ return render_template("reset_password.html", form=form)
@main.route("/<project_id>/edit", methods=["GET", "POST"])
@@ -324,40 +387,38 @@ def edit_project():
edit_form.contact_email.data = g.project.contact_email
return render_template(
- "edit_project.html",
- edit_form=edit_form,
- current_view="edit_project"
+ "edit_project.html", edit_form=edit_form, current_view="edit_project"
)
@main.route("/<project_id>/delete")
def delete_project():
g.project.remove_project()
- flash(_('Project successfully deleted'))
+ flash(_("Project successfully deleted"))
- return redirect(request.headers.get('Referer') or url_for('.home'))
+ return redirect(request.headers.get("Referer") or url_for(".home"))
@main.route("/<project_id>/export/<string:file>.<string:format>")
def export_project(file, format):
- if file == 'transactions':
+ if file == "transactions":
export = g.project.get_transactions_to_settle_bill(pretty_output=True)
elif file == "bills":
export = g.project.get_pretty_bills(export_format=format)
else:
- abort(404, 'No such export type')
+ abort(404, "No such export type")
if format == "json":
file2export = list_of_dicts2json(export)
elif format == "csv":
file2export = list_of_dicts2csv(export)
else:
- abort(404, 'No such export format')
+ abort(404, "No such export format")
return send_file(
file2export,
attachment_filename="%s-%s.%s" % (g.project.id, file, format),
- as_attachment=True
+ as_attachment=True,
)
@@ -377,16 +438,18 @@ def demo():
Create a demo project if it doesn't exists yet (or has been deleted)
If the demo project is deactivated, one is redirected to the create project form
"""
- is_demo_project_activated = current_app.config['ACTIVATE_DEMO_PROJECT']
+ is_demo_project_activated = current_app.config["ACTIVATE_DEMO_PROJECT"]
project = Project.query.get("demo")
if not project and not is_demo_project_activated:
- raise Redirect303(url_for(".create_project",
- project_id='demo'))
+ raise Redirect303(url_for(".create_project", project_id="demo"))
if not project and is_demo_project_activated:
- project = Project(id="demo", name=u"demonstration",
- password=generate_password_hash("demo"),
- contact_email="demo@notmyidea.org")
+ project = Project(
+ id="demo",
+ name="demonstration",
+ password=generate_password_hash("demo"),
+ contact_email="demo@notmyidea.org",
+ )
db.session.add(project)
db.session.commit()
session[project.id] = True
@@ -403,15 +466,19 @@ def invite():
if form.validate():
# send the email
- message_body = render_template("invitation_mail.%s.j2" %
- get_locale().language)
-
- message_title = _("You have been invited to share your "
- "expenses for %(project)s", project=g.project.name)
- msg = Message(message_title,
- body=message_body,
- recipients=[email.strip()
- for email in form.emails.data.split(",")])
+ message_body = render_template(
+ "invitation_mail.%s.j2" % get_locale().language
+ )
+
+ message_title = _(
+ "You have been invited to share your " "expenses for %(project)s",
+ project=g.project.name,
+ )
+ msg = Message(
+ message_title,
+ body=message_body,
+ recipients=[email.strip() for email in form.emails.data.split(",")],
+ )
current_app.mail.send(msg)
flash(_("Your invitations have been sent"))
return redirect(url_for(".list_bills"))
@@ -423,17 +490,19 @@ def invite():
def list_bills():
bill_form = get_billform_for(g.project)
# set the last selected payer as default choice if exists
- if 'last_selected_payer' in session:
- bill_form.payer.data = session['last_selected_payer']
+ if "last_selected_payer" in session:
+ bill_form.payer.data = session["last_selected_payer"]
# Preload the "owers" relationship for all bills
bills = g.project.get_bills().options(orm.subqueryload(Bill.owers))
- return render_template("list_bills.html",
- bills=bills, member_form=MemberForm(g.project),
- bill_form=bill_form,
- add_bill=request.values.get('add_bill', False),
- current_view="list_bills",
- )
+ return render_template(
+ "list_bills.html",
+ bills=bills,
+ member_form=MemberForm(g.project),
+ bill_form=bill_form,
+ add_bill=request.values.get("add_bill", False),
+ current_view="list_bills",
+ )
@main.route("/<project_id>/members/add", methods=["GET", "POST"])
@@ -452,8 +521,11 @@ def add_member():
@main.route("/<project_id>/members/<member_id>/reactivate", methods=["POST"])
def reactivate(member_id):
- person = Person.query.filter(Person.id == member_id)\
- .filter(Project.id == g.project.id).all()
+ person = (
+ Person.query.filter(Person.id == member_id)
+ .filter(Project.id == g.project.id)
+ .all()
+ )
if person:
person[0].activated = True
db.session.commit()
@@ -466,23 +538,27 @@ def remove_member(member_id):
member = g.project.remove_member(member_id)
if member:
if not member.activated:
- flash(_("User '%(name)s' has been deactivated. It will still "
+ flash(
+ _(
+ "User '%(name)s' has been deactivated. It will still "
"appear in the users list until its balance "
- "becomes zero.", name=member.name))
+ "becomes zero.",
+ name=member.name,
+ )
+ )
else:
flash(_("User '%(name)s' has been removed", name=member.name))
return redirect(url_for(".list_bills"))
-@main.route("/<project_id>/members/<member_id>/edit",
- methods=["POST", "GET"])
+@main.route("/<project_id>/members/<member_id>/edit", methods=["POST", "GET"])
def edit_member(member_id):
member = Person.query.get(member_id, g.project)
if not member:
raise NotFound()
form = MemberForm(g.project, edit=True)
- if request.method == 'POST' and form.validate():
+ if request.method == "POST" and form.validate():
form.save(g.project, member)
db.session.commit()
flash(_("User '%(name)s' has been edited", name=member.name))
@@ -495,10 +571,10 @@ def edit_member(member_id):
@main.route("/<project_id>/add", methods=["GET", "POST"])
def add_bill():
form = get_billform_for(g.project)
- if request.method == 'POST':
+ if request.method == "POST":
if form.validate():
# save last selected payer in session
- session['last_selected_payer'] = form.payer.data
+ session["last_selected_payer"] = form.payer.data
session.update()
bill = Bill()
@@ -509,9 +585,9 @@ def add_bill():
args = {}
if form.submit2.data:
- args['add_bill'] = True
+ args["add_bill"] = True
- return redirect(url_for('.list_bills', **args))
+ return redirect(url_for(".list_bills", **args))
return render_template("add_bill.html", form=form)
@@ -521,13 +597,13 @@ def delete_bill(bill_id):
# fixme: everyone is able to delete a bill
bill = Bill.query.get(g.project, bill_id)
if not bill:
- return redirect(url_for('.list_bills'))
+ return redirect(url_for(".list_bills"))
db.session.delete(bill)
db.session.commit()
flash(_("The bill has been deleted"))
- return redirect(url_for('.list_bills'))
+ return redirect(url_for(".list_bills"))
@main.route("/<project_id>/edit/<int:bill_id>", methods=["GET", "POST"])
@@ -539,12 +615,12 @@ def edit_bill(bill_id):
form = get_billform_for(g.project, set_default=False)
- if request.method == 'POST' and form.validate():
+ if request.method == "POST" and form.validate():
form.save(bill, g.project)
db.session.commit()
flash(_("The bill has been modified"))
- return redirect(url_for('.list_bills'))
+ return redirect(url_for(".list_bills"))
if not form.errors:
form.fill(bill)
@@ -554,21 +630,17 @@ def edit_bill(bill_id):
@main.route("/lang/<lang>")
def change_lang(lang):
- session['lang'] = lang
+ session["lang"] = lang
session.update()
- return redirect(request.headers.get('Referer') or url_for('.home'))
+ return redirect(request.headers.get("Referer") or url_for(".home"))
@main.route("/<project_id>/settle_bills")
def settle_bill():
"""Compute the sum each one have to pay to each other and display it"""
bills = g.project.get_transactions_to_settle_bill()
- return render_template(
- "settle_bills.html",
- bills=bills,
- current_view='settle_bill',
- )
+ return render_template("settle_bills.html", bills=bills, current_view="settle_bill")
@main.route("/<project_id>/statistics")
@@ -577,22 +649,25 @@ def statistics():
return render_template(
"statistics.html",
members_stats=g.project.members_stats,
- current_view='statistics',
+ current_view="statistics",
)
@main.route("/dashboard")
@requires_admin()
def dashboard():
- is_admin_dashboard_activated = current_app.config['ACTIVATE_ADMIN_DASHBOARD']
+ is_admin_dashboard_activated = current_app.config["ACTIVATE_ADMIN_DASHBOARD"]
return render_template(
"dashboard.html",
projects=Project.query.all(),
- is_admin_dashboard_activated=is_admin_dashboard_activated
+ is_admin_dashboard_activated=is_admin_dashboard_activated,
)
-@main.route('/favicon.ico')
+@main.route("/favicon.ico")
def favicon():
- return send_from_directory(os.path.join(main.root_path, 'static'),
- 'favicon.ico', mimetype='image/vnd.microsoft.icon')
+ return send_from_directory(
+ os.path.join(main.root_path, "static"),
+ "favicon.ico",
+ mimetype="image/vnd.microsoft.icon",
+ )