aboutsummaryrefslogtreecommitdiff
path: root/budget
diff options
context:
space:
mode:
authorAlexis Metaireau <alexis@notmyidea.org>2011-09-09 21:21:37 +0200
committerAlexis Metaireau <alexis@notmyidea.org>2011-09-09 21:21:37 +0200
commitef353d643c274d19e7816e9cfa436e38f1d1edea (patch)
treece88603d11ed7b10015677db1ce97ad3cf23ec4e /budget
parent45dc6edacbc9cb771fd038cdc719fd953c1a6771 (diff)
downloadihatemoney-mirror-ef353d643c274d19e7816e9cfa436e38f1d1edea.zip
ihatemoney-mirror-ef353d643c274d19e7816e9cfa436e38f1d1edea.tar.gz
ihatemoney-mirror-ef353d643c274d19e7816e9cfa436e38f1d1edea.tar.bz2
Refactor the application to use blueprints.
This allows to isolate some behavior in the context of the web application so the API and the web application can behave in different ways.
Diffstat (limited to 'budget')
-rw-r--r--budget/run.py22
-rw-r--r--budget/templates/add_bill.html2
-rw-r--r--budget/templates/add_member.html2
-rw-r--r--budget/templates/authenticate.html2
-rw-r--r--budget/templates/edit_bill.html2
-rw-r--r--budget/templates/forms.html2
-rw-r--r--budget/templates/home.html6
-rw-r--r--budget/templates/invitation_mail2
-rw-r--r--budget/templates/layout.html10
-rw-r--r--budget/templates/list_bills.html14
-rw-r--r--budget/templates/send_invites.html4
-rw-r--r--budget/tests.py32
-rw-r--r--budget/web.py93
13 files changed, 104 insertions, 89 deletions
diff --git a/budget/run.py b/budget/run.py
new file mode 100644
index 0000000..b1fad19
--- /dev/null
+++ b/budget/run.py
@@ -0,0 +1,22 @@
+from web import main, db, mail
+import api
+
+from flask import *
+
+app = Flask(__name__)
+app.config.from_object("default_settings")
+app.register_blueprint(main)
+
+# db
+db.init_app(app)
+db.app = app
+db.create_all()
+
+# mail
+mail.init_app(app)
+
+def main():
+ app.run(host="0.0.0.0", debug=True)
+
+if __name__ == '__main__':
+ main()
diff --git a/budget/templates/add_bill.html b/budget/templates/add_bill.html
index 0b575d7..3b29896 100644
--- a/budget/templates/add_bill.html
+++ b/budget/templates/add_bill.html
@@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% block top_menu %}
-<a href="{{ url_for('list_bills') }}">Back to the list</a>
+<a href="{{ url_for(".list_bills") }}">Back to the list</a>
{% endblock %}
{% block content %}
diff --git a/budget/templates/add_member.html b/budget/templates/add_member.html
index 5739791..62fcf9e 100644
--- a/budget/templates/add_member.html
+++ b/budget/templates/add_member.html
@@ -1,6 +1,6 @@
{% extends "layout.html" %}
{% block content %}
- <form action="{{ url_for("add_member") }}" method="post">
+ <form action="{{ url_for(".add_member") }}" method="post">
{{ forms.add_member(form) }}
</form>
{% endblock %}
diff --git a/budget/templates/authenticate.html b/budget/templates/authenticate.html
index 0ad8815..9852d6a 100644
--- a/budget/templates/authenticate.html
+++ b/budget/templates/authenticate.html
@@ -8,7 +8,7 @@
{% if create_project %}
<p class="info">The project you are trying to access do not exist, do you want
-to <a href="{{ url_for("create_project", project_id=create_project) }}">create it</a>?
+to <a href="{{ url_for(".create_project", project_id=create_project) }}">create it</a>?
</p>
{% endif %}
<form action="" method="POST" accept-charset="utf-8">
diff --git a/budget/templates/edit_bill.html b/budget/templates/edit_bill.html
index f069193..9c272ae 100644
--- a/budget/templates/edit_bill.html
+++ b/budget/templates/edit_bill.html
@@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% block top_menu %}
-<a href="{{ url_for('list_bills') }}">Back to the list</a>
+<a href="{{ url_for(".list_bills") }}">Back to the list</a>
{% endblock %}
{% block content %}
diff --git a/budget/templates/forms.html b/budget/templates/forms.html
index 7b512ff..b027763 100644
--- a/budget/templates/forms.html
+++ b/budget/templates/forms.html
@@ -80,7 +80,7 @@
{{ input(form.emails) }}
<div class="actions">
<button class="btn">Send the invitations</button>
- <a href="{{ url_for("list_bills") }}">No, thanks</a>
+ <a href="{{ url_for(".list_bills") }}">No, thanks</a>
</div>
{% endmacro %}
diff --git a/budget/templates/home.html b/budget/templates/home.html
index 1b77cbd..ceb3b57 100644
--- a/budget/templates/home.html
+++ b/budget/templates/home.html
@@ -7,7 +7,7 @@
<div id="header">
<div class="slide">
<h1><span>Manage your shared <br>expenses, easily</span></h1>
- <a href="{{ url_for("demo") }}" class="about_link">Try out the demo</a>
+ <a href="{{ url_for(".demo") }}" class="about_link">Try out the demo</a>
</div>
<div class="additional-content">
<p>You're sharing a house?<br /> Going on holidays with friends?<br /> Simply sharing money with others? <br /><strong>We can help!</strong></p>
@@ -22,14 +22,14 @@
<div class="row">
<div class="span8 columns">
- <form action="{{ url_for('authenticate') }}" method="post">
+ <form action="{{ url_for(".authenticate") }}" method="post">
<h3>Log to an existing project...</h3>
{{ forms.authenticate(auth_form, home=True) }}
<button class="btn">log in</button>
</form>
</div>
<div class="span8 columns">
- <form class="create" action="{{ url_for('create_project') }}" method="post">
+ <form class="create" action="{{ url_for(".create_project") }}" method="post">
<h3>...or create a new one</h3>
{{ forms.create_project(project_form, home=True) }}
<button class="btn">let's get started</button>
diff --git a/budget/templates/invitation_mail b/budget/templates/invitation_mail
index 2844efd..f041db0 100644
--- a/budget/templates/invitation_mail
+++ b/budget/templates/invitation_mail
@@ -4,7 +4,7 @@ Someone using the email adress {{ g.project.contact_email }} invited you to shar
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: {{ config['SITE_URL'] }}{{ url_for("list_bills") }}, the password is "{{ g.project.password }}".
+You can access it here: {{ config['SITE_URL'] }}{{ url_for(".list_bills") }}, the password is "{{ g.project.password }}".
Enjoy,
Some weird guys
diff --git a/budget/templates/layout.html b/budget/templates/layout.html
index cb7ab10..afdda85 100644
--- a/budget/templates/layout.html
+++ b/budget/templates/layout.html
@@ -4,7 +4,7 @@
<head>
<title>Account manager</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
- <link rel=stylesheet type=text/css href="{{ url_for('static', filename='main.css') }}">
+ <link rel=stylesheet type=text/css href="{{ url_for("static", filename='main.css') }}">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
@@ -42,7 +42,7 @@
<body>
<div class="topbar">
- <h3><a class="logo" href="{% if g.project %}{{ url_for("list_bills") }}{% endif %}">#! money?</a></h3>
+ <h3><a class="logo" href="{% if g.project %}{{ url_for(".list_bills") }}{% endif %}">#! money?</a></h3>
{% if g.project %}
<ul>
<li class="active"><a href="">Bills</a></li>
@@ -56,12 +56,12 @@
<li class="divider"></li>
{% for id, name in session['projects'] %}
{% if id != g.project.id %}
- <li><a href="{{ url_for("list_bills", project_id=id) }}">switch to {{ name }}</a></li>
+ <li><a href="{{ url_for(".list_bills", project_id=id) }}">switch to {{ name }}</a></li>
{% endif %}
{% endfor %}
- <li><a href="{{ url_for("create_project") }}">Start a new project</a></li>
+ <li><a href="{{ url_for(".create_project") }}">Start a new project</a></li>
<li class="divider"></li>
- <li><a href="{{ url_for("exit") }}">Logout</a></li>
+ <li><a href="{{ url_for(".exit") }}">Logout</a></li>
</ul>
</li>
</ul>
diff --git a/budget/templates/list_bills.html b/budget/templates/list_bills.html
index b485f81..545de6a 100644
--- a/budget/templates/list_bills.html
+++ b/budget/templates/list_bills.html
@@ -48,20 +48,20 @@
<td class="{% if balance[member] > 0 %}positive{% elif balance[member] < 0 %}negative{% endif %}">
{% if balance[member] > 0 %}+{% endif %}{{ balance[member] }}
</td>
- <td> {% if member.activated %}<a class="remove" href="{{ url_for("remove_member", member_id=member.id) }}">delete</a>{% else %}<a href="{{ url_for("reactivate", member_id=member.id) }}">reactivate</a>{% endif %}</td>
+ <td> {% if member.activated %}<a class="remove" href="{{ url_for(".remove_member", member_id=member.id) }}">delete</a>{% else %}<a href="{{ url_for(".reactivate", member_id=member.id) }}">reactivate</a>{% endif %}</td>
</tr>
{% endif %}
{% endfor %}
</table>
- <form action="{{ url_for("add_member") }}" method="post">
+ <form action="{{ url_for(".add_member") }}" method="post">
{{ forms.add_member(member_form) }}
</form>
{% endblock %}
{% block content %}
-<a id="new-bill" href="{{ url_for('add_bill') }}" class="primary">Add a new bill</a>
- <form id="bill-form" action="{{ url_for('add_bill') }}" method="post" style="display: none">
+<a id="new-bill" href="{{ url_for(".add_bill") }}" class="primary">Add a new bill</a>
+ <form id="bill-form" action="{{ url_for(".add_bill") }}" method="post" style="display: none">
<a id="hide-bill-form" href="#">hide this form</a>
{{ forms.add_bill(bill_form) }}
</form>
@@ -77,15 +77,15 @@
<td>{{ bill.what }}</td>
<td>{% for ower in bill.owers %}{{ ower.name }} {% endfor %}</td>
<td>{{ bill.amount }} ({{ bill.pay_each() }} each)</td>
- <td><a href="{{ url_for("edit_bill", bill_id=bill.id) }}">edit</a>
- <a class="delete" href="{{ url_for("delete_bill", bill_id=bill.id) }}">delete</a></td>
+ <td><a href="{{ url_for(".edit_bill", bill_id=bill.id) }}">edit</a>
+ <a class="delete" href="{{ url_for(".delete_bill", bill_id=bill.id) }}">delete</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
- <p>Nothing to list yet. You probably want to <a id="empty-new-bill" href="{{ url_for("add_bill") }}">add a bill</a> ?</p>
+ <p>Nothing to list yet. You probably want to <a id="empty-new-bill" href="{{ url_for(".add_bill") }}">add a bill</a> ?</p>
{% endif %}
</div>
{% endblock %}
diff --git a/budget/templates/send_invites.html b/budget/templates/send_invites.html
index bf018e2..ec68333 100644
--- a/budget/templates/send_invites.html
+++ b/budget/templates/send_invites.html
@@ -4,14 +4,14 @@
<ol>
<li>Create the project</li>
<li><strong>Invite people</strong></li>
- <li><a href="{{ url_for("list_bills") }}">Use it!</a></li>
+ <li><a href="{{ url_for(".list_bills") }}">Use it!</a></li>
</ol>
{% endblock %}
{% block content %}
<h2>Invite people to join this project</h2>
<p>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.</p>
-<p>If you prefer, you can <a href="{{ url_for("list_bills") }}">skip this step</a> and notify them yourself</p>
+<p>If you prefer, you can <a href="{{ url_for(".list_bills") }}">skip this step</a> and notify them yourself</p>
{% include "display_errors.html" %}
<form class="invites" method="post" accept-charset="utf-8">
diff --git a/budget/tests.py b/budget/tests.py
index 9efb78a..db37e46 100644
--- a/budget/tests.py
+++ b/budget/tests.py
@@ -5,22 +5,22 @@ import unittest
from flask import session
-import web
+import run
import models
class TestCase(unittest.TestCase):
def setUp(self):
- web.app.config['TESTING'] = True
+ run.app.config['TESTING'] = True
- web.app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///memory"
- web.app.config['CSRF_ENABLED'] = False # simplify the tests
- self.app = web.app.test_client()
+ run.app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///memory"
+ run.app.config['CSRF_ENABLED'] = False # simplify the tests
+ self.app = run.app.test_client()
- models.db.init_app(web.app)
- web.mail.init_app(web.app)
+ models.db.init_app(run.app)
+ run.mail.init_app(run.app)
- models.db.app = web.app
+ models.db.app = run.app
models.db.create_all()
def tearDown(self):
@@ -57,7 +57,7 @@ class BudgetTestCase(TestCase):
are checked properly.
"""
# sending a message to one person
- with web.mail.record_messages() as outbox:
+ with run.mail.record_messages() as outbox:
# create a project
self.login("raclette")
@@ -70,7 +70,7 @@ class BudgetTestCase(TestCase):
self.assertEqual(outbox[0].recipients, ["alexis@notmyidea.org"])
# sending a message to multiple persons
- with web.mail.record_messages() as outbox:
+ with run.mail.record_messages() as outbox:
self.app.post("/raclette/invite", data=
{"emails": 'alexis@notmyidea.org, toto@notmyidea.org'})
@@ -80,13 +80,13 @@ class BudgetTestCase(TestCase):
["alexis@notmyidea.org", "toto@notmyidea.org"])
# mail address checking
- with web.mail.record_messages() as outbox:
+ with run.mail.record_messages() as outbox:
response = self.app.post("/raclette/invite", data={"emails": "toto"})
self.assertEqual(len(outbox), 0) # no message sent
self.assertIn("The email toto is not valid", response.data)
# mixing good and wrong adresses shouldn't send any messages
- with web.mail.record_messages() as outbox:
+ with run.mail.record_messages() as outbox:
self.app.post("/raclette/invite", data=
{"emails": 'alexis@notmyidea.org, alexis'}) # not valid
@@ -95,7 +95,7 @@ class BudgetTestCase(TestCase):
def test_project_creation(self):
- with web.app.test_client() as c:
+ with run.app.test_client() as c:
# add a valid project
c.post("/create", data={
@@ -188,7 +188,7 @@ class BudgetTestCase(TestCase):
def test_demo(self):
# Test that it is possible to connect automatically by going onto /demo
- with web.app.test_client() as c:
+ with run.app.test_client() as c:
models.db.session.add(models.Project(id="demo", name=u"demonstration",
password="demo", contact_email="demo@notmyidea.org"))
models.db.session.commit()
@@ -213,7 +213,7 @@ class BudgetTestCase(TestCase):
self.assertIn("Authentication", resp.data)
# try to connect with wrong credentials should not work
- with web.app.test_client() as c:
+ with run.app.test_client() as c:
resp = c.post("/authenticate",
data={'id': 'raclette', 'password': 'nope'})
@@ -221,7 +221,7 @@ class BudgetTestCase(TestCase):
self.assertNotIn('raclette', session)
# try to connect with the right credentials should work
- with web.app.test_client() as c:
+ with run.app.test_client() as c:
resp = c.post("/authenticate",
data={'id': 'raclette', 'password': 'raclette'})
diff --git a/budget/web.py b/budget/web.py
index 1b1b61a..f72a686 100644
--- a/budget/web.py
+++ b/budget/web.py
@@ -9,21 +9,21 @@ from forms import (ProjectForm, AuthenticationForm, BillForm, MemberForm,
InviteForm, CreateArchiveForm)
from utils import get_billform_for, Redirect303
-# create the application, initialize stuff
-app = Flask(__name__)
-app.config.from_object("default_settings")
-mail = Mail()
+"""
+The blueprint for the web interface.
-# db
-db.init_app(app)
-db.app = app
-db.create_all()
+Contains all the interaction logic with the end user (except forms which
+are directly handled in the forms module.
-# mail
-mail.init_app(app)
+Basically, this blueprint takes care of the authentication and provides
+some shortcuts to make your life better when coding (see `pull_project`
+and `add_project_id` for a quick overview
+"""
+main = Blueprint("main", __name__)
+mail = Mail()
-@app.url_defaults
+@main.url_defaults
def add_project_id(endpoint, values):
"""Add the project id to the url calls if it is expected.
@@ -31,10 +31,10 @@ 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'):
+ if current_app.url_map.is_endpoint_expecting(endpoint, 'project_id'):
values['project_id'] = g.project.id
-@app.url_value_preprocessor
+@main.url_value_preprocessor
def pull_project(endpoint, values):
"""When a request contains a project_id value, transform it directly
into a project by checking the credentials are stored in session.
@@ -49,16 +49,16 @@ def pull_project(endpoint, values):
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))
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 Redirect303(
- url_for("authenticate", project_id=project_id))
+ url_for(".authenticate", project_id=project_id))
-@app.route("/authenticate", methods=["GET", "POST"])
+@main.route("/authenticate", methods=["GET", "POST"])
def authenticate(project_id=None):
"""Authentication form"""
form = AuthenticationForm()
@@ -76,7 +76,7 @@ def authenticate(project_id=None):
# if credentials are already in session, redirect
if project_id in session and project.password == session[project_id]:
setattr(g, 'project', project)
- return redirect(url_for("list_bills"))
+ return redirect(url_for(".list_bills"))
# else process the form
if request.method == "POST":
@@ -92,19 +92,19 @@ def authenticate(project_id=None):
session[project_id] = form.password.data
session.update()
setattr(g, 'project', project)
- return redirect(url_for("list_bills"))
+ return redirect(url_for(".list_bills"))
return render_template("authenticate.html", form=form,
create_project=create_project)
-@app.route("/")
+@main.route("/")
def home():
project_form = ProjectForm()
auth_form = AuthenticationForm()
return render_template("home.html", project_form=project_form,
auth_form=auth_form, session=session)
-@app.route("/create", methods=["GET", "POST"])
+@main.route("/create", methods=["GET", "POST"])
def create_project():
form = ProjectForm()
if request.method == "GET" and 'project_id' in request.values:
@@ -122,17 +122,17 @@ def create_project():
session.update()
# redirect the user to the next step (invite)
- return redirect(url_for("invite", project_id=project.id))
+ return redirect(url_for(".invite", project_id=project.id))
return render_template("create_project.html", form=form)
-@app.route("/exit")
+@main.route("/exit")
def exit():
# delete the session
session.clear()
- return redirect(url_for("home"))
+ return redirect(url_for(".home"))
-@app.route("/demo")
+@main.route("/demo")
def demo():
"""
Authenticate the user for the demonstration project and redirect him to
@@ -147,9 +147,9 @@ def demo():
db.session.add(project)
db.session.commit()
session[project.id] = project.password
- return redirect(url_for("list_bills", project_id=project.id))
+ return redirect(url_for(".list_bills", project_id=project.id))
-@app.route("/<project_id>/invite", methods=["GET", "POST"])
+@main.route("/<project_id>/invite", methods=["GET", "POST"])
def invite():
"""Send invitations for this particular project"""
@@ -169,11 +169,11 @@ def invite():
for email in form.emails.data.split(",")])
mail.send(msg)
flash("You invitations have been sent")
- return redirect(url_for("list_bills"))
+ return redirect(url_for(".list_bills"))
return render_template("send_invites.html", form=form)
-@app.route("/<project_id>/")
+@main.route("/<project_id>/")
def list_bills():
bills = g.project.get_bills()
return render_template("list_bills.html",
@@ -181,7 +181,7 @@ def list_bills():
bill_form=get_billform_for(g.project)
)
-@app.route("/<project_id>/members/add", methods=["GET", "POST"])
+@main.route("/<project_id>/members/add", methods=["GET", "POST"])
def add_member():
# FIXME manage form errors on the list_bills page
form = MemberForm(g.project)
@@ -194,14 +194,14 @@ def add_member():
person[0].activated = True
db.session.commit()
flash("%s is part of this project again" % person[0].name)
- return redirect(url_for("list_bills"))
+ return redirect(url_for(".list_bills"))
db.session.add(Person(name=form.name.data, project=g.project))
db.session.commit()
- return redirect(url_for("list_bills"))
+ return redirect(url_for(".list_bills"))
return render_template("add_member.html", form=form)
-@app.route("/<project_id>/members/<member_id>/reactivate", methods=["GET",])
+@main.route("/<project_id>/members/<member_id>/reactivate", methods=["GET",])
def reactivate(member_id):
person = Person.query.filter(Person.id == member_id)\
.filter(Project.id == g.project.id).all()
@@ -209,10 +209,10 @@ def reactivate(member_id):
person[0].activated = True
db.session.commit()
flash("%s is part of this project again" % person[0].name)
- return redirect(url_for("list_bills"))
+ return redirect(url_for(".list_bills"))
-@app.route("/<project_id>/members/<member_id>/delete", methods=["GET", "POST"])
+@main.route("/<project_id>/members/<member_id>/delete", methods=["GET", "POST"])
def remove_member(member_id):
member = g.project.remove_member(member_id)
if member.activated == False:
@@ -220,9 +220,9 @@ def remove_member(member_id):
else:
flash("User '%s' has been removed" % member.name)
- return redirect(url_for("list_bills"))
+ return redirect(url_for(".list_bills"))
-@app.route("/<project_id>/add", methods=["GET", "POST"])
+@main.route("/<project_id>/add", methods=["GET", "POST"])
def add_bill():
form = get_billform_for(g.project)
if request.method == 'POST':
@@ -232,22 +232,22 @@ def add_bill():
db.session.commit()
flash("The bill has been added")
- return redirect(url_for('list_bills'))
+ return redirect(url_for('.list_bills'))
return render_template("add_bill.html", form=form)
-@app.route("/<project_id>/delete/<int:bill_id>")
+@main.route("/<project_id>/delete/<int:bill_id>")
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'))
+ return redirect(url_for('.list_bills'))
-@app.route("/<project_id>/edit/<int:bill_id>", methods=["GET", "POST"])
+@main.route("/<project_id>/edit/<int:bill_id>", methods=["GET", "POST"])
def edit_bill(bill_id):
bill = Bill.query.get_or_404(bill_id)
form = get_billform_for(g.project, set_default=False)
@@ -256,17 +256,17 @@ def edit_bill(bill_id):
db.session.commit()
flash("The bill has been modified")
- return redirect(url_for('list_bills'))
+ return redirect(url_for('.list_bills'))
form.fill(bill)
return render_template("add_bill.html", form=form, edit=True)
-@app.route("/<project_id>/compute")
+@main.route("/<project_id>/compute")
def compute_bills():
"""Compute the sum each one have to pay to each other and display it"""
return render_template("compute_bills.html")
-@app.route("/<project_id>/archives/create")
+@main.route("/<project_id>/archives/create")
def create_archive():
form = CreateArchiveForm()
if request.method == "POST":
@@ -275,10 +275,3 @@ def create_archive():
flash("The data from XX to XX has been archived")
return render_template("create_archive.html", form=form)
-
-
-def main():
- app.run(host="0.0.0.0", debug=True)
-
-if __name__ == '__main__':
- main()