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

Filter bills #1309

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
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ services:
- SECRET_KEY=tralala
- SESSION_COOKIE_SECURE=True
- SHOW_ADMIN_EMAIL=True
- SQLALCHEMY_DATABASE_URI=sqlite:////database/ihatemoney.db
- SQLALCHEMY_DATABASE_URI=sqlite:///database/ihatemoney.db
- SQLALCHEMY_TRACK_MODIFICATIONS=False
- APPLICATION_ROOT=/
- ENABLE_CAPTCHA=False
Expand Down
1 change: 0 additions & 1 deletion hatch_build.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import sys

from hatchling.builders.hooks.plugin.interface import BuildHookInterface


Expand Down
Binary file added ihatemoney.db
Binary file not shown.
2 changes: 1 addition & 1 deletion ihatemoney/conf-templates/ihatemoney.cfg.j2
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ DEBUG = False

# The database URI, reprensenting the type of database and how to connect to it.
# Enter an absolute path here.
SQLALCHEMY_DATABASE_URI = 'sqlite:////var/lib/ihatemoney/ihatemoney.sqlite'
SQLALCHEMY_DATABASE_URI = 'sqlite:///var/lib/ihatemoney/ihatemoney.sqlite'
SQLACHEMY_ECHO = DEBUG

# Will likely become the default value in flask-sqlalchemy >=3 ; could be removed
Expand Down
2 changes: 1 addition & 1 deletion ihatemoney/default_settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Verbose and documented settings are in conf-templates/ihatemoney.cfg.j2
DEBUG = SQLACHEMY_ECHO = False
SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/ihatemoney.db"
SQLALCHEMY_DATABASE_URI = "sqlite:///C:/Users/sylvie c/Documents/GitHub/ihatemoney/ihatemoney.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "tralala"
MAIL_DEFAULT_SENDER = "Budget manager <[email protected]>"
Expand Down
7 changes: 7 additions & 0 deletions ihatemoney/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,15 @@ class EditProjectForm(FlaskForm):
description=_("Enter a new code if you want to change it"),
)
contact_email = StringField(_("Email"), validators=[DataRequired(), Email()])

# Create a checkbox in project settings to enable project history (keeps track of project transactions,
# like adding/settling bills). "Y/N" determines if transaction history is being recorded for the project.
project_history = BooleanField(_("Enable project history"))

# Create a checkbox in project settings to allow for recording source IP address from a given transaction listed
# in project history. "Y/N" determines if an IP address will be attached to a created entry in project history.
ip_recording = BooleanField(_("Use IP tracking for project history"))

currency_helper = CurrencyConverter()
default_currency = SelectField(
_("Default Currency"),
Expand Down
13 changes: 13 additions & 0 deletions ihatemoney/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@ def order_bills(query):
.order_by(Bill.id.desc())
)

@staticmethod
def filter_by_date(query, start, end):
if start and end:
return query.filter(Bill.date.between(start, end))
else:
return query


def get_bill_weights(self):
"""
Return all bills for this project, along with the sum of weight for each bill.
Expand All @@ -285,6 +293,11 @@ def get_bill_weights_ordered(self):
"""Ordered version of get_bill_weights"""
return self.order_bills(self.get_bill_weights())

def get_filtered_date_bill_weights_ordered(self, start, end):
bill_weights_ordered = self.get_bill_weights_ordered()
filtered_bill_weights = self.filter_by_date(bill_weights_ordered, start,end )
return filtered_bill_weights

def get_member_bills(self, member_id):
"""Return the list of bills related to a specific member"""
return (
Expand Down
9 changes: 9 additions & 0 deletions ihatemoney/templates/list_bills.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ <h3 class="modal-title">{{ _('Add a bill') }}</h3>
<li class="page-item {% if bills.page == bills.pages %}disabled{% endif %}"><a class="page-link" href="{{ url_for('main.list_bills', page=bills.next_num) }}">{{ _("Older bills") }} &raquo;</a></li>
</ul>
{% endif %}
<form action="{{ url_for(".list_bills") }}" method="post">
{{ csrf_form.csrf_token }}
<label for="start">Start Date:</label>
<input type="date" id="start" name="start" value="{{ start if start else '' }}">
<label for="end">End Date:</label>
<input type="date" id="end" name="end" value="{{ end if end else '' }}">
<input type="submit" value="Enter">
</form>

<span id="new-bill" class="ml-auto pb-2" {% if not g.project.members %} data-toggle="tooltip" title="{{_('You should start by adding participants')}}" {% endif %}>
<a href="{{ url_for('.add_bill') }}" class="btn btn-primary {% if not g.project.members %} disabled {% endif %}" data-toggle="modal" data-keyboard="true" data-target="#bill-form" autofocus>
<i class="icon icon-white before-text">{{ static_include("images/plus.svg") | safe }}</i>
Expand Down
41 changes: 41 additions & 0 deletions ihatemoney/tests/filterbydate_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest
from unittest.mock import Mock
from ihatemoney.models import Project, Bill


@pytest.fixture
def test_filter_by_date(Project):
# Prepare mock data
mock_query = Mock()
start_date = '2024-01-01'
end_date = '2024-12-31'

# Mock the methods being called inside filter_by_date
Project.query.filter.return_value = Mock() # Assuming you're using SQLAlchemy's Query object

# Call the method to test
result = Project.filter_by_date(mock_query, start_date, end_date)

# Assertions
assert result == Project.query.filter.return_value # Check if the method returns the expected result
Project.query.filter.assert_called_once_with(Bill.date >= start_date,
Bill.date <= end_date) # Check if filter was called with the correct arguments


def test_get_filtered_date_bill_weights_ordered(Project):
# Prepare mock data
start_date = '2024-01-01'
end_date = '2024-12-31'


Project.get_bill_weights_ordered.return_value = Mock()
Project.filter_by_date.return_value = Mock()

# Call the method to test
result = Project.get_filtered_date_bill_weights_ordered(start_date, end_date)

# Assertions
assert result == Project.filter_by_date.return_value # Check if the method returns the expected result
Project.filter_by_date.assert_called_once_with(
Project.get_bill_weights_ordered.return_value, start_date,
end_date) # Check if filter_by_date was called with the correct arguments
38 changes: 26 additions & 12 deletions ihatemoney/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ def set_show_admin_dashboard_link(endpoint, values):
"""

g.show_admin_dashboard_link = (
current_app.config["ACTIVATE_ADMIN_DASHBOARD"]
and current_app.config["ADMIN_PASSWORD"]
current_app.config["ACTIVATE_ADMIN_DASHBOARD"]
and current_app.config["ADMIN_PASSWORD"]
)
g.logout_form = LogoutForm()

Expand Down Expand Up @@ -199,7 +199,7 @@ def admin():
if request.method == "POST" and form.validate():
# Valid password
if check_password_hash(
current_app.config["ADMIN_PASSWORD"], form.admin_password.data
current_app.config["ADMIN_PASSWORD"], form.admin_password.data
):
session["is_admin"] = True
session.update()
Expand Down Expand Up @@ -642,7 +642,7 @@ def invite():
return render_template("send_invites.html", form=form, qrcode=qrcode_svg)


@main.route("/<project_id>/")
@main.route("/<project_id>/", methods=["GET", "POST"])
def list_bills():
bill_form = get_billform_for(g.project)
# Used for CSRF validation
Expand All @@ -658,18 +658,31 @@ def list_bills():
if "last_selected_payer" in session:
bill_form.payer.data = session["last_selected_payer"]
if (
"last_selected_payed_for" in session
and g.project.id in session["last_selected_payed_for"]
"last_selected_payed_for" in session
and g.project.id in session["last_selected_payed_for"]
):
bill_form.payed_for.data = session["last_selected_payed_for"][g.project.id]

# 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
)

if request.method == "GET":
# Retrieve ordered bill weights for the project
weighted_bills = g.project.get_bill_weights_ordered().paginate(
per_page=100, error_out=True
)
elif request.method == "POST":
# Retrieve start_date and end_date from form data
start = request.form.get('start')
end = request.form.get('end')

# Retrieve filtered bill weights by date
weighted_bills = g.project.get_filtered_date_bill_weights_ordered(start, end).paginate(
per_page=100, error_out=True
)

# Render the template with the appropriate data
return render_template(
"list_bills.html",
bills=weighted_bills,
Expand All @@ -678,9 +691,10 @@ def list_bills():
csrf_form=csrf_form,
add_bill=request.values.get("add_bill", False),
current_view="list_bills",
start=start if request.method == "POST" else None,
end=end if request.method == "POST" else None,
)


@main.route("/<project_id>/members/add", methods=["GET", "POST"])
def add_member():
# FIXME manage form errors on the list_bills page
Expand Down Expand Up @@ -974,8 +988,8 @@ def feed(token):
return "", 304

if (
request.if_modified_since
and request.if_modified_since.replace(tzinfo=None) >= last_modified
request.if_modified_since
and request.if_modified_since.replace(tzinfo=None) >= last_modified
):
return "", 304

Expand Down