diff options
| author | Alexis M <alexis@notmyidea.org> | 2019-10-11 20:20:13 +0200 |
|---|---|---|
| committer | Alexis Metaireau <alexis@notmyidea.org> | 2019-10-14 21:20:38 +0200 |
| commit | f260a2c9e7b2f34d49ef4c2e50ce83a2361cf343 (patch) | |
| tree | dedf02275cb089fb2d954a668de6f8eff794036a /ihatemoney/web.py | |
| parent | f2a0b9f3f0e24617f698ce74943cdabdea01431e (diff) | |
| download | ihatemoney-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.py | 329 |
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", + ) |
