From 28a3abf96d010db8253b53933d6ab36c4dd412d9 Mon Sep 17 00:00:00 2001 From: Alexis Metaireau Date: Fri, 19 Aug 2011 23:44:54 +0200 Subject: [PATCH] No need anymore to pass the project_id to the urls. The project is now directly added to the context local g object, and injected on the fly into the urls that need it. This commits also add ideas found while reading the flask documentation. The project can be enhanced in many ways, some ideas are stated there. --- TODO | 7 ++ budget/templates/add_bill.html | 4 +- budget/templates/add_member.html | 2 +- budget/templates/edit_bill.html | 4 +- budget/templates/invitation_mail | 4 +- budget/templates/list_bills.html | 18 ++--- budget/templates/send_invites.html | 4 +- budget/web.py | 122 ++++++++++++++++------------- 8 files changed, 93 insertions(+), 72 deletions(-) diff --git a/TODO b/TODO index e69de29..a759652 100644 --- a/TODO +++ b/TODO @@ -0,0 +1,7 @@ +* use flask.instance_path to get/store configuration. See http://flask.pocoo.org/docs/config/#instance-folders +* Attach the current projec to g and modify the url_for to use it. http://flask.pocoo.org/docs/patterns/urlprocessors/ +* Use class based views to factorize the code if there is some code to factorize, see http://flask.pocoo.org/docs/views/ +* Use request.args.get('next') to redirect when authenticating +* Move the flask app to __init__.py (http://flask.pocoo.org/docs/patterns/packages/) +* Eventually move the url definition into a specific section +* Render templates automatically using a decorator. see http://stackoverflow.com/questions/7054099/using-flask-blueprint-for-some-static-pages/7056374#7056374 diff --git a/budget/templates/add_bill.html b/budget/templates/add_bill.html index d844b6a..5b3a768 100644 --- a/budget/templates/add_bill.html +++ b/budget/templates/add_bill.html @@ -1,14 +1,14 @@ {% extends "layout.html" %} {% block top_menu %} -Back to the list +Back to the list {% endblock %} {% block content %}

Add a new bill

-
+ {{ forms.add_bill(form) }}
diff --git a/budget/templates/add_member.html b/budget/templates/add_member.html index 8d24aee..5739791 100644 --- a/budget/templates/add_member.html +++ b/budget/templates/add_member.html @@ -1,6 +1,6 @@ {% extends "layout.html" %} {% block content %} -
+ {{ forms.add_member(form) }}
{% endblock %} diff --git a/budget/templates/edit_bill.html b/budget/templates/edit_bill.html index c78eb09..03a1a26 100644 --- a/budget/templates/edit_bill.html +++ b/budget/templates/edit_bill.html @@ -1,14 +1,14 @@ {% extends "layout.html" %} {% block top_menu %} -Back to the list +Back to the list {% endblock %} {% block content %}

Edit a bill

-
+ {{ forms.add_bill(form) }}
diff --git a/budget/templates/invitation_mail b/budget/templates/invitation_mail index 53991ed..83a7840 100644 --- a/budget/templates/invitation_mail +++ b/budget/templates/invitation_mail @@ -1,10 +1,10 @@ Hi, -Someone using the email adress {{ email }} invited you to share your expenses for {{ project.name }} on our application. +Someone using the email adress {{ g.project.contact_email }} invited you to share your expenses for {{ g.project.name }} on our application. It's as simple as saying what did you paid for, for who, and how much did it cost you, we are caring about the rest. -You can access it here: {{ SITE_URL }}{{ url_for("list_bills", project_id=project.id) }}, the password is "{{ project.password }}". +You can access it here: {{ SITE_URL }}{{ url_for("list_bills") }}, the password is "{{ g.project.password }}". Enjoy, Some weird guys diff --git a/budget/templates/list_bills.html b/budget/templates/list_bills.html index fe564e1..94b0f8f 100644 --- a/budget/templates/list_bills.html +++ b/budget/templates/list_bills.html @@ -42,24 +42,24 @@ $('.members li').hover(function(){ {% block content %}
-
+ {{ forms.add_member(member_form) }}
- Add a bill + Add a bill - + {% if bills.count() > 0 %} @@ -72,15 +72,15 @@ $('.members li').hover(function(){ - + {% endfor %}
{{ bill.what }} {% for ower in bill.owers %}{{ ower.name }} {% endfor %} {{ bill.amount }} ({{ bill.pay_each() }} each)edit - deleteedit + delete
{% else %} -

Nothing to list yet. You probably want to add a bill ?

+

Nothing to list yet. You probably want to add a bill ?

{% endif %}
{% endblock %} diff --git a/budget/templates/send_invites.html b/budget/templates/send_invites.html index f618803..fa46c81 100644 --- a/budget/templates/send_invites.html +++ b/budget/templates/send_invites.html @@ -3,12 +3,12 @@

Invite people to join this project

Specify a (coma separated) list of email adresses you want to notify about the creation of this budget management project and we will send them an email for you.

-

If you prefer, you can skip this step and notify them yourself

+

If you prefer, you can skip this step and notify them yourself

{% include "display_errors.html" %}
{{ form.hidden_tag() }}

{{ form.emails.label }}
{{ form.emails }}

-

{{ form.submit }} No, thanks

+

{{ form.submit }} No, thanks

{% endblock %} diff --git a/budget/web.py b/budget/web.py index e952109..14e058c 100644 --- a/budget/web.py +++ b/budget/web.py @@ -1,8 +1,8 @@ from collections import defaultdict -from flask import (Flask, session, request, redirect, url_for, render_template, - flash) +from flask import * from flaskext.mail import Mail, Message +from werkzeug.routing import RequestRedirect # local modules from models import db, Project, Person, Bill @@ -35,8 +35,6 @@ def authenticate(redirect_url=None): form = AuthenticationForm() project_id = form.id.data - - redirect_url = redirect_url or url_for("list_bills", project_id=project_id) project = Project.query.get(project_id) create_project = False # We don't want to create the project by default if not project: @@ -47,6 +45,7 @@ def authenticate(redirect_url=None): else: # if credentials are already in session, redirect if project_id in session and project.password == session[project_id]: + redirect_url = redirect_url or url_for("list_bills") return redirect(redirect_url) # else process the form @@ -62,6 +61,8 @@ def authenticate(redirect_url=None): session["projects"].insert(0, (project_id, project.name)) session[project_id] = form.password.data session.update() + setattr(g, 'project', project) + redirect_url = redirect_url or url_for("list_bills") return redirect(redirect_url) return render_template("authenticate.html", form=form, @@ -95,9 +96,32 @@ def exit(): session.clear() return redirect(url_for("home")) -@app.route("//invite", methods=["GET", "POST"]) -@requires_auth -def invite(project): +@app.url_defaults +def add_project_id(endpoint, values): + if 'project_id' in values or not hasattr(g, 'project'): + return + if app.url_map.is_endpoint_expecting(endpoint, 'project_id'): + values['project_id'] = g.project.id + +@app.url_value_preprocessor +def pull_project(endpoint, values): + if not values: + values = {} + project_id = values.pop('project_id', None) + if project_id: + project = Project.query.get(project_id) + if not project: + raise RequestRedirect(url_for("create_project")) + if project.id in session and session[project.id] == project.password: + # add project into kwargs and call the original function + g.project = project + else: + # redirect to authentication page + raise RequestRedirect( + url_for("authenticate", redirect_url=request.url)) + +@app.route("//invite", methods=["GET", "POST"]) +def invite(): form = InviteForm() @@ -105,51 +129,46 @@ def invite(project): if form.validate(): # send the email - message_body = render_template("invitation_mail", - email=project.contact_email, project=project) + message_body = render_template("invitation_mail") message_title = "You have been invited to share your"\ - + " expenses for %s" % project.name + + " expenses for %s" % g.project.name msg = Message(message_title, body=message_body, recipients=[email.strip() for email in form.emails.data.split(",")]) mail.send(msg) - return redirect(url_for("list_bills", project_id=project.id)) + return redirect(url_for("list_bills")) - return render_template("send_invites.html", form=form, project=project) + return render_template("send_invites.html", form=form) -@app.route("//") -@requires_auth -def list_bills(project): +@app.route("//") +def list_bills(): bills = Bill.query.join(Person, Project)\ .filter(Bill.payer_id == Person.id)\ .filter(Person.project_id == Project.id)\ - .filter(Project.id == project.id)\ + .filter(Project.id == g.project.id)\ .order_by(Bill.date.desc()) return render_template("list_bills.html", - bills=bills, project=project, - member_form=MemberForm(project), - bill_form=get_billform_for(project) + bills=bills, member_form=MemberForm(g.project), + bill_form=get_billform_for(g.project) ) -@app.route("//members/add", methods=["GET", "POST"]) -@requires_auth -def add_member(project): +@app.route("//members/add", methods=["GET", "POST"]) +def add_member(): # FIXME manage form errors on the list_bills page - form = MemberForm(project) + form = MemberForm(g.project) if request.method == "POST": if form.validate(): - db.session.add(Person(name=form.name.data, project=project)) + db.session.add(Person(name=form.name.data, project=g.project)) db.session.commit() - return redirect(url_for("list_bills", project_id=project.id)) - return render_template("add_member.html", form=form, project=project) + return redirect(url_for("list_bills")) + return render_template("add_member.html", form=form) -@app.route("//members//delete", methods=["GET", "POST"]) -@requires_auth -def remove_member(project, member_id): +@app.route("//members//delete", methods=["GET", "POST"]) +def remove_member(member_id): person = Person.query.get_or_404(member_id) - if person.project == project: + if person.project == g.project: if not person.has_bills(): db.session.delete(person) db.session.commit() @@ -158,12 +177,11 @@ def remove_member(project, member_id): person.activated = False db.session.commit() flash("User '%s' has been desactivated" % person.name) - return redirect(url_for("list_bills", project_id=project.id)) + return redirect(url_for("list_bills")) -@app.route("//add", methods=["GET", "POST"]) -@requires_auth -def add_bill(project): - form = get_billform_for(project) +@app.route("//add", methods=["GET", "POST"]) +def add_bill(): + form = get_billform_for(g.project) if request.method == 'POST': if form.validate(): bill = Bill() @@ -171,47 +189,43 @@ def add_bill(project): db.session.commit() flash("The bill has been added") - return redirect(url_for('list_bills', project_id=project.id)) + return redirect(url_for('list_bills')) - return render_template("add_bill.html", form=form, project=project) + return render_template("add_bill.html", form=form) -@app.route("//delete/") -@requires_auth -def delete_bill(project, bill_id): +@app.route("//delete/") +def delete_bill(bill_id): bill = Bill.query.get_or_404(bill_id) db.session.delete(bill) db.session.commit() flash("The bill has been deleted") - return redirect(url_for('list_bills', project_id=project.id)) + return redirect(url_for('list_bills')) -@app.route("//edit/", methods=["GET", "POST"]) -@requires_auth -def edit_bill(project, bill_id): +@app.route("//edit/", methods=["GET", "POST"]) +def edit_bill(bill_id): bill = Bill.query.get_or_404(bill_id) - form = get_billform_for(project, set_default=False) + form = get_billform_for(g.project, set_default=False) if request.method == 'POST' and form.validate(): form.save(bill) db.session.commit() flash("The bill has been modified") - return redirect(url_for('list_bills', project_id=project.id)) + return redirect(url_for('list_bills')) form.fill(bill) - return render_template("edit_bill.html", form=form, project=project, bill_id=bill_id) + return render_template("edit_bill.html", form=form, bill_id=bill_id) -@app.route("//compute") -@requires_auth -def compute_bills(project): +@app.route("//compute") +def compute_bills(): """Compute the sum each one have to pay to each other and display it""" - return render_template("compute_bills.html", project=project) + return render_template("compute_bills.html") -@app.route("//reset") -@requires_auth -def reset_bills(project): +@app.route("//reset") +def reset_bills(): """Reset the list of bills""" # FIXME replace with the archive feature # get all the bills which are not processed