proper import form (fix messy errors)

This commit is contained in:
Youe Graillot 2021-11-28 00:59:57 +01:00
parent d0a8668de6
commit d24efac10a
4 changed files with 149 additions and 128 deletions

View file

@ -182,13 +182,16 @@ class EditProjectForm(FlaskForm):
return project
class UploadForm(FlaskForm):
class ImportProjectForm(FlaskForm):
file = FileField(
"File",
validators=[
FileRequired(),
"JSON",
validators=[FileRequired(), FileAllowed(["json", "JSON"], "JSON only!")],
],
description=_("Import previously exported JSON file"),
)
submit = SubmitField(_("Import"))
class ProjectForm(EditProjectForm):
@ -240,7 +243,7 @@ class ProjectFormWithCaptcha(ProjectForm):
raise ValidationError(Markup(message))
class DestructiveActionProjectForm(FlaskForm):
class DeleteProjectForm(FlaskForm):
"""Used for any important "delete" action linked to a project:
- delete project itself

View file

@ -29,7 +29,7 @@
<div class="container edit-project">
<h2>{{ _("Edit project") }}</h2>
<form class="form-horizontal" method="post">
<form id="edit-project" class="form-horizontal" method="post">
{{ forms.edit_project(edit_form) }}
</form>
@ -38,23 +38,10 @@
{{ forms.delete_project(delete_form) }}
</form>
<h2>{{ _("Import JSON") }}</h2>
<form class="form-horizontal" method="post" enctype="multipart/form-data">
{{ import_form.hidden_tag() }}
<div class="custom-file">
<div class="form-group">
{{ import_form.file(class="custom-file-input") }}
<small class="form-text text-muted">
{{ import_form.file.description }}
</small>
</div>
<label class="custom-file-label" for="customFile">{{ _('Choose file') }}</label>
</div>
<div class="actions">
{{ import_form.submit(class="btn btn-primary") }}
</div>
<h2>{{ _("Import project") }}</h2>
<form id="import-project" class="form-horizontal" action="{{ url_for(".import_project") }}" method="post" enctype="multipart/form-data">
{{ forms.import_project(import_form) }}
</form>
<h2>{{ _("Download project's data") }}</h2>

View file

@ -118,13 +118,25 @@
{% endmacro %}
{% macro upload_json(form) %}
{% macro import_project(form) %}
{% include "display_errors.html" %}
{{ form.hidden_tag() }}
{{ form.file }}
<div class="custom-file">
<div class="form-group">
{{ form.file(class="custom-file-input") }}
<small class="form-text text-muted">
{{ form.file.description }}
</small>
</div>
<label class="custom-file-label" for="customFile">{{ _('Choose file') }}</label>
</div>
<div class="actions">
<button class="btn btn-primary">{{ _("Import") }}</button>
</div>
{% endmacro %}
{% macro delete_project_history(form) %}

View file

@ -41,16 +41,16 @@ from ihatemoney.emails import send_creation_email
from ihatemoney.forms import (
AdminAuthenticationForm,
AuthenticationForm,
DestructiveActionProjectForm,
DeleteProjectForm,
EditProjectForm,
EmptyForm,
ImportProjectForm,
InviteForm,
MemberForm,
PasswordReminder,
ProjectForm,
ProjectFormWithCaptcha,
ResetPasswordForm,
UploadForm,
get_billform_for,
)
from ihatemoney.history import get_history, get_history_queries
@ -412,17 +412,8 @@ def reset_password():
@main.route("/<project_id>/edit", methods=["GET", "POST"])
def edit_project():
edit_form = EditProjectForm(id=g.project.id)
delete_form = DestructiveActionProjectForm(id=g.project.id)
import_form = UploadForm()
# Import form
if import_form.validate_on_submit():
try:
import_project(import_form.file.data.stream, g.project)
flash(_("Project successfully uploaded"))
return redirect(url_for("main.list_bills"))
except ValueError as e:
flash(e.args[0], category="danger")
import_form = ImportProjectForm(id=g.project.id)
delete_form = DeleteProjectForm(id=g.project.id)
# Edit form
if edit_form.validate_on_submit():
@ -446,24 +437,40 @@ def edit_project():
return render_template(
"edit_project.html",
edit_form=edit_form,
delete_form=delete_form,
import_form=import_form,
delete_form=delete_form,
current_view="edit_project",
)
def import_project(file, project):
json_file = json.load(file)
@main.route("/<project_id>/import", methods=["POST"])
def import_project():
form = ImportProjectForm()
if form.validate():
try:
data = form.file.data
if data.mimetype == "application/json":
json_file = json.load(data.stream)
else:
raise ValueError("Unsupported file type")
# Check if JSON is correct
attr = ["what", "payer_name", "payer_weight", "amount", "currency", "date", "owers"]
attr = [
"what",
"payer_name",
"payer_weight",
"amount",
"currency",
"date",
"owers",
]
attr.sort()
currencies = set()
for e in json_file:
# If currency is absent, empty, or explicitly set to XXX
# set it to project default.
if e.get("currency", "") in ["", "XXX"]:
e["currency"] = project.default_currency
e["currency"] = g.project.default_currency
if len(e) != len(attr):
raise ValueError(_("Invalid JSON"))
list_attr = []
@ -476,7 +483,7 @@ def import_project(file, project):
currencies.add(e["currency"])
# Additional checks if project has no default currency
if project.default_currency == CurrencyConverter.no_currency:
if g.project.default_currency == CurrencyConverter.no_currency:
# If bills have currencies, they must be consistent
if len(currencies - {CurrencyConverter.no_currency}) >= 2:
raise ValueError(
@ -490,7 +497,7 @@ def import_project(file, project):
# From json : export list of members
members_json = get_members(json_file)
members = project.members
members = g.project.members
members_already_here = list()
for m in members:
members_already_here.append(str(m))
@ -504,7 +511,7 @@ def import_project(file, project):
# List bills not in the project
# Same format than JSON element
project_bills = project.get_pretty_bills()
project_bills = g.project.get_pretty_bills()
bill_to_add = list()
for j in json_file:
same = False
@ -517,11 +524,11 @@ def import_project(file, project):
# Add users to DB
for m in members_to_add:
Person(name=m[0], project=project, weight=m[1])
Person(name=m[0], project=g.project, weight=m[1])
db.session.commit()
id_dict = {}
for i in project.members:
for i in g.project.members:
id_dict[i.name] = i.id
# Create bills
@ -531,7 +538,7 @@ def import_project(file, project):
owers_id.append(id_dict[ower])
bill = Bill()
form = get_billform_for(project)
form = get_billform_for(g.project)
form.what = b["what"]
form.amount = b["amount"]
form.original_currency = b["currency"]
@ -539,15 +546,27 @@ def import_project(file, project):
form.payer = id_dict[b["payer_name"]]
form.payed_for = owers_id
db.session.add(form.fake_form(bill, project))
db.session.add(form.fake_form(bill, g.project))
# Add bills to DB
db.session.commit()
flash(_("Project successfully uploaded"))
return redirect(url_for("main.list_bills"))
except ValueError as e:
flash(e.args[0], category="danger")
return render_template(
"edit_project.html",
current_view="edit_project",
)
else:
for component, errors in form.errors.items():
flash(_(component + ": ") + ", ".join(errors), category="danger")
return redirect(url_for(".edit_project"))
@main.route("/<project_id>/delete", methods=["POST"])
def delete_project():
form = DestructiveActionProjectForm(id=g.project.id)
form = DeleteProjectForm(id=g.project.id)
if form.validate():
g.project.remove_project()
flash(_("Project successfully deleted"))
@ -838,7 +857,7 @@ def history():
any_ip_addresses = any(event["ip"] for event in history)
delete_form = DestructiveActionProjectForm()
delete_form = DeleteProjectForm()
return render_template(
"history.html",
current_view="history",
@ -854,7 +873,7 @@ def history():
@main.route("/<project_id>/erase_history", methods=["POST"])
def erase_history():
"""Erase all history entries associated with this project."""
form = DestructiveActionProjectForm(id=g.project.id)
form = DeleteProjectForm(id=g.project.id)
if not form.validate():
flash(
format_form_errors(form, _("Error deleting project history")),
@ -873,7 +892,7 @@ def erase_history():
@main.route("/<project_id>/strip_ip_addresses", methods=["POST"])
def strip_ip_addresses():
"""Strip ip addresses from history entries associated with this project."""
form = DestructiveActionProjectForm(id=g.project.id)
form = DeleteProjectForm(id=g.project.id)
if not form.validate():
flash(
format_form_errors(form, _("Error deleting recorded IP addresses")),