mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-29 09:52:36 +02:00
Move export form to links
This commit is contained in:
parent
09d887cde2
commit
0edf8634a4
7 changed files with 83 additions and 81 deletions
|
@ -215,16 +215,3 @@ class InviteForm(FlaskForm):
|
||||||
except email_validator.EmailNotValidError:
|
except email_validator.EmailNotValidError:
|
||||||
raise ValidationError(_("The email %(email)s is not valid",
|
raise ValidationError(_("The email %(email)s is not valid",
|
||||||
email=email))
|
email=email))
|
||||||
|
|
||||||
|
|
||||||
class ExportForm(FlaskForm):
|
|
||||||
export_type = SelectField(
|
|
||||||
_("What do you want to download ?"),
|
|
||||||
validators=[Required()],
|
|
||||||
coerce=str,
|
|
||||||
choices=[("bills", _("bills")), ("transactions", _("transactions"))])
|
|
||||||
export_format = SelectField(
|
|
||||||
_("Export file format"),
|
|
||||||
validators=[Required()],
|
|
||||||
coerce=str,
|
|
||||||
choices=[("csv", "csv"), ("json", "json")])
|
|
||||||
|
|
|
@ -442,6 +442,11 @@ tr:hover .extra-info {
|
||||||
border-bottom: 0.2em solid transparent;
|
border-bottom: 0.2em solid transparent;
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.download-project .icon svg {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
.icon.plus svg {
|
.icon.plus svg {
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
|
|
1
ihatemoney/static/images/file-alt.svg
Normal file
1
ihatemoney/static/images/file-alt.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm64 236c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-64c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-72v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm96-114.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"/></svg>
|
After Width: | Height: | Size: 557 B |
1
ihatemoney/static/images/file-csv-solid.svg
Normal file
1
ihatemoney/static/images/file-csv-solid.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file-csv" class="svg-inline--fa fa-file-csv fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm-96 144c0 4.42-3.58 8-8 8h-8c-8.84 0-16 7.16-16 16v32c0 8.84 7.16 16 16 16h8c4.42 0 8 3.58 8 8v16c0 4.42-3.58 8-8 8h-8c-26.51 0-48-21.49-48-48v-32c0-26.51 21.49-48 48-48h8c4.42 0 8 3.58 8 8v16zm44.27 104H160c-4.42 0-8-3.58-8-8v-16c0-4.42 3.58-8 8-8h12.27c5.95 0 10.41-3.5 10.41-6.62 0-1.3-.75-2.66-2.12-3.84l-21.89-18.77c-8.47-7.22-13.33-17.48-13.33-28.14 0-21.3 19.02-38.62 42.41-38.62H200c4.42 0 8 3.58 8 8v16c0 4.42-3.58 8-8 8h-12.27c-5.95 0-10.41 3.5-10.41 6.62 0 1.3.75 2.66 2.12 3.84l21.89 18.77c8.47 7.22 13.33 17.48 13.33 28.14.01 21.29-19 38.62-42.39 38.62zM256 264v20.8c0 20.27 5.7 40.17 16 56.88 10.3-16.7 16-36.61 16-56.88V264c0-4.42 3.58-8 8-8h16c4.42 0 8 3.58 8 8v20.8c0 35.48-12.88 68.89-36.28 94.09-3.02 3.25-7.27 5.11-11.72 5.11s-8.7-1.86-11.72-5.11c-23.4-25.2-36.28-58.61-36.28-94.09V264c0-4.42 3.58-8 8-8h16c4.42 0 8 3.58 8 8zm121-159L279.1 7c-4.5-4.5-10.6-7-17-7H256v128h128v-6.1c0-6.3-2.5-12.4-7-16.9z"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -8,12 +8,48 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{{ _("Edit this project") }}</h2>
|
<h2>{{ _("Edit project") }}</h2>
|
||||||
<form class="form-horizontal" method="post">
|
<p>
|
||||||
{{ forms.edit_project(edit_form) }}
|
<form class="form-horizontal" method="post">
|
||||||
</form></br>
|
{{ forms.edit_project(edit_form) }}
|
||||||
<h2>{{ _("Download this project's data") }}</h2>
|
</form>
|
||||||
<form class="form-horizontal" method="post">
|
</p>
|
||||||
{{ forms.export_project(export_form) }}
|
|
||||||
</form>
|
<h2>{{ _("Download project's data") }}</h2>
|
||||||
|
<p>
|
||||||
|
<div class="list-group download-project">
|
||||||
|
<div class="list-group-item list-group-item-action">
|
||||||
|
<h5 class="d-flex w-100 justify-content-between">
|
||||||
|
<span class="mb-1">{{ _('Bill items') }}</span>
|
||||||
|
<span>
|
||||||
|
<a href="{{ url_for('.export_project', file='bills', format='json') }}" download class="badge badge-secondary">
|
||||||
|
<i class="icon plus">{{ static_include("images/file-alt.svg") | safe }}</i>
|
||||||
|
JSON
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('.export_project', file='bills', format='csv') }}" download class="badge badge-secondary">
|
||||||
|
<i class="icon plus">{{ static_include("images/file-csv-solid.svg") | safe }}</i>
|
||||||
|
CSV
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
<p class="mb-1 text-muted">{{ _('Download the list of bills with owner, amount, reason,... ') }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="list-group-item list-group-item-action">
|
||||||
|
<h5 class="d-flex w-100 justify-content-between">
|
||||||
|
<span class="mb-1">{{ _('Settle plans') }}</span>
|
||||||
|
<span>
|
||||||
|
<a href="{{ url_for('.export_project', file='transactions', format='json') }}" download class="badge badge-secondary">
|
||||||
|
<i class="icon plus">{{ static_include("images/file-alt.svg") | safe }}</i>
|
||||||
|
JSON
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('.export_project', file='transactions', format='csv') }}" download class="badge badge-secondary">
|
||||||
|
<i class="icon plus">{{ static_include("images/file-csv-solid.svg") | safe }}</i>
|
||||||
|
CSV
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</h5>
|
||||||
|
<p class="mb-1 text-muted">{{ _('Download the list of transactions needed to settle the current bills.') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -912,10 +912,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
||||||
})
|
})
|
||||||
|
|
||||||
# generate json export of bills
|
# generate json export of bills
|
||||||
resp = self.client.post("/raclette/edit", data={
|
resp = self.client.get("/raclette/export/bills.json")
|
||||||
'export_format': 'json',
|
|
||||||
'export_type': 'bills'
|
|
||||||
})
|
|
||||||
expected = [{
|
expected = [{
|
||||||
'date': '2017-01-01',
|
'date': '2017-01-01',
|
||||||
'what': 'refund',
|
'what': 'refund',
|
||||||
|
@ -941,10 +938,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
||||||
self.assertEqual(json.loads(resp.data.decode('utf-8')), expected)
|
self.assertEqual(json.loads(resp.data.decode('utf-8')), expected)
|
||||||
|
|
||||||
# generate csv export of bills
|
# generate csv export of bills
|
||||||
resp = self.client.post("/raclette/edit", data={
|
resp = self.client.get("/raclette/export/bills.csv")
|
||||||
'export_format': 'csv',
|
|
||||||
'export_type': 'bills'
|
|
||||||
})
|
|
||||||
expected = [
|
expected = [
|
||||||
"date,what,amount,payer_name,payer_weight,owers",
|
"date,what,amount,payer_name,payer_weight,owers",
|
||||||
"2017-01-01,refund,13.33,tata,1.0,fred",
|
"2017-01-01,refund,13.33,tata,1.0,fred",
|
||||||
|
@ -959,20 +953,14 @@ class BudgetTestCase(IhatemoneyTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# generate json export of transactions
|
# generate json export of transactions
|
||||||
resp = self.client.post("/raclette/edit", data={
|
resp = self.client.get("/raclette/export/transactions.json")
|
||||||
'export_format': 'json',
|
|
||||||
'export_type': 'transactions'
|
|
||||||
})
|
|
||||||
expected = [{"amount": 127.33, "receiver": "fred", "ower": "alexis"},
|
expected = [{"amount": 127.33, "receiver": "fred", "ower": "alexis"},
|
||||||
{"amount": 55.34, "receiver": "fred", "ower": "tata"},
|
{"amount": 55.34, "receiver": "fred", "ower": "tata"},
|
||||||
{"amount": 2.00, "receiver": "fred", "ower": "p\xe9p\xe9"}]
|
{"amount": 2.00, "receiver": "fred", "ower": "p\xe9p\xe9"}]
|
||||||
self.assertEqual(json.loads(resp.data.decode('utf-8')), expected)
|
self.assertEqual(json.loads(resp.data.decode('utf-8')), expected)
|
||||||
|
|
||||||
# generate csv export of transactions
|
# generate csv export of transactions
|
||||||
resp = self.client.post("/raclette/edit", data={
|
resp = self.client.get("/raclette/export/transactions.csv")
|
||||||
'export_format': 'csv',
|
|
||||||
'export_type': 'transactions'
|
|
||||||
})
|
|
||||||
|
|
||||||
expected = ["amount,receiver,ower",
|
expected = ["amount,receiver,ower",
|
||||||
"127.33,fred,alexis",
|
"127.33,fred,alexis",
|
||||||
|
@ -986,23 +974,9 @@ class BudgetTestCase(IhatemoneyTestCase):
|
||||||
set(received_lines[i].strip("\r").split(","))
|
set(received_lines[i].strip("\r").split(","))
|
||||||
)
|
)
|
||||||
|
|
||||||
# wrong export_format should return a 200 and export form
|
# wrong export_format should return a 404
|
||||||
resp = self.client.post("/raclette/edit", data={
|
resp = self.client.get("/raclette/export/transactions.wrong")
|
||||||
'export_format': 'wrong_export_format',
|
self.assertEqual(resp.status_code, 404)
|
||||||
'export_type': 'transactions'
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
|
||||||
self.assertIn('id="export_format" name="export_format"', resp.data.decode('utf-8'))
|
|
||||||
|
|
||||||
# wrong export_type should return a 200 and export form
|
|
||||||
resp = self.client.post("/raclette/edit", data={
|
|
||||||
'export_format': 'json',
|
|
||||||
'export_type': 'wrong_export_type'
|
|
||||||
})
|
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
|
||||||
self.assertIn('id="export_format" name="export_format"', resp.data.decode('utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
class APITestCase(IhatemoneyTestCase):
|
class APITestCase(IhatemoneyTestCase):
|
||||||
|
|
|
@ -11,7 +11,7 @@ and `add_project_id` for a quick overview)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint, current_app, flash, g, redirect, render_template, request,
|
abort, Blueprint, current_app, flash, g, redirect, render_template, request,
|
||||||
session, url_for, send_file, send_from_directory
|
session, url_for, send_file, send_from_directory
|
||||||
)
|
)
|
||||||
from flask_mail import Message
|
from flask_mail import Message
|
||||||
|
@ -25,8 +25,7 @@ from functools import wraps
|
||||||
from ihatemoney.models import db, Project, Person, Bill
|
from ihatemoney.models import db, Project, Person, Bill
|
||||||
from ihatemoney.forms import (
|
from ihatemoney.forms import (
|
||||||
AdminAuthenticationForm, AuthenticationForm, EditProjectForm,
|
AdminAuthenticationForm, AuthenticationForm, EditProjectForm,
|
||||||
InviteForm, MemberForm, PasswordReminder, ResetPasswordForm, ProjectForm, get_billform_for,
|
InviteForm, MemberForm, PasswordReminder, ResetPasswordForm, ProjectForm, get_billform_for
|
||||||
ExportForm
|
|
||||||
)
|
)
|
||||||
from ihatemoney.utils import Redirect303, list_of_dicts2json, list_of_dicts2csv, LoginThrottler
|
from ihatemoney.utils import Redirect303, list_of_dicts2json, list_of_dicts2csv, LoginThrottler
|
||||||
|
|
||||||
|
@ -309,7 +308,6 @@ def reset_password():
|
||||||
@main.route("/<project_id>/edit", methods=["GET", "POST"])
|
@main.route("/<project_id>/edit", methods=["GET", "POST"])
|
||||||
def edit_project():
|
def edit_project():
|
||||||
edit_form = EditProjectForm()
|
edit_form = EditProjectForm()
|
||||||
export_form = ExportForm()
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if edit_form.validate():
|
if edit_form.validate():
|
||||||
project = edit_form.update(g.project)
|
project = edit_form.update(g.project)
|
||||||
|
@ -317,28 +315,6 @@ def edit_project():
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return redirect(url_for(".list_bills"))
|
return redirect(url_for(".list_bills"))
|
||||||
|
|
||||||
if export_form.validate():
|
|
||||||
export_format = export_form.export_format.data
|
|
||||||
export_type = export_form.export_type.data
|
|
||||||
|
|
||||||
if export_type == 'transactions':
|
|
||||||
export = g.project.get_transactions_to_settle_bill(
|
|
||||||
pretty_output=True)
|
|
||||||
if export_type == "bills":
|
|
||||||
export = g.project.get_pretty_bills(
|
|
||||||
export_format=export_format)
|
|
||||||
|
|
||||||
if export_format == "json":
|
|
||||||
file2export = list_of_dicts2json(export)
|
|
||||||
if export_format == "csv":
|
|
||||||
file2export = list_of_dicts2csv(export)
|
|
||||||
|
|
||||||
return send_file(file2export,
|
|
||||||
attachment_filename="%s-%s.%s" %
|
|
||||||
(g.project.id, export_type, export_format),
|
|
||||||
as_attachment=True
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
edit_form.name.data = g.project.name
|
edit_form.name.data = g.project.name
|
||||||
edit_form.contact_email.data = g.project.contact_email
|
edit_form.contact_email.data = g.project.contact_email
|
||||||
|
@ -346,7 +322,6 @@ def edit_project():
|
||||||
return render_template(
|
return render_template(
|
||||||
"edit_project.html",
|
"edit_project.html",
|
||||||
edit_form=edit_form,
|
edit_form=edit_form,
|
||||||
export_form=export_form,
|
|
||||||
current_view="edit_project"
|
current_view="edit_project"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -359,6 +334,29 @@ def delete_project():
|
||||||
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':
|
||||||
|
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')
|
||||||
|
|
||||||
|
if format == "json":
|
||||||
|
file2export = list_of_dicts2json(export)
|
||||||
|
elif format == "csv":
|
||||||
|
file2export = list_of_dicts2csv(export)
|
||||||
|
else:
|
||||||
|
abort(404, 'No such export format')
|
||||||
|
|
||||||
|
return send_file(
|
||||||
|
file2export,
|
||||||
|
attachment_filename="%s-%s.%s" % (g.project.id, file, format),
|
||||||
|
as_attachment=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/exit")
|
@main.route("/exit")
|
||||||
def exit():
|
def exit():
|
||||||
# delete the session
|
# delete the session
|
||||||
|
|
Loading…
Reference in a new issue