Skip to content

Commit

Permalink
[Performance] Get weight sum along with bills to scale (#949)
Browse files Browse the repository at this point in the history
* get weight sum along with bills to scale

otherwise, we need to get the weight sum for each displayed bill.
Here, we are much more scalable

* add test

* format

* remove unused import

* oops, restore pagination to 100

* add comments

* format

* rename method to make it clearer

And also, make it static, since it doesn't rely on instance.

* improve comments and naming

* improve naming

* missing article
  • Loading branch information
Glandos authored Jan 21, 2022
1 parent c8cbe43 commit 023ec71
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 14 deletions.
28 changes: 26 additions & 2 deletions ihatemoney/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,37 @@ def get_bills_unordered(self):

def get_bills(self):
"""Return the list of bills related to this project"""
return self.order_bills(self.get_bills_unordered())

@staticmethod
def order_bills(query):
return (
self.get_bills_unordered()
.order_by(Bill.date.desc())
query.order_by(Bill.date.desc())
.order_by(Bill.creation_date.desc())
.order_by(Bill.id.desc())
)

def get_bill_weights(self):
"""
Return all bills for this project, along with the sum of weight for each bill.
Each line is a (float, Bill) tuple.
Result is unordered.
"""
return (
db.session.query(func.sum(Person.weight), Bill)
.options(orm.subqueryload(Bill.owers))
.select_from(Person)
.join(billowers, Bill, Project)
.filter(Person.project_id == Project.id)
.filter(Project.id == self.id)
.group_by(Bill.id)
)

def get_bill_weights_ordered(self):
"""Ordered version of get_bill_weights"""
return self.order_bills(self.get_bill_weights())

def get_member_bills(self, member_id):
"""Return the list of bills related to a specific member"""
return (
Expand Down
10 changes: 5 additions & 5 deletions ihatemoney/templates/list_bills.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% extends "sidebar_table_layout.html" %}

{%- macro bill_amount(bill, currency=bill.original_currency, amount=bill.amount) %}
{{ amount|currency(currency) }} ({{ _("%(amount)s each", amount=bill.pay_each_default(amount)|currency(currency)) }})
{%- macro weighted_bill_amount(bill, weights, currency=bill.original_currency, amount=bill.amount) %}
{{ amount|currency(currency) }} ({{ _("%(amount)s each", amount=(amount / weights)|currency(currency)) }})
{% endmacro -%}

{% block title %} - {{ g.project.name }}{% endblock %}
Expand Down Expand Up @@ -109,7 +109,7 @@ <h3 class="modal-title">{{ _('Add a bill') }}</h3>
</th><th>{{ _("Actions") }}</th></tr>
</thead>
<tbody>
{% for bill in bills.items %}
{% for (weights, bill) in bills.items %}
<tr owers="{{bill.owers|join(',','id')}}" payer="{{bill.payer.id}}">
<td>
<span data-toggle="tooltip" data-placement="top"
Expand All @@ -128,8 +128,8 @@ <h3 class="modal-title">{{ _('Add a bill') }}</h3>
{%- endif %}</td>
<td>
<span data-toggle="tooltip" data-placement="top"
title="{{ bill_amount(bill, g.project.default_currency, bill.converted_amount) if bill.original_currency != g.project.default_currency else '' }}">
{{ bill_amount(bill) }}
title="{{ weighted_bill_amount(bill, weights, g.project.default_currency, bill.converted_amount) if bill.original_currency != g.project.default_currency else '' }}">
{{ weighted_bill_amount(bill, weights) }}
</span>
</td>
<td class="bill-actions">
Expand Down
56 changes: 56 additions & 0 deletions ihatemoney/tests/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,62 @@ def test_demo_project_deletion(self):


class ModelsTestCase(IhatemoneyTestCase):
def test_weighted_bills(self):
"""Test the SQL request that fetch all bills and weights"""
self.post_project("raclette")

# add members
self.client.post("/raclette/members/add", data={"name": "zorglub", "weight": 2})
self.client.post("/raclette/members/add", data={"name": "fred"})
self.client.post("/raclette/members/add", data={"name": "tata"})
# Add a member with a balance=0 :
self.client.post("/raclette/members/add", data={"name": "pépé"})

# create bills
self.client.post(
"/raclette/add",
data={
"date": "2011-08-10",
"what": "fromage à raclette",
"payer": 1,
"payed_for": [1, 2, 3],
"amount": "10.0",
},
)

self.client.post(
"/raclette/add",
data={
"date": "2011-08-10",
"what": "red wine",
"payer": 2,
"payed_for": [1],
"amount": "20",
},
)

self.client.post(
"/raclette/add",
data={
"date": "2011-08-10",
"what": "delicatessen",
"payer": 1,
"payed_for": [1, 2],
"amount": "10",
},
)
project = models.Project.query.get_by_name(name="raclette")
for (weight, bill) in project.get_bill_weights().all():
if bill.what == "red wine":
pay_each_expected = 20 / 2
self.assertEqual(bill.amount / weight, pay_each_expected)
if bill.what == "fromage à raclette":
pay_each_expected = 10 / 4
self.assertEqual(bill.amount / weight, pay_each_expected)
if bill.what == "delicatessen":
pay_each_expected = 10 / 3
self.assertEqual(bill.amount / weight, pay_each_expected)

def test_bill_pay_each(self):

self.post_project("raclette")
Expand Down
14 changes: 7 additions & 7 deletions ihatemoney/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
)
from flask_babel import gettext as _
from flask_mail import Message
from sqlalchemy import orm
from sqlalchemy_continuum import Operation
from werkzeug.exceptions import NotFound
from werkzeug.security import check_password_hash, generate_password_hash
Expand Down Expand Up @@ -609,16 +608,17 @@ def list_bills():
# set the last selected payer as default choice if exists
if "last_selected_payer" in session:
bill_form.payer.data = session["last_selected_payer"]
# Preload the "owers" relationship for all bills
bills = (
g.project.get_bills()
.options(orm.subqueryload(Bill.owers))
.paginate(per_page=100, error_out=True)

# Each item will be a (weight_sum, Bill) tuple.
# TODO: improve this awkward result using column_property:
# https://docs.sqlalchemy.org/en/14/orm/mapped_sql_expr.html.
weighted_bills = g.project.get_bill_weights_ordered().paginate(
per_page=100, error_out=True
)

return render_template(
"list_bills.html",
bills=bills,
bills=weighted_bills,
member_form=MemberForm(g.project),
bill_form=bill_form,
csrf_form=csrf_form,
Expand Down

0 comments on commit 023ec71

Please sign in to comment.