mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-05-05 20:51:49 +02:00
proper import form (fix messy errors)
This commit is contained in:
parent
d0a8668de6
commit
d24efac10a
4 changed files with 149 additions and 128 deletions
|
@ -182,13 +182,16 @@ class EditProjectForm(FlaskForm):
|
||||||
return project
|
return project
|
||||||
|
|
||||||
|
|
||||||
class UploadForm(FlaskForm):
|
class ImportProjectForm(FlaskForm):
|
||||||
file = FileField(
|
file = FileField(
|
||||||
|
"File",
|
||||||
|
validators=[
|
||||||
|
FileRequired(),
|
||||||
"JSON",
|
"JSON",
|
||||||
validators=[FileRequired(), FileAllowed(["json", "JSON"], "JSON only!")],
|
validators=[FileRequired(), FileAllowed(["json", "JSON"], "JSON only!")],
|
||||||
|
],
|
||||||
description=_("Import previously exported JSON file"),
|
description=_("Import previously exported JSON file"),
|
||||||
)
|
)
|
||||||
submit = SubmitField(_("Import"))
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectForm(EditProjectForm):
|
class ProjectForm(EditProjectForm):
|
||||||
|
@ -240,7 +243,7 @@ class ProjectFormWithCaptcha(ProjectForm):
|
||||||
raise ValidationError(Markup(message))
|
raise ValidationError(Markup(message))
|
||||||
|
|
||||||
|
|
||||||
class DestructiveActionProjectForm(FlaskForm):
|
class DeleteProjectForm(FlaskForm):
|
||||||
"""Used for any important "delete" action linked to a project:
|
"""Used for any important "delete" action linked to a project:
|
||||||
|
|
||||||
- delete project itself
|
- delete project itself
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<div class="container edit-project">
|
<div class="container edit-project">
|
||||||
|
|
||||||
<h2>{{ _("Edit project") }}</h2>
|
<h2>{{ _("Edit project") }}</h2>
|
||||||
<form class="form-horizontal" method="post">
|
<form id="edit-project" class="form-horizontal" method="post">
|
||||||
{{ forms.edit_project(edit_form) }}
|
{{ forms.edit_project(edit_form) }}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -38,23 +38,10 @@
|
||||||
{{ forms.delete_project(delete_form) }}
|
{{ forms.delete_project(delete_form) }}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2>{{ _("Import JSON") }}</h2>
|
|
||||||
<form class="form-horizontal" method="post" enctype="multipart/form-data">
|
|
||||||
{{ import_form.hidden_tag() }}
|
|
||||||
|
|
||||||
<div class="custom-file">
|
<h2>{{ _("Import project") }}</h2>
|
||||||
<div class="form-group">
|
<form id="import-project" class="form-horizontal" action="{{ url_for(".import_project") }}" method="post" enctype="multipart/form-data">
|
||||||
{{ import_form.file(class="custom-file-input") }}
|
{{ forms.import_project(import_form) }}
|
||||||
<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>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2>{{ _("Download project's data") }}</h2>
|
<h2>{{ _("Download project's data") }}</h2>
|
||||||
|
|
|
@ -118,13 +118,25 @@
|
||||||
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro upload_json(form) %}
|
{% macro import_project(form) %}
|
||||||
|
|
||||||
{% include "display_errors.html" %}
|
{% include "display_errors.html" %}
|
||||||
{{ form.hidden_tag() }}
|
{{ 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">
|
<div class="actions">
|
||||||
<button class="btn btn-primary">{{ _("Import") }}</button>
|
<button class="btn btn-primary">{{ _("Import") }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro delete_project_history(form) %}
|
{% macro delete_project_history(form) %}
|
||||||
|
|
|
@ -41,16 +41,16 @@ from ihatemoney.emails import send_creation_email
|
||||||
from ihatemoney.forms import (
|
from ihatemoney.forms import (
|
||||||
AdminAuthenticationForm,
|
AdminAuthenticationForm,
|
||||||
AuthenticationForm,
|
AuthenticationForm,
|
||||||
DestructiveActionProjectForm,
|
DeleteProjectForm,
|
||||||
EditProjectForm,
|
EditProjectForm,
|
||||||
EmptyForm,
|
EmptyForm,
|
||||||
|
ImportProjectForm,
|
||||||
InviteForm,
|
InviteForm,
|
||||||
MemberForm,
|
MemberForm,
|
||||||
PasswordReminder,
|
PasswordReminder,
|
||||||
ProjectForm,
|
ProjectForm,
|
||||||
ProjectFormWithCaptcha,
|
ProjectFormWithCaptcha,
|
||||||
ResetPasswordForm,
|
ResetPasswordForm,
|
||||||
UploadForm,
|
|
||||||
get_billform_for,
|
get_billform_for,
|
||||||
)
|
)
|
||||||
from ihatemoney.history import get_history, get_history_queries
|
from ihatemoney.history import get_history, get_history_queries
|
||||||
|
@ -412,17 +412,8 @@ 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(id=g.project.id)
|
edit_form = EditProjectForm(id=g.project.id)
|
||||||
delete_form = DestructiveActionProjectForm(id=g.project.id)
|
import_form = ImportProjectForm(id=g.project.id)
|
||||||
import_form = UploadForm()
|
delete_form = DeleteProjectForm(id=g.project.id)
|
||||||
# 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")
|
|
||||||
|
|
||||||
# Edit form
|
# Edit form
|
||||||
if edit_form.validate_on_submit():
|
if edit_form.validate_on_submit():
|
||||||
|
@ -446,108 +437,136 @@ def edit_project():
|
||||||
return render_template(
|
return render_template(
|
||||||
"edit_project.html",
|
"edit_project.html",
|
||||||
edit_form=edit_form,
|
edit_form=edit_form,
|
||||||
delete_form=delete_form,
|
|
||||||
import_form=import_form,
|
import_form=import_form,
|
||||||
|
delete_form=delete_form,
|
||||||
current_view="edit_project",
|
current_view="edit_project",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def import_project(file, project):
|
@main.route("/<project_id>/import", methods=["POST"])
|
||||||
json_file = json.load(file)
|
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
|
# Check if JSON is correct
|
||||||
attr = ["what", "payer_name", "payer_weight", "amount", "currency", "date", "owers"]
|
attr = [
|
||||||
attr.sort()
|
"what",
|
||||||
currencies = set()
|
"payer_name",
|
||||||
for e in json_file:
|
"payer_weight",
|
||||||
# If currency is absent, empty, or explicitly set to XXX
|
"amount",
|
||||||
# set it to project default.
|
"currency",
|
||||||
if e.get("currency", "") in ["", "XXX"]:
|
"date",
|
||||||
e["currency"] = project.default_currency
|
"owers",
|
||||||
if len(e) != len(attr):
|
]
|
||||||
raise ValueError(_("Invalid JSON"))
|
attr.sort()
|
||||||
list_attr = []
|
currencies = set()
|
||||||
for i in e:
|
for e in json_file:
|
||||||
list_attr.append(i)
|
# If currency is absent, empty, or explicitly set to XXX
|
||||||
list_attr.sort()
|
# set it to project default.
|
||||||
if list_attr != attr:
|
if e.get("currency", "") in ["", "XXX"]:
|
||||||
raise ValueError(_("Invalid JSON"))
|
e["currency"] = g.project.default_currency
|
||||||
# Keep track of currencies
|
if len(e) != len(attr):
|
||||||
currencies.add(e["currency"])
|
raise ValueError(_("Invalid JSON"))
|
||||||
|
list_attr = []
|
||||||
|
for i in e:
|
||||||
|
list_attr.append(i)
|
||||||
|
list_attr.sort()
|
||||||
|
if list_attr != attr:
|
||||||
|
raise ValueError(_("Invalid JSON"))
|
||||||
|
# Keep track of currencies
|
||||||
|
currencies.add(e["currency"])
|
||||||
|
|
||||||
# Additional checks if project has no default 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 bills have currencies, they must be consistent
|
||||||
if len(currencies - {CurrencyConverter.no_currency}) >= 2:
|
if len(currencies - {CurrencyConverter.no_currency}) >= 2:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
_(
|
_(
|
||||||
"Cannot add bills in multiple currencies to a project without default currency"
|
"Cannot add bills in multiple currencies to a project without default currency"
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
# Strip currency from bills (since it's the same for every bill)
|
||||||
|
for e in json_file:
|
||||||
|
e["currency"] = CurrencyConverter.no_currency
|
||||||
|
|
||||||
|
# From json : export list of members
|
||||||
|
members_json = get_members(json_file)
|
||||||
|
members = g.project.members
|
||||||
|
members_already_here = list()
|
||||||
|
for m in members:
|
||||||
|
members_already_here.append(str(m))
|
||||||
|
|
||||||
|
# List all members not in the project and weight associated
|
||||||
|
# List of tuples (name,weight)
|
||||||
|
members_to_add = list()
|
||||||
|
for i in members_json:
|
||||||
|
if str(i[0]) not in members_already_here:
|
||||||
|
members_to_add.append(i)
|
||||||
|
|
||||||
|
# List bills not in the project
|
||||||
|
# Same format than JSON element
|
||||||
|
project_bills = g.project.get_pretty_bills()
|
||||||
|
bill_to_add = list()
|
||||||
|
for j in json_file:
|
||||||
|
same = False
|
||||||
|
for p in project_bills:
|
||||||
|
if same_bill(p, j):
|
||||||
|
same = True
|
||||||
|
break
|
||||||
|
if not same:
|
||||||
|
bill_to_add.append(j)
|
||||||
|
|
||||||
|
# Add users to DB
|
||||||
|
for m in members_to_add:
|
||||||
|
Person(name=m[0], project=g.project, weight=m[1])
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
id_dict = {}
|
||||||
|
for i in g.project.members:
|
||||||
|
id_dict[i.name] = i.id
|
||||||
|
|
||||||
|
# Create bills
|
||||||
|
for b in bill_to_add:
|
||||||
|
owers_id = list()
|
||||||
|
for ower in b["owers"]:
|
||||||
|
owers_id.append(id_dict[ower])
|
||||||
|
|
||||||
|
bill = Bill()
|
||||||
|
form = get_billform_for(g.project)
|
||||||
|
form.what = b["what"]
|
||||||
|
form.amount = b["amount"]
|
||||||
|
form.original_currency = b["currency"]
|
||||||
|
form.date = parse(b["date"])
|
||||||
|
form.payer = id_dict[b["payer_name"]]
|
||||||
|
form.payed_for = owers_id
|
||||||
|
|
||||||
|
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",
|
||||||
)
|
)
|
||||||
# Strip currency from bills (since it's the same for every bill)
|
else:
|
||||||
for e in json_file:
|
for component, errors in form.errors.items():
|
||||||
e["currency"] = CurrencyConverter.no_currency
|
flash(_(component + ": ") + ", ".join(errors), category="danger")
|
||||||
|
return redirect(url_for(".edit_project"))
|
||||||
# From json : export list of members
|
|
||||||
members_json = get_members(json_file)
|
|
||||||
members = project.members
|
|
||||||
members_already_here = list()
|
|
||||||
for m in members:
|
|
||||||
members_already_here.append(str(m))
|
|
||||||
|
|
||||||
# List all members not in the project and weight associated
|
|
||||||
# List of tuples (name,weight)
|
|
||||||
members_to_add = list()
|
|
||||||
for i in members_json:
|
|
||||||
if str(i[0]) not in members_already_here:
|
|
||||||
members_to_add.append(i)
|
|
||||||
|
|
||||||
# List bills not in the project
|
|
||||||
# Same format than JSON element
|
|
||||||
project_bills = project.get_pretty_bills()
|
|
||||||
bill_to_add = list()
|
|
||||||
for j in json_file:
|
|
||||||
same = False
|
|
||||||
for p in project_bills:
|
|
||||||
if same_bill(p, j):
|
|
||||||
same = True
|
|
||||||
break
|
|
||||||
if not same:
|
|
||||||
bill_to_add.append(j)
|
|
||||||
|
|
||||||
# Add users to DB
|
|
||||||
for m in members_to_add:
|
|
||||||
Person(name=m[0], project=project, weight=m[1])
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
id_dict = {}
|
|
||||||
for i in project.members:
|
|
||||||
id_dict[i.name] = i.id
|
|
||||||
|
|
||||||
# Create bills
|
|
||||||
for b in bill_to_add:
|
|
||||||
owers_id = list()
|
|
||||||
for ower in b["owers"]:
|
|
||||||
owers_id.append(id_dict[ower])
|
|
||||||
|
|
||||||
bill = Bill()
|
|
||||||
form = get_billform_for(project)
|
|
||||||
form.what = b["what"]
|
|
||||||
form.amount = b["amount"]
|
|
||||||
form.original_currency = b["currency"]
|
|
||||||
form.date = parse(b["date"])
|
|
||||||
form.payer = id_dict[b["payer_name"]]
|
|
||||||
form.payed_for = owers_id
|
|
||||||
|
|
||||||
db.session.add(form.fake_form(bill, project))
|
|
||||||
|
|
||||||
# Add bills to DB
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
@main.route("/<project_id>/delete", methods=["POST"])
|
@main.route("/<project_id>/delete", methods=["POST"])
|
||||||
def delete_project():
|
def delete_project():
|
||||||
form = DestructiveActionProjectForm(id=g.project.id)
|
form = DeleteProjectForm(id=g.project.id)
|
||||||
if form.validate():
|
if form.validate():
|
||||||
g.project.remove_project()
|
g.project.remove_project()
|
||||||
flash(_("Project successfully deleted"))
|
flash(_("Project successfully deleted"))
|
||||||
|
@ -838,7 +857,7 @@ def history():
|
||||||
|
|
||||||
any_ip_addresses = any(event["ip"] for event in history)
|
any_ip_addresses = any(event["ip"] for event in history)
|
||||||
|
|
||||||
delete_form = DestructiveActionProjectForm()
|
delete_form = DeleteProjectForm()
|
||||||
return render_template(
|
return render_template(
|
||||||
"history.html",
|
"history.html",
|
||||||
current_view="history",
|
current_view="history",
|
||||||
|
@ -854,7 +873,7 @@ def history():
|
||||||
@main.route("/<project_id>/erase_history", methods=["POST"])
|
@main.route("/<project_id>/erase_history", methods=["POST"])
|
||||||
def erase_history():
|
def erase_history():
|
||||||
"""Erase all history entries associated with this project."""
|
"""Erase all history entries associated with this project."""
|
||||||
form = DestructiveActionProjectForm(id=g.project.id)
|
form = DeleteProjectForm(id=g.project.id)
|
||||||
if not form.validate():
|
if not form.validate():
|
||||||
flash(
|
flash(
|
||||||
format_form_errors(form, _("Error deleting project history")),
|
format_form_errors(form, _("Error deleting project history")),
|
||||||
|
@ -873,7 +892,7 @@ def erase_history():
|
||||||
@main.route("/<project_id>/strip_ip_addresses", methods=["POST"])
|
@main.route("/<project_id>/strip_ip_addresses", methods=["POST"])
|
||||||
def strip_ip_addresses():
|
def strip_ip_addresses():
|
||||||
"""Strip ip addresses from history entries associated with this project."""
|
"""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():
|
if not form.validate():
|
||||||
flash(
|
flash(
|
||||||
format_form_errors(form, _("Error deleting recorded IP addresses")),
|
format_form_errors(form, _("Error deleting recorded IP addresses")),
|
||||||
|
|
Loading…
Reference in a new issue