From ef3b9aac7123909669e9ec708f1adc1f158641ad Mon Sep 17 00:00:00 2001 From: Youe Graillot Date: Sun, 28 Nov 2021 15:58:06 +0100 Subject: [PATCH] refactoring --- ihatemoney/api/common.py | 3 +- ihatemoney/forms.py | 30 ++++++------ ihatemoney/models.py | 102 +++++++++++++++++++++++++++++++++++---- ihatemoney/web.py | 96 +++++++----------------------------- 4 files changed, 125 insertions(+), 106 deletions(-) diff --git a/ihatemoney/api/common.py b/ihatemoney/api/common.py index bc35ac93..bfca1795 100644 --- a/ihatemoney/api/common.py +++ b/ihatemoney/api/common.py @@ -151,8 +151,7 @@ class BillsHandler(Resource): def post(self, project): form = get_billform_for(project, True, meta={"csrf": False}) if form.validate(): - bill = Bill() - form.save(bill, project) + bill = form.export(project) db.session.add(bill) db.session.commit() return bill.id, 201 diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index 2c3bc252..69fed821 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -23,7 +23,7 @@ from wtforms.validators import ( ) from ihatemoney.currency_convertor import CurrencyConverter -from ihatemoney.models import LoggingMode, Person, Project +from ihatemoney.models import Bill, LoggingMode, Person, Project from ihatemoney.utils import ( eval_arithmetic_expression, render_localized_currency, @@ -321,33 +321,31 @@ class BillForm(FlaskForm): submit = SubmitField(_("Submit")) submit2 = SubmitField(_("Submit and add a new one")) + def export(self, project): + return Bill( + self.amount.data, + self.date.data, + self.external_link.data, + self.original_currency.data, + Person.query.get_by_ids(project, self.payed_for.data), + self.payer.data, + project.default_currency, + self.what.data, + ) + def save(self, bill, project): bill.payer_id = self.payer.data bill.amount = self.amount.data bill.what = self.what.data bill.external_link = self.external_link.data bill.date = self.date.data - bill.owers = [Person.query.get(ower, project) for ower in self.payed_for.data] + bill.owers = Person.query.get_by_ids(project, self.payed_for.data) bill.original_currency = self.original_currency.data bill.converted_amount = self.currency_helper.exchange_currency( bill.amount, bill.original_currency, project.default_currency ) return bill - def fake_form(self, bill, project): - bill.payer_id = self.payer - bill.amount = self.amount - bill.what = self.what - bill.external_link = "" - bill.date = self.date - bill.owers = [Person.query.get(ower, project) for ower in self.payed_for] - bill.original_currency = self.original_currency - bill.converted_amount = self.currency_helper.exchange_currency( - bill.amount, bill.original_currency, project.default_currency - ) - - return bill - def fill(self, bill, project): self.payer.data = bill.payer_id self.amount.data = bill.amount diff --git a/ihatemoney/models.py b/ihatemoney/models.py index 473e7c0b..a4c73521 100644 --- a/ihatemoney/models.py +++ b/ihatemoney/models.py @@ -1,6 +1,7 @@ from collections import defaultdict from datetime import datetime +from dateutil.parser import parse from debts import settle from flask import current_app, g from flask_sqlalchemy import BaseQuery, SQLAlchemy @@ -19,6 +20,7 @@ from werkzeug.security import generate_password_hash from ihatemoney.currency_convertor import CurrencyConverter from ihatemoney.patch_sqlalchemy_continuum import PatchedBuilder +from ihatemoney.utils import get_members, same_bill from ihatemoney.versioning import ( ConditionalVersioningManager, LoggingMode, @@ -320,6 +322,42 @@ class Project(db.Model): db.session.add(self) db.session.commit() + def import_bills(self, bills: list[dict]): + """Import bills from a list of dictionaries""" + # Add members not already in the project + members_project = [str(m) for m in self.members] + members_new = [ + m for m in get_members(bills) if str(m[0]) not in members_project + ] + for m in members_new: + Person(name=m[0], project=self, weight=m[1]) + db.session.commit() + + # Import bills not already in the project + bills_project = self.get_pretty_bills() + id_dict = {m.name: m.id for m in self.members} + for b in bills: + same = False + for b_p in bills_project: + if same_bill(b_p, b): + same = True + break + if not same: + # Create bills + db.session.add( + Bill( + b["amount"], + parse(b["date"]), + "", + b["currency"], + Person.query.get_by_names(b["owers"], self), + id_dict[b["payer_name"]], + self.default_currency, + b["what"], + ) + ) + db.session.commit() + def remove_member(self, member_id): """Remove a member from the project. @@ -435,16 +473,19 @@ class Project(db.Model): ("Alice", 20, ("Amina", "Alice"), "Beer !"), ("Amina", 50, ("Amina", "Alice", "Georg"), "AMAP"), ) - for (payer, amount, owers, subject) in operations: - bill = Bill() - bill.payer_id = members[payer].id - bill.what = subject - bill.owers = [members[name] for name in owers] - bill.amount = amount - bill.original_currency = "XXX" - bill.converted_amount = amount - - db.session.add(bill) + for (payer, amount, owers, what) in operations: + db.session.add( + Bill( + amount, + None, + None, + "XXX", + [members[name] for name in owers], + members[payer].id, + project.default_currency, + what, + ) + ) db.session.commit() return project @@ -459,6 +500,13 @@ class Person(db.Model): .one_or_none() ) + def get_by_names(self, names, project): + return ( + Person.query.filter(Person.name.in_(names)) + .filter(Person.project_id == project.id) + .all() + ) + def get(self, id, project=None): if not project: project = g.project @@ -468,6 +516,15 @@ class Person(db.Model): .one_or_none() ) + def get_by_ids(self, ids, project=None): + if not project: + project = g.project + return ( + Person.query.filter(Person.id.in_(ids)) + .filter(Person.project_id == project.id) + .all() + ) + query_class = PersonQuery # Direct SQLAlchemy-Continuum to track changes to this model @@ -561,6 +618,31 @@ class Bill(db.Model): archive = db.Column(db.Integer, db.ForeignKey("archive.id")) + currency_helper = CurrencyConverter() + + def __init__( + self, + amount, + date, + external_link, + original_currency, + owers, + payer_id, + project_default_currency, + what, + ) -> None: + super().__init__() + self.amount = amount + self.date = date + self.external_link = external_link + self.original_currency = original_currency + self.owers = owers + self.payer_id = payer_id + self.what = what + self.converted_amount = self.currency_helper.exchange_currency( + self.amount, self.original_currency, project_default_currency + ) + @property def _to_serialize(self): return { diff --git a/ihatemoney/web.py b/ihatemoney/web.py index cc6ae28d..b5eeea1e 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -60,11 +60,9 @@ from ihatemoney.utils import ( Redirect303, csv2list_of_dicts, format_form_errors, - get_members, list_of_dicts2csv, list_of_dicts2json, render_localized_template, - same_bill, send_email, ) @@ -451,37 +449,33 @@ def import_project(): try: data = form.file.data if data.mimetype == "application/json": - json_file = json.load(data.stream) + bills = json.load(data.stream) elif data.mimetype == "text/csv": try: - json_file = csv2list_of_dicts(data) - except Exception as e: + bills = csv2list_of_dicts(data) + except Exception as b: raise ValueError(_("Unable to parse CSV")) else: raise ValueError("Unsupported file type") - # Check if JSON is correct + # Check data attr = [ - "what", - "payer_name", - "payer_weight", "amount", "currency", "date", "owers", + "payer_name", + "payer_weight", + "what", ] - 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"] = g.project.default_currency + for b in bills: + if b.get("currency", "") in ["", "XXX"]: + b["currency"] = g.project.default_currency for a in attr: - if a not in e: + if a not in b: raise ValueError(_("Missing attribute {}").format(a)) - # Keep track of currencies - currencies.add(e["currency"]) + currencies.add(b["currency"]) # Additional checks if project has no default currency if g.project.default_currency == CurrencyConverter.no_currency: @@ -493,68 +487,15 @@ def import_project(): ) ) # Strip currency from bills (since it's the same for every bill) - for e in json_file: - e["currency"] = CurrencyConverter.no_currency + for b in bills: + b["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)) + g.project.import_bills(bills) - # 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") + except ValueError as b: + flash(b.args[0], category="danger") return redirect(url_for(".edit_project")) else: for component, errors in form.errors.items(): @@ -777,8 +718,7 @@ def add_bill(): session["last_selected_payer"] = form.payer.data session.update() - bill = Bill() - db.session.add(form.save(bill, g.project)) + db.session.add(form.export(g.project)) db.session.commit() flash(_("The bill has been added"))