mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-05-14 16:31:49 +02:00
Compare commits
9 commits
3eb1dca285
...
b6cfeecf69
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b6cfeecf69 | ||
![]() |
61ea1f54d2 | ||
![]() |
299c384908 | ||
![]() |
4e9ff9b1ac | ||
![]() |
2aa410c68f | ||
![]() |
7505cbe25a | ||
![]() |
83a60b1289 | ||
![]() |
ce20f9adea | ||
2b21795e94 |
11 changed files with 124 additions and 18 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -8,7 +8,7 @@ on:
|
|||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install uv and set the python version
|
||||
|
@ -21,7 +21,7 @@ jobs:
|
|||
test:
|
||||
# Dependency on linting to avoid running our expensive matrix test for nothing
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
# Use postgresql and MariaDB versions of Debian bookworm
|
||||
services:
|
||||
postgres:
|
||||
|
@ -111,7 +111,7 @@ jobs:
|
|||
}}
|
||||
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install uv and set the python version
|
||||
|
|
4
.github/workflows/dockerhub.yml
vendored
4
.github/workflows/dockerhub.yml
vendored
|
@ -10,7 +10,7 @@ on:
|
|||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
@ -19,7 +19,7 @@ jobs:
|
|||
run: docker compose -f docker-compose.test.yml run sut
|
||||
|
||||
build_upload:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
needs: test
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
|
|
|
@ -173,6 +173,14 @@ URL you want.
|
|||
- **Default value:** `""` (empty string)
|
||||
- **Production value:** The URL of your chosing.
|
||||
|
||||
## SITE_NAME
|
||||
|
||||
It is possible to change the name of the site to something at your liking.
|
||||
|
||||
- **Default value:** `"I Hate Money"` (empty string)
|
||||
- **Production value:** The name of your choosing
|
||||
|
||||
|
||||
## Configuring email sending
|
||||
|
||||
By default, Ihatemoney sends emails using a local SMTP server, but it's
|
||||
|
|
|
@ -3,6 +3,7 @@ DEBUG = SQLACHEMY_ECHO = False
|
|||
SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/ihatemoney.db"
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SECRET_KEY = "tralala"
|
||||
SITE_NAME = "I Hate Money"
|
||||
MAIL_DEFAULT_SENDER = "Budget manager <admin@example.com>"
|
||||
SHOW_ADMIN_EMAIL = True
|
||||
ACTIVATE_DEMO_PROJECT = True
|
||||
|
|
|
@ -4,6 +4,7 @@ import getpass
|
|||
import os
|
||||
import random
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
import click
|
||||
from flask.cli import FlaskGroup
|
||||
|
@ -93,5 +94,31 @@ def delete_project(project_name):
|
|||
db.session.commit()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument("print_emails", default=False)
|
||||
@click.argument("bills", default=0) # default values will get total projects
|
||||
@click.argument("days", default=73000) # approximately 200 years
|
||||
def get_project_count(print_emails, bills, days):
|
||||
"""Count projets with at least x bills and at less than x days old"""
|
||||
projects = [
|
||||
pr
|
||||
for pr in Project.query.all()
|
||||
if pr.get_bills().count() > bills
|
||||
and pr.get_bills()[0].date
|
||||
> datetime.date.today() - datetime.timedelta(days=days)
|
||||
]
|
||||
click.secho("Number of projects: " + str(len(projects)))
|
||||
|
||||
if print_emails:
|
||||
emails = set([pr.contact_email for pr in projects])
|
||||
emails_str = ", ".join(emails)
|
||||
if len(emails) > 1:
|
||||
click.secho("Contact emails: " + emails_str)
|
||||
elif len(emails) == 1:
|
||||
click.secho("Contact email: " + emails_str)
|
||||
else:
|
||||
click.secho("No contact emails found")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="h-100">
|
||||
<head>
|
||||
<title>{{ _("Account manager") }}{% block title %}{% endblock %}</title>
|
||||
<title>{{ SITE_NAME }} — {{ _("Account manager") }}{% block title %}{% endblock %}</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel=stylesheet type=text/css href="{{ url_for("static", filename='css/main.css') }}">
|
||||
|
|
|
@ -238,7 +238,10 @@ class TestBudget(IhatemoneyTestCase):
|
|||
url, data={"password": "pass", "password_confirmation": "pass"}
|
||||
)
|
||||
resp = self.login("raclette", password="pass")
|
||||
assert "<title>Account manager - raclette</title>" in resp.data.decode("utf-8")
|
||||
assert (
|
||||
"<title>I Hate Money — Account manager - raclette</title>"
|
||||
in resp.data.decode("utf-8")
|
||||
)
|
||||
# Test empty and null tokens
|
||||
resp = self.client.get("/reset-password")
|
||||
assert "No token provided" in resp.data.decode("utf-8")
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
DEBUG = False
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///budget.db'
|
||||
SQLACHEMY_ECHO = DEBUG
|
||||
SITE_NAME = "I Hate Money"
|
||||
|
||||
SECRET_KEY = "supersecret"
|
||||
|
||||
|
|
|
@ -3,13 +3,17 @@ import smtplib
|
|||
import socket
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import orm
|
||||
from werkzeug.security import check_password_hash
|
||||
|
||||
from ihatemoney import models
|
||||
from ihatemoney.currency_convertor import CurrencyConverter
|
||||
from ihatemoney.manage import delete_project, generate_config, password_hash
|
||||
from ihatemoney.manage import (
|
||||
delete_project,
|
||||
generate_config,
|
||||
get_project_count,
|
||||
password_hash,
|
||||
)
|
||||
from ihatemoney.run import load_configuration
|
||||
from ihatemoney.tests.common.ihatemoney_testcase import BaseTestCase, IhatemoneyTestCase
|
||||
|
||||
|
@ -229,6 +233,65 @@ class TestModels(IhatemoneyTestCase):
|
|||
pay_each_expected = 10 / 3
|
||||
assert bill.pay_each() == pay_each_expected
|
||||
|
||||
def test_demo_project_count(self):
|
||||
"""Test command the get-project-count"""
|
||||
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"})
|
||||
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",
|
||||
},
|
||||
)
|
||||
|
||||
assert self.get_project("raclette").has_bills()
|
||||
|
||||
# Now check the different parameters
|
||||
runner = self.app.test_cli_runner()
|
||||
result0 = runner.invoke(get_project_count)
|
||||
assert result0.output.strip() == "Number of projects: 1"
|
||||
|
||||
# With more than 1 bill, without printing emails
|
||||
result1 = runner.invoke(get_project_count, "False 1")
|
||||
assert result1.output.strip() == "Number of projects: 1"
|
||||
|
||||
# With more than 2 bill, without printing emails
|
||||
result2 = runner.invoke(get_project_count, "False 2")
|
||||
assert result2.output.strip() == "Number of projects: 0"
|
||||
|
||||
# With more than 0 days old
|
||||
result3 = runner.invoke(get_project_count, "False 0 0")
|
||||
assert result3.output.strip() == "Number of projects: 0"
|
||||
|
||||
result4 = runner.invoke(get_project_count, "False 0 20000")
|
||||
assert result4.output.strip() == "Number of projects: 1"
|
||||
|
||||
# Print emails
|
||||
result5 = runner.invoke(get_project_count, "True")
|
||||
assert "raclette@notmyidea.org" in result5.output
|
||||
|
||||
|
||||
class TestEmailFailure(IhatemoneyTestCase):
|
||||
def test_creation_email_failure_smtp(self):
|
||||
|
@ -401,9 +464,7 @@ class TestCurrencyConverter:
|
|||
|
||||
def test_failing_remote(self):
|
||||
rates = {}
|
||||
with patch("requests.Response.json", new=lambda _: {}), pytest.warns(
|
||||
UserWarning
|
||||
):
|
||||
with patch("requests.Response.json", new=lambda _: {}):
|
||||
# we need a non-patched converter, but it seems that MagickMock
|
||||
# is mocking EVERY instance of the class method. Too bad.
|
||||
rates = CurrencyConverter.get_rates(self.converter)
|
||||
|
|
|
@ -137,6 +137,11 @@ def set_show_admin_dashboard_link(endpoint, values):
|
|||
g.logout_form = LogoutForm()
|
||||
|
||||
|
||||
@main.context_processor
|
||||
def add_template_variables():
|
||||
return {"SITE_NAME": current_app.config.get("SITE_NAME")}
|
||||
|
||||
|
||||
@main.url_value_preprocessor
|
||||
def pull_project(endpoint, values):
|
||||
"""When a request contains a project_id value, transform it directly
|
||||
|
|
|
@ -27,7 +27,7 @@ classifiers = [
|
|||
|
||||
dependencies = [
|
||||
"blinker>=1.4,<2",
|
||||
"cachetools>=4.1,<5",
|
||||
"cachetools>=4.1,<6",
|
||||
"debts>=0.5,<1",
|
||||
"email_validator>=1.0,<3",
|
||||
"Flask>=2,<4",
|
||||
|
@ -43,9 +43,9 @@ dependencies = [
|
|||
"itsdangerous>=2,<3",
|
||||
"Jinja2>=3,<4",
|
||||
"python-dateutil",
|
||||
"qrcode>=7.1,<8",
|
||||
"qrcode>=7.1,<9",
|
||||
"requests>=2.25,<3",
|
||||
"SQLAlchemy>=1.3.0,<1.5",
|
||||
"SQLAlchemy>=1.3.0,<2.1",
|
||||
"SQLAlchemy-Continuum>=1.3.12,<2", # New 1.4 changes API, see #728
|
||||
"Werkzeug>=2,<3",
|
||||
"WTForms>=2.3.3,<3.3",]
|
||||
|
@ -53,11 +53,11 @@ dependencies = [
|
|||
[project.optional-dependencies]
|
||||
database = [
|
||||
# Python 3.11 support starts in 2.9.2
|
||||
"psycopg2-binary>=2.9.2,<2.9.9",
|
||||
"psycopg2-binary>=2.9.2,<2.9.11",
|
||||
"PyMySQL>=0.9,<1.2",
|
||||
]
|
||||
dev = [
|
||||
"ruff==0.6.8",
|
||||
"ruff==0.8.4",
|
||||
"flake8==5.0.4",
|
||||
"isort==5.11.5",
|
||||
"vermin==1.6.0",
|
||||
|
@ -68,7 +68,7 @@ dev = [
|
|||
doc = [
|
||||
"Sphinx>=7.0.1,<9",
|
||||
"docutils==0.20.1",
|
||||
"myst-parser>=2,<3",
|
||||
"myst-parser>=2,<5",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
|
Loading…
Reference in a new issue