diff --git a/docker-compose.yml b/docker-compose.yml index df893beb..cea47350 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/hatch_build.py b/hatch_build.py index d536daed..bedd89b3 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -1,5 +1,4 @@ import sys - from hatchling.builders.hooks.plugin.interface import BuildHookInterface diff --git a/ihatemoney.db b/ihatemoney.db new file mode 100644 index 00000000..00c32ee6 Binary files /dev/null and b/ihatemoney.db differ diff --git a/ihatemoney/conf-templates/ihatemoney.cfg.j2 b/ihatemoney/conf-templates/ihatemoney.cfg.j2 index ba4793da..72e91973 100644 --- a/ihatemoney/conf-templates/ihatemoney.cfg.j2 +++ b/ihatemoney/conf-templates/ihatemoney.cfg.j2 @@ -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 diff --git a/ihatemoney/default_settings.py b/ihatemoney/default_settings.py index 48112afb..b477462e 100644 --- a/ihatemoney/default_settings.py +++ b/ihatemoney/default_settings.py @@ -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 " diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index e1ddabe8..fb2aff4f 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -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"), diff --git a/ihatemoney/models.py b/ihatemoney/models.py index c591b85b..4513b3e7 100644 --- a/ihatemoney/models.py +++ b/ihatemoney/models.py @@ -264,6 +264,14 @@ class Project(db.Model): .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. @@ -285,6 +293,11 @@ class Project(db.Model): """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 ( diff --git a/ihatemoney/templates/list_bills.html b/ihatemoney/templates/list_bills.html index 79e25262..545e1148 100644 --- a/ihatemoney/templates/list_bills.html +++ b/ihatemoney/templates/list_bills.html @@ -105,6 +105,15 @@
  • {{ _("Older bills") }} »
  • {% endif %} +
    + {{ csrf_form.csrf_token }} + + + + + +
    + {{ static_include("images/plus.svg") | safe }} diff --git a/ihatemoney/tests/filterbydate_test.py b/ihatemoney/tests/filterbydate_test.py new file mode 100644 index 00000000..91732657 --- /dev/null +++ b/ihatemoney/tests/filterbydate_test.py @@ -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 diff --git a/ihatemoney/web.py b/ihatemoney/web.py index 0d0bdd20..4ee71385 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -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() @@ -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() @@ -642,7 +642,7 @@ def invite(): return render_template("send_invites.html", form=form, qrcode=qrcode_svg) -@main.route("//") +@main.route("//", methods=["GET", "POST"]) def list_bills(): bill_form = get_billform_for(g.project) # Used for CSRF validation @@ -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, @@ -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("//members/add", methods=["GET", "POST"]) def add_member(): # FIXME manage form errors on the list_bills page @@ -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