mirror of
https://github.com/YunoHost-Apps/ihatemoney_ynh.git
synced 2024-09-03 19:26:15 +02:00
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.
This commit is contained in:
parent
402d756bf1
commit
28a3abf96d
8 changed files with 93 additions and 72 deletions
7
TODO
7
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
|
|
@ -1,14 +1,14 @@
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
{% block top_menu %}
|
{% block top_menu %}
|
||||||
<a href="{{ url_for('list_bills', project_id=project.id) }}">Back to the list</a>
|
<a href="{{ url_for('list_bills') }}">Back to the list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Add a new bill</h2>
|
<h2>Add a new bill</h2>
|
||||||
|
|
||||||
<div class="container span-24 add-bill uniForm" style="width: 400px">
|
<div class="container span-24 add-bill uniForm" style="width: 400px">
|
||||||
<form action="{{ url_for('add_bill', project_id=project.id) }}" method="post" class=uniForm">
|
<form action="{{ url_for('add_bill') }}" method="post" class=uniForm">
|
||||||
{{ forms.add_bill(form) }}
|
{{ forms.add_bill(form) }}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="{{ url_for("add_member", project_id=project.id) }}" method="post">
|
<form action="{{ url_for("add_member") }}" method="post">
|
||||||
{{ forms.add_member(form) }}
|
{{ forms.add_member(form) }}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
{% block top_menu %}
|
{% block top_menu %}
|
||||||
<a href="{{ url_for('list_bills', project_id=project.id) }}">Back to the list</a>
|
<a href="{{ url_for('list_bills') }}">Back to the list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Edit a bill</h2>
|
<h2>Edit a bill</h2>
|
||||||
|
|
||||||
<div class="container span-24 add-bill uniForm" style="width: 400px">
|
<div class="container span-24 add-bill uniForm" style="width: 400px">
|
||||||
<form action="{{ url_for('edit_bill', project_id=project.id, bill_id=bill_id) }}" method="post" class=uniForm">
|
<form action="{{ url_for('edit_bill', bill_id=bill_id) }}" method="post" class=uniForm">
|
||||||
{{ forms.add_bill(form) }}
|
{{ forms.add_bill(form) }}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
Hi,
|
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.
|
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,
|
Enjoy,
|
||||||
Some weird guys
|
Some weird guys
|
||||||
|
|
|
@ -42,24 +42,24 @@ $('.members li').hover(function(){
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="leftmenu" class="span-6">
|
<div id="leftmenu" class="span-6">
|
||||||
<ul class="members">
|
<ul class="members">
|
||||||
{% set balance = project.get_balance() %}
|
{% set balance = g.project.get_balance() %}
|
||||||
{% for member in project.active_members %}
|
{% for member in g.project.active_members %}
|
||||||
<li class="{{ loop.cycle("even", "odd") }}">
|
<li class="{{ loop.cycle("even", "odd") }}">
|
||||||
<span class="balance {% if balance[member] > 0 %}positive{% elif balance[member] < 0 %}negative{% endif %}">{{ balance[member] }}</span>
|
<span class="balance {% if balance[member] > 0 %}positive{% elif balance[member] < 0 %}negative{% endif %}">{{ balance[member] }}</span>
|
||||||
{{ member.name }}
|
{{ member.name }}
|
||||||
<a class="remove" href="{{ url_for("remove_member", project_id=project.id, member_id=member.id) }}">delete</a></li>
|
<a class="remove" href="{{ url_for("remove_member", member_id=member.id) }}">delete</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<form action="{{ url_for("add_member", project_id=project.id) }}" method="post">
|
<form action="{{ url_for("add_member") }}" method="post">
|
||||||
{{ forms.add_member(member_form) }}
|
{{ forms.add_member(member_form) }}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="content" class="uniForm span-18 last">
|
<div id="content" class="uniForm span-18 last">
|
||||||
<a id="add_bill_button" class="awesome large green button fright" href="{{ url_for('add_bill', project_id=project.id) }}">Add a bill</a>
|
<a id="add_bill_button" class="awesome large green button fright" href="{{ url_for('add_bill') }}">Add a bill</a>
|
||||||
|
|
||||||
<a id="hide_bill_form" class="awesome button fright" style="display: none;" href="#">Hide form</a>
|
<a id="hide_bill_form" class="awesome button fright" style="display: none;" href="#">Hide form</a>
|
||||||
|
|
||||||
<form id="add_bill" action="{{ url_for('add_bill', project_id=project.id) }}" method="post" style="width: 400px; display: none">{{ forms.add_bill(bill_form) }}</form>
|
<form id="add_bill" action="{{ url_for('add_bill') }}" method="post" style="width: 400px; display: none">{{ forms.add_bill(bill_form) }}</form>
|
||||||
|
|
||||||
{% if bills.count() > 0 %}
|
{% if bills.count() > 0 %}
|
||||||
<table class="list_bills">
|
<table class="list_bills">
|
||||||
|
@ -72,15 +72,15 @@ $('.members li').hover(function(){
|
||||||
<td>{{ bill.what }}</td>
|
<td>{{ bill.what }}</td>
|
||||||
<td>{% for ower in bill.owers %}{{ ower.name }} {% endfor %}</td>
|
<td>{% for ower in bill.owers %}{{ ower.name }} {% endfor %}</td>
|
||||||
<td>{{ bill.amount }} ({{ bill.pay_each() }} each)</td>
|
<td>{{ bill.amount }} ({{ bill.pay_each() }} each)</td>
|
||||||
<td><a href="{{ url_for("edit_bill", bill_id=bill.id, project_id=project.id) }}">edit</a>
|
<td><a href="{{ url_for("edit_bill", bill_id=bill.id) }}">edit</a>
|
||||||
<a href="{{ url_for("delete_bill", bill_id=bill.id, project_id=project.id) }}">delete</a></td>
|
<a href="{{ url_for("delete_bill", bill_id=bill.id) }}">delete</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Nothing to list yet. You probably want to <a href="{{ url_for("add_bill", project_id=project.id) }}">add a bill</a> ?</p>
|
<p>Nothing to list yet. You probably want to <a href="{{ url_for("add_bill") }}">add a bill</a> ?</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
<h2>Invite people to join this project</h2>
|
<h2>Invite people to join this project</h2>
|
||||||
<p>Specify a (coma separated) list of email adresses you want to notify about the
|
<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>
|
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", project_id=project.id) }}">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" %}
|
{% include "display_errors.html" %}
|
||||||
<form method="post" accept-charset="utf-8">
|
<form method="post" accept-charset="utf-8">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
<p>{{ form.emails.label }}<br /> {{ form.emails }}</p>
|
<p>{{ form.emails.label }}<br /> {{ form.emails }}</p>
|
||||||
<p>{{ form.submit }} <a href="{{ url_for("list_bills", project_id=project.id) }}">No, thanks</a></p>
|
<p>{{ form.submit }} <a href="{{ url_for("list_bills") }}">No, thanks</a></p>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
122
budget/web.py
122
budget/web.py
|
@ -1,8 +1,8 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from flask import (Flask, session, request, redirect, url_for, render_template,
|
from flask import *
|
||||||
flash)
|
|
||||||
from flaskext.mail import Mail, Message
|
from flaskext.mail import Mail, Message
|
||||||
|
from werkzeug.routing import RequestRedirect
|
||||||
|
|
||||||
# local modules
|
# local modules
|
||||||
from models import db, Project, Person, Bill
|
from models import db, Project, Person, Bill
|
||||||
|
@ -35,8 +35,6 @@ def authenticate(redirect_url=None):
|
||||||
form = AuthenticationForm()
|
form = AuthenticationForm()
|
||||||
|
|
||||||
project_id = form.id.data
|
project_id = form.id.data
|
||||||
|
|
||||||
redirect_url = redirect_url or url_for("list_bills", project_id=project_id)
|
|
||||||
project = Project.query.get(project_id)
|
project = Project.query.get(project_id)
|
||||||
create_project = False # We don't want to create the project by default
|
create_project = False # We don't want to create the project by default
|
||||||
if not project:
|
if not project:
|
||||||
|
@ -47,6 +45,7 @@ def authenticate(redirect_url=None):
|
||||||
else:
|
else:
|
||||||
# if credentials are already in session, redirect
|
# if credentials are already in session, redirect
|
||||||
if project_id in session and project.password == session[project_id]:
|
if project_id in session and project.password == session[project_id]:
|
||||||
|
redirect_url = redirect_url or url_for("list_bills")
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
# else process the form
|
# else process the form
|
||||||
|
@ -62,6 +61,8 @@ def authenticate(redirect_url=None):
|
||||||
session["projects"].insert(0, (project_id, project.name))
|
session["projects"].insert(0, (project_id, project.name))
|
||||||
session[project_id] = form.password.data
|
session[project_id] = form.password.data
|
||||||
session.update()
|
session.update()
|
||||||
|
setattr(g, 'project', project)
|
||||||
|
redirect_url = redirect_url or url_for("list_bills")
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
return render_template("authenticate.html", form=form,
|
return render_template("authenticate.html", form=form,
|
||||||
|
@ -95,9 +96,32 @@ def exit():
|
||||||
session.clear()
|
session.clear()
|
||||||
return redirect(url_for("home"))
|
return redirect(url_for("home"))
|
||||||
|
|
||||||
@app.route("/<string:project_id>/invite", methods=["GET", "POST"])
|
@app.url_defaults
|
||||||
@requires_auth
|
def add_project_id(endpoint, values):
|
||||||
def invite(project):
|
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("/<project_id>/invite", methods=["GET", "POST"])
|
||||||
|
def invite():
|
||||||
|
|
||||||
form = InviteForm()
|
form = InviteForm()
|
||||||
|
|
||||||
|
@ -105,51 +129,46 @@ def invite(project):
|
||||||
if form.validate():
|
if form.validate():
|
||||||
# send the email
|
# send the email
|
||||||
|
|
||||||
message_body = render_template("invitation_mail",
|
message_body = render_template("invitation_mail")
|
||||||
email=project.contact_email, project=project)
|
|
||||||
|
|
||||||
message_title = "You have been invited to share your"\
|
message_title = "You have been invited to share your"\
|
||||||
+ " expenses for %s" % project.name
|
+ " expenses for %s" % g.project.name
|
||||||
msg = Message(message_title,
|
msg = Message(message_title,
|
||||||
body=message_body,
|
body=message_body,
|
||||||
recipients=[email.strip()
|
recipients=[email.strip()
|
||||||
for email in form.emails.data.split(",")])
|
for email in form.emails.data.split(",")])
|
||||||
mail.send(msg)
|
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("/<string:project_id>/")
|
@app.route("/<project_id>/")
|
||||||
@requires_auth
|
def list_bills():
|
||||||
def list_bills(project):
|
|
||||||
bills = Bill.query.join(Person, Project)\
|
bills = Bill.query.join(Person, Project)\
|
||||||
.filter(Bill.payer_id == Person.id)\
|
.filter(Bill.payer_id == Person.id)\
|
||||||
.filter(Person.project_id == Project.id)\
|
.filter(Person.project_id == Project.id)\
|
||||||
.filter(Project.id == project.id)\
|
.filter(Project.id == g.project.id)\
|
||||||
.order_by(Bill.date.desc())
|
.order_by(Bill.date.desc())
|
||||||
return render_template("list_bills.html",
|
return render_template("list_bills.html",
|
||||||
bills=bills, project=project,
|
bills=bills, member_form=MemberForm(g.project),
|
||||||
member_form=MemberForm(project),
|
bill_form=get_billform_for(g.project)
|
||||||
bill_form=get_billform_for(project)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.route("/<string:project_id>/members/add", methods=["GET", "POST"])
|
@app.route("/<project_id>/members/add", methods=["GET", "POST"])
|
||||||
@requires_auth
|
def add_member():
|
||||||
def add_member(project):
|
|
||||||
# FIXME manage form errors on the list_bills page
|
# FIXME manage form errors on the list_bills page
|
||||||
form = MemberForm(project)
|
form = MemberForm(g.project)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if form.validate():
|
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()
|
db.session.commit()
|
||||||
return redirect(url_for("list_bills", project_id=project.id))
|
return redirect(url_for("list_bills"))
|
||||||
return render_template("add_member.html", form=form, project=project)
|
return render_template("add_member.html", form=form)
|
||||||
|
|
||||||
@app.route("/<string:project_id>/members/<int:member_id>/delete", methods=["GET", "POST"])
|
@app.route("/<project_id>/members/<member_id>/delete", methods=["GET", "POST"])
|
||||||
@requires_auth
|
def remove_member(member_id):
|
||||||
def remove_member(project, member_id):
|
|
||||||
person = Person.query.get_or_404(member_id)
|
person = Person.query.get_or_404(member_id)
|
||||||
if person.project == project:
|
if person.project == g.project:
|
||||||
if not person.has_bills():
|
if not person.has_bills():
|
||||||
db.session.delete(person)
|
db.session.delete(person)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -158,12 +177,11 @@ def remove_member(project, member_id):
|
||||||
person.activated = False
|
person.activated = False
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash("User '%s' has been desactivated" % person.name)
|
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("/<string:project_id>/add", methods=["GET", "POST"])
|
@app.route("/<project_id>/add", methods=["GET", "POST"])
|
||||||
@requires_auth
|
def add_bill():
|
||||||
def add_bill(project):
|
form = get_billform_for(g.project)
|
||||||
form = get_billform_for(project)
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
if form.validate():
|
if form.validate():
|
||||||
bill = Bill()
|
bill = Bill()
|
||||||
|
@ -171,47 +189,43 @@ def add_bill(project):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
flash("The bill has been added")
|
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("/<string:project_id>/delete/<int:bill_id>")
|
@app.route("/<project_id>/delete/<int:bill_id>")
|
||||||
@requires_auth
|
def delete_bill(bill_id):
|
||||||
def delete_bill(project, bill_id):
|
|
||||||
bill = Bill.query.get_or_404(bill_id)
|
bill = Bill.query.get_or_404(bill_id)
|
||||||
db.session.delete(bill)
|
db.session.delete(bill)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash("The bill has been deleted")
|
flash("The bill has been deleted")
|
||||||
|
|
||||||
return redirect(url_for('list_bills', project_id=project.id))
|
return redirect(url_for('list_bills'))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<string:project_id>/edit/<int:bill_id>", methods=["GET", "POST"])
|
@app.route("/<project_id>/edit/<int:bill_id>", methods=["GET", "POST"])
|
||||||
@requires_auth
|
def edit_bill(bill_id):
|
||||||
def edit_bill(project, bill_id):
|
|
||||||
bill = Bill.query.get_or_404(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():
|
if request.method == 'POST' and form.validate():
|
||||||
form.save(bill)
|
form.save(bill)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
flash("The bill has been modified")
|
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)
|
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("/<string:project_id>/compute")
|
@app.route("/<project_id>/compute")
|
||||||
@requires_auth
|
def compute_bills():
|
||||||
def compute_bills(project):
|
|
||||||
"""Compute the sum each one have to pay to each other and display it"""
|
"""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("/<string:project_id>/reset")
|
@app.route("/<project_id>/reset")
|
||||||
@requires_auth
|
def reset_bills():
|
||||||
def reset_bills(project):
|
|
||||||
"""Reset the list of bills"""
|
"""Reset the list of bills"""
|
||||||
# FIXME replace with the archive feature
|
# FIXME replace with the archive feature
|
||||||
# get all the bills which are not processed
|
# get all the bills which are not processed
|
||||||
|
|
Loading…
Reference in a new issue