From f2e992cb9e6ad6103a8dd260f6b061e23d6375f1 Mon Sep 17 00:00:00 2001 From: alligu Date: Mon, 20 Apr 2020 17:05:07 -0400 Subject: [PATCH] added tagging functionality and filtering by date, amount, payer, and tag --- ihatemoney/forms.py | 8 +++- ihatemoney/models.py | 2 + ihatemoney/static/css/main.css | 8 ++++ ihatemoney/templates/list_bills.html | 67 +++++++++++++++++++++++++++- ihatemoney/tests/tests.py | 12 +++++ ihatemoney/web.py | 3 +- 6 files changed, 97 insertions(+), 3 deletions(-) diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index 495eefa12..0426b7586 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -1,7 +1,7 @@ from flask_wtf.form import FlaskForm from wtforms.fields.core import SelectField, SelectMultipleField from wtforms.fields.html5 import DateField, DecimalField, URLField -from wtforms.fields.simple import PasswordField, SubmitField, StringField, BooleanField +from wtforms.fields.simple import PasswordField, SubmitField, StringField, BooleanField, HiddenField from wtforms.validators import ( Email, DataRequired, @@ -199,6 +199,7 @@ class ResetPasswordForm(FlaskForm): class BillForm(FlaskForm): date = DateField(_("Date"), validators=[DataRequired()], default=datetime.now) what = StringField(_("What?"), validators=[DataRequired()]) + tag = HiddenField(_("Tag"), default="") payer = SelectField(_("Payer"), validators=[DataRequired()], coerce=int) amount = CalculatorStringField(_("Amount paid"), validators=[DataRequired()]) external_link = URLField( @@ -216,6 +217,9 @@ def save(self, bill, project): bill.payer_id = self.payer.data bill.amount = self.amount.data bill.what = self.what.data + tag = list(set(part[1:] for part in bill.what.split() if part.startswith('#'))) + if tag: + bill.tag = tag[0] 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] @@ -225,6 +229,7 @@ def fake_form(self, bill, project): bill.payer_id = self.payer bill.amount = self.amount bill.what = self.what + bill.tag = self.tag bill.external_link = "" bill.date = self.date bill.owers = [Person.query.get(ower, project) for ower in self.payed_for] @@ -235,6 +240,7 @@ def fill(self, bill): self.payer.data = bill.payer_id self.amount.data = bill.amount self.what.data = bill.what + self.tag.data = bill.tag self.external_link.data = bill.external_link self.date.data = bill.date self.payed_for.data = [int(ower.id) for ower in bill.owers] diff --git a/ihatemoney/models.py b/ihatemoney/models.py index d765c93db..53214946b 100644 --- a/ihatemoney/models.py +++ b/ihatemoney/models.py @@ -434,6 +434,7 @@ def delete(self, project, id): date = db.Column(db.Date, default=datetime.now) creation_date = db.Column(db.Date, default=datetime.now) what = db.Column(db.UnicodeText) + tag = db.Column(db.UnicodeText) external_link = db.Column(db.UnicodeText) archive = db.Column(db.Integer, db.ForeignKey("archive.id")) @@ -448,6 +449,7 @@ def _to_serialize(self): "date": self.date, "creation_date": self.creation_date, "what": self.what, + "tag": self.tag, "external_link": self.external_link, } diff --git a/ihatemoney/static/css/main.css b/ihatemoney/static/css/main.css index 7d91c38df..b531793df 100644 --- a/ihatemoney/static/css/main.css +++ b/ihatemoney/static/css/main.css @@ -174,6 +174,14 @@ body { width: 5em; } +#bill-search { + color: white; + background: #8f9296; + margin-top: 10px; + border-radius: .25rem; + padding: .375rem .75rem; +} + .invites textarea { width: 800px; height: 100px; diff --git a/ihatemoney/templates/list_bills.html b/ihatemoney/templates/list_bills.html index 0f2a68a5b..9d6fb1498 100644 --- a/ihatemoney/templates/list_bills.html +++ b/ihatemoney/templates/list_bills.html @@ -17,6 +17,44 @@ }); }); + // remove duplicate tags + var usedTags = {}; + $("select[name='tag-search-select'] > option").each(function() { + if (usedTags[this.text]) { + $(this).remove(); + } else { + usedTags[this.text] = this.value; + } + }); + + // add default values to payer select + $('#payer-search-select').prepend(new Option('', '', true, true)); + + // add default values to date select + $('#date-search-select').prepend(new Option('', '', true, true)); + + $('#btn-bill-filter').click(function() { + var dateValue = $('#date-search-select').val(); + var payerValue = $('#payer-search-select option:selected').val(); + var amountValue = $('#amount-search-select').val(); + if (amountValue.substr(amountValue.length - 3) === '.00') { + amountValue = amountValue.substr(0, amountValue.length - 3) + } + var amountWithZero = amountValue + ".0" + var tagValue = $('#tag-search-select').val(); + + var matching = $('#bill_table tbody tr').filter(function(){ + return (payerValue != "" && $(this).attr('payer') !== payerValue) || $(this).attr('date') !== dateValue || (amountValue != "" && $(this).attr('amount') !== amountValue && $(this).attr('amount') !== amountWithZero) || (tagValue != "" && $(this).attr('tag') !== tagValue); + }); + + matching.hide(); + $('#bill_table tbody tr').not(matching).show(200); + }); + + $('#btn-bill-showall').click(function() { + $('#bill_table tbody tr').show(200); + }); + var highlight_owers = function(){ var ower_ids = $(this).attr("owers").split(','); var payer_id = $(this).attr("payer"); @@ -110,11 +148,38 @@ {% if bills.total > 0 %}
+ + {% for bill in bills.items %} - +
{{ _("When?") }}{{ _("Who paid?") }}{{ _("For what?") }}{{ _("For whom?") }}{{ _("How much?") }}{{ _("Actions") }}
diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index 5dff64d9e..7e0efb5cd 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -1215,6 +1215,7 @@ def test_import_new_project(self): { "date": "2017-01-01", "what": "refund", + "tag": "", "amount": 13.33, "payer_name": "tata", "payer_weight": 1.0, @@ -1223,6 +1224,7 @@ def test_import_new_project(self): { "date": "2016-12-31", "what": "red wine", + "tag": "", "amount": 200.0, "payer_name": "fred", "payer_weight": 1.0, @@ -1231,6 +1233,7 @@ def test_import_new_project(self): { "date": "2016-12-31", "what": "fromage a raclette", + "tag": "", "amount": 10.0, "payer_name": "alexis", "payer_weight": 2.0, @@ -1290,6 +1293,7 @@ def test_import_partial_project(self): data={ "date": "2016-12-31", "what": "red wine", + "tag": "", "payer": 2, "payed_for": [1, 3], "amount": "200", @@ -1300,6 +1304,7 @@ def test_import_partial_project(self): { "date": "2017-01-01", "what": "refund", + "tag": "", "amount": 13.33, "payer_name": "tata", "payer_weight": 1.0, @@ -1308,6 +1313,7 @@ def test_import_partial_project(self): { # This expense does not have to be present twice. "date": "2016-12-31", "what": "red wine", + "tag": "", "amount": 200.0, "payer_name": "fred", "payer_weight": 1.0, @@ -1316,6 +1322,7 @@ def test_import_partial_project(self): { "date": "2016-12-31", "what": "fromage a raclette", + "tag": "", "amount": 10.0, "payer_name": "alexis", "payer_weight": 2.0, @@ -1380,6 +1387,7 @@ def test_import_wrong_json(self): { # amount missing "date": "2017-01-01", "what": "refund", + "tag": "", "payer_name": "tata", "payer_weight": 1.0, "owers": ["fred"], @@ -1784,6 +1792,7 @@ def test_bills(self): } got = json.loads(req.data.decode("utf-8")) + del got["tag"] self.assertEqual( datetime.date.today(), datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(), @@ -1858,6 +1867,7 @@ def test_bills(self): datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(), ) del got["creation_date"] + del got["tag"] self.assertDictEqual(expected, got) # delete a bill @@ -1934,6 +1944,7 @@ def test_bills_with_calculation(self): datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(), ) del got["creation_date"] + del got["tag"] self.assertDictEqual(expected, got) # should raise errors @@ -2075,6 +2086,7 @@ def test_weighted_bills(self): datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(), ) del got["creation_date"] + del got["tag"] self.assertDictEqual(expected, got) # getting it should return a 404 diff --git a/ihatemoney/web.py b/ihatemoney/web.py index 744d1bfa9..d84908f98 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -426,7 +426,7 @@ def import_project(file, project): json_file = json.load(file) # Check if JSON is correct - attr = ["what", "payer_name", "payer_weight", "amount", "date", "owers"] + attr = ["what", "tag", "payer_name", "payer_weight", "amount", "date", "owers"] attr.sort() for e in json_file: if len(e) != len(attr): @@ -483,6 +483,7 @@ def import_project(file, project): bill = Bill() form = get_billform_for(project) form.what = b["what"] + form.tag = b["tag"] form.amount = b["amount"] form.date = parse(b["date"]) form.payer = id_dict[b["payer_name"]]