From 3b2e11ab6369dfa853383840dc28915b60c2d062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Fri, 5 Jan 2018 22:06:11 +0100 Subject: [PATCH 01/32] Include all .j2 files in the packaged version. I've also renamed the templates to *.j2 in order to make things clearer to others. Having extensions with the name of the locale doesn't seem to be a good practice, and would need us to add the locales in the MANIFEST file each time we add one. Fix #305 --- MANIFEST.in | 2 +- .../templates/{invitation_mail.en => invitation_mail.en.j2} | 4 ++-- .../templates/{invitation_mail.fr => invitation_mail.fr.j2} | 6 +++--- .../{password_reminder.en => password_reminder.en.j2} | 4 ++-- .../{password_reminder.fr => password_reminder.fr.j2} | 0 .../templates/{reminder_mail.en => reminder_mail.en.j2} | 0 .../templates/{reminder_mail.fr => reminder_mail.fr.j2} | 0 ihatemoney/web.py | 6 +++--- 8 files changed, 11 insertions(+), 11 deletions(-) rename ihatemoney/templates/{invitation_mail.en => invitation_mail.en.j2} (70%) rename ihatemoney/templates/{invitation_mail.fr => invitation_mail.fr.j2} (53%) rename ihatemoney/templates/{password_reminder.en => password_reminder.en.j2} (80%) rename ihatemoney/templates/{password_reminder.fr => password_reminder.fr.j2} (100%) rename ihatemoney/templates/{reminder_mail.en => reminder_mail.en.j2} (100%) rename ihatemoney/templates/{reminder_mail.fr => reminder_mail.fr.j2} (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 91d0edb2..74ea23b1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ include *.rst -recursive-include ihatemoney *.rst *.py *.yaml *.po *.mo *.html *.css *.js *.eot *.svg *.woff *.txt *.png *.ini *.cfg +recursive-include ihatemoney *.rst *.py *.yaml *.po *.mo *.html *.css *.js *.eot *.svg *.woff *.txt *.png *.ini *.cfg *.j2 include LICENSE CONTRIBUTORS CHANGELOG.rst requirements.txt diff --git a/ihatemoney/templates/invitation_mail.en b/ihatemoney/templates/invitation_mail.en.j2 similarity index 70% rename from ihatemoney/templates/invitation_mail.en rename to ihatemoney/templates/invitation_mail.en.j2 index eeaafdb9..42be0d25 100644 --- a/ihatemoney/templates/invitation_mail.en +++ b/ihatemoney/templates/invitation_mail.en.j2 @@ -2,10 +2,10 @@ Hi, Someone using the email address {{ g.project.contact_email }} invited you to share your expenses for "{{ g.project.name }}". -It's as simple as saying what did you paid for, for who, and how much did it cost you, we are caring about the rest. +It's as simple as saying what did you pay for, for whom, and how much did it cost you, we are caring about the rest. You can log in using this link: {{ url_for(".authenticate", _external=True, token=g.project.generate_token()) }}. -Once logged in you can use the following link which is easier to remember: {{ url_for(".list_bills", _external=True) }} +Once logged-in, you can use the following link which is easier to remember: {{ url_for(".list_bills", _external=True) }} If your cookie gets deleted or if you log out, you will need to log back in using the first link. Enjoy, diff --git a/ihatemoney/templates/invitation_mail.fr b/ihatemoney/templates/invitation_mail.fr.j2 similarity index 53% rename from ihatemoney/templates/invitation_mail.fr rename to ihatemoney/templates/invitation_mail.fr.j2 index a95f9e9e..197edcca 100644 --- a/ihatemoney/templates/invitation_mail.fr +++ b/ihatemoney/templates/invitation_mail.fr.j2 @@ -1,11 +1,11 @@ Salut, -Quelqu'un avec l'addresse email "{{ g.project.contact_email }}" vous à invité à partager vos dépenses pour "{{ g.project.name }}". +Quelqu'un avec l'adresse "{{ g.project.contact_email }}" vous à invité à partager vos dépenses pour "{{ g.project.name }}". -C'est aussi simple que de dire qui à payé pour quoi, pour qui, et combien celà à coûté, on s'occuppe du reste. +C'est aussi simple que de dire qui à payé pour quoi, pour qui, et combien celà à coûté, on s’occupe du reste. Vous pouvez vous authentifier avec le lien suivant: {{ url_for(".authenticate", _external=True, token=g.project.generate_token()) }}. Une fois authentifié, vous pouvez utiliser le lien suivant qui est plus facile à mémoriser: {{ url_for(".list_bills", _external=True) }} -Si votre cookie est supprimé ou si vous vous déconnectez, voous devrez vous réauthentifier en utilisant le premier lien. +Si votre cookie est supprimé ou si vous vous déconnectez, vous devrez vous authentifier à nouveau en utilisant le premier lien. Have fun, diff --git a/ihatemoney/templates/password_reminder.en b/ihatemoney/templates/password_reminder.en.j2 similarity index 80% rename from ihatemoney/templates/password_reminder.en rename to ihatemoney/templates/password_reminder.en.j2 index bc7e609c..c6543546 100644 --- a/ihatemoney/templates/password_reminder.en +++ b/ihatemoney/templates/password_reminder.en.j2 @@ -1,8 +1,8 @@ Hi, -You requested to reset the password of the following project: "{{ project.name }}". +You requested to reset the password of the following project: "{{ project.name }}". You can reset it here: {{ url_for(".reset_password", _external=True, token=project.generate_token(expiration=3600)) }}. -This link is only valid for 1 hour. +This link is only valid for one hour. Hope this helps, Some weird guys (with beards) diff --git a/ihatemoney/templates/password_reminder.fr b/ihatemoney/templates/password_reminder.fr.j2 similarity index 100% rename from ihatemoney/templates/password_reminder.fr rename to ihatemoney/templates/password_reminder.fr.j2 diff --git a/ihatemoney/templates/reminder_mail.en b/ihatemoney/templates/reminder_mail.en.j2 similarity index 100% rename from ihatemoney/templates/reminder_mail.en rename to ihatemoney/templates/reminder_mail.en.j2 diff --git a/ihatemoney/templates/reminder_mail.fr b/ihatemoney/templates/reminder_mail.fr.j2 similarity index 100% rename from ihatemoney/templates/reminder_mail.fr rename to ihatemoney/templates/reminder_mail.fr.j2 diff --git a/ihatemoney/web.py b/ihatemoney/web.py index e6df385a..6b1b3589 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -242,7 +242,7 @@ def create_project(): message_title = _("You have just created '%(project)s' " "to share your expenses", project=g.project.name) - message_body = render_template("reminder_mail.%s" % + message_body = render_template("reminder_mail.%s.j2" % get_locale().language) msg = Message(message_title, @@ -273,7 +273,7 @@ def remind_password(): project = Project.query.get(form.id.data) # send a link to reset the password - password_reminder = "password_reminder.%s" % get_locale().language + password_reminder = "password_reminder.%s.j2" % get_locale().language current_app.mail.send(Message( "password recovery", body=render_template(password_reminder, project=project), @@ -395,7 +395,7 @@ def invite(): if form.validate(): # send the email - message_body = render_template("invitation_mail.%s" % + message_body = render_template("invitation_mail.%s.j2" % get_locale().language) message_title = _("You have been invited to share your " From 2019b398f164aa3f7edb8af439c92f1a32d2e920 Mon Sep 17 00:00:00 2001 From: JocelynDelalande Date: Sun, 7 Jan 2018 00:27:42 +0100 Subject: [PATCH 02/32] manage commands testing (#313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename manage.ConfigTemplate → manage.GenerateConfig To be consistent with the CLI name: `generate-config`. * Add tests for manage.py commands * Run tests from pip-installed package To be able to detect packaging-related issues on test runs. refs #305 --- dev-requirements.txt | 1 + ihatemoney/manage.py | 8 ++++---- ihatemoney/tests/tests.py | 27 +++++++++++++++++++++++++++ tox.ini | 13 +++++++++++-- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 04358ae8..28116fac 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -3,3 +3,4 @@ tox pytest Flask-Testing Flake8 +mock; python_version < '3.3' diff --git a/ihatemoney/manage.py b/ihatemoney/manage.py index 797e6c4b..315cfac1 100755 --- a/ihatemoney/manage.py +++ b/ihatemoney/manage.py @@ -4,7 +4,7 @@ import os import pkgutil import random import sys -from getpass import getpass +import getpass from flask_script import Manager, Command, Option from flask_migrate import Migrate, MigrateCommand @@ -20,11 +20,11 @@ class GeneratePasswordHash(Command): """Get password from user and hash it without printing it in clear text.""" def run(self): - password = getpass(prompt='Password: ') + password = getpass.getpass(prompt='Password: ') print(generate_password_hash(password)) -class ConfigTemplate(Command): +class GenerateConfig(Command): def get_options(self): return [ Option('config_file', choices=[ @@ -74,7 +74,7 @@ def main(): manager = Manager(app) manager.add_command('db', MigrateCommand) manager.add_command('generate_password_hash', GeneratePasswordHash) - manager.add_command('generate-config', ConfigTemplate) + manager.add_command('generate-config', GenerateConfig) manager.run() diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index de53c584..d4b6d7a1 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -4,6 +4,10 @@ try: import unittest2 as unittest except ImportError: import unittest # NOQA +try: + from unittest.mock import patch +except ImportError: + from mock import patch import os import json @@ -16,6 +20,7 @@ from flask import session from flask_testing import TestCase from ihatemoney.run import create_app, db, load_configuration +from ihatemoney.manage import GenerateConfig, GeneratePasswordHash from ihatemoney import models from ihatemoney import utils @@ -1406,5 +1411,27 @@ class ServerTestCase(IhatemoneyTestCase): self.assertStatus(200, req) +class CommandTestCase(BaseTestCase): + def test_generate_config(self): + """ Simply checks that all config file generation + - raise no exception + - produce something non-empty + """ + cmd = GenerateConfig() + for config_file in cmd.get_options()[0].kwargs['choices']: + with patch('sys.stdout', new=six.StringIO()) as stdout: + cmd.run(config_file) + print(stdout.getvalue()) + self.assertNotEqual(len(stdout.getvalue().strip()), 0) + + def test_generate_password_hash(self): + cmd = GeneratePasswordHash() + with patch('sys.stdout', new=six.StringIO()) as stdout, \ + patch('getpass.getpass', new=lambda prompt: 'secret'): # NOQA + cmd.run() + print(stdout.getvalue()) + self.assertEqual(len(stdout.getvalue().strip()), 187) + + if __name__ == "__main__": unittest.main() diff --git a/tox.ini b/tox.ini index ddef0a1a..15c2b40f 100644 --- a/tox.ini +++ b/tox.ini @@ -6,22 +6,31 @@ skip_missing_interpreters = True commands = python --version - py.test ihatemoney/tests/tests.py + py.test --pyargs ihatemoney.tests.tests + deps = -rdev-requirements.txt -rrequirements.txt -install_command = pip install --pre {opts} {packages} +# To be sure we are importing ihatemoney pkg from pip-installed version +changedir = /tmp + +install_command = + pip install --pre {opts} {packages} + pip install . + [testenv:docs] commands = sphinx-build -a -n -b html -d docs/_build/doctrees docs docs/_build/html deps = -rdocs/requirements.txt +changedir = {toxinidir} [testenv:lint] commands = flake8 ihatemoney deps = -rdev-requirements.txt +changedir = {toxinidir} [flake8] exclude = migrations From f1e2a2d84ceb13951c2547c6548eb823b60ce77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Sun, 7 Jan 2018 21:43:35 +0100 Subject: [PATCH 03/32] Add a changelog entry --- CHANGELOG.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index de1356e5..bc1024df 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,10 @@ This document describes changes between each past release. 2.1 (unreleased) ---------------- -- Nothing changed yet. +Fixed +===== + +- Include all .j2 files in the packaged version (#308) 2.0 (2017-12-27) From c24ee6f1c4e1c1090f0833f65b795512fc03de30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Sun, 7 Jan 2018 22:46:07 +0100 Subject: [PATCH 04/32] Update the CHANGELOG. --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bc1024df..9aa61bef 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,7 +9,7 @@ This document describes changes between each past release. Fixed ===== -- Include all .j2 files in the packaged version (#308) +- Fix the "IOError" crash when running `ihatemoney generate-config` (#308) 2.0 (2017-12-27) From 0504fd82f52317d902e85a6e448048d5ef1c58d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Fri, 5 Jan 2018 22:36:31 +0100 Subject: [PATCH 05/32] Fix the supervisord template. The script was relying on the presence of an environment variable, which is only set when the virtualenv is activated. But a virtualenv does not have to be activated to work (it's possible to call the python command directly). This fixes it by relying on `sys.executable` which should be correct at all times. Fixes #306 --- ihatemoney/conf-templates/supervisord.conf.j2 | 2 +- ihatemoney/manage.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ihatemoney/conf-templates/supervisord.conf.j2 b/ihatemoney/conf-templates/supervisord.conf.j2 index fa16c0cf..605f39d9 100644 --- a/ihatemoney/conf-templates/supervisord.conf.j2 +++ b/ihatemoney/conf-templates/supervisord.conf.j2 @@ -1,5 +1,5 @@ [program:ihatemoney] -command={{ venv_path }}/bin/gunicorn -c /etc/ihatemoney/gunicorn.conf.py ihatemoney.wsgi:application +command={{ bin_path }}/gunicorn -c /etc/ihatemoney/gunicorn.conf.py ihatemoney.wsgi:application user=ihatemoney autostart=true autorestart=true diff --git a/ihatemoney/manage.py b/ihatemoney/manage.py index 315cfac1..73bca579 100755 --- a/ihatemoney/manage.py +++ b/ihatemoney/manage.py @@ -49,9 +49,11 @@ class GenerateConfig(Command): os.path.join('conf-templates/', config_file) + '.j2' ).decode('utf-8') + bin_path = os.path.dirname(sys.executable) + print(Template(template_content).render( pkg_path=os.path.abspath(os.path.dirname(__file__)), - venv_path=os.environ.get('VIRTUAL_ENV'), + bin_path=bin_path, secret_key=self.gen_secret_key(), )) From 230eafdf58c46b983936cbf4f70b712bbddfd8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Sun, 14 Jan 2018 16:52:52 +0100 Subject: [PATCH 06/32] Use Jinja2 strict rendering. For this I had to create an Jinja2 explicit environment, so I put a function in `ihatemoney.utils.create_jinja2_env(strict_rendering=False)`. When using this environment and if `strict_rendering` is activated, templates using undefined variables will now error out rather than failing silently. --- .../conf-templates/apache-vhost.conf.j2 | 3 +-- ihatemoney/manage.py | 14 ++++++-------- ihatemoney/utils.py | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/ihatemoney/conf-templates/apache-vhost.conf.j2 b/ihatemoney/conf-templates/apache-vhost.conf.j2 index 3246d27f..0527d310 100644 --- a/ihatemoney/conf-templates/apache-vhost.conf.j2 +++ b/ihatemoney/conf-templates/apache-vhost.conf.j2 @@ -1,8 +1,7 @@ ServerAdmin admin@example.com # CUSTOMIZE ServerName ihatemoney.example.com # CUSTOMIZE - - WSGIDaemonProcess ihatemoney user=www-data group=www-data threads=5 python-path={{ pkg_path }} {% if venv_path %}python-home={{ venv_path }}{% endif %} + WSGIDaemonProcess ihatemoney user=www-data group=www-data threads=5 python-path={{ pkg_path }} {% if bin_path %}python-home={{ bin_path }}{% endif %} WSGIScriptAlias / {{ pkg_path }}/wsgi.py WSGIPassAuthorization On diff --git a/ihatemoney/manage.py b/ihatemoney/manage.py index 73bca579..9058b390 100755 --- a/ihatemoney/manage.py +++ b/ihatemoney/manage.py @@ -1,18 +1,17 @@ #!/usr/bin/env python import os -import pkgutil import random import sys import getpass from flask_script import Manager, Command, Option from flask_migrate import Migrate, MigrateCommand -from jinja2 import Template from werkzeug.security import generate_password_hash from ihatemoney.run import create_app from ihatemoney.models import db +from ihatemoney.utils import create_jinja_env class GeneratePasswordHash(Command): @@ -44,15 +43,14 @@ class GenerateConfig(Command): for i in range(50)]) def run(self, config_file): - template_content = pkgutil.get_data( - 'ihatemoney', - os.path.join('conf-templates/', config_file) + '.j2' - ).decode('utf-8') + env = create_jinja_env('conf-templates', strict_rendering=True) + template = env.get_template('%s.j2' % config_file) bin_path = os.path.dirname(sys.executable) + pkg_path = os.path.abspath(os.path.dirname(__file__)) - print(Template(template_content).render( - pkg_path=os.path.abspath(os.path.dirname(__file__)), + print(template.render( + pkg_path=pkg_path, bin_path=bin_path, secret_key=self.gen_secret_key(), )) diff --git a/ihatemoney/utils.py b/ihatemoney/utils.py index 6af0112c..a25e3b97 100644 --- a/ihatemoney/utils.py +++ b/ihatemoney/utils.py @@ -2,7 +2,7 @@ import base64 import re from io import BytesIO, StringIO -from jinja2 import filters +import jinja2 from json import dumps from flask import redirect from werkzeug.routing import HTTPException, RoutingException @@ -83,7 +83,7 @@ def minimal_round(*args, **kw): from http://stackoverflow.com/questions/28458524/ """ # Use the original round filter, to deal with the extra arguments - res = filters.do_round(*args, **kw) + res = jinja2.filters.do_round(*args, **kw) # Test if the result is equivalent to an integer and # return depending on it ires = int(res) @@ -170,3 +170,18 @@ class LoginThrottler(): def reset(self, ip): self._attempts.pop(ip, None) + + +def create_jinja_env(folder, strict_rendering=False): + """Creates and return a Jinja2 Environment object, used, to load the + templates. + + :param strict_rendering: + if set to `True`, all templates which use an undefined variable will + throw an exception (default to `False`). + """ + loader = jinja2.PackageLoader('ihatemoney', folder) + kwargs = {'loader': loader} + if strict_rendering: + kwargs['undefined'] = jinja2.StrictUndefined + return jinja2.Environment(**kwargs) From 830718e1fe5f18959f455a696ebc2172a2d5f253 Mon Sep 17 00:00:00 2001 From: Richard Coates Date: Thu, 25 Jan 2018 17:34:37 +0100 Subject: [PATCH 07/32] Make sidebar scrollable (#316) * Make sidebar scrollable Make sidebar scrollable. * Update CHANGELOG.rst Fixes #318 --- CHANGELOG.rst | 1 + ihatemoney/static/css/main.css | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9aa61bef..efd0648b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Fixed ===== - Fix the "IOError" crash when running `ihatemoney generate-config` (#308) +- Made the left-hand sidebar scrollable (#318) 2.0 (2017-12-27) diff --git a/ihatemoney/static/css/main.css b/ihatemoney/static/css/main.css index b0120ca9..4b35b376 100644 --- a/ihatemoney/static/css/main.css +++ b/ihatemoney/static/css/main.css @@ -74,6 +74,7 @@ body { background-repeat: no-repeat; height: 100%; color: black; + overflow-y: scroll; } @media (min-width: 768px) { From b93ea4830d5290def99d597f17292a8aa5d4c090 Mon Sep 17 00:00:00 2001 From: 0livd Date: Thu, 25 Jan 2018 17:41:28 +0100 Subject: [PATCH 08/32] API: Migrate from flask-rest to flask-restful (#315) The flask-rest custom json encoder is still needed and thus was added to ihatemoney's utils. Closes #298 --- CHANGELOG.rst | 5 ++ ihatemoney/api.py | 158 +++++++++++++++++++------------------- ihatemoney/run.py | 4 +- ihatemoney/tests/tests.py | 14 ++-- ihatemoney/utils.py | 27 ++++++- requirements.txt | 2 +- 6 files changed, 121 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index efd0648b..ea2b61e5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,11 @@ This document describes changes between each past release. 2.1 (unreleased) ---------------- +Changed +======= + +- Use flask-restful instead of deprecated flask-rest for the REST API (#315) + Fixed ===== diff --git a/ihatemoney/api.py b/ihatemoney/api.py index 827202c8..31ed06cc 100644 --- a/ihatemoney/api.py +++ b/ihatemoney/api.py @@ -1,62 +1,68 @@ # -*- coding: utf-8 -*- from flask import Blueprint, request -from flask_rest import RESTResource, need_auth +from flask_restful import Resource, Api, abort from wtforms.fields.core import BooleanField from ihatemoney.models import db, Project, Person, Bill from ihatemoney.forms import (ProjectForm, EditProjectForm, MemberForm, get_billform_for) from werkzeug.security import check_password_hash +from functools import wraps api = Blueprint("api", __name__, url_prefix="/api") +restful_api = Api(api) -def check_project(*args, **kwargs): +def need_auth(f): """Check the request for basic authentication for a given project. - Return the project if the authorization is good, False otherwise + Return the project if the authorization is good, abort the request with a 401 otherwise """ - auth = request.authorization + @wraps(f) + def wrapper(*args, **kwargs): + auth = request.authorization + project_id = kwargs.get("project_id") - # project_id should be contained in kwargs and equal to the username - if auth and "project_id" in kwargs and \ - auth.username == kwargs["project_id"]: - project = Project.query.get(auth.username) - if project and check_password_hash(project.password, auth.password): - return project - return False + if auth and project_id and auth.username == project_id: + project = Project.query.get(auth.username) + if project and check_password_hash(project.password, auth.password): + # The whole project object will be passed instead of project_id + kwargs.pop("project_id") + return f(*args, project=project, **kwargs) + abort(401) + return wrapper -class ProjectHandler(object): - - def add(self): +class ProjectsHandler(Resource): + def post(self): form = ProjectForm(meta={'csrf': False}) if form.validate(): project = form.save() db.session.add(project) db.session.commit() - return 201, project.id - return 400, form.errors + return project.id, 201 + return form.errors, 400 + + +class ProjectHandler(Resource): + method_decorators = [need_auth] - @need_auth(check_project, "project") def get(self, project): - return 200, project + return project - @need_auth(check_project, "project") def delete(self, project): db.session.delete(project) db.session.commit() - return 200, "DELETED" + return "DELETED" - @need_auth(check_project, "project") - def update(self, project): + def put(self, project): form = EditProjectForm(meta={'csrf': False}) if form.validate(): form.update(project) db.session.commit() - return 200, "UPDATED" - return 400, form.errors + return "UPDATED" + return form.errors, 400 class APIMemberForm(MemberForm): @@ -71,98 +77,92 @@ class APIMemberForm(MemberForm): return super(APIMemberForm, self).save(project, person) -class MemberHandler(object): +class MembersHandler(Resource): + method_decorators = [need_auth] - def get(self, project, member_id): - member = Person.query.get(member_id, project) - if not member or member.project != project: - return 404, "Not Found" - return 200, member + def get(self, project): + return project.members - def list(self, project): - return 200, project.members - - def add(self, project): + def post(self, project): form = MemberForm(project, meta={'csrf': False}) if form.validate(): member = Person() form.save(project, member) db.session.commit() - return 201, member.id - return 400, form.errors + return member.id, 201 + return form.errors, 400 - def update(self, project, member_id): + +class MemberHandler(Resource): + method_decorators = [need_auth] + + def get(self, project, member_id): + member = Person.query.get(member_id, project) + if not member or member.project != project: + return "Not Found", 404 + return member + + def put(self, project, member_id): form = APIMemberForm(project, meta={'csrf': False}, edit=True) if form.validate(): member = Person.query.get(member_id, project) form.save(project, member) db.session.commit() - return 200, member - return 400, form.errors + return member + return form.errors, 400 def delete(self, project, member_id): if project.remove_member(member_id): - return 200, "OK" - return 404, "Not Found" + return "OK" + return "Not Found", 404 -class BillHandler(object): +class BillsHandler(Resource): + method_decorators = [need_auth] - def get(self, project, bill_id): - bill = Bill.query.get(project, bill_id) - if not bill: - return 404, "Not Found" - return 200, bill - - def list(self, project): + def get(self, project): return project.get_bills().all() - def add(self, project): + def post(self, project): form = get_billform_for(project, True, meta={'csrf': False}) if form.validate(): bill = Bill() form.save(bill, project) db.session.add(bill) db.session.commit() - return 201, bill.id - return 400, form.errors + return bill.id, 201 + return form.errors, 400 - def update(self, project, bill_id): + +class BillHandler(Resource): + method_decorators = [need_auth] + + def get(self, project, bill_id): + bill = Bill.query.get(project, bill_id) + if not bill: + return "Not Found", 404 + return bill, 200 + + def put(self, project, bill_id): form = get_billform_for(project, True, meta={'csrf': False}) if form.validate(): bill = Bill.query.get(project, bill_id) form.save(bill, project) db.session.commit() - return 200, bill.id - return 400, form.errors + return bill.id, 200 + return form.errors, 400 def delete(self, project, bill_id): bill = Bill.query.delete(project, bill_id) db.session.commit() if not bill: - return 404, "Not Found" - return 200, "OK" + return "Not Found", 404 + return "OK", 200 -project_resource = RESTResource( - name="project", - route="/projects", - app=api, - actions=["add", "update", "delete", "get"], - handler=ProjectHandler()) - -member_resource = RESTResource( - name="member", - inject_name="project", - route="/projects//members", - app=api, - handler=MemberHandler(), - authentifier=check_project) - -bill_resource = RESTResource( - name="bill", - inject_name="project", - route="/projects//bills", - app=api, - handler=BillHandler(), - authentifier=check_project) +restful_api.add_resource(ProjectsHandler, '/projects') +restful_api.add_resource(ProjectHandler, '/projects/') +restful_api.add_resource(MembersHandler, "/projects//members") +restful_api.add_resource(MemberHandler, "/projects//members/") +restful_api.add_resource(BillsHandler, "/projects//bills") +restful_api.add_resource(BillHandler, "/projects//bills/") diff --git a/ihatemoney/run.py b/ihatemoney/run.py index e3a7c1e5..a8de26f0 100644 --- a/ihatemoney/run.py +++ b/ihatemoney/run.py @@ -11,7 +11,7 @@ from werkzeug.contrib.fixers import ProxyFix from ihatemoney.api import api from ihatemoney.models import db -from ihatemoney.utils import PrefixedWSGI, minimal_round +from ihatemoney.utils import PrefixedWSGI, minimal_round, IhmJSONEncoder from ihatemoney.web import main as web_interface from ihatemoney import default_settings @@ -68,6 +68,8 @@ def load_configuration(app, configuration=None): app.config.from_pyfile(env_var_config) else: app.config.from_pyfile('ihatemoney.cfg', silent=True) + # Configure custom JSONEncoder used by the API + app.config['RESTFUL_JSON'] = {'cls': IhmJSONEncoder} def validate_configuration(app): diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index d4b6d7a1..c13131c4 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -1053,7 +1053,7 @@ class APITestCase(IhatemoneyTestCase): }) self.assertTrue(400, resp.status_code) - self.assertEqual('{"contact_email": ["Invalid email address."]}', + self.assertEqual('{"contact_email": ["Invalid email address."]}\n', resp.data.decode('utf-8')) # create it @@ -1139,7 +1139,7 @@ class APITestCase(IhatemoneyTestCase): headers=self.get_auth("raclette")) self.assertStatus(200, req) - self.assertEqual('[]', req.data.decode('utf-8')) + self.assertEqual('[]\n', req.data.decode('utf-8')) # add a member req = self.client.post("/api/projects/raclette/members", data={ @@ -1148,7 +1148,7 @@ class APITestCase(IhatemoneyTestCase): # the id of the new member should be returned self.assertStatus(201, req) - self.assertEqual("1", req.data.decode('utf-8')) + self.assertEqual("1\n", req.data.decode('utf-8')) # the list of members should contain one member req = self.client.get("/api/projects/raclette/members", @@ -1223,7 +1223,7 @@ class APITestCase(IhatemoneyTestCase): headers=self.get_auth("raclette")) self.assertStatus(200, req) - self.assertEqual('[]', req.data.decode('utf-8')) + self.assertEqual('[]\n', req.data.decode('utf-8')) def test_bills(self): # create a project @@ -1239,7 +1239,7 @@ class APITestCase(IhatemoneyTestCase): headers=self.get_auth("raclette")) self.assertStatus(200, req) - self.assertEqual("[]", req.data.decode('utf-8')) + self.assertEqual("[]\n", req.data.decode('utf-8')) # add a bill req = self.client.post("/api/projects/raclette/bills", data={ @@ -1252,7 +1252,7 @@ class APITestCase(IhatemoneyTestCase): # should return the id self.assertStatus(201, req) - self.assertEqual(req.data.decode('utf-8'), "1") + self.assertEqual(req.data.decode('utf-8'), "1\n") # get this bill details req = self.client.get("/api/projects/raclette/bills/1", @@ -1288,7 +1288,7 @@ class APITestCase(IhatemoneyTestCase): }, headers=self.get_auth("raclette")) self.assertStatus(400, req) - self.assertEqual('{"date": ["This field is required."]}', req.data.decode('utf-8')) + self.assertEqual('{"date": ["This field is required."]}\n', req.data.decode('utf-8')) # edit a bill req = self.client.put("/api/projects/raclette/bills/1", data={ diff --git a/ihatemoney/utils.py b/ihatemoney/utils.py index 6af0112c..aaae2a08 100644 --- a/ihatemoney/utils.py +++ b/ihatemoney/utils.py @@ -3,7 +3,7 @@ import re from io import BytesIO, StringIO from jinja2 import filters -from json import dumps +from json import dumps, JSONEncoder from flask import redirect from werkzeug.routing import HTTPException, RoutingException import six @@ -170,3 +170,28 @@ class LoginThrottler(): def reset(self, ip): self._attempts.pop(ip, None) + + +class IhmJSONEncoder(JSONEncoder): + """Subclass of the default encoder to support custom objects. + Taken from the deprecated flask-rest package.""" + def default(self, o): + if hasattr(o, "_to_serialize"): + # build up the object + data = {} + for attr in o._to_serialize: + data[attr] = getattr(o, attr) + return data + elif hasattr(o, "isoformat"): + return o.isoformat() + else: + try: + from flask_babel import speaklater + if isinstance(o, speaklater.LazyString): + try: + return unicode(o) # For python 2. + except NameError: + return str(o) # For python 3. + except ImportError: + pass + return JSONEncoder.default(self, o) diff --git a/requirements.txt b/requirements.txt index 64610abd..c2fe5348 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ flask-mail>=0.8 Flask-Migrate>=1.8.0 Flask-script flask-babel -flask-rest>=1.3 +flask-restful>=0.3.6 jinja2>=2.6 raven blinker From 434ee8b85251ee14cb0535cbb92b9b28b84f0b8d Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Fri, 26 Jan 2018 16:17:43 +0100 Subject: [PATCH 09/32] Display sidebar scroll-bar only if required This fix a regression from #316 (scrollbar was displayed all the time). Note that the padding-bottom value is totally empiric, but proved OK on my Fx and Chrome instances + some responsive tests. There might be finer solutions, feel free :-). --- ihatemoney/static/css/main.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ihatemoney/static/css/main.css b/ihatemoney/static/css/main.css index 4b35b376..94ca4bd8 100644 --- a/ihatemoney/static/css/main.css +++ b/ihatemoney/static/css/main.css @@ -74,12 +74,13 @@ body { background-repeat: no-repeat; height: 100%; color: black; - overflow-y: scroll; + overflow-y: auto; } @media (min-width: 768px) { .sidebar { position: fixed; + padding-bottom: 4.5rem; } } From cf7bd572489aad17bf60026c6618d0ff49f822a9 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Sun, 4 Feb 2018 13:10:57 +0100 Subject: [PATCH 10/32] Remove unused CSS odd/even classes That was forgotten from fe39258630e55d4a3e1297a01a1c8fd39bad3a4e --- ihatemoney/templates/dashboard.html | 2 +- ihatemoney/templates/settle_bills.html | 2 +- ihatemoney/templates/statistics.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ihatemoney/templates/dashboard.html b/ihatemoney/templates/dashboard.html index b1220bd4..807e3e2f 100644 --- a/ihatemoney/templates/dashboard.html +++ b/ihatemoney/templates/dashboard.html @@ -4,7 +4,7 @@ {% for project in projects|sort(attribute='name') %} - + {% if project.has_bills() %} diff --git a/ihatemoney/templates/settle_bills.html b/ihatemoney/templates/settle_bills.html index b67a9b85..7ec5e290 100644 --- a/ihatemoney/templates/settle_bills.html +++ b/ihatemoney/templates/settle_bills.html @@ -22,7 +22,7 @@ {% for bill in bills %} - + diff --git a/ihatemoney/templates/statistics.html b/ihatemoney/templates/statistics.html index 061c6299..ae1c80ea 100644 --- a/ihatemoney/templates/statistics.html +++ b/ihatemoney/templates/statistics.html @@ -22,7 +22,7 @@ {% for member in members %} - + From 389c7b8bcd2813d8549858265432859259942fd6 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Sat, 3 Feb 2018 17:49:12 +0100 Subject: [PATCH 11/32] Remove dead code --- ihatemoney/templates/statistics.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ihatemoney/templates/statistics.html b/ihatemoney/templates/statistics.html index ae1c80ea..ee59cb9f 100644 --- a/ihatemoney/templates/statistics.html +++ b/ihatemoney/templates/statistics.html @@ -5,7 +5,7 @@
{{ _("Project") }}{{ _("Number of members") }}{{ _("Number of bills") }}{{_("Newest bill")}}{{_("Oldest bill")}}{{_("Actions")}}
{{ project.name }}{{ project.members | count }}{{ project.get_bills().count() }}{{ project.get_bills().all()[0].date }}
{{ _("Who pays?") }}{{ _("To whom?") }}{{ _("How much?") }}
{{ bill.ower }} {{ bill.receiver }} {{ "%0.2f"|format(bill.amount) }}
{{ _("Who?") }}{{ _("Paid") }}{{ _("Spent") }}{{ _("Balance") }}
{{ member.name }} {{ "%0.2f"|format(paid[member.id]) }} {{ "%0.2f"|format(spent[member.id]) }}
{% set balance = g.project.balance %} {% for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id]|round(2) != 0 %} - +
{{ member.name }} {% if balance[member.id]|round(2) > 0 %}+{% endif %}{{ "%.2f" | format(balance[member.id]) }} From b1a4572e8c72e1d7f49b07aaeb5be0f3603bf0a7 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Sat, 3 Feb 2018 18:04:06 +0100 Subject: [PATCH 12/32] Change statistics data structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clearer data structure, and simpler template This commit has a side effect: sidebar now hides disabled members. IMHO, the disabled members should either be hidden or shown consistently between sidebar and central table. Previous status was: shown in sidebar (if balance ≠ 0) and hidden in central table. --- ihatemoney/templates/statistics.html | 19 +++++++++---------- ihatemoney/tests/tests.py | 24 ++++++++++++------------ ihatemoney/web.py | 27 ++++++++++++++------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/ihatemoney/templates/statistics.html b/ihatemoney/templates/statistics.html index ee59cb9f..1b07a33f 100644 --- a/ihatemoney/templates/statistics.html +++ b/ihatemoney/templates/statistics.html @@ -3,12 +3,11 @@ {% block sidebar %}
- {% set balance = g.project.balance %} - {% for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id]|round(2) != 0 %} + {% for stat in members_stats| sort(attribute='member.name') %} - - + {% endfor %} @@ -21,12 +20,12 @@
{{ member.name }} - {% if balance[member.id]|round(2) > 0 %}+{% endif %}{{ "%.2f" | format(balance[member.id]) }} + {{ stat.member.name }} + {% if stat.balance|round(2) > 0 %}+{% endif %}{{ "%.2f" | format(stat.balance) }}
- {% for member in members %} + {% for stat in members_stats %} - - - - + + + + {% endfor %} diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index c13131c4..35820325 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -750,24 +750,24 @@ class BudgetTestCase(IhatemoneyTestCase): }) response = self.client.get("/raclette/statistics") - self.assertIn("\n " - + "\n " - + "\n " + self.assertIn("\n " + + "\n " + + "\n " + "\n", response.data.decode('utf-8')) - self.assertIn("\n " - + "\n " - + "\n " + self.assertIn("\n " + + "\n " + + "\n " + "\n", response.data.decode('utf-8')) - self.assertIn("\n " - + "\n " - + "\n " + self.assertIn("\n " + + "\n " + + "\n " + "\n", response.data.decode('utf-8')) - self.assertIn("\n " - + "\n " - + "\n " + self.assertIn("\n " + + "\n " + + "\n " + "\n", response.data.decode('utf-8')) diff --git a/ihatemoney/web.py b/ihatemoney/web.py index 6b1b3589..85b02e5b 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -566,21 +566,22 @@ def settle_bill(): @main.route("//statistics") def statistics(): """Compute what each member has paid and spent and display it""" - members = g.project.active_members - balance = g.project.balance - paid = {} - spent = {} - for member in members: - paid[member.id] = sum([bill.amount - for bill in g.project.get_member_bills(member.id).all()]) - spent[member.id] = sum([bill.pay_each() * member.weight - for bill in g.project.get_bills().all() if member in bill.owers]) + members_stats = [{ + 'member': member, + 'paid': sum([ + bill.amount + for bill in g.project.get_member_bills(member.id).all() + ]), + 'spent': sum([ + bill.pay_each() * member.weight + for bill in g.project.get_bills().all() if member in bill.owers + ]), + 'balance': g.project.balance[member.id] + } for member in g.project.active_members] + return render_template( "statistics.html", - members=members, - balance=balance, - paid=paid, - spent=spent, + members_stats=members_stats, current_view='statistics', ) From 036cd05e5716a694f575b3c65f6541f04a8b48bf Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Sat, 3 Feb 2018 18:26:44 +0100 Subject: [PATCH 13/32] Move member stats computation to a dedicated method --- ihatemoney/models.py | 20 ++++++++++++++++++++ ihatemoney/web.py | 15 +-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ihatemoney/models.py b/ihatemoney/models.py index aa3083d6..c6ce23fb 100644 --- a/ihatemoney/models.py +++ b/ihatemoney/models.py @@ -52,6 +52,26 @@ class Project(db.Model): return balances + @property + def members_stats(self): + """Compute what each member has paid + + :return: one stat dict per member + :rtype list: + """ + return [{ + 'member': member, + 'paid': sum([ + bill.amount + for bill in self.get_member_bills(member.id).all() + ]), + 'spent': sum([ + bill.pay_each() * member.weight + for bill in self.get_bills().all() if member in bill.owers + ]), + 'balance': self.balance[member.id] + } for member in self.active_members] + @property def uses_weights(self): return len([i for i in self.members if i.weight != 1]) > 0 diff --git a/ihatemoney/web.py b/ihatemoney/web.py index 85b02e5b..1e162024 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -566,22 +566,9 @@ def settle_bill(): @main.route("//statistics") def statistics(): """Compute what each member has paid and spent and display it""" - members_stats = [{ - 'member': member, - 'paid': sum([ - bill.amount - for bill in g.project.get_member_bills(member.id).all() - ]), - 'spent': sum([ - bill.pay_each() * member.weight - for bill in g.project.get_bills().all() if member in bill.owers - ]), - 'balance': g.project.balance[member.id] - } for member in g.project.active_members] - return render_template( "statistics.html", - members_stats=members_stats, + members_stats=g.project.members_stats, current_view='statistics', ) From b95ea7f4e68a0794a44e68621a8210bb4db43e67 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Sat, 3 Feb 2018 18:52:04 +0100 Subject: [PATCH 14/32] Add statistics support to API --- CHANGELOG.rst | 5 +++++ docs/api.rst | 22 ++++++++++++++++++++++ ihatemoney/api.py | 8 ++++++++ ihatemoney/tests/tests.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ea2b61e5..fccb08d0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,11 @@ Fixed - Fix the "IOError" crash when running `ihatemoney generate-config` (#308) - Made the left-hand sidebar scrollable (#318) +Added +===== + +- Statistics API (#343) + 2.0 (2017-12-27) ---------------- diff --git a/docs/api.rst b/docs/api.rst index b82c6f3e..0ae42144 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -164,3 +164,25 @@ And you can of course `DELETE` them at `/api/projects//bills/`:: $ curl --basic -u demo:demo -X DELETE\ https://ihatemoney.org/api/projects/demo/bills/80\ "OK" + + +Statistics +---------- + +You can get some project stats with a `GET` on `/api/projects//statistics`:: + + $ curl --basic -u demo:demo https://ihatemoney.org/api/projects/demo/statistics + [ + { + "balance": 12.5, + "member": {"activated": True, "id": 1, "name": "alexis", "weight": 1.0}, + "paid": 25.0, + "spent": 12.5 + }, + { + "balance": -12.5, + "member": {"activated": True, "id": 2, "name": "fred", "weight": 1.0}, + "paid": 0, + "spent": 12.5 + } + ] diff --git a/ihatemoney/api.py b/ihatemoney/api.py index 31ed06cc..6068cf72 100644 --- a/ihatemoney/api.py +++ b/ihatemoney/api.py @@ -65,6 +65,13 @@ class ProjectHandler(Resource): return form.errors, 400 +class ProjectStatsHandler(Resource): + method_decorators = [need_auth] + + def get(self, project): + return project.members_stats + + class APIMemberForm(MemberForm): """ Member is not disablable via a Form. @@ -163,6 +170,7 @@ class BillHandler(Resource): restful_api.add_resource(ProjectsHandler, '/projects') restful_api.add_resource(ProjectHandler, '/projects/') restful_api.add_resource(MembersHandler, "/projects//members") +restful_api.add_resource(ProjectStatsHandler, "/projects//statistics") restful_api.add_resource(MemberHandler, "/projects//members/") restful_api.add_resource(BillsHandler, "/projects//bills") restful_api.add_resource(BillHandler, "/projects//bills/") diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index 35820325..3797f09d 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -1325,6 +1325,40 @@ class APITestCase(IhatemoneyTestCase): headers=self.get_auth("raclette")) self.assertStatus(404, req) + def test_statistics(self): + # create a project + self.api_create("raclette") + + # add members + self.api_add_member("raclette", "alexis") + self.api_add_member("raclette", "fred") + + # add a bill + req = self.client.post("/api/projects/raclette/bills", data={ + 'date': '2011-08-10', + 'what': 'fromage', + 'payer': "1", + 'payed_for': ["1", "2"], + 'amount': '25', + }, headers=self.get_auth("raclette")) + + # get the list of bills (should be empty) + req = self.client.get("/api/projects/raclette/statistics", + headers=self.get_auth("raclette")) + self.assertStatus(200, req) + self.assertEqual([ + {'balance': 12.5, + 'member': {'activated': True, 'id': 1, + 'name': 'alexis', 'weight': 1.0}, + 'paid': 25.0, + 'spent': 12.5}, + {'balance': -12.5, + 'member': {'activated': True, 'id': 2, + 'name': 'fred', 'weight': 1.0}, + 'paid': 0, + 'spent': 12.5}], + json.loads(req.data.decode('utf-8'))) + def test_username_xss(self): # create a project # self.api_create("raclette") From d9471733f82f948e4e77ac91d6280fa5c4856358 Mon Sep 17 00:00:00 2001 From: 0livd Date: Wed, 7 Feb 2018 09:37:21 +0100 Subject: [PATCH 15/32] Fix some anti patterns in docker deployment (#321) - Use exec to run gunicorn and avoid creating a new process. - Add the possibility to pass any additional parameters to gunicorn. - Use only one gunicorn worker by default as the usual way to scale the app in production would be to use the scale command of the cluster scheduler. Additional workers could still be added by passing the "-w" gunicorn parameter to docker run. --- Dockerfile | 5 ++--- conf/confandrun.sh | 9 +++++---- docs/installation.rst | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index ba2752a1..feb423da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,9 +26,8 @@ ENV DEBUG="False" \ ACTIVATE_DEMO_PROJECT="True" \ ADMIN_PASSWORD="" \ ALLOW_PUBLIC_PROJECT_CREATION="True" \ - ACTIVATE_ADMIN_DASHBOARD="False" \ - GUNICORN_NUM_WORKERS="3" + ACTIVATE_ADMIN_DASHBOARD="False" VOLUME /database EXPOSE 8000 -CMD ["/ihatemoney/conf/confandrun.sh"] +ENTRYPOINT ["/ihatemoney/conf/confandrun.sh"] diff --git a/conf/confandrun.sh b/conf/confandrun.sh index e76a8e8a..e37a5739 100755 --- a/conf/confandrun.sh +++ b/conf/confandrun.sh @@ -17,7 +17,8 @@ ADMIN_PASSWORD = "$ADMIN_PASSWORD" ALLOW_PUBLIC_PROJECT_CREATION = $ALLOW_PUBLIC_PROJECT_CREATION ACTIVATE_ADMIN_DASHBOARD = $ACTIVATE_ADMIN_DASHBOARD EOF -gunicorn ihatemoney.wsgi:application \ --b 0.0.0.0:8000 \ ---log-syslog \ --w "$GUNICORN_NUM_WORKERS" +# Start gunicorn without forking +exec gunicorn ihatemoney.wsgi:application \ + -b 0.0.0.0:8000 \ + --log-syslog \ + "$@" diff --git a/docs/installation.rst b/docs/installation.rst index 0c3cfac1..e7d586ec 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -154,9 +154,10 @@ A volume can also be specified to persist the default database file:: docker run -d -p 8000:8000 -v /host/path/to/database:/database ihatemoney -The following gunicorn parameters are also available:: +Additional gunicorn parameters can be passed using the docker ``CMD`` parameter. +For example, use the following command to add more gunicorn workers:: - GUNICORN_NUM_WORKERS (default: 3) + docker run -d -p 8000:8000 ihatemoney -w 3 Configuration ============= From 225849ac716aee7627961b92f9d627e7bfde4430 Mon Sep 17 00:00:00 2001 From: 0livd Date: Wed, 7 Feb 2018 09:37:49 +0100 Subject: [PATCH 16/32] Docker: Fix gunicorn not using ihm system package (#320) Fixes #319 --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index feb423da..fbc0f4ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,11 @@ RUN mkdir /ihatemoney &&\ mkdir -p /etc/ihatemoney &&\ pip install --no-cache-dir gunicorn pymysql -WORKDIR /ihatemoney -COPY . . +COPY . /ihatemoney ARG INSTALL_FROM_PYPI="False" RUN if [ "$INSTALL_FROM_PYPI" = True ]; then\ pip install --no-cache-dir ihatemoney ; else\ - pip install --no-cache-dir -e . ; \ + pip install --no-cache-dir -e /ihatemoney ; \ fi ENV DEBUG="False" \ From 131fe8a8b4c6aeafc35f455b60f208d6e0268812 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Fri, 16 Feb 2018 23:13:51 +0100 Subject: [PATCH 17/32] Add missing CHANGELOG and CONTRIBUTORS entries for v2.1 --- CHANGELOG.rst | 6 +++++- CONTRIBUTORS | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fccb08d0..95c7a311 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,24 +3,28 @@ Changelog This document describes changes between each past release. -2.1 (unreleased) +2.1 (2018-02-16) ---------------- Changed ======= - Use flask-restful instead of deprecated flask-rest for the REST API (#315) +- Make sidebar scrollable. Usefull for large groups (#316) Fixed ===== - Fix the "IOError" crash when running `ihatemoney generate-config` (#308) - Made the left-hand sidebar scrollable (#318) +- Fix and enhanche Docker support (#320, #321) Added ===== - Statistics API (#343) +- Allow to disable/enable member via API (#301) +- Enable basic Apache auth passthrough for API (#303) 2.0 (2017-12-27) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9a817980..008ae90c 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -9,6 +9,7 @@ Alexis Metaireau Arnaud Bos Baptiste Jonglez Berteh +donkers Feth AREZKI Frédéric Sureau Jocelyn Delalande From d3d1ddfa9164a43695fc921d22c348e25256db66 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Fri, 16 Feb 2018 23:18:12 +0100 Subject: [PATCH 18/32] Preparing release 2.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6531286f..b623978b 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ ENTRY_POINTS = { setup(name='ihatemoney', - version='2.1.dev0', + version='2.1', description='A simple shared budget manager web application.', long_description="{}\n\n{}".format(README.encode('utf-8'), CHANGELOG.encode('utf-8')), license='Custom BSD Beerware', From c3b2af3df30f12964dbebad105e05af43de33af9 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Fri, 16 Feb 2018 23:21:36 +0100 Subject: [PATCH 19/32] Back to development: 2.1.1 --- CHANGELOG.rst | 6 ++++++ setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 95c7a311..81df6bbf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,12 @@ Changelog This document describes changes between each past release. +2.1.1 (unreleased) +------------------ + +- Nothing changed yet. + + 2.1 (2018-02-16) ---------------- diff --git a/setup.py b/setup.py index b623978b..ec40c852 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ ENTRY_POINTS = { setup(name='ihatemoney', - version='2.1', + version='2.1.1.dev0', description='A simple shared budget manager web application.', long_description="{}\n\n{}".format(README.encode('utf-8'), CHANGELOG.encode('utf-8')), license='Custom BSD Beerware', From 74f72a46d55fbf1507acbe5ad9ac3df9e92a822e Mon Sep 17 00:00:00 2001 From: Adrien CLERC Date: Tue, 15 May 2018 21:45:24 +0200 Subject: [PATCH 20/32] new extraction with pybabel --- ihatemoney/messages.pot | 388 +++++++++++++++++++++++++--------------- 1 file changed, 245 insertions(+), 143 deletions(-) diff --git a/ihatemoney/messages.pot b/ihatemoney/messages.pot index 0b1759b3..4a152593 100644 --- a/ihatemoney/messages.pot +++ b/ihatemoney/messages.pot @@ -1,111 +1,127 @@ # Translations template for PROJECT. -# Copyright (C) 2013 ORGANIZATION +# Copyright (C) 2018 ORGANIZATION # This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2013. +# FIRST AUTHOR , 2018. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2013-10-13 21:32+0200\n" +"POT-Creation-Date: 2018-05-15 21:43+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 0.9.6\n" +"Generated-By: Babel 2.5.3\n" -#: forms.py:22 -msgid "Select all" -msgstr "" - -#: forms.py:22 -msgid "Select none" -msgstr "" - -#: forms.py:61 +#: forms.py:46 msgid "Project name" msgstr "" -#: forms.py:62 forms.py:86 forms.py:102 +#: forms.py:47 forms.py:71 forms.py:88 msgid "Private code" msgstr "" -#: forms.py:63 +#: forms.py:48 msgid "Email" msgstr "" -#: forms.py:85 forms.py:101 forms.py:107 +#: forms.py:70 forms.py:87 forms.py:98 msgid "Project identifier" msgstr "" -#: forms.py:87 templates/send_invites.html:5 +#: forms.py:72 msgid "Create the project" msgstr "" -#: forms.py:92 +#: forms.py:77 msgid "" "The project identifier is used to log in and for the URL of the project. " "We tried to generate an identifier for you but a project with this " "identifier already exists. Please create a new identifier that you will " -"be able to remember." +"be able to remember" msgstr "" -#: forms.py:103 +#: forms.py:89 forms.py:94 msgid "Get in" msgstr "" -#: forms.py:108 +#: forms.py:93 +msgid "Admin password" +msgstr "" + +#: forms.py:99 msgid "Send me the code by email" msgstr "" -#: forms.py:112 +#: forms.py:103 msgid "This project does not exists" msgstr "" -#: forms.py:116 +#: forms.py:108 +msgid "Password mismatch" +msgstr "" + +#: forms.py:109 +msgid "Password" +msgstr "" + +#: forms.py:110 +msgid "Password confirmation" +msgstr "" + +#: forms.py:111 +msgid "Reset password" +msgstr "" + +#: forms.py:115 msgid "Date" msgstr "" -#: forms.py:117 +#: forms.py:116 msgid "What?" msgstr "" -#: forms.py:118 +#: forms.py:117 msgid "Payer" msgstr "" -#: forms.py:119 +#: forms.py:118 msgid "Amount paid" msgstr "" -#: forms.py:120 templates/list_bills.html:103 +#: forms.py:119 templates/forms.html:100 templates/list_bills.html:101 msgid "For whom?" msgstr "" -#: forms.py:122 +#: forms.py:121 msgid "Submit" msgstr "" -#: forms.py:123 +#: forms.py:122 msgid "Submit and add a new one" msgstr "" -#: forms.py:149 +#: forms.py:146 msgid "Bills can't be null" msgstr "" -#: forms.py:154 +#: forms.py:151 msgid "Name" msgstr "" -#: forms.py:155 templates/forms.html:95 +#: forms.py:152 +msgid "Weight" +msgstr "" + +#: forms.py:153 templates/forms.html:123 msgid "Add" msgstr "" -#: forms.py:163 +#: forms.py:162 msgid "User name incorrect" msgstr "" @@ -113,105 +129,151 @@ msgstr "" msgid "This project already have this member" msgstr "" -#: forms.py:178 +#: forms.py:183 msgid "People to notify" msgstr "" -#: forms.py:179 +#: forms.py:184 msgid "Send invites" msgstr "" -#: forms.py:185 +#: forms.py:190 #, python-format msgid "The email %(email)s is not valid" msgstr "" -#: forms.py:191 -msgid "Start date" +#: forms.py:196 +msgid "What do you want to download ?" msgstr "" -#: forms.py:192 -msgid "End date" +#: forms.py:199 +msgid "bills" msgstr "" -#: web.py:95 +#: forms.py:199 +msgid "transactions" +msgstr "" + +#: forms.py:201 +msgid "Export file format" +msgstr "" + +#: web.py:129 +msgid "Too many failed login attempts, please retry later." +msgstr "" + +#: web.py:144 +#, python-format +msgid "This admin password is not the right one. Only %(num)d attempts left." +msgstr "" + +#: web.py:167 +msgid "You either provided a bad token or no project identifier." +msgstr "" + +#: web.py:195 msgid "This private code is not the right one" msgstr "" -#: web.py:147 +#: web.py:242 #, python-format msgid "You have just created '%(project)s' to share your expenses" msgstr "" -#: web.py:165 +#: web.py:260 #, python-format msgid "%(msg_compl)sThe project identifier is %(project)s" msgstr "" -#: web.py:185 -msgid "a mail has been sent to you with the password" +#: web.py:281 +msgid "A link to reset your password has been sent to your email." msgstr "" -#: web.py:211 +#: web.py:291 +msgid "No token provided" +msgstr "" + +#: web.py:294 +msgid "Invalid token" +msgstr "" + +#: web.py:297 +msgid "Unknown project" +msgstr "" + +#: web.py:303 +msgid "Password successfully reset." +msgstr "" + +#: web.py:351 msgid "Project successfully deleted" msgstr "" -#: web.py:254 +#: web.py:401 #, python-format msgid "You have been invited to share your expenses for %(project)s" msgstr "" -#: web.py:261 +#: web.py:408 msgid "Your invitations have been sent" msgstr "" -#: web.py:290 +#: web.py:439 #, python-format msgid "%(member)s had been added" msgstr "" -#: web.py:303 +#: web.py:452 #, python-format msgid "%(name)s is part of this project again" msgstr "" -#: web.py:312 +#: web.py:461 #, python-format -msgid "User '%(name)s' has been deactivated" +msgid "" +"User '%(name)s' has been deactivated. It will still appear in the users " +"list until its balance becomes zero." msgstr "" -#: web.py:314 +#: web.py:465 #, python-format msgid "User '%(name)s' has been removed" msgstr "" -#: web.py:331 +#: web.py:480 +#, python-format +msgid "User '%(name)s' has been edited" +msgstr "" + +#: web.py:500 msgid "The bill has been added" msgstr "" -#: web.py:351 +#: web.py:520 msgid "The bill has been deleted" msgstr "" -#: web.py:369 +#: web.py:538 msgid "The bill has been modified" msgstr "" -#: templates/add_bill.html:9 +#: templates/add_bill.html:9 templates/edit_member.html:9 msgid "Back to the list" msgstr "" -#: templates/authenticate.html:6 -msgid "" -"The project you are trying to access do not exist, do you want \n" -"to" +#: templates/admin.html:10 +msgid "Administration tasks are currently disabled." msgstr "" -#: templates/authenticate.html:7 +#: templates/authenticate.html:6 +msgid "The project you are trying to access do not exist, do you want to" +msgstr "" + +#: templates/authenticate.html:8 msgid "create it" msgstr "" -#: templates/authenticate.html:7 +#: templates/authenticate.html:8 msgid "?" msgstr "" @@ -239,6 +301,24 @@ msgstr "" msgid "Oldest bill" msgstr "" +#: templates/dashboard.html:5 templates/list_bills.html:101 +msgid "Actions" +msgstr "" + +#: templates/dashboard.html:17 templates/list_bills.html:65 +#: templates/list_bills.html:111 +msgid "edit" +msgstr "" + +#: templates/dashboard.html:18 templates/forms.html:83 +#: templates/list_bills.html:112 +msgid "delete" +msgstr "" + +#: templates/dashboard.html:25 +msgid "The Dashboard is currently deactivated." +msgstr "" + #: templates/edit_project.html:6 templates/list_bills.html:24 msgid "you sure?" msgstr "" @@ -247,44 +327,59 @@ msgstr "" msgid "Edit this project" msgstr "" -#: templates/forms.html:23 +#: templates/edit_project.html:15 +msgid "Download this project's data" +msgstr "" + +#: templates/forms.html:27 msgid "Can't remember the password?" msgstr "" -#: templates/forms.html:26 +#: templates/forms.html:30 msgid "Cancel" msgstr "" -#: templates/forms.html:68 +#: templates/forms.html:82 msgid "Edit the project" msgstr "" -#: templates/forms.html:69 templates/list_bills.html:70 -#: templates/list_bills.html:114 -msgid "delete" -msgstr "" - -#: templates/forms.html:77 +#: templates/forms.html:91 msgid "Edit this bill" msgstr "" -#: templates/forms.html:77 templates/list_bills.html:94 +#: templates/forms.html:91 templates/list_bills.html:89 msgid "Add a bill" msgstr "" -#: templates/forms.html:95 -msgid "Type user name here" -msgstr "" - -#: templates/forms.html:102 -msgid "Send the invitations" +#: templates/forms.html:103 +msgid "Select all" msgstr "" #: templates/forms.html:103 +msgid "Select none" +msgstr "" + +#: templates/forms.html:122 +msgid "Type user name here" +msgstr "" + +#: templates/forms.html:129 +msgid "Edit this member" +msgstr "" + +#: templates/forms.html:145 +msgid "Send the invitations" +msgstr "" + +#: templates/forms.html:146 msgid "No, thanks" msgstr "" -#: templates/home.html:8 +#: templates/forms.html:157 +msgid "Download" +msgstr "" + +#: templates/home.html:7 msgid "Manage your shared
expenses, easily" msgstr "" @@ -292,39 +387,39 @@ msgstr "" msgid "Try out the demo" msgstr "" -#: templates/home.html:12 +#: templates/home.html:13 msgid "You're sharing a house?" msgstr "" -#: templates/home.html:12 +#: templates/home.html:13 msgid "Going on holidays with friends?" msgstr "" -#: templates/home.html:12 +#: templates/home.html:13 msgid "Simply sharing money with others?" msgstr "" -#: templates/home.html:12 +#: templates/home.html:13 msgid "We can help!" msgstr "" -#: templates/home.html:24 +#: templates/home.html:21 msgid "Log to an existing project" msgstr "" -#: templates/home.html:28 +#: templates/home.html:25 msgid "log in" msgstr "" -#: templates/home.html:29 +#: templates/home.html:26 msgid "can't remember your password?" msgstr "" -#: templates/home.html:36 +#: templates/home.html:34 templates/home.html:42 msgid "or create a new one" msgstr "" -#: templates/home.html:40 +#: templates/home.html:38 msgid "let's get started" msgstr "" @@ -338,91 +433,91 @@ msgstr "" msgid "Account manager" msgstr "" -#: templates/layout.html:45 templates/settle_bills.html:4 +#: templates/layout.html:39 msgid "Bills" msgstr "" -#: templates/layout.html:46 templates/settle_bills.html:5 +#: templates/layout.html:40 msgid "Settle" msgstr "" -#: templates/layout.html:53 +#: templates/layout.html:41 +msgid "Statistics" +msgstr "" + +#: templates/layout.html:48 msgid "options" msgstr "" -#: templates/layout.html:55 +#: templates/layout.html:50 msgid "Project settings" msgstr "" -#: templates/layout.html:59 +#: templates/layout.html:54 msgid "switch to" msgstr "" -#: templates/layout.html:62 +#: templates/layout.html:57 msgid "Start a new project" msgstr "" -#: templates/layout.html:64 +#: templates/layout.html:59 msgid "Logout" msgstr "" -#: templates/layout.html:92 +#: templates/layout.html:66 +msgid "Dashboard" +msgstr "" + +#: templates/layout.html:89 msgid "This is a free software" msgstr "" -#: templates/layout.html:92 +#: templates/layout.html:89 msgid "you can contribute and improve it!" msgstr "" -#: templates/list_bills.html:74 +#: templates/list_bills.html:63 +msgid "deactivate" +msgstr "" + +#: templates/list_bills.html:70 msgid "reactivate" msgstr "" -#: templates/list_bills.html:88 -msgid "The project identifier is" +#: templates/list_bills.html:82 +msgid "Invite people to join this project!" msgstr "" -#: templates/list_bills.html:88 -msgid "remember it!" -msgstr "" - -#: templates/list_bills.html:89 +#: templates/list_bills.html:83 msgid "Add a new bill" msgstr "" -#: templates/list_bills.html:103 +#: templates/list_bills.html:101 msgid "When?" msgstr "" -#: templates/list_bills.html:103 +#: templates/list_bills.html:101 msgid "Who paid?" msgstr "" -#: templates/list_bills.html:103 +#: templates/list_bills.html:101 msgid "For what?" msgstr "" -#: templates/list_bills.html:103 templates/settle_bills.html:31 +#: templates/list_bills.html:101 templates/settle_bills.html:22 msgid "How much?" msgstr "" -#: templates/list_bills.html:103 -msgid "Actions" -msgstr "" - -#: templates/list_bills.html:111 +#: templates/list_bills.html:109 msgid "each" msgstr "" -#: templates/list_bills.html:113 -msgid "edit" -msgstr "" - -#: templates/list_bills.html:122 +#: templates/list_bills.html:120 msgid "Nothing to list yet. You probably want to" msgstr "" -#: templates/list_bills.html:122 +#: templates/list_bills.html:120 msgid "add a bill" msgstr "" @@ -434,43 +529,50 @@ msgstr "" msgid "Your projects" msgstr "" -#: templates/send_invites.html:6 -msgid "Invite people" +#: templates/reset_password.html:7 +msgid "Reset your password" msgstr "" -#: templates/send_invites.html:7 -msgid "Use it!" -msgstr "" - -#: templates/send_invites.html:11 +#: templates/send_invites.html:4 msgid "Invite people to join this project" msgstr "" -#: templates/send_invites.html:12 +#: templates/send_invites.html:5 msgid "" -"Specify a (coma separated) list of email adresses you want to notify " -"about the \n" +"Specify a (comma separated) list of email adresses you want to notify " +"about the\n" "creation of this budget management project and we will send them an email" " for you." msgstr "" -#: templates/send_invites.html:14 -msgid "If you prefer, you can" +#: templates/send_invites.html:7 +msgid "" +"If you prefer, you can share the project identifier and the shared\n" +"password by other communication means. Or even directly share the " +"following link:" msgstr "" -#: templates/send_invites.html:14 -msgid "skip this step" -msgstr "" - -#: templates/send_invites.html:14 -msgid "and notify them yourself" -msgstr "" - -#: templates/settle_bills.html:31 +#: templates/settle_bills.html:22 msgid "Who pays?" msgstr "" -#: templates/settle_bills.html:31 +#: templates/settle_bills.html:22 msgid "To whom?" msgstr "" +#: templates/statistics.html:21 +msgid "Who?" +msgstr "" + +#: templates/statistics.html:21 +msgid "Paid" +msgstr "" + +#: templates/statistics.html:21 +msgid "Spent" +msgstr "" + +#: templates/statistics.html:21 +msgid "Balance" +msgstr "" + From fb31868278af06322273ad94c6e58eaa32bb5823 Mon Sep 17 00:00:00 2001 From: Adrien CLERC Date: Tue, 15 May 2018 21:47:01 +0200 Subject: [PATCH 21/32] update locales from template with pybabel --- .../translations/fr/LC_MESSAGES/messages.po | 491 ++++++++++-------- 1 file changed, 264 insertions(+), 227 deletions(-) diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.po b/ihatemoney/translations/fr/LC_MESSAGES/messages.po index b3440987..334fa8e5 100644 --- a/ihatemoney/translations/fr/LC_MESSAGES/messages.po +++ b/ihatemoney/translations/fr/LC_MESSAGES/messages.po @@ -7,129 +7,122 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2013-10-13 21:32+0200\n" +"POT-Creation-Date: 2018-05-15 21:43+0200\n" "PO-Revision-Date: 2011-10-14 23:51+0200\n" "Last-Translator: Quentin Roy \n" +"Language: fr\n" "Language-Team: fr \n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 0.9.6\n" +"Generated-By: Babel 2.5.3\n" -#: forms.py:22 -msgid "Select all" -msgstr "Tout cocher" - -#: forms.py:22 -msgid "Select none" -msgstr "Tout décocher" - -#: forms.py:61 +#: forms.py:46 msgid "Project name" msgstr "Nom de projet" -#: forms.py:62 forms.py:86 forms.py:102 +#: forms.py:47 forms.py:71 forms.py:88 msgid "Private code" msgstr "Code d'accès" -#: forms.py:63 +#: forms.py:48 msgid "Email" msgstr "Email" -#: forms.py:85 forms.py:101 forms.py:107 +#: forms.py:70 forms.py:87 forms.py:98 msgid "Project identifier" msgstr "Identifiant du projet" -#: forms.py:87 -msgid "Admin password" -msgstr "Mot de passe administrateur" - -#: forms.py:87 templates/send_invites.html:5 +#: forms.py:72 msgid "Create the project" msgstr "Créer le projet" -#: forms.py:92 +#: forms.py:77 msgid "" "The project identifier is used to log in and for the URL of the project. " "We tried to generate an identifier for you but a project with this " "identifier already exists. Please create a new identifier that you will " -"be able to remember." +"be able to remember" msgstr "" -"L'identifiant du projet est utilisé pour se connecter." -"Nous avons essayé de générer un identifiant mais " -"celui ci existe déjà. Merci de créer un nouvel identifiant que vous serez" -" capable de retenir" -#: forms.py:103 +#: forms.py:89 forms.py:94 msgid "Get in" msgstr "Entrer" -#: forms.py:107 +#: forms.py:93 +msgid "Admin password" +msgstr "Mot de passe administrateur" + +#: forms.py:99 +msgid "Send me the code by email" +msgstr "Envoyez moi le code par email" + +#: forms.py:103 +msgid "This project does not exists" +msgstr "Ce projet n'existe pas" + +#: forms.py:108 msgid "Password mismatch" msgstr "Les mots de passe fournis ne sont pas les mêmes." #: forms.py:109 -msgid "Password confirmation" -msgstr "Confirmation du mot de passe" - -#: forms.py:107 msgid "Password" msgstr "Mot de passe" -#: forms.py:108 -msgid "Send me the code by email" -msgstr "Envoyez moi le code par email" +#: forms.py:110 +msgid "Password confirmation" +msgstr "Confirmation du mot de passe" -#: forms.py:112 -msgid "This project does not exists" -msgstr "Ce projet n'existe pas" +#: forms.py:111 +msgid "Reset password" +msgstr "" -#: forms.py:116 +#: forms.py:115 msgid "Date" msgstr "Date" -#: forms.py:117 +#: forms.py:116 msgid "What?" msgstr "Quoi ?" -#: forms.py:118 +#: forms.py:117 msgid "Payer" msgstr "Payeur" -#: forms.py:119 +#: forms.py:118 msgid "Amount paid" msgstr "Montant" -#: forms.py:120 templates/list_bills.html:103 +#: forms.py:119 templates/forms.html:100 templates/list_bills.html:101 msgid "For whom?" msgstr "Pour qui ?" -#: forms.py:122 +#: forms.py:121 msgid "Submit" msgstr "Valider" -#: forms.py:123 +#: forms.py:122 msgid "Submit and add a new one" msgstr "Valider et ajouter une autre facture" -#: forms.py:149 +#: forms.py:146 msgid "Bills can't be null" msgstr "Le montant d'une facture ne peut pas être nul." -#: forms.py:154 +#: forms.py:151 msgid "Name" msgstr "Nom" -#: forms.py:155 +#: forms.py:152 msgid "Weight" msgstr "Parts" -#: forms.py:155 templates/forms.html:95 +#: forms.py:153 templates/forms.html:123 msgid "Add" msgstr "Ajouter" -#: forms.py:163 +#: forms.py:162 msgid "User name incorrect" msgstr "Nom d'utilisateur incorrect" @@ -137,159 +130,158 @@ msgstr "Nom d'utilisateur incorrect" msgid "This project already have this member" msgstr "Ce membre existe déjà pour ce projet" -#: forms.py:178 +#: forms.py:183 msgid "People to notify" msgstr "Personnes à prévenir" -#: forms.py:179 +#: forms.py:184 msgid "Send invites" msgstr "Envoyer les invitations" -#: forms.py:185 +#: forms.py:190 #, python-format msgid "The email %(email)s is not valid" msgstr "L'email %(email)s est invalide" -#: forms.py:191 -msgid "Start date" -msgstr "Date de départ" - -#: forms.py:192 -msgid "End date" -msgstr "Date de fin" - -#: forms.py:202 +#: forms.py:196 msgid "What do you want to download ?" msgstr "Que voulez-vous télécharger ?" -#: forms.py:205 +#: forms.py:199 msgid "bills" msgstr "factures" -#: forms.py:205 +#: forms.py:199 msgid "transactions" msgstr "remboursements" -#: forms.py:206 +#: forms.py:201 msgid "Export file format" msgstr "Format du fichier d'export" -#: web.py:95 -msgid "You either provided a bad token or no project identifier." -msgstr "L'identifiant du projet ou le token fourni n'est pas correct." - -#: web.py:95 -msgid "This private code is not the right one" -msgstr "Le code que vous avez entré n'est pas correct" - -#: web.py:106 -msgid "This admin password is not the right one. Only %(num)d attempts left." -msgstr "Le mot de passe administrateur que vous avez entré n'est pas correct. Plus que %(num)d tentatives." - -#: web.py:106 +#: web.py:129 msgid "Too many failed login attempts, please retry later." msgstr "Trop d'échecs d'authentification successifs, veuillez réessayer plus tard." -#: web.py:147 +#: web.py:144 +#, python-format +msgid "This admin password is not the right one. Only %(num)d attempts left." +msgstr "" +"Le mot de passe administrateur que vous avez entré n'est pas correct. " +"Plus que %(num)d tentatives." + +#: web.py:167 +msgid "You either provided a bad token or no project identifier." +msgstr "L'identifiant du projet ou le token fourni n'est pas correct." + +#: web.py:195 +msgid "This private code is not the right one" +msgstr "Le code que vous avez entré n'est pas correct" + +#: web.py:242 #, python-format msgid "You have just created '%(project)s' to share your expenses" msgstr "Vous venez de créer '%(project)s' pour partager vos dépenses" -#: web.py:165 +#: web.py:260 #, python-format msgid "%(msg_compl)sThe project identifier is %(project)s" msgstr "L'identifiant de ce projet est '%(project)s'" -#: web.py:185 +#: web.py:281 msgid "A link to reset your password has been sent to your email." msgstr "Un lien pour changer votre mot de passe vous a été envoyé par mail." -#: web.py:211 +#: web.py:291 +msgid "No token provided" +msgstr "" + +#: web.py:294 +msgid "Invalid token" +msgstr "Token invalide" + +#: web.py:297 +msgid "Unknown project" +msgstr "Project inconnu" + +#: web.py:303 +msgid "Password successfully reset." +msgstr "Le mot de passe a été changé avec succès." + +#: web.py:351 msgid "Project successfully deleted" msgstr "Projet supprimé" -#: web.py:254 +#: web.py:401 #, python-format msgid "You have been invited to share your expenses for %(project)s" msgstr "Vous avez été invité à partager vos dépenses pour %(project)s" -#: web.py:259 -#, python-format -msgid ""No token provided"" -msgstr "Aucun token n'a été fourni." - -#: web.py:259 -#, python-format -msgid "Unknown project" -msgstr "Project inconnu" - -#: web.py:261 -#, python-format -msgid "Invalid token" -msgstr "Token invalide" - -#: web.py:267 -#, python-format -msgid "Password successfully reset." -msgstr "Le mot de passe a été changé avec succès." - -#: web.py:261 +#: web.py:408 msgid "Your invitations have been sent" msgstr "Vos invitations ont bien été envoyées" -#: web.py:290 +#: web.py:439 #, python-format msgid "%(member)s had been added" msgstr "%(member)s a bien été ajouté" -#: web.py:303 +#: web.py:452 #, python-format msgid "%(name)s is part of this project again" msgstr "%(name)s a rejoint le projet" -#: web.py:312 +#: web.py:461 #, python-format -msgid "User '%(name)s' has been deactivated" -msgstr "Le membre '%(name)s' a été désactivé" +msgid "" +"User '%(name)s' has been deactivated. It will still appear in the users " +"list until its balance becomes zero." +msgstr "" +"Le membre '%(name)s' a été désactivé. Il continuera d'apparaître jusqu'à " +"ce que sa balance devienne égale à zéro." -#: web.py:314 +#: web.py:465 #, python-format -msgid "User '%(name)s' has been deactivated. It will still appear in the users list until its balance becomes zero." -msgstr "Le membre '%(name)s' a été désactivé. Il continuera d'apparaître jusqu'à ce que sa balance devienne égale à zéro." +msgid "User '%(name)s' has been removed" +msgstr "" -#: web.py:331 +#: web.py:480 +#, python-format +msgid "User '%(name)s' has been edited" +msgstr "" + +#: web.py:500 msgid "The bill has been added" msgstr "La facture a bien été ajoutée" -#: web.py:351 +#: web.py:520 msgid "The bill has been deleted" msgstr "La facture a été supprimée" -#: web.py:369 +#: web.py:538 msgid "The bill has been modified" msgstr "La facture a été modifiée" -#: templates/add_bill.html:9 +#: templates/add_bill.html:9 templates/edit_member.html:9 msgid "Back to the list" msgstr "Retourner à la liste" +#: templates/admin.html:10 +msgid "Administration tasks are currently disabled." +msgstr "Les tâches d'administration sont actuellement désactivées." + #: templates/authenticate.html:6 -msgid "" -"The project you are trying to access do not exist, do you want to" +msgid "The project you are trying to access do not exist, do you want to" msgstr "Le projet auquel vous essayez d'acceder n'existe pas. Souhaitez vous" -#: templates/authenticate.html:7 +#: templates/authenticate.html:8 msgid "create it" msgstr "le créer" -#: templates/authenticate.html:7 +#: templates/authenticate.html:8 msgid "?" msgstr " ?" -#: templates/authenticate.html:7 -msgid "Administration tasks are currently disabled." -msgstr "Les tâches d'administration sont actuellement désactivées." - #: templates/create_project.html:4 msgid "Create a new project" msgstr "Créer un nouveau projet" @@ -314,6 +306,20 @@ msgstr "Facture la plus récente" msgid "Oldest bill" msgstr "Facture la plus ancienne" +#: templates/dashboard.html:5 templates/list_bills.html:101 +msgid "Actions" +msgstr "Actions" + +#: templates/dashboard.html:17 templates/list_bills.html:65 +#: templates/list_bills.html:111 +msgid "edit" +msgstr "éditer" + +#: templates/dashboard.html:18 templates/forms.html:83 +#: templates/list_bills.html:112 +msgid "delete" +msgstr "supprimer" + #: templates/dashboard.html:25 msgid "The Dashboard is currently deactivated." msgstr "La page d'administration est actuellement désactivée." @@ -326,60 +332,59 @@ msgstr "c'est sûr ?" msgid "Edit this project" msgstr "Éditer ce projet" -#: templates/forms.html:23 -msgid "Can't remember the password?" -msgstr "Vous ne vous souvenez plus du code d'accès ?" - -#: templates/forms.html:26 -msgid "Cancel" -msgstr "Annuler" - -#: templates/forms.html:68 -msgid "Edit the project" -msgstr "Éditer le projet" - -#: templates/list_bills.html:70 -msgid "deactivate" -msgstr "désactiver" - -#: templates/forms.html:69 templates/list_bills.html:70 -#: templates/list_bills.html:114 -msgid "delete" -msgstr "supprimer" - -#: templates/forms.html:77 -msgid "Edit this bill" -msgstr "Éditer cette facture" - -#: templates/forms.html:77 templates/list_bills.html:94 -msgid "Add a bill" -msgstr "Ajouter une facture" - -#: templates/forms.html:95 -msgid "Type user name here" -msgstr "Nouveau participant" - -#: templates/forms.html:100 -msgid "Edit this member" -msgstr "Éditer ce participant" - -#: templates/forms.html:102 -msgid "Send the invitations" -msgstr "Envoyer les invitations" - -#: templates/forms.html:103 -msgid "No, thanks" -msgstr "Non merci" - -#: templates/forms.html:136 +#: templates/edit_project.html:15 msgid "Download this project's data" msgstr "Télécharger les données de ce projet" -#: templates/forms.html:136 +#: templates/forms.html:27 +msgid "Can't remember the password?" +msgstr "Vous ne vous souvenez plus du code d'accès ?" + +#: templates/forms.html:30 +msgid "Cancel" +msgstr "Annuler" + +#: templates/forms.html:82 +msgid "Edit the project" +msgstr "Éditer le projet" + +#: templates/forms.html:91 +msgid "Edit this bill" +msgstr "Éditer cette facture" + +#: templates/forms.html:91 templates/list_bills.html:89 +msgid "Add a bill" +msgstr "Ajouter une facture" + +#: templates/forms.html:103 +msgid "Select all" +msgstr "Tout cocher" + +#: templates/forms.html:103 +msgid "Select none" +msgstr "Tout décocher" + +#: templates/forms.html:122 +msgid "Type user name here" +msgstr "Nouveau participant" + +#: templates/forms.html:129 +msgid "Edit this member" +msgstr "Éditer ce participant" + +#: templates/forms.html:145 +msgid "Send the invitations" +msgstr "Envoyer les invitations" + +#: templates/forms.html:146 +msgid "No, thanks" +msgstr "Non merci" + +#: templates/forms.html:157 msgid "Download" msgstr "Télécharger" -#: templates/home.html:8 +#: templates/home.html:7 msgid "Manage your shared
expenses, easily" msgstr "Gérez vos dépenses
partagées, facilement" @@ -387,39 +392,39 @@ msgstr "Gérez vos dépenses
partagées, facilement" msgid "Try out the demo" msgstr "Essayez la démo" -#: templates/home.html:12 +#: templates/home.html:13 msgid "You're sharing a house?" msgstr "Vous êtes en colocation ?" -#: templates/home.html:12 +#: templates/home.html:13 msgid "Going on holidays with friends?" msgstr "Partez en vacances avec des amis ?" -#: templates/home.html:12 +#: templates/home.html:13 msgid "Simply sharing money with others?" msgstr "Ça vous arrive de partager de l'argent avec d'autres ?" -#: templates/home.html:12 +#: templates/home.html:13 msgid "We can help!" msgstr "On peut vous aider !" -#: templates/home.html:24 +#: templates/home.html:21 msgid "Log to an existing project" msgstr "Se connecter à un projet existant" -#: templates/home.html:28 +#: templates/home.html:25 msgid "log in" msgstr "se connecter" -#: templates/home.html:29 +#: templates/home.html:26 msgid "can't remember your password?" msgstr "vous ne vous souvenez plus du code d'accès ?" -#: templates/home.html:36 +#: templates/home.html:34 templates/home.html:42 msgid "or create a new one" msgstr "ou créez en un nouveau" -#: templates/home.html:40 +#: templates/home.html:38 msgid "let's get started" msgstr "c'est parti !" @@ -435,95 +440,91 @@ msgstr "" msgid "Account manager" msgstr "Gestion de comptes" -#: templates/layout.html:45 templates/settle_bills.html:4 +#: templates/layout.html:39 msgid "Bills" msgstr "Factures" -#: templates/layout.html:46 templates/settle_bills.html:5 +#: templates/layout.html:40 msgid "Settle" msgstr "Remboursements" -#: templates/layout.html:50 +#: templates/layout.html:41 msgid "Statistics" msgstr "Statistiques" -#: templates/layout.html:53 +#: templates/layout.html:48 msgid "options" msgstr "options" -#: templates/layout.html:55 +#: templates/layout.html:50 msgid "Project settings" msgstr "Options du projet" -#: templates/layout.html:59 +#: templates/layout.html:54 msgid "switch to" msgstr "aller à" -#: templates/layout.html:62 +#: templates/layout.html:57 msgid "Start a new project" msgstr "Nouveau projet" -#: templates/layout.html:64 +#: templates/layout.html:59 msgid "Logout" msgstr "Se déconnecter" -#: templates/layout.html:92 +#: templates/layout.html:66 +msgid "Dashboard" +msgstr "" + +#: templates/layout.html:89 msgid "This is a free software" msgstr "Ceci est un logiciel libre" -#: templates/layout.html:92 +#: templates/layout.html:89 msgid "you can contribute and improve it!" msgstr "vous pouvez y contribuer et l'améliorer" -#: templates/list_bills.html:74 +#: templates/list_bills.html:63 +msgid "deactivate" +msgstr "désactiver" + +#: templates/list_bills.html:70 msgid "reactivate" msgstr "ré-activer" -#: templates/list_bills.html:88 -msgid "Invite" -msgstr "Invitez" - -#: templates/list_bills.html:88 +#: templates/list_bills.html:82 msgid "Invite people to join this project!" msgstr "Invitez d'autres personnes à rejoindre ce projet !" -#: templates/list_bills.html:89 +#: templates/list_bills.html:83 msgid "Add a new bill" msgstr "Nouvelle facture" -#: templates/list_bills.html:103 +#: templates/list_bills.html:101 msgid "When?" msgstr "Quand ?" -#: templates/list_bills.html:103 +#: templates/list_bills.html:101 msgid "Who paid?" msgstr "Qui a payé ?" -#: templates/list_bills.html:103 +#: templates/list_bills.html:101 msgid "For what?" msgstr "Pour quoi ?" -#: templates/list_bills.html:103 templates/settle_bills.html:31 +#: templates/list_bills.html:101 templates/settle_bills.html:22 msgid "How much?" msgstr "Combien ?" -#: templates/list_bills.html:103 -msgid "Actions" -msgstr "Actions" - -#: templates/list_bills.html:111 +#: templates/list_bills.html:109 msgid "each" msgstr "chacun" -#: templates/list_bills.html:113 -msgid "edit" -msgstr "éditer" - -#: templates/list_bills.html:122 +#: templates/list_bills.html:120 msgid "Nothing to list yet. You probably want to" msgstr "Rien à lister pour l'instant. Vous voulez surement" -#: templates/list_bills.html:122 +#: templates/list_bills.html:120 msgid "add a bill" msgstr "ajouter une facture" @@ -535,15 +536,15 @@ msgstr "Rappel du code d'accès" msgid "Your projects" msgstr "Vos projets" -#: templates/reset_password.html:2 +#: templates/reset_password.html:7 msgid "Reset your password" msgstr "Changez votre mot de passe" -#: templates/send_invites.html:11 +#: templates/send_invites.html:4 msgid "Invite people to join this project" msgstr "Invitez des personnes à rejoindre ce projet" -#: templates/send_invites.html:12 +#: templates/send_invites.html:5 msgid "" "Specify a (comma separated) list of email adresses you want to notify " "about the\n" @@ -553,33 +554,69 @@ msgstr "" "Entrez les addresses des personnes que vous souhaitez inviter, séparées " "par des virgules. On s'occupe de leur envoyer un email." -#: templates/send_invites.html:14 -msgid "If you prefer, you can share the project identifier and the shared\n" -"password by other communication means. Or even directly share the following link:" -msgstr "Si vous préférez vous pouvez partager l'identifiant du projet et son mot " -"de passe par un autre moyen de communication. Ou directement partager le lien " -"suivant :" +#: templates/send_invites.html:7 +msgid "" +"If you prefer, you can share the project identifier and the shared\n" +"password by other communication means. Or even directly share the " +"following link:" +msgstr "" +"Si vous préférez vous pouvez partager l'identifiant du projet et son mot " +"de passe par un autre moyen de communication. Ou directement partager le " +"lien suivant :" -#: templates/settle_bills.html:31 +#: templates/settle_bills.html:22 msgid "Who pays?" msgstr "Qui doit payer ?" -#: templates/settle_bills.html:31 +#: templates/settle_bills.html:22 msgid "To whom?" msgstr "Pour qui ?" -#: templates/statistics.html:22 +#: templates/statistics.html:21 msgid "Who?" msgstr "Qui ?" -#: templates/statistics.html:22 +#: templates/statistics.html:21 msgid "Paid" msgstr "A payé" -#: templates/statistics.html:22 +#: templates/statistics.html:21 msgid "Spent" msgstr "A dépensé" -#: templates/statistics.html:22 +#: templates/statistics.html:21 msgid "Balance" msgstr "Solde" + +#~ msgid "" +#~ "The project identifier is used to " +#~ "log in and for the URL of " +#~ "the project. We tried to generate " +#~ "an identifier for you but a " +#~ "project with this identifier already " +#~ "exists. Please create a new identifier" +#~ " that you will be able to " +#~ "remember." +#~ msgstr "" +#~ "L'identifiant du projet est utilisé pour" +#~ " se connecter.Nous avons essayé de " +#~ "générer un identifiant mais celui ci " +#~ "existe déjà. Merci de créer un " +#~ "nouvel identifiant que vous serez " +#~ "capable de retenir" + +#~ msgid "Start date" +#~ msgstr "Date de départ" + +#~ msgid "End date" +#~ msgstr "Date de fin" + +#~ msgid "\"No token provided\"" +#~ msgstr "Aucun token n'a été fourni." + +#~ msgid "User '%(name)s' has been deactivated" +#~ msgstr "Le membre '%(name)s' a été désactivé" + +#~ msgid "Invite" +#~ msgstr "Invitez" + From 1947a5ae785ab17d5da09548819f1e4ba6b0d85a Mon Sep 17 00:00:00 2001 From: Adrien CLERC Date: Tue, 15 May 2018 21:50:31 +0200 Subject: [PATCH 22/32] update fr l10n --- .../translations/fr/LC_MESSAGES/messages.po | 103 +++++++++--------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.po b/ihatemoney/translations/fr/LC_MESSAGES/messages.po index 334fa8e5..5179f605 100644 --- a/ihatemoney/translations/fr/LC_MESSAGES/messages.po +++ b/ihatemoney/translations/fr/LC_MESSAGES/messages.po @@ -2,20 +2,21 @@ # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # Alexis Métaireau , 2011. -# +# Adrien CLERC, 2018. msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2018-05-15 21:43+0200\n" -"PO-Revision-Date: 2011-10-14 23:51+0200\n" -"Last-Translator: Quentin Roy \n" -"Language: fr\n" +"PO-Revision-Date: 2018-05-15 22:00+0200\n" +"Last-Translator: Adrien CLERC <>\n" "Language-Team: fr \n" -"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Virtaal 0.7.1\n" "Generated-By: Babel 2.5.3\n" #: forms.py:46 @@ -24,7 +25,7 @@ msgstr "Nom de projet" #: forms.py:47 forms.py:71 forms.py:88 msgid "Private code" -msgstr "Code d'accès" +msgstr "Code d’accès" #: forms.py:48 msgid "Email" @@ -45,6 +46,9 @@ msgid "" "identifier already exists. Please create a new identifier that you will " "be able to remember" msgstr "" +"L’identifiant du projet est utilisé pour se connecter et pour l’URL du " +"projet. Nous avons essayé de générer un identifiant mais celui ci existe " +"déjà. Merci de créer un nouvel identifiant que vous serez capable de retenir" #: forms.py:89 forms.py:94 msgid "Get in" @@ -60,11 +64,11 @@ msgstr "Envoyez moi le code par email" #: forms.py:103 msgid "This project does not exists" -msgstr "Ce projet n'existe pas" +msgstr "Ce projet n’existe pas" #: forms.py:108 msgid "Password mismatch" -msgstr "Les mots de passe fournis ne sont pas les mêmes." +msgstr "Les mots de passe sont différents" #: forms.py:109 msgid "Password" @@ -76,7 +80,7 @@ msgstr "Confirmation du mot de passe" #: forms.py:111 msgid "Reset password" -msgstr "" +msgstr "Réinitialiser le mot de passe" #: forms.py:115 msgid "Date" @@ -108,7 +112,7 @@ msgstr "Valider et ajouter une autre facture" #: forms.py:146 msgid "Bills can't be null" -msgstr "Le montant d'une facture ne peut pas être nul." +msgstr "Le montant d’une facture ne peut pas être nul" #: forms.py:151 msgid "Name" @@ -124,7 +128,7 @@ msgstr "Ajouter" #: forms.py:162 msgid "User name incorrect" -msgstr "Nom d'utilisateur incorrect" +msgstr "Nom d’utilisateur incorrect" #: forms.py:167 msgid "This project already have this member" @@ -141,11 +145,11 @@ msgstr "Envoyer les invitations" #: forms.py:190 #, python-format msgid "The email %(email)s is not valid" -msgstr "L'email %(email)s est invalide" +msgstr "L’email %(email)s est invalide" #: forms.py:196 msgid "What do you want to download ?" -msgstr "Que voulez-vous télécharger ?" +msgstr "Que voulez-vous télécharger ?" #: forms.py:199 msgid "bills" @@ -157,36 +161,36 @@ msgstr "remboursements" #: forms.py:201 msgid "Export file format" -msgstr "Format du fichier d'export" +msgstr "Format du fichier d’export" #: web.py:129 msgid "Too many failed login attempts, please retry later." -msgstr "Trop d'échecs d'authentification successifs, veuillez réessayer plus tard." +msgstr "Trop d'échecs d’authentification successifs, veuillez réessayer plus tard." #: web.py:144 #, python-format msgid "This admin password is not the right one. Only %(num)d attempts left." msgstr "" -"Le mot de passe administrateur que vous avez entré n'est pas correct. " +"Le mot de passe administrateur que vous avez entré n’est pas correct. " "Plus que %(num)d tentatives." #: web.py:167 msgid "You either provided a bad token or no project identifier." -msgstr "L'identifiant du projet ou le token fourni n'est pas correct." +msgstr "L’identifiant du projet ou le token fourni n’est pas correct." #: web.py:195 msgid "This private code is not the right one" -msgstr "Le code que vous avez entré n'est pas correct" +msgstr "Le code que vous avez entré n’est pas correct" #: web.py:242 #, python-format msgid "You have just created '%(project)s' to share your expenses" -msgstr "Vous venez de créer '%(project)s' pour partager vos dépenses" +msgstr "Vous venez de créer « %(project)s » pour partager vos dépenses" #: web.py:260 #, python-format msgid "%(msg_compl)sThe project identifier is %(project)s" -msgstr "L'identifiant de ce projet est '%(project)s'" +msgstr "%(msg_compl)sL’identifiant de ce projet est %(project)s" #: web.py:281 msgid "A link to reset your password has been sent to your email." @@ -194,7 +198,7 @@ msgstr "Un lien pour changer votre mot de passe vous a été envoyé par mail." #: web.py:291 msgid "No token provided" -msgstr "" +msgstr "Aucun token n’a été fourni" #: web.py:294 msgid "Invalid token" @@ -237,18 +241,18 @@ msgid "" "User '%(name)s' has been deactivated. It will still appear in the users " "list until its balance becomes zero." msgstr "" -"Le membre '%(name)s' a été désactivé. Il continuera d'apparaître jusqu'à " -"ce que sa balance devienne égale à zéro." +"Le membre « %(name)s » a été désactivé. Il continuera d’apparaître jusqu'à " +"ce que sa balance soit nulle." #: web.py:465 #, python-format msgid "User '%(name)s' has been removed" -msgstr "" +msgstr "Le membre « %(name)s » a été supprimé" #: web.py:480 #, python-format msgid "User '%(name)s' has been edited" -msgstr "" +msgstr "Le membre « %(name)s » a été édité" #: web.py:500 msgid "The bill has been added" @@ -268,11 +272,11 @@ msgstr "Retourner à la liste" #: templates/admin.html:10 msgid "Administration tasks are currently disabled." -msgstr "Les tâches d'administration sont actuellement désactivées." +msgstr "Les tâches d’administration sont actuellement désactivées." #: templates/authenticate.html:6 msgid "The project you are trying to access do not exist, do you want to" -msgstr "Le projet auquel vous essayez d'acceder n'existe pas. Souhaitez vous" +msgstr "Le projet auquel vous essayez d’accéder n’existe pas, souhaitez vous" #: templates/authenticate.html:8 msgid "create it" @@ -280,7 +284,7 @@ msgstr "le créer" #: templates/authenticate.html:8 msgid "?" -msgstr " ?" +msgstr " ?" #: templates/create_project.html:4 msgid "Create a new project" @@ -322,11 +326,11 @@ msgstr "supprimer" #: templates/dashboard.html:25 msgid "The Dashboard is currently deactivated." -msgstr "La page d'administration est actuellement désactivée." +msgstr "Le tableau de bord est actuellement désactivée." #: templates/edit_project.html:6 templates/list_bills.html:24 msgid "you sure?" -msgstr "c'est sûr ?" +msgstr "c’est sûr ?" #: templates/edit_project.html:11 msgid "Edit this project" @@ -338,7 +342,7 @@ msgstr "Télécharger les données de ce projet" #: templates/forms.html:27 msgid "Can't remember the password?" -msgstr "Vous ne vous souvenez plus du code d'accès ?" +msgstr "Vous ne vous souvenez plus du code d’accès ?" #: templates/forms.html:30 msgid "Cancel" @@ -386,7 +390,7 @@ msgstr "Télécharger" #: templates/home.html:7 msgid "Manage your shared
expenses, easily" -msgstr "Gérez vos dépenses
partagées, facilement" +msgstr "Gérez vos dépenses
partagées, facilement" #: templates/home.html:9 msgid "Try out the demo" @@ -402,7 +406,7 @@ msgstr "Partez en vacances avec des amis ?" #: templates/home.html:13 msgid "Simply sharing money with others?" -msgstr "Ça vous arrive de partager de l'argent avec d'autres ?" +msgstr "Ça vous arrive de partager de l’argent avec d’autres ?" #: templates/home.html:13 msgid "We can help!" @@ -418,7 +422,7 @@ msgstr "se connecter" #: templates/home.html:26 msgid "can't remember your password?" -msgstr "vous ne vous souvenez plus du code d'accès ?" +msgstr "vous ne vous souvenez plus du code d’accès ?" #: templates/home.html:34 templates/home.html:42 msgid "or create a new one" @@ -426,15 +430,15 @@ msgstr "ou créez en un nouveau" #: templates/home.html:38 msgid "let's get started" -msgstr "c'est parti !" +msgstr "c’est parti !" #: templates/home.html:51 msgid "" "This access code will be sent to your friends. It is stored as-is by the " "server, so don\\'t reuse a personal password!" msgstr "" -"Ce code d\\'accès va être envoyé à vos amis et stocké en clair sur le " -"serveur.N\\'utilisez pas un mot de passe personnel !" +"Ce code d’accès va être envoyé à vos amis et stocké en clair sur le serveur. " +"N’utilisez pas un mot de passe personnel !" #: templates/layout.html:5 msgid "Account manager" @@ -474,7 +478,7 @@ msgstr "Se déconnecter" #: templates/layout.html:66 msgid "Dashboard" -msgstr "" +msgstr "Tableau de bord" #: templates/layout.html:89 msgid "This is a free software" @@ -482,7 +486,7 @@ msgstr "Ceci est un logiciel libre" #: templates/layout.html:89 msgid "you can contribute and improve it!" -msgstr "vous pouvez y contribuer et l'améliorer" +msgstr "vous pouvez y contribuer et l’améliorer !" #: templates/list_bills.html:63 msgid "deactivate" @@ -494,7 +498,7 @@ msgstr "ré-activer" #: templates/list_bills.html:82 msgid "Invite people to join this project!" -msgstr "Invitez d'autres personnes à rejoindre ce projet !" +msgstr "Invitez d’autres personnes à rejoindre ce projet !" #: templates/list_bills.html:83 msgid "Add a new bill" @@ -522,7 +526,7 @@ msgstr "chacun" #: templates/list_bills.html:120 msgid "Nothing to list yet. You probably want to" -msgstr "Rien à lister pour l'instant. Vous voulez surement" +msgstr "Rien à lister pour l’instant. Vous voulez surement" #: templates/list_bills.html:120 msgid "add a bill" @@ -530,7 +534,7 @@ msgstr "ajouter une facture" #: templates/password_reminder.html:4 msgid "Password reminder" -msgstr "Rappel du code d'accès" +msgstr "Rappel du code d’accès" #: templates/recent_projects.html:2 msgid "Your projects" @@ -551,8 +555,8 @@ msgid "" "creation of this budget management project and we will send them an email" " for you." msgstr "" -"Entrez les addresses des personnes que vous souhaitez inviter, séparées " -"par des virgules. On s'occupe de leur envoyer un email." +"Entrez les adresses des personnes que vous souhaitez inviter,\n" +"séparées par des virgules, on s’occupe de leur envoyer un email." #: templates/send_invites.html:7 msgid "" @@ -560,9 +564,9 @@ msgid "" "password by other communication means. Or even directly share the " "following link:" msgstr "" -"Si vous préférez vous pouvez partager l'identifiant du projet et son mot " -"de passe par un autre moyen de communication. Ou directement partager le " -"lien suivant :" +"Si vous préférez vous pouvez partager l’identifiant du projet\n" +"et son mot de passe par un autre moyen de communication. Ou directement " +"partager le lien suivant :" #: templates/settle_bills.html:22 msgid "Who pays?" @@ -598,7 +602,7 @@ msgstr "Solde" #~ " that you will be able to " #~ "remember." #~ msgstr "" -#~ "L'identifiant du projet est utilisé pour" +#~ "L’identifiant du projet est utilisé pour" #~ " se connecter.Nous avons essayé de " #~ "générer un identifiant mais celui ci " #~ "existe déjà. Merci de créer un " @@ -612,11 +616,10 @@ msgstr "Solde" #~ msgstr "Date de fin" #~ msgid "\"No token provided\"" -#~ msgstr "Aucun token n'a été fourni." +#~ msgstr "Aucun token n’a été fourni." #~ msgid "User '%(name)s' has been deactivated" #~ msgstr "Le membre '%(name)s' a été désactivé" #~ msgid "Invite" #~ msgstr "Invitez" - From 0a9d16b40a715b96625bd29e9d1c82d77a352dd9 Mon Sep 17 00:00:00 2001 From: Adrien CLERC Date: Tue, 15 May 2018 22:05:42 +0200 Subject: [PATCH 23/32] compile translations to MO --- .../translations/fr/LC_MESSAGES/messages.mo | Bin 9762 -> 9912 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.mo b/ihatemoney/translations/fr/LC_MESSAGES/messages.mo index 47b801d4f61bafde6443423ad1f7325ec73e465d..c48d7cb8eb991df15c4e83242e6e115e1d40addd 100644 GIT binary patch delta 3121 zcma*neQZ^C9mnzCEilSu1zHMiDQ$n3_O@l?p)HV(LO>oTTA0DXBtF39-a|R=KJA=) ztAeb^k|idZjGf8Etg#tm=4NJd>6YP;WNY0(jbULkCQ8D#pcof*3qH)?)BAIJG||6I zn)^Brzu)is@cW%UIe2d8BON16#+YN(#`NLi*oi;J7=C~QcoXO1idn|Y#0@wNw__YX zhI4RFWCoXU@8fJ7!+H2!)cgxL17DkEM$CLJe!_#rcnxpEzhOO2WA!?0L>`$taXPL+ zaxw#`d0S8c4xkcwAi6(}wcMXX1w4aVuYy|d2l0`qi`RLez;9q1{yZA+7krHS7-iJp zDO`e2VLM*LMfe_S{%yA#(}gt{CW^c=1E@r{BTJf3;ZjVGaG}_VBh*6IB0oTNU>ez}LW}A|C-TblqguWdRrz4_d_QX3NR|sNa17PDZ=trNg39y) zDxpiL1>ZuQnRifIbPHAaJSHcw0iVRRsD!Sf)_WJV-k(w9V^rNpd{fPZT9&{D+<^*^ zLbdiNT0Dg}@Fm=Y-zNPszJuHFKH8%Bqu7ZrViK>S3ap}hb+QdtVK>erzWEFn$}oql zZN7q8Jc&9B|3Hha4AQub{HPP3L{)MCTX6_g@xzf{Lmje5BcDKR>C?CtpTYISH^1US zpV<=nsR}-e3S36IYrcVM-5FGXXR!reKn1uGy}yb|=msj#?;~#_O)wuu@8cXKorzi; zA+~AaLIIYe7Q72p;bzonzbAU`;fa_rUq&VP&xKQ!w2`%1ybtw%VHmgL5u|zMRaC&Y zBCn$2UT>iPI)xwdpc!XzA~s?YHNin*nH;L}hfsU?RqVk>aV1Wo#@|H#%q@PjZaoJ{ zTbji1b3}D?0K4#`P4r)_De^#pP9Q~@Z=#+*hYI{Es=|w?ui-kXWACF{ehby9Do(6A zn8dTV5;^eZXSfWn;~K1`T8+OusLW^|(doI!6V3cJYQ+1f-gOdInXE;X_Hk6H8PpzS zkw5b#ezf8#BpLI4T#j#|GK_IzR9Q8uC2gp6R<^JQD!rcv>dj76z)z!km_sG_Ad+h^WH{>*#P{VXQweFE2E51zm*YW^Rh_qSTv162~|NGYQR?7%kc#jR*@ z7!}}gq$u-Z^!y6m;Ql7=z+YOvVN51CK=>V0;x)8Kho&1laUZIq$49u(W}e1f_!O%0 zo2X+to&WS|StFLP8}GnT)TUlSCH5!e6EpM4UWcX|Ev`Uq!9Az~_o6!eIaHjHEEk$^ z3^(CJxEWu=tyrC$s$eH}bAK4s!jq^3M^T@}^Qh0^Wz<>vEo$5!qkEODMB=DKYLNuC znIu7Xe!SP3`pO4M-4<9U~yiBkmK78gvJQm!T zy*PM#_KCT+RJh;C+oD$(b~8?9eE;n`Vl#^=-~U3v8y~CL9UK4qoU^g-M5r#Trnc&V;srsw1e!_4WL|3p>IUCVpcEbm=o`+5gfuL|B+(A@W*Cw&7wJ$Lr> z_4LFCQ+{dry++OfzT(qzRRDwMsv8@%3Z z!=-ODk9GKkJh9zFhbAkYlP~#ZcdA%)vPvgiAk8qBbBp(d6Y5&_w(g#+xOunarn0V2 zu35+C3asE*?U54Y(iz7_)Z7TRkYi}~{PgaU4 zZ?eJ@WF_AYyWUWlA}3rueB&llLewYci>_Y^D_Lg!;IeH9m->yp zdxqVmEcz0i+J$|I{_w@g#8Bv86PrWVFX;?TFF87I;+cuDWOqK5bC&uOV-x3t+V*7l zvcJ76>lfUT&6l%TXN4K0flltA=h*+9V*kBuB15HWZG_0W U1<&y&#u6qS4e}??d+hPQ0RA*Yk^lez delta 2999 zcmZA2e{9tC9mny{m31vGU=``Ll(wJiud70DcP$OG78%HouoZ9}VNMP4%iWjqh2CAc zA5DefWo~HpOT;g8W=5xtMo%+!DU*dRbLKY3mMM}j7oFgmEo?E6{h`jy1-Iv`J+frU z@qK)Me7>L0`}6vIzU8M|CWjvAC@eO{9I7^^5szXoK8Ia+9s^i8- z8l*O}9@Tyes^h(=0uGk1zlB$G{RnEDAEDlxLiP8vnu0NmWlnQLzJ(h2&)ACZmG9S3 z{xPnv#f4ZzwL5`r_;YN>8C;5$3+A3%409bu6}}B=Vzwh)n>!1fT*t{iRJ7kiRs0mH z;R)1>B~<07a0#A4wfk$?f1#47r8>>D8*W!~@pT`Th0q-Cdy+4UvcoI8t1~tJdrmrMBu#fRg4<|~% zK~rn8=5M(?w;d0dO{%raF?Rbxb8WRh^q9L*o|lL0A5CI*9rdg^DUWUsNGUTYB4XMqCbtA*qf+?&!hUk zh-!ZsS7Tuw(_Y8PTD%Frfr`F_8u&aa!m7@>!WN^xgD`5Xqo_T!6V+}{`8tOx@bN4r!XE@QCzKH7J6l#F8s5Lu>D&!*S#U;wIZEM=*()-+$9m}N0y@Z=+ zoBC??j|2Xynru}nVG~ZyoqDFGBH+JT+s?mxrfyrXF);PQ!p{e)GP$|OA1*2e0+Ff5 z>Vg&i1J`7y)-QgjqQ9>v7VU}k+1}VdU+?m0Z!}tS%eJ0jcihW*=~T~p`W>*n(O7J@ z-AdcrH_#vRuc{9=e=+Z-a$d>~r|+_Zne<)z^X}(Hl8!eTiKmnP%KB3_T4UDcJNdrDCpBw7b*!Ov;P+pVWschQfX{*t{^@+0D%My7Am~*|2|Wuq9yqiQvgV*za%f zI6l!3ninxU{OQhiuHI|7(eH13(5gI}aP4j{Wj3ZWNr$QCxr*=c+>A|x{d0{6gJI&L z(%qhuntePwPR{!Km)_ZK2h8@_SiRJ^GfG3VIW_L(+yhs%2|v`-?$s5! znVYQdH?8z9G_`WCdfDT#x%p&m%C*@vgN!*@JL+a_vh-}y%|^_yGdAXqsYBXIWXImlkg{6VubQ+OH)A@vB=`2OSC+(J)RT>7~4EF5~1_On*n~AqIW3wT0tHtt$f&f0Lbx zg`IfZO%PWq>`u_wRrwM7rF4Feqa^HjI-fN+yXD&N2>Y?nrg>ZOP^gQ7o(ip^zjs3; z95=VE<9L5t7snHA2m4snl#_H>QhR0XsWa=ubKZEVXog&ybd$R>uKiy_e_eYgZ|`jn zv#{fyn Date: Sun, 17 Jun 2018 09:48:19 +0200 Subject: [PATCH 24/32] fix fr l10n --- ihatemoney/translations/fr/LC_MESSAGES/messages.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.po b/ihatemoney/translations/fr/LC_MESSAGES/messages.po index 5179f605..a4a3e1b8 100644 --- a/ihatemoney/translations/fr/LC_MESSAGES/messages.po +++ b/ihatemoney/translations/fr/LC_MESSAGES/messages.po @@ -242,7 +242,7 @@ msgid "" "list until its balance becomes zero." msgstr "" "Le membre « %(name)s » a été désactivé. Il continuera d’apparaître jusqu'à " -"ce que sa balance soit nulle." +"ce que son solde soit nul." #: web.py:465 #, python-format From a5240fb8a331f50ffc986c25a4a2ddf17e8fd693 Mon Sep 17 00:00:00 2001 From: Adrien CLERC Date: Sun, 17 Jun 2018 09:49:10 +0200 Subject: [PATCH 25/32] compile l10n --- .../translations/fr/LC_MESSAGES/messages.mo | Bin 9912 -> 9909 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.mo b/ihatemoney/translations/fr/LC_MESSAGES/messages.mo index c48d7cb8eb991df15c4e83242e6e115e1d40addd..3fa8d8f4c52f34ffbdb3e90d8579f61864b1ce5a 100644 GIT binary patch delta 315 zcmXZWKMO%&6vy#X;u3CGM3K71z2r|8qlHXjVzU?+Zi$4#!Yl9s*o{&Wv+`QZ?8^6v z<@ucR>pZ7(Yh7BWzP6r-NH!&s!yOjzfk}L01dX(a8!a?3j~;BIhHVUD7Y%e!&9AVE z8?50S>*%&b;@GfcEm?{IfgiUR#vOX`h$XzD51**!UjP1s>JZKT9ipQ;Ac_IZ{PQx# t=qsrHcTnx`*`zv!Lr}y8s{U|Zdi6wTJTj-_VSm8u%*@ezc;2Uyt}g*bB=7(L delta 318 zcmXZWu}Z^G6vpub(k8XFC@GXy0)dzif^A1fsdNo)bt}XeP`JdwI20)vd;+&Fc>p&{ z5X4>ZSvuR@|D~CJ=X{)d`97ns=;O{PauEq!kq}Ef$6vH@hds2;L>d^NgJW#sH5xd_ zBV6DyD%AHYoZ>59;SbKx^hAajc~VHga8J<2H?*)s6E`@)Z*1Wf_1#~+i@z^&h`PuL z>H-7oU|8oD*k`{){r(2^|A|l9Wl#hod_ldxS4*>d+KZjrSjAbAI!})exsyFBl~!xl GKKKVbhb3MB From 26d1e038772798cc3a916643a0e71cf504a8fd3d Mon Sep 17 00:00:00 2001 From: Adrien CLERC Date: Tue, 19 Jun 2018 22:35:23 +0200 Subject: [PATCH 26/32] Add changelog entry --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 81df6bbf..663a861b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,7 @@ This document describes changes between each past release. 2.1.1 (unreleased) ------------------ -- Nothing changed yet. +- Regenerate translations (#338) 2.1 (2018-02-16) From f9cc4e56230ce04f58d457bfc8f468d56e53cb36 Mon Sep 17 00:00:00 2001 From: mduret Date: Mon, 16 Jul 2018 22:08:15 +0200 Subject: [PATCH 27/32] fix install with pip10 (#341) * fix the docker build with pip10 * maj tricks https://github.com/spiral-project/ihatemoney/pull/341 --- setup.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index ec40c852..974a70f7 100644 --- a/setup.py +++ b/setup.py @@ -2,16 +2,6 @@ import codecs import os from setuptools import setup, find_packages -try: - from pip.req import parse_requirements - from pip.download import PipSession -except ImportError: - print('Cannot find pip.') - raise - -# Get requirements from the requirements.txt file. -pip_requirements = parse_requirements("requirements.txt", session=PipSession()) -install_requires = [str(ir.req) for ir in pip_requirements] here = os.path.abspath(os.path.dirname(__file__)) @@ -23,6 +13,13 @@ def read_file(filename): return content +def parse_requirements(filename): + """ load requirements from a pip requirements file """ + with open(filename) as lines: + lineiter = (line.strip() for line in lines) + return [line for line in lineiter if line and not line.startswith("#")] + + README = read_file('README.rst') CHANGELOG = read_file('CHANGELOG.rst') @@ -59,5 +56,5 @@ setup(name='ihatemoney', packages=find_packages(), include_package_data=True, zip_safe=False, - install_requires=install_requires, + install_requires=parse_requirements('requirements.txt'), entry_points=ENTRY_POINTS) From c3f8ddd274a40b164b5fceeab44c1c26cf053b04 Mon Sep 17 00:00:00 2001 From: JocelynDelalande Date: Mon, 16 Jul 2018 22:55:54 +0200 Subject: [PATCH 28/32] Fix Apache conf template, without relying on environment var (#359) `python-home` is prefered over `python-path`. It will work with or without a virtualenv. See http://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIDaemonProcess.html --- ihatemoney/conf-templates/apache-vhost.conf.j2 | 2 +- ihatemoney/manage.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ihatemoney/conf-templates/apache-vhost.conf.j2 b/ihatemoney/conf-templates/apache-vhost.conf.j2 index 0527d310..e169589a 100644 --- a/ihatemoney/conf-templates/apache-vhost.conf.j2 +++ b/ihatemoney/conf-templates/apache-vhost.conf.j2 @@ -1,7 +1,7 @@ ServerAdmin admin@example.com # CUSTOMIZE ServerName ihatemoney.example.com # CUSTOMIZE - WSGIDaemonProcess ihatemoney user=www-data group=www-data threads=5 python-path={{ pkg_path }} {% if bin_path %}python-home={{ bin_path }}{% endif %} + WSGIDaemonProcess ihatemoney user=www-data group=www-data threads=5 python-home={{ sys_prefix }} WSGIScriptAlias / {{ pkg_path }}/wsgi.py WSGIPassAuthorization On diff --git a/ihatemoney/manage.py b/ihatemoney/manage.py index 9058b390..3207b558 100755 --- a/ihatemoney/manage.py +++ b/ihatemoney/manage.py @@ -52,6 +52,7 @@ class GenerateConfig(Command): print(template.render( pkg_path=pkg_path, bin_path=bin_path, + sys_prefix=sys.prefix, secret_key=self.gen_secret_key(), )) From 026c1ec4aaa7ea758b16f01f4842630228c38907 Mon Sep 17 00:00:00 2001 From: Jocelyn Delalande Date: Mon, 16 Jul 2018 23:00:31 +0200 Subject: [PATCH 29/32] Add missing ChangeLog entry --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 663a861b..499f0e60 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,11 @@ This document describes changes between each past release. 2.1.1 (unreleased) ------------------ +Fixed +===== + - Regenerate translations (#338) +- Fix broken install with pip ≥ 10 (#340) 2.1 (2018-02-16) From 9caf213e1d4f2f92944000f2c072fcece885f407 Mon Sep 17 00:00:00 2001 From: JocelynDelalande Date: Tue, 17 Jul 2018 00:22:23 +0200 Subject: [PATCH 30/32] Document MySQL setup (#357) PyMySQL is more difficult to install since its version 0.9 since it now depends on *cryptography* lib, which in turns depends on OpenSSL and Python dev files. See https://github.com/PyMySQL/PyMySQL/issues/697 --- CHANGELOG.rst | 5 +++++ docs/installation.rst | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 499f0e60..15df733a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,11 @@ Fixed - Regenerate translations (#338) - Fix broken install with pip ≥ 10 (#340) +Added +===== + +- Document MySQL setup (#357) + 2.1 (2018-02-16) ---------------- diff --git a/docs/installation.rst b/docs/installation.rst index e7d586ec..4829c5de 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -63,6 +63,22 @@ Once installed, you can start a test server:: And point your browser at `http://localhost:5000 `_. +Configure database with MySQL/MariaDB (optional) +================================================ + +Only required if you prefer MySQL/MariaDB over SQLite. + +1. Install PyMySQL dependencies. On Debian or Ubuntu, that would be:: + + apt install python3-dev libssl-dev + +2. Install PyMySQL (within your virtualenv):: + + pip install 'PyMySQL>=0.9,<0.10' + +3. Create an empty database and a database user +4. Configure :ref:`SQLALCHEMY_DATABASE_URI ` accordingly + Deploy it ========= @@ -159,6 +175,8 @@ For example, use the following command to add more gunicorn workers:: docker run -d -p 8000:8000 ihatemoney -w 3 +.. _configuration: + Configuration ============= @@ -187,7 +205,7 @@ Production values are recommended values for use in production. | | | **Production value:** `ihatemoney conf-example ihatemoney.cfg` sets it to something | | | | random, which is good. | +-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| MAIL_DEFAULT_SENDER | ``("Budget manager", | A python tuple describing the name and email address to use when sending | +| MAIL_DEFAULT_SENDER | ``("Budget manager", | A python tuple describing the name and email address to use when sending | | | "budget@notmyidea.org")`` | emails. | | | | | | | | **Production value:** Any tuple you want. | From 94e2befb8290053e1159925e782a17a2dfb19e20 Mon Sep 17 00:00:00 2001 From: Carey Metcalfe Date: Fri, 3 Aug 2018 11:11:12 -0400 Subject: [PATCH 31/32] Fix cffi installation in Dockerfile (#364) The Python cffi package requires the libc, libffi and openssl development packages, as well as gcc to compile it. --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fbc0f4ac..cbe4af6e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM python:3.6-alpine -RUN mkdir /ihatemoney &&\ +RUN apk add gcc libc-dev libffi-dev openssl-dev &&\ + mkdir /ihatemoney &&\ mkdir -p /etc/ihatemoney &&\ pip install --no-cache-dir gunicorn pymysql From 9ece687eb04959267c212da9e5abc288e7e4b84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Sun, 5 Aug 2018 14:14:55 +0200 Subject: [PATCH 32/32] Update the changelog entry --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 663a861b..58b52153 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ This document describes changes between each past release. ------------------ - Regenerate translations (#338) +- Fix the supervisord template (#309) 2.1 (2018-02-16)
{{ _("Who?") }}{{ _("Paid") }}{{ _("Spent") }}{{ _("Balance") }}
{{ member.name }}{{ "%0.2f"|format(paid[member.id]) }}{{ "%0.2f"|format(spent[member.id]) }}{{ "%0.2f"|format(balance[member.id]) }}{{ stat.member.name }}{{ "%0.2f"|format(stat.paid) }}{{ "%0.2f"|format(stat.spent) }}{{ "%0.2f"|format(stat.balance) }}
alexis20.0031.67alexis20.0031.67-11.67fred20.005.83fred20.005.8314.17tata0.002.50tata0.002.50-2.50toto0.000.00toto0.000.000.00