mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-29 09:52:36 +02:00
[Performance] Get weight sum along with bills to scale (#949)
* 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
This commit is contained in:
parent
c8cbe43ee2
commit
023ec71536
4 changed files with 94 additions and 14 deletions
|
@ -250,13 +250,37 @@ class Project(db.Model):
|
||||||
|
|
||||||
def get_bills(self):
|
def get_bills(self):
|
||||||
"""Return the list of bills related to this project"""
|
"""Return the list of bills related to this project"""
|
||||||
|
return self.order_bills(self.get_bills_unordered())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def order_bills(query):
|
||||||
return (
|
return (
|
||||||
self.get_bills_unordered()
|
query.order_by(Bill.date.desc())
|
||||||
.order_by(Bill.date.desc())
|
|
||||||
.order_by(Bill.creation_date.desc())
|
.order_by(Bill.creation_date.desc())
|
||||||
.order_by(Bill.id.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):
|
def get_member_bills(self, member_id):
|
||||||
"""Return the list of bills related to a specific member"""
|
"""Return the list of bills related to a specific member"""
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "sidebar_table_layout.html" %}
|
{% extends "sidebar_table_layout.html" %}
|
||||||
|
|
||||||
{%- macro bill_amount(bill, currency=bill.original_currency, amount=bill.amount) %}
|
{%- macro weighted_bill_amount(bill, weights, currency=bill.original_currency, amount=bill.amount) %}
|
||||||
{{ amount|currency(currency) }} ({{ _("%(amount)s each", amount=bill.pay_each_default(amount)|currency(currency)) }})
|
{{ amount|currency(currency) }} ({{ _("%(amount)s each", amount=(amount / weights)|currency(currency)) }})
|
||||||
{% endmacro -%}
|
{% endmacro -%}
|
||||||
|
|
||||||
{% block title %} - {{ g.project.name }}{% endblock %}
|
{% block title %} - {{ g.project.name }}{% endblock %}
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
</th><th>{{ _("Actions") }}</th></tr>
|
</th><th>{{ _("Actions") }}</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for bill in bills.items %}
|
{% for (weights, bill) in bills.items %}
|
||||||
<tr owers="{{bill.owers|join(',','id')}}" payer="{{bill.payer.id}}">
|
<tr owers="{{bill.owers|join(',','id')}}" payer="{{bill.payer.id}}">
|
||||||
<td>
|
<td>
|
||||||
<span data-toggle="tooltip" data-placement="top"
|
<span data-toggle="tooltip" data-placement="top"
|
||||||
|
@ -128,8 +128,8 @@
|
||||||
{%- endif %}</td>
|
{%- endif %}</td>
|
||||||
<td>
|
<td>
|
||||||
<span data-toggle="tooltip" data-placement="top"
|
<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 '' }}">
|
title="{{ weighted_bill_amount(bill, weights, g.project.default_currency, bill.converted_amount) if bill.original_currency != g.project.default_currency else '' }}">
|
||||||
{{ bill_amount(bill) }}
|
{{ weighted_bill_amount(bill, weights) }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="bill-actions">
|
<td class="bill-actions">
|
||||||
|
|
|
@ -108,6 +108,62 @@ class CommandTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
|
||||||
class ModelsTestCase(IhatemoneyTestCase):
|
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):
|
def test_bill_pay_each(self):
|
||||||
|
|
||||||
self.post_project("raclette")
|
self.post_project("raclette")
|
||||||
|
|
|
@ -28,7 +28,6 @@ from flask import (
|
||||||
)
|
)
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_mail import Message
|
from flask_mail import Message
|
||||||
from sqlalchemy import orm
|
|
||||||
from sqlalchemy_continuum import Operation
|
from sqlalchemy_continuum import Operation
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
@ -609,16 +608,17 @@ def list_bills():
|
||||||
# set the last selected payer as default choice if exists
|
# set the last selected payer as default choice if exists
|
||||||
if "last_selected_payer" in session:
|
if "last_selected_payer" in session:
|
||||||
bill_form.payer.data = session["last_selected_payer"]
|
bill_form.payer.data = session["last_selected_payer"]
|
||||||
# Preload the "owers" relationship for all bills
|
|
||||||
bills = (
|
# Each item will be a (weight_sum, Bill) tuple.
|
||||||
g.project.get_bills()
|
# TODO: improve this awkward result using column_property:
|
||||||
.options(orm.subqueryload(Bill.owers))
|
# https://docs.sqlalchemy.org/en/14/orm/mapped_sql_expr.html.
|
||||||
.paginate(per_page=100, error_out=True)
|
weighted_bills = g.project.get_bill_weights_ordered().paginate(
|
||||||
|
per_page=100, error_out=True
|
||||||
)
|
)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"list_bills.html",
|
"list_bills.html",
|
||||||
bills=bills,
|
bills=weighted_bills,
|
||||||
member_form=MemberForm(g.project),
|
member_form=MemberForm(g.project),
|
||||||
bill_form=bill_form,
|
bill_form=bill_form,
|
||||||
csrf_form=csrf_form,
|
csrf_form=csrf_form,
|
||||||
|
|
Loading…
Reference in a new issue