Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change settle endpoint to use POST instead of GET #1303

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions ihatemoney/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
BooleanField,
DateField,
DecimalField,
HiddenField,
IntegerField,
Label,
PasswordField,
SelectField,
Expand Down Expand Up @@ -438,6 +440,22 @@ def validate_original_currency(self, field):
raise ValidationError(msg)


class HiddenCommaDecimalField(HiddenField, CommaDecimalField):
pass


class HiddenIntegerField(HiddenField, IntegerField):
pass


class SettlementForm(FlaskForm):
"""Used internally for validation, not directly visible to users"""

amount = HiddenCommaDecimalField("Amount", validators=[DataRequired()])
sender_id = HiddenIntegerField("Sender", validators=[DataRequired()])
receiver_id = HiddenIntegerField("Receiver", validators=[DataRequired()])


class MemberForm(FlaskForm):
name = StringField(_("Name"), validators=[DataRequired()], filters=[strip_filter])

Expand Down
19 changes: 12 additions & 7 deletions ihatemoney/templates/settle_bills.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@
<table id="bill_table" class="split_bills table table-striped">
<thead><tr><th>{{ _("Who pays?") }}</th><th>{{ _("To whom?") }}</th><th>{{ _("How much?") }}</th><th>{{ _("Settled?") }}</th></tr></thead>
<tbody>
{% for bill in bills %}
<tr receiver={{bill.receiver.id}}>
<td>{{ bill.ower }}</td>
<td>{{ bill.receiver }}</td>
<td>{{ bill.amount|currency }}</td>
{% for transaction in transactions %}
<tr>
<td>{{ transaction.ower }}</td>
<td>{{ transaction.receiver }}</td>
<td>{{ transaction.amount|currency }}</td>
<td>
<span id="settle-bill" class="ml-auto pb-2">
<a href="{{ url_for('.settle', amount = bill.amount, ower_id = bill.ower.id, payer_id = bill.receiver.id) }}" class="btn btn-primary">
{{ ("Settle") }}
<form class="" action="{{ url_for(".add_settlement_bill") }}" method="POST">
{{ settlement_form.csrf_token }}
{{ settlement_form.amount(value=transaction.amount) }}
{{ settlement_form.sender_id(value=transaction.ower.id) }}
{{ settlement_form.receiver_id(value=transaction.receiver.id) }}
<button class="btn btn-primary" type="submit" title="{{ _("Settle") }}">{{ _("Settle") }}</button>
</form>
</a>
</span>
</td>
Expand Down
112 changes: 102 additions & 10 deletions ihatemoney/tests/budget_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1358,23 +1358,25 @@ def test_settle_button(self):
count = 0
for t in transactions:
count += 1
self.client.get(
"/raclette/settle"
+ "/"
+ str(t["amount"])
+ "/"
+ str(t["ower"].id)
+ "/"
+ str(t["receiver"].id)
self.client.post(
"/raclette/settle",
data={
"amount": t["amount"],
"sender_id": t["ower"].id,
"receiver_id": t["receiver"].id,
},
)
temp_transactions = project.get_transactions_to_settle_bill()
# test if the one has disappeared
assert len(temp_transactions) == len(transactions) - count

# test if theres a new one with bill_type reimbursement
# test if there is a new one with bill_type reimbursement
bill = project.get_newest_bill()
assert bill.bill_type == models.BillType.REIMBURSEMENT
return

# There should be no more settlement to do at the end
transactions = project.get_transactions_to_settle_bill()
assert len(transactions) == 0

def test_settle_zero(self):
self.post_project("raclette")
Expand Down Expand Up @@ -1463,6 +1465,78 @@ def test_access_other_projects(self):
# Create and log in as another project
self.post_project("tartiflette")

# Add a participant in this second project
self.client.post("/tartiflette/members/add", data={"name": "pirate"})
pirate = models.Person.query.filter(models.Person.id == 5).one()
assert pirate.name == "pirate"

# Try to add a new bill in another project
self.client.post(
"/raclette/add",
data={
"date": "2017-01-01",
"what": "fromage frelaté",
"payer": 2,
"payed_for": [2, 3, 4],
"bill_type": "Expense",
"amount": "100.0",
},
)
# Ensure it has not been created
raclette = self.get_project("raclette")
assert raclette.get_bills().count() == 1

# Try to add a new bill in our project that references members of another project.
# First with invalid payed_for IDs.
self.client.post(
"/tartiflette/add",
data={
"date": "2017-01-01",
"what": "soupe",
"payer": 5,
"payed_for": [3],
"bill_type": "Expense",
"amount": "5000.0",
},
)
# Ensure it has not been created
piratebill = models.Bill.query.filter(models.Bill.what == "soupe").one_or_none()
assert piratebill is None, "piratebill 1 should not exist"

# Then with invalid payer ID
self.client.post(
"/tartiflette/add",
data={
"date": "2017-02-01",
"what": "pain",
"payer": 3,
"payed_for": [5],
"bill_type": "Expense",
"amount": "5000.0",
},
)
# Ensure it has not been created
piratebill = models.Bill.query.filter(models.Bill.what == "pain").one_or_none()
assert piratebill is None, "piratebill 2 should not exist"

# Make sure we can actually create valid bills
self.client.post(
"/tartiflette/add",
data={
"date": "2017-03-01",
"what": "baguette",
"payer": 5,
"payed_for": [5],
"bill_type": "Expense",
"amount": "5.0",
},
)
# Ensure it has been created
okbill = models.Bill.query.filter(models.Bill.what == "baguette").one_or_none()
assert okbill is not None, "Bill baguette should exist"
assert okbill.what == "baguette"

# Now try to access and modify existing bills
modified_bill = {
"date": "2018-12-31",
"what": "roblochon",
Expand Down Expand Up @@ -1556,6 +1630,24 @@ def test_access_other_projects(self):
member = models.Person.query.filter(models.Person.id == 1).one_or_none()
assert member is None

# test new settle endpoint to add bills with wrong payer / payed_for
self.client.post("/exit")
self.client.post(
"/authenticate", data={"id": "tartiflette", "password": "tartiflette"}
)
self.client.post(
"/tartiflette/settle",
data={
"sender_id": 4,
"receiver_id": 5,
"amount": "42.0",
},
)
piratebill = models.Bill.query.filter(
models.Bill.bill_type == models.BillType.REIMBURSEMENT
).one_or_none()
assert piratebill is None, "piratebill 3 should not exist"

@pytest.mark.skip(reason="Currency conversion is broken")
def test_currency_switch(self):
# A project should be editable
Expand Down
4 changes: 3 additions & 1 deletion ihatemoney/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,9 @@ def format_form_errors(form, prefix):
)
else:
error_list = "</li><li>".join(
str(error) for (field, errors) in form.errors.items() for error in errors
f"<strong>{field}</strong> {error}"
for (field, errors) in form.errors.items()
for error in errors
)
errors = f"<ul><li>{error_list}</li></ul>"
# I18N: Form error with a list of errors
Expand Down
36 changes: 27 additions & 9 deletions ihatemoney/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
ProjectForm,
ProjectFormWithCaptcha,
ResetPasswordForm,
SettlementForm,
get_billform_for,
)
from ihatemoney.history import get_history, get_history_queries, purge_history
Expand Down Expand Up @@ -846,24 +847,41 @@ def change_lang(lang):
@main.route("/<project_id>/settle_bills")
def settle_bill():
"""Compute the sum each one have to pay to each other and display it"""
bills = g.project.get_transactions_to_settle_bill()
return render_template("settle_bills.html", bills=bills, current_view="settle_bill")
transactions = g.project.get_transactions_to_settle_bill()
settlement_form = SettlementForm()
return render_template(
"settle_bills.html",
transactions=transactions,
settlement_form=settlement_form,
current_view="settle_bill",
)


@main.route("/<project_id>/settle", methods=["POST"])
def add_settlement_bill():
"""Create a bill to register a settlement"""
form = SettlementForm(id=g.project.id)
if not form.validate():
flash(
format_form_errors(form, _("Error creating settlement bill")),
category="danger",
)
return redirect(url_for(".settle_bill"))

# TODO: check that sender and receiver ID are valid and part of this project

@main.route("/<project_id>/settle/<amount>/<int:ower_id>/<int:payer_id>")
def settle(amount, ower_id, payer_id):
new_reinbursement = Bill(
amount=float(amount),
settlement = Bill(
amount=form.amount.data,
date=datetime.datetime.today(),
owers=[Person.query.get(payer_id)],
payer_id=ower_id,
owers=[Person.query.get(form.receiver_id.data)],
payer_id=form.sender_id.data,
project_default_currency=g.project.default_currency,
bill_type=BillType.REIMBURSEMENT,
what=_("Settlement"),
)
session.update()

db.session.add(new_reinbursement)
db.session.add(settlement)
db.session.commit()

return redirect(url_for(".settle_bill"))
Expand Down
Loading