mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-05-01 10:42:24 +02:00
Merge branch 'master' into almet/fix-supervisord-template
This commit is contained in:
commit
1d0880f3cb
21 changed files with 817 additions and 556 deletions
|
@ -3,13 +3,34 @@ Changelog
|
||||||
|
|
||||||
This document describes changes between each past release.
|
This document describes changes between each past release.
|
||||||
|
|
||||||
2.1 (unreleased)
|
2.1.1 (unreleased)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Regenerate translations (#338)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Fixed
|
||||||
=====
|
=====
|
||||||
|
|
||||||
- Fix the "IOError" crash when running `ihatemoney generate-config` (#308)
|
- 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)
|
2.0 (2017-12-27)
|
||||||
|
|
|
@ -9,6 +9,7 @@ Alexis Metaireau <alexis@notmyidea.org>
|
||||||
Arnaud Bos <arnaud.tlse@gmail.com>
|
Arnaud Bos <arnaud.tlse@gmail.com>
|
||||||
Baptiste Jonglez <git@bitsofnetworks.org>
|
Baptiste Jonglez <git@bitsofnetworks.org>
|
||||||
Berteh <berteh@gmail.com>
|
Berteh <berteh@gmail.com>
|
||||||
|
donkers <thedonkers@gmail.com>
|
||||||
Feth AREZKI <feth@tuttu.info>
|
Feth AREZKI <feth@tuttu.info>
|
||||||
Frédéric Sureau <fredericsureau@gmail.com>
|
Frédéric Sureau <fredericsureau@gmail.com>
|
||||||
Jocelyn Delalande <jocelyn@crapouillou.net>
|
Jocelyn Delalande <jocelyn@crapouillou.net>
|
||||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -4,12 +4,11 @@ RUN mkdir /ihatemoney &&\
|
||||||
mkdir -p /etc/ihatemoney &&\
|
mkdir -p /etc/ihatemoney &&\
|
||||||
pip install --no-cache-dir gunicorn pymysql
|
pip install --no-cache-dir gunicorn pymysql
|
||||||
|
|
||||||
WORKDIR /ihatemoney
|
COPY . /ihatemoney
|
||||||
COPY . .
|
|
||||||
ARG INSTALL_FROM_PYPI="False"
|
ARG INSTALL_FROM_PYPI="False"
|
||||||
RUN if [ "$INSTALL_FROM_PYPI" = True ]; then\
|
RUN if [ "$INSTALL_FROM_PYPI" = True ]; then\
|
||||||
pip install --no-cache-dir ihatemoney ; else\
|
pip install --no-cache-dir ihatemoney ; else\
|
||||||
pip install --no-cache-dir -e . ; \
|
pip install --no-cache-dir -e /ihatemoney ; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ENV DEBUG="False" \
|
ENV DEBUG="False" \
|
||||||
|
@ -26,9 +25,8 @@ ENV DEBUG="False" \
|
||||||
ACTIVATE_DEMO_PROJECT="True" \
|
ACTIVATE_DEMO_PROJECT="True" \
|
||||||
ADMIN_PASSWORD="" \
|
ADMIN_PASSWORD="" \
|
||||||
ALLOW_PUBLIC_PROJECT_CREATION="True" \
|
ALLOW_PUBLIC_PROJECT_CREATION="True" \
|
||||||
ACTIVATE_ADMIN_DASHBOARD="False" \
|
ACTIVATE_ADMIN_DASHBOARD="False"
|
||||||
GUNICORN_NUM_WORKERS="3"
|
|
||||||
|
|
||||||
VOLUME /database
|
VOLUME /database
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
CMD ["/ihatemoney/conf/confandrun.sh"]
|
ENTRYPOINT ["/ihatemoney/conf/confandrun.sh"]
|
||||||
|
|
|
@ -17,7 +17,8 @@ ADMIN_PASSWORD = "$ADMIN_PASSWORD"
|
||||||
ALLOW_PUBLIC_PROJECT_CREATION = $ALLOW_PUBLIC_PROJECT_CREATION
|
ALLOW_PUBLIC_PROJECT_CREATION = $ALLOW_PUBLIC_PROJECT_CREATION
|
||||||
ACTIVATE_ADMIN_DASHBOARD = $ACTIVATE_ADMIN_DASHBOARD
|
ACTIVATE_ADMIN_DASHBOARD = $ACTIVATE_ADMIN_DASHBOARD
|
||||||
EOF
|
EOF
|
||||||
gunicorn ihatemoney.wsgi:application \
|
# Start gunicorn without forking
|
||||||
|
exec gunicorn ihatemoney.wsgi:application \
|
||||||
-b 0.0.0.0:8000 \
|
-b 0.0.0.0:8000 \
|
||||||
--log-syslog \
|
--log-syslog \
|
||||||
-w "$GUNICORN_NUM_WORKERS"
|
"$@"
|
||||||
|
|
22
docs/api.rst
22
docs/api.rst
|
@ -164,3 +164,25 @@ And you can of course `DELETE` them at `/api/projects/<id>/bills/<bill-id>`::
|
||||||
$ curl --basic -u demo:demo -X DELETE\
|
$ curl --basic -u demo:demo -X DELETE\
|
||||||
https://ihatemoney.org/api/projects/demo/bills/80\
|
https://ihatemoney.org/api/projects/demo/bills/80\
|
||||||
"OK"
|
"OK"
|
||||||
|
|
||||||
|
|
||||||
|
Statistics
|
||||||
|
----------
|
||||||
|
|
||||||
|
You can get some project stats with a `GET` on `/api/projects/<id>/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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
|
@ -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
|
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
|
Configuration
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -1,62 +1,75 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from flask import Blueprint, request
|
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 wtforms.fields.core import BooleanField
|
||||||
|
|
||||||
from ihatemoney.models import db, Project, Person, Bill
|
from ihatemoney.models import db, Project, Person, Bill
|
||||||
from ihatemoney.forms import (ProjectForm, EditProjectForm, MemberForm,
|
from ihatemoney.forms import (ProjectForm, EditProjectForm, MemberForm,
|
||||||
get_billform_for)
|
get_billform_for)
|
||||||
from werkzeug.security import check_password_hash
|
from werkzeug.security import check_password_hash
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
|
||||||
api = Blueprint("api", __name__, url_prefix="/api")
|
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.
|
"""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
|
||||||
"""
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
auth = request.authorization
|
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 and auth.username == project_id:
|
||||||
if auth and "project_id" in kwargs and \
|
|
||||||
auth.username == kwargs["project_id"]:
|
|
||||||
project = Project.query.get(auth.username)
|
project = Project.query.get(auth.username)
|
||||||
if project and check_password_hash(project.password, auth.password):
|
if project and check_password_hash(project.password, auth.password):
|
||||||
return project
|
# The whole project object will be passed instead of project_id
|
||||||
return False
|
kwargs.pop("project_id")
|
||||||
|
return f(*args, project=project, **kwargs)
|
||||||
|
abort(401)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class ProjectHandler(object):
|
class ProjectsHandler(Resource):
|
||||||
|
def post(self):
|
||||||
def add(self):
|
|
||||||
form = ProjectForm(meta={'csrf': False})
|
form = ProjectForm(meta={'csrf': False})
|
||||||
if form.validate():
|
if form.validate():
|
||||||
project = form.save()
|
project = form.save()
|
||||||
db.session.add(project)
|
db.session.add(project)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return 201, project.id
|
return project.id, 201
|
||||||
return 400, form.errors
|
return form.errors, 400
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectHandler(Resource):
|
||||||
|
method_decorators = [need_auth]
|
||||||
|
|
||||||
@need_auth(check_project, "project")
|
|
||||||
def get(self, project):
|
def get(self, project):
|
||||||
return 200, project
|
return project
|
||||||
|
|
||||||
@need_auth(check_project, "project")
|
|
||||||
def delete(self, project):
|
def delete(self, project):
|
||||||
db.session.delete(project)
|
db.session.delete(project)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return 200, "DELETED"
|
return "DELETED"
|
||||||
|
|
||||||
@need_auth(check_project, "project")
|
def put(self, project):
|
||||||
def update(self, project):
|
|
||||||
form = EditProjectForm(meta={'csrf': False})
|
form = EditProjectForm(meta={'csrf': False})
|
||||||
if form.validate():
|
if form.validate():
|
||||||
form.update(project)
|
form.update(project)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return 200, "UPDATED"
|
return "UPDATED"
|
||||||
return 400, form.errors
|
return form.errors, 400
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectStatsHandler(Resource):
|
||||||
|
method_decorators = [need_auth]
|
||||||
|
|
||||||
|
def get(self, project):
|
||||||
|
return project.members_stats
|
||||||
|
|
||||||
|
|
||||||
class APIMemberForm(MemberForm):
|
class APIMemberForm(MemberForm):
|
||||||
|
@ -71,98 +84,93 @@ class APIMemberForm(MemberForm):
|
||||||
return super(APIMemberForm, self).save(project, person)
|
return super(APIMemberForm, self).save(project, person)
|
||||||
|
|
||||||
|
|
||||||
class MemberHandler(object):
|
class MembersHandler(Resource):
|
||||||
|
method_decorators = [need_auth]
|
||||||
|
|
||||||
def get(self, project, member_id):
|
def get(self, project):
|
||||||
member = Person.query.get(member_id, project)
|
return project.members
|
||||||
if not member or member.project != project:
|
|
||||||
return 404, "Not Found"
|
|
||||||
return 200, member
|
|
||||||
|
|
||||||
def list(self, project):
|
def post(self, project):
|
||||||
return 200, project.members
|
|
||||||
|
|
||||||
def add(self, project):
|
|
||||||
form = MemberForm(project, meta={'csrf': False})
|
form = MemberForm(project, meta={'csrf': False})
|
||||||
if form.validate():
|
if form.validate():
|
||||||
member = Person()
|
member = Person()
|
||||||
form.save(project, member)
|
form.save(project, member)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return 201, member.id
|
return member.id, 201
|
||||||
return 400, form.errors
|
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)
|
form = APIMemberForm(project, meta={'csrf': False}, edit=True)
|
||||||
if form.validate():
|
if form.validate():
|
||||||
member = Person.query.get(member_id, project)
|
member = Person.query.get(member_id, project)
|
||||||
form.save(project, member)
|
form.save(project, member)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return 200, member
|
return member
|
||||||
return 400, form.errors
|
return form.errors, 400
|
||||||
|
|
||||||
def delete(self, project, member_id):
|
def delete(self, project, member_id):
|
||||||
if project.remove_member(member_id):
|
if project.remove_member(member_id):
|
||||||
return 200, "OK"
|
return "OK"
|
||||||
return 404, "Not Found"
|
return "Not Found", 404
|
||||||
|
|
||||||
|
|
||||||
class BillHandler(object):
|
class BillsHandler(Resource):
|
||||||
|
method_decorators = [need_auth]
|
||||||
|
|
||||||
def get(self, project, bill_id):
|
def get(self, project):
|
||||||
bill = Bill.query.get(project, bill_id)
|
|
||||||
if not bill:
|
|
||||||
return 404, "Not Found"
|
|
||||||
return 200, bill
|
|
||||||
|
|
||||||
def list(self, project):
|
|
||||||
return project.get_bills().all()
|
return project.get_bills().all()
|
||||||
|
|
||||||
def add(self, project):
|
def post(self, project):
|
||||||
form = get_billform_for(project, True, meta={'csrf': False})
|
form = get_billform_for(project, True, meta={'csrf': False})
|
||||||
if form.validate():
|
if form.validate():
|
||||||
bill = Bill()
|
bill = Bill()
|
||||||
form.save(bill, project)
|
form.save(bill, project)
|
||||||
db.session.add(bill)
|
db.session.add(bill)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return 201, bill.id
|
return bill.id, 201
|
||||||
return 400, form.errors
|
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})
|
form = get_billform_for(project, True, meta={'csrf': False})
|
||||||
if form.validate():
|
if form.validate():
|
||||||
bill = Bill.query.get(project, bill_id)
|
bill = Bill.query.get(project, bill_id)
|
||||||
form.save(bill, project)
|
form.save(bill, project)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return 200, bill.id
|
return bill.id, 200
|
||||||
return 400, form.errors
|
return form.errors, 400
|
||||||
|
|
||||||
def delete(self, project, bill_id):
|
def delete(self, project, bill_id):
|
||||||
bill = Bill.query.delete(project, bill_id)
|
bill = Bill.query.delete(project, bill_id)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if not bill:
|
if not bill:
|
||||||
return 404, "Not Found"
|
return "Not Found", 404
|
||||||
return 200, "OK"
|
return "OK", 200
|
||||||
|
|
||||||
|
|
||||||
project_resource = RESTResource(
|
restful_api.add_resource(ProjectsHandler, '/projects')
|
||||||
name="project",
|
restful_api.add_resource(ProjectHandler, '/projects/<string:project_id>')
|
||||||
route="/projects",
|
restful_api.add_resource(MembersHandler, "/projects/<string:project_id>/members")
|
||||||
app=api,
|
restful_api.add_resource(ProjectStatsHandler, "/projects/<string:project_id>/statistics")
|
||||||
actions=["add", "update", "delete", "get"],
|
restful_api.add_resource(MemberHandler, "/projects/<string:project_id>/members/<int:member_id>")
|
||||||
handler=ProjectHandler())
|
restful_api.add_resource(BillsHandler, "/projects/<string:project_id>/bills")
|
||||||
|
restful_api.add_resource(BillHandler, "/projects/<string:project_id>/bills/<int:bill_id>")
|
||||||
member_resource = RESTResource(
|
|
||||||
name="member",
|
|
||||||
inject_name="project",
|
|
||||||
route="/projects/<project_id>/members",
|
|
||||||
app=api,
|
|
||||||
handler=MemberHandler(),
|
|
||||||
authentifier=check_project)
|
|
||||||
|
|
||||||
bill_resource = RESTResource(
|
|
||||||
name="bill",
|
|
||||||
inject_name="project",
|
|
||||||
route="/projects/<project_id>/bills",
|
|
||||||
app=api,
|
|
||||||
handler=BillHandler(),
|
|
||||||
authentifier=check_project)
|
|
||||||
|
|
|
@ -1,111 +1,127 @@
|
||||||
# Translations template for PROJECT.
|
# Translations template for PROJECT.
|
||||||
# Copyright (C) 2013 ORGANIZATION
|
# Copyright (C) 2018 ORGANIZATION
|
||||||
# This file is distributed under the same license as the PROJECT project.
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 0.9.6\n"
|
"Generated-By: Babel 2.5.3\n"
|
||||||
|
|
||||||
#: forms.py:22
|
#: forms.py:46
|
||||||
msgid "Select all"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: forms.py:22
|
|
||||||
msgid "Select none"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: forms.py:61
|
|
||||||
msgid "Project name"
|
msgid "Project name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:62 forms.py:86 forms.py:102
|
#: forms.py:47 forms.py:71 forms.py:88
|
||||||
msgid "Private code"
|
msgid "Private code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:63
|
#: forms.py:48
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:85 forms.py:101 forms.py:107
|
#: forms.py:70 forms.py:87 forms.py:98
|
||||||
msgid "Project identifier"
|
msgid "Project identifier"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:87 templates/send_invites.html:5
|
#: forms.py:72
|
||||||
msgid "Create the project"
|
msgid "Create the project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:92
|
#: forms.py:77
|
||||||
msgid ""
|
msgid ""
|
||||||
"The project identifier is used to log in and for the URL of the project. "
|
"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 "
|
"We tried to generate an identifier for you but a project with this "
|
||||||
"identifier already exists. Please create a new identifier that you will "
|
"identifier already exists. Please create a new identifier that you will "
|
||||||
"be able to remember."
|
"be able to remember"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:103
|
#: forms.py:89 forms.py:94
|
||||||
msgid "Get in"
|
msgid "Get in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:108
|
#: forms.py:93
|
||||||
|
msgid "Admin password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms.py:99
|
||||||
msgid "Send me the code by email"
|
msgid "Send me the code by email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:112
|
#: forms.py:103
|
||||||
msgid "This project does not exists"
|
msgid "This project does not exists"
|
||||||
msgstr ""
|
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"
|
msgid "Date"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:117
|
#: forms.py:116
|
||||||
msgid "What?"
|
msgid "What?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:118
|
#: forms.py:117
|
||||||
msgid "Payer"
|
msgid "Payer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:119
|
#: forms.py:118
|
||||||
msgid "Amount paid"
|
msgid "Amount paid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:120 templates/list_bills.html:103
|
#: forms.py:119 templates/forms.html:100 templates/list_bills.html:101
|
||||||
msgid "For whom?"
|
msgid "For whom?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:122
|
#: forms.py:121
|
||||||
msgid "Submit"
|
msgid "Submit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:123
|
#: forms.py:122
|
||||||
msgid "Submit and add a new one"
|
msgid "Submit and add a new one"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:149
|
#: forms.py:146
|
||||||
msgid "Bills can't be null"
|
msgid "Bills can't be null"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:154
|
#: forms.py:151
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:155 templates/forms.html:95
|
#: forms.py:152
|
||||||
|
msgid "Weight"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms.py:153 templates/forms.html:123
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:163
|
#: forms.py:162
|
||||||
msgid "User name incorrect"
|
msgid "User name incorrect"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -113,105 +129,151 @@ msgstr ""
|
||||||
msgid "This project already have this member"
|
msgid "This project already have this member"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:178
|
#: forms.py:183
|
||||||
msgid "People to notify"
|
msgid "People to notify"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:179
|
#: forms.py:184
|
||||||
msgid "Send invites"
|
msgid "Send invites"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:185
|
#: forms.py:190
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The email %(email)s is not valid"
|
msgid "The email %(email)s is not valid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:191
|
#: forms.py:196
|
||||||
msgid "Start date"
|
msgid "What do you want to download ?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: forms.py:192
|
#: forms.py:199
|
||||||
msgid "End date"
|
msgid "bills"
|
||||||
msgstr ""
|
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"
|
msgid "This private code is not the right one"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:147
|
#: web.py:242
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You have just created '%(project)s' to share your expenses"
|
msgid "You have just created '%(project)s' to share your expenses"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:165
|
#: web.py:260
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(msg_compl)sThe project identifier is %(project)s"
|
msgid "%(msg_compl)sThe project identifier is %(project)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:185
|
#: web.py:281
|
||||||
msgid "a mail has been sent to you with the password"
|
msgid "A link to reset your password has been sent to your email."
|
||||||
msgstr ""
|
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"
|
msgid "Project successfully deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:254
|
#: web.py:401
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You have been invited to share your expenses for %(project)s"
|
msgid "You have been invited to share your expenses for %(project)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:261
|
#: web.py:408
|
||||||
msgid "Your invitations have been sent"
|
msgid "Your invitations have been sent"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:290
|
#: web.py:439
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(member)s had been added"
|
msgid "%(member)s had been added"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:303
|
#: web.py:452
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(name)s is part of this project again"
|
msgid "%(name)s is part of this project again"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:312
|
#: web.py:461
|
||||||
#, python-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:314
|
#: web.py:465
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "User '%(name)s' has been removed"
|
msgid "User '%(name)s' has been removed"
|
||||||
msgstr ""
|
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"
|
msgid "The bill has been added"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:351
|
#: web.py:520
|
||||||
msgid "The bill has been deleted"
|
msgid "The bill has been deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: web.py:369
|
#: web.py:538
|
||||||
msgid "The bill has been modified"
|
msgid "The bill has been modified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/add_bill.html:9
|
#: templates/add_bill.html:9 templates/edit_member.html:9
|
||||||
msgid "Back to the list"
|
msgid "Back to the list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/authenticate.html:6
|
#: templates/admin.html:10
|
||||||
msgid ""
|
msgid "Administration tasks are currently disabled."
|
||||||
"The project you are trying to access do not exist, do you want \n"
|
|
||||||
"to"
|
|
||||||
msgstr ""
|
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"
|
msgid "create it"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/authenticate.html:7
|
#: templates/authenticate.html:8
|
||||||
msgid "?"
|
msgid "?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -239,6 +301,24 @@ msgstr ""
|
||||||
msgid "Oldest bill"
|
msgid "Oldest bill"
|
||||||
msgstr ""
|
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
|
#: templates/edit_project.html:6 templates/list_bills.html:24
|
||||||
msgid "you sure?"
|
msgid "you sure?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -247,44 +327,59 @@ msgstr ""
|
||||||
msgid "Edit this project"
|
msgid "Edit this project"
|
||||||
msgstr ""
|
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?"
|
msgid "Can't remember the password?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/forms.html:26
|
#: templates/forms.html:30
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/forms.html:68
|
#: templates/forms.html:82
|
||||||
msgid "Edit the project"
|
msgid "Edit the project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/forms.html:69 templates/list_bills.html:70
|
#: templates/forms.html:91
|
||||||
#: templates/list_bills.html:114
|
|
||||||
msgid "delete"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/forms.html:77
|
|
||||||
msgid "Edit this bill"
|
msgid "Edit this bill"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/forms.html:77 templates/list_bills.html:94
|
#: templates/forms.html:91 templates/list_bills.html:89
|
||||||
msgid "Add a bill"
|
msgid "Add a bill"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/forms.html:95
|
#: templates/forms.html:103
|
||||||
msgid "Type user name here"
|
msgid "Select all"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/forms.html:102
|
|
||||||
msgid "Send the invitations"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/forms.html:103
|
#: 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"
|
msgid "No, thanks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:8
|
#: templates/forms.html:157
|
||||||
|
msgid "Download"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/home.html:7
|
||||||
msgid "Manage your shared <br>expenses, easily"
|
msgid "Manage your shared <br>expenses, easily"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -292,39 +387,39 @@ msgstr ""
|
||||||
msgid "Try out the demo"
|
msgid "Try out the demo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:12
|
#: templates/home.html:13
|
||||||
msgid "You're sharing a house?"
|
msgid "You're sharing a house?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:12
|
#: templates/home.html:13
|
||||||
msgid "Going on holidays with friends?"
|
msgid "Going on holidays with friends?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:12
|
#: templates/home.html:13
|
||||||
msgid "Simply sharing money with others?"
|
msgid "Simply sharing money with others?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:12
|
#: templates/home.html:13
|
||||||
msgid "We can help!"
|
msgid "We can help!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:24
|
#: templates/home.html:21
|
||||||
msgid "Log to an existing project"
|
msgid "Log to an existing project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:28
|
#: templates/home.html:25
|
||||||
msgid "log in"
|
msgid "log in"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:29
|
#: templates/home.html:26
|
||||||
msgid "can't remember your password?"
|
msgid "can't remember your password?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:36
|
#: templates/home.html:34 templates/home.html:42
|
||||||
msgid "or create a new one"
|
msgid "or create a new one"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/home.html:40
|
#: templates/home.html:38
|
||||||
msgid "let's get started"
|
msgid "let's get started"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -338,91 +433,91 @@ msgstr ""
|
||||||
msgid "Account manager"
|
msgid "Account manager"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:45 templates/settle_bills.html:4
|
#: templates/layout.html:39
|
||||||
msgid "Bills"
|
msgid "Bills"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:46 templates/settle_bills.html:5
|
#: templates/layout.html:40
|
||||||
msgid "Settle"
|
msgid "Settle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:53
|
#: templates/layout.html:41
|
||||||
|
msgid "Statistics"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/layout.html:48
|
||||||
msgid "options"
|
msgid "options"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:55
|
#: templates/layout.html:50
|
||||||
msgid "Project settings"
|
msgid "Project settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:59
|
#: templates/layout.html:54
|
||||||
msgid "switch to"
|
msgid "switch to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:62
|
#: templates/layout.html:57
|
||||||
msgid "Start a new project"
|
msgid "Start a new project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:64
|
#: templates/layout.html:59
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:92
|
#: templates/layout.html:66
|
||||||
|
msgid "Dashboard"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/layout.html:89
|
||||||
msgid "This is a free software"
|
msgid "This is a free software"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/layout.html:92
|
#: templates/layout.html:89
|
||||||
msgid "you can contribute and improve it!"
|
msgid "you can contribute and improve it!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:74
|
#: templates/list_bills.html:63
|
||||||
|
msgid "deactivate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/list_bills.html:70
|
||||||
msgid "reactivate"
|
msgid "reactivate"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:88
|
#: templates/list_bills.html:82
|
||||||
msgid "The project identifier is"
|
msgid "Invite people to join this project!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:88
|
#: templates/list_bills.html:83
|
||||||
msgid "remember it!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/list_bills.html:89
|
|
||||||
msgid "Add a new bill"
|
msgid "Add a new bill"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:103
|
#: templates/list_bills.html:101
|
||||||
msgid "When?"
|
msgid "When?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:103
|
#: templates/list_bills.html:101
|
||||||
msgid "Who paid?"
|
msgid "Who paid?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:103
|
#: templates/list_bills.html:101
|
||||||
msgid "For what?"
|
msgid "For what?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:103 templates/settle_bills.html:31
|
#: templates/list_bills.html:101 templates/settle_bills.html:22
|
||||||
msgid "How much?"
|
msgid "How much?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:103
|
#: templates/list_bills.html:109
|
||||||
msgid "Actions"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/list_bills.html:111
|
|
||||||
msgid "each"
|
msgid "each"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:113
|
#: templates/list_bills.html:120
|
||||||
msgid "edit"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/list_bills.html:122
|
|
||||||
msgid "Nothing to list yet. You probably want to"
|
msgid "Nothing to list yet. You probably want to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/list_bills.html:122
|
#: templates/list_bills.html:120
|
||||||
msgid "add a bill"
|
msgid "add a bill"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -434,43 +529,50 @@ msgstr ""
|
||||||
msgid "Your projects"
|
msgid "Your projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/send_invites.html:6
|
#: templates/reset_password.html:7
|
||||||
msgid "Invite people"
|
msgid "Reset your password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/send_invites.html:7
|
#: templates/send_invites.html:4
|
||||||
msgid "Use it!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/send_invites.html:11
|
|
||||||
msgid "Invite people to join this project"
|
msgid "Invite people to join this project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/send_invites.html:12
|
#: templates/send_invites.html:5
|
||||||
msgid ""
|
msgid ""
|
||||||
"Specify a (coma separated) list of email adresses you want to notify "
|
"Specify a (comma separated) list of email adresses you want to notify "
|
||||||
"about the\n"
|
"about the\n"
|
||||||
"creation of this budget management project and we will send them an email"
|
"creation of this budget management project and we will send them an email"
|
||||||
" for you."
|
" for you."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/send_invites.html:14
|
#: templates/send_invites.html:7
|
||||||
msgid "If you prefer, you can"
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/send_invites.html:14
|
#: templates/settle_bills.html:22
|
||||||
msgid "skip this step"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/send_invites.html:14
|
|
||||||
msgid "and notify them yourself"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: templates/settle_bills.html:31
|
|
||||||
msgid "Who pays?"
|
msgid "Who pays?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/settle_bills.html:31
|
#: templates/settle_bills.html:22
|
||||||
msgid "To whom?"
|
msgid "To whom?"
|
||||||
msgstr ""
|
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 ""
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,26 @@ class Project(db.Model):
|
||||||
|
|
||||||
return balances
|
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
|
@property
|
||||||
def uses_weights(self):
|
def uses_weights(self):
|
||||||
return len([i for i in self.members if i.weight != 1]) > 0
|
return len([i for i in self.members if i.weight != 1]) > 0
|
||||||
|
|
|
@ -11,7 +11,7 @@ from werkzeug.contrib.fixers import ProxyFix
|
||||||
|
|
||||||
from ihatemoney.api import api
|
from ihatemoney.api import api
|
||||||
from ihatemoney.models import db
|
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.web import main as web_interface
|
||||||
|
|
||||||
from ihatemoney import default_settings
|
from ihatemoney import default_settings
|
||||||
|
@ -68,6 +68,8 @@ def load_configuration(app, configuration=None):
|
||||||
app.config.from_pyfile(env_var_config)
|
app.config.from_pyfile(env_var_config)
|
||||||
else:
|
else:
|
||||||
app.config.from_pyfile('ihatemoney.cfg', silent=True)
|
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):
|
def validate_configuration(app):
|
||||||
|
|
|
@ -74,11 +74,13 @@ body {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: black;
|
color: black;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
padding-bottom: 4.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<table id="bill_table" class="table table-striped">
|
<table id="bill_table" class="table table-striped">
|
||||||
<thead><tr><th>{{ _("Project") }}</th><th>{{ _("Number of members") }}</th><th>{{ _("Number of bills") }}</th><th>{{_("Newest bill")}}</th><th>{{_("Oldest bill")}}</th><th>{{_("Actions")}}</th></tr></thead>
|
<thead><tr><th>{{ _("Project") }}</th><th>{{ _("Number of members") }}</th><th>{{ _("Number of bills") }}</th><th>{{_("Newest bill")}}</th><th>{{_("Oldest bill")}}</th><th>{{_("Actions")}}</th></tr></thead>
|
||||||
<tbody>{% for project in projects|sort(attribute='name') %}
|
<tbody>{% for project in projects|sort(attribute='name') %}
|
||||||
<tr class="{{ loop.cycle("odd", "even") }}">
|
<tr>
|
||||||
<td>{{ project.name }}</td><td>{{ project.members | count }}</td><td>{{ project.get_bills().count() }}</td>
|
<td>{{ project.name }}</td><td>{{ project.members | count }}</td><td>{{ project.get_bills().count() }}</td>
|
||||||
{% if project.has_bills() %}
|
{% if project.has_bills() %}
|
||||||
<td>{{ project.get_bills().all()[0].date }}</td>
|
<td>{{ project.get_bills().all()[0].date }}</td>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<thead><tr><th>{{ _("Who pays?") }}</th><th>{{ _("To whom?") }}</th><th>{{ _("How much?") }}</th></tr></thead>
|
<thead><tr><th>{{ _("Who pays?") }}</th><th>{{ _("To whom?") }}</th><th>{{ _("How much?") }}</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for bill in bills %}
|
{% for bill in bills %}
|
||||||
<tr class="{{ loop.cycle("odd", "even") }}" receiver={{bill.receiver.id}}>
|
<tr receiver={{bill.receiver.id}}>
|
||||||
<td>{{ bill.ower }}</td>
|
<td>{{ bill.ower }}</td>
|
||||||
<td>{{ bill.receiver }}</td>
|
<td>{{ bill.receiver }}</td>
|
||||||
<td>{{ "%0.2f"|format(bill.amount) }}</td>
|
<td>{{ "%0.2f"|format(bill.amount) }}</td>
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
<div id="table_overflow">
|
<div id="table_overflow">
|
||||||
<table class="balance table">
|
<table class="balance table">
|
||||||
{% set balance = g.project.balance %}
|
{% for stat in members_stats| sort(attribute='member.name') %}
|
||||||
{% for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id]|round(2) != 0 %}
|
<tr>
|
||||||
<tr id="bal-member-{{ member.id }}" action={% if member.activated %}delete{% else %}reactivate{% endif %}>
|
<td class="balance-name">{{ stat.member.name }}</td>
|
||||||
<td class="balance-name">{{ member.name }}</td>
|
<td class="balance-value {% if stat.balance|round(2) > 0 %}positive{% elif stat.balance|round(2) < 0 %}negative{% endif %}">
|
||||||
<td class="balance-value {% if balance[member.id]|round(2) > 0 %}positive{% elif balance[member.id]|round(2) < 0 %}negative{% endif %}">
|
{% if stat.balance|round(2) > 0 %}+{% endif %}{{ "%.2f" | format(stat.balance) }}
|
||||||
{% if balance[member.id]|round(2) > 0 %}+{% endif %}{{ "%.2f" | format(balance[member.id]) }}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -21,12 +20,12 @@
|
||||||
<table id="bill_table" class="split_bills table table-striped">
|
<table id="bill_table" class="split_bills table table-striped">
|
||||||
<thead><tr><th>{{ _("Who?") }}</th><th>{{ _("Paid") }}</th><th>{{ _("Spent") }}</th><th>{{ _("Balance") }}</th></tr></thead>
|
<thead><tr><th>{{ _("Who?") }}</th><th>{{ _("Paid") }}</th><th>{{ _("Spent") }}</th><th>{{ _("Balance") }}</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for member in members %}
|
{% for stat in members_stats %}
|
||||||
<tr class="{{ loop.cycle("odd", "even") }}">
|
<tr>
|
||||||
<td>{{ member.name }}</td>
|
<td>{{ stat.member.name }}</td>
|
||||||
<td>{{ "%0.2f"|format(paid[member.id]) }}</td>
|
<td>{{ "%0.2f"|format(stat.paid) }}</td>
|
||||||
<td>{{ "%0.2f"|format(spent[member.id]) }}</td>
|
<td>{{ "%0.2f"|format(stat.spent) }}</td>
|
||||||
<td>{{ "%0.2f"|format(balance[member.id]) }}</td>
|
<td>{{ "%0.2f"|format(stat.balance) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1053,7 +1053,7 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
})
|
})
|
||||||
|
|
||||||
self.assertTrue(400, resp.status_code)
|
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'))
|
resp.data.decode('utf-8'))
|
||||||
|
|
||||||
# create it
|
# create it
|
||||||
|
@ -1139,7 +1139,7 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
headers=self.get_auth("raclette"))
|
headers=self.get_auth("raclette"))
|
||||||
|
|
||||||
self.assertStatus(200, req)
|
self.assertStatus(200, req)
|
||||||
self.assertEqual('[]', req.data.decode('utf-8'))
|
self.assertEqual('[]\n', req.data.decode('utf-8'))
|
||||||
|
|
||||||
# add a member
|
# add a member
|
||||||
req = self.client.post("/api/projects/raclette/members", data={
|
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
|
# the id of the new member should be returned
|
||||||
self.assertStatus(201, req)
|
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
|
# the list of members should contain one member
|
||||||
req = self.client.get("/api/projects/raclette/members",
|
req = self.client.get("/api/projects/raclette/members",
|
||||||
|
@ -1223,7 +1223,7 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
headers=self.get_auth("raclette"))
|
headers=self.get_auth("raclette"))
|
||||||
|
|
||||||
self.assertStatus(200, req)
|
self.assertStatus(200, req)
|
||||||
self.assertEqual('[]', req.data.decode('utf-8'))
|
self.assertEqual('[]\n', req.data.decode('utf-8'))
|
||||||
|
|
||||||
def test_bills(self):
|
def test_bills(self):
|
||||||
# create a project
|
# create a project
|
||||||
|
@ -1239,7 +1239,7 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
headers=self.get_auth("raclette"))
|
headers=self.get_auth("raclette"))
|
||||||
self.assertStatus(200, req)
|
self.assertStatus(200, req)
|
||||||
|
|
||||||
self.assertEqual("[]", req.data.decode('utf-8'))
|
self.assertEqual("[]\n", req.data.decode('utf-8'))
|
||||||
|
|
||||||
# add a bill
|
# add a bill
|
||||||
req = self.client.post("/api/projects/raclette/bills", data={
|
req = self.client.post("/api/projects/raclette/bills", data={
|
||||||
|
@ -1252,7 +1252,7 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
|
|
||||||
# should return the id
|
# should return the id
|
||||||
self.assertStatus(201, req)
|
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
|
# get this bill details
|
||||||
req = self.client.get("/api/projects/raclette/bills/1",
|
req = self.client.get("/api/projects/raclette/bills/1",
|
||||||
|
@ -1288,7 +1288,7 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
}, headers=self.get_auth("raclette"))
|
}, headers=self.get_auth("raclette"))
|
||||||
|
|
||||||
self.assertStatus(400, req)
|
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
|
# edit a bill
|
||||||
req = self.client.put("/api/projects/raclette/bills/1", data={
|
req = self.client.put("/api/projects/raclette/bills/1", data={
|
||||||
|
@ -1325,6 +1325,40 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
headers=self.get_auth("raclette"))
|
headers=self.get_auth("raclette"))
|
||||||
self.assertStatus(404, req)
|
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):
|
def test_username_xss(self):
|
||||||
# create a project
|
# create a project
|
||||||
# self.api_create("raclette")
|
# self.api_create("raclette")
|
||||||
|
|
Binary file not shown.
|
@ -2,293 +2,289 @@
|
||||||
# Copyright (C) 2011 ORGANIZATION
|
# Copyright (C) 2011 ORGANIZATION
|
||||||
# This file is distributed under the same license as the PROJECT project.
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
# Alexis Métaireau <alexis@notmyidea.org>, 2011.
|
# Alexis Métaireau <alexis@notmyidea.org>, 2011.
|
||||||
#
|
# Adrien CLERC, 2018.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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"
|
"PO-Revision-Date: 2018-05-15 22:00+0200\n"
|
||||||
"Last-Translator: Quentin Roy <royque@gmail.com>\n"
|
"Last-Translator: Adrien CLERC <>\n"
|
||||||
"Language-Team: fr <LL@li.org>\n"
|
"Language-Team: fr <LL@li.org>\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
"Language: fr\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 0.9.6\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:22
|
#: forms.py:46
|
||||||
msgid "Select all"
|
|
||||||
msgstr "Tout cocher"
|
|
||||||
|
|
||||||
#: forms.py:22
|
|
||||||
msgid "Select none"
|
|
||||||
msgstr "Tout décocher"
|
|
||||||
|
|
||||||
#: forms.py:61
|
|
||||||
msgid "Project name"
|
msgid "Project name"
|
||||||
msgstr "Nom de projet"
|
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"
|
msgid "Private code"
|
||||||
msgstr "Code d'accès"
|
msgstr "Code d’accès"
|
||||||
|
|
||||||
#: forms.py:63
|
#: forms.py:48
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr "Email"
|
||||||
|
|
||||||
#: forms.py:85 forms.py:101 forms.py:107
|
#: forms.py:70 forms.py:87 forms.py:98
|
||||||
msgid "Project identifier"
|
msgid "Project identifier"
|
||||||
msgstr "Identifiant du projet"
|
msgstr "Identifiant du projet"
|
||||||
|
|
||||||
#: forms.py:87
|
#: forms.py:72
|
||||||
msgid "Admin password"
|
|
||||||
msgstr "Mot de passe administrateur"
|
|
||||||
|
|
||||||
#: forms.py:87 templates/send_invites.html:5
|
|
||||||
msgid "Create the project"
|
msgid "Create the project"
|
||||||
msgstr "Créer le projet"
|
msgstr "Créer le projet"
|
||||||
|
|
||||||
#: forms.py:92
|
#: forms.py:77
|
||||||
msgid ""
|
msgid ""
|
||||||
"The project identifier is used to log in and for the URL of the project. "
|
"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 "
|
"We tried to generate an identifier for you but a project with this "
|
||||||
"identifier already exists. Please create a new identifier that you will "
|
"identifier already exists. Please create a new identifier that you will "
|
||||||
"be able to remember."
|
"be able to remember"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"L'identifiant du projet est utilisé pour se connecter."
|
"L’identifiant du projet est utilisé pour se connecter et pour l’URL du "
|
||||||
"Nous avons essayé de générer un identifiant mais "
|
"projet. Nous avons essayé de générer un identifiant mais celui ci existe "
|
||||||
"celui ci existe déjà. Merci de créer un nouvel identifiant que vous serez"
|
"déjà. Merci de créer un nouvel identifiant que vous serez capable de retenir"
|
||||||
" capable de retenir"
|
|
||||||
|
|
||||||
#: forms.py:103
|
#: forms.py:89 forms.py:94
|
||||||
msgid "Get in"
|
msgid "Get in"
|
||||||
msgstr "Entrer"
|
msgstr "Entrer"
|
||||||
|
|
||||||
#: forms.py:107
|
#: forms.py:93
|
||||||
msgid "Password mismatch"
|
msgid "Admin password"
|
||||||
msgstr "Les mots de passe fournis ne sont pas les mêmes."
|
msgstr "Mot de passe administrateur"
|
||||||
|
|
||||||
#: forms.py:109
|
#: forms.py:99
|
||||||
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"
|
msgid "Send me the code by email"
|
||||||
msgstr "Envoyez moi le code par email"
|
msgstr "Envoyez moi le code par email"
|
||||||
|
|
||||||
#: forms.py:112
|
#: forms.py:103
|
||||||
msgid "This project does not exists"
|
msgid "This project does not exists"
|
||||||
msgstr "Ce projet n'existe pas"
|
msgstr "Ce projet n’existe pas"
|
||||||
|
|
||||||
#: forms.py:116
|
#: forms.py:108
|
||||||
|
msgid "Password mismatch"
|
||||||
|
msgstr "Les mots de passe sont différents"
|
||||||
|
|
||||||
|
#: forms.py:109
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "Mot de passe"
|
||||||
|
|
||||||
|
#: forms.py:110
|
||||||
|
msgid "Password confirmation"
|
||||||
|
msgstr "Confirmation du mot de passe"
|
||||||
|
|
||||||
|
#: forms.py:111
|
||||||
|
msgid "Reset password"
|
||||||
|
msgstr "Réinitialiser le mot de passe"
|
||||||
|
|
||||||
|
#: forms.py:115
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Date"
|
msgstr "Date"
|
||||||
|
|
||||||
#: forms.py:117
|
#: forms.py:116
|
||||||
msgid "What?"
|
msgid "What?"
|
||||||
msgstr "Quoi ?"
|
msgstr "Quoi ?"
|
||||||
|
|
||||||
#: forms.py:118
|
#: forms.py:117
|
||||||
msgid "Payer"
|
msgid "Payer"
|
||||||
msgstr "Payeur"
|
msgstr "Payeur"
|
||||||
|
|
||||||
#: forms.py:119
|
#: forms.py:118
|
||||||
msgid "Amount paid"
|
msgid "Amount paid"
|
||||||
msgstr "Montant"
|
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?"
|
msgid "For whom?"
|
||||||
msgstr "Pour qui ?"
|
msgstr "Pour qui ?"
|
||||||
|
|
||||||
#: forms.py:122
|
#: forms.py:121
|
||||||
msgid "Submit"
|
msgid "Submit"
|
||||||
msgstr "Valider"
|
msgstr "Valider"
|
||||||
|
|
||||||
#: forms.py:123
|
#: forms.py:122
|
||||||
msgid "Submit and add a new one"
|
msgid "Submit and add a new one"
|
||||||
msgstr "Valider et ajouter une autre facture"
|
msgstr "Valider et ajouter une autre facture"
|
||||||
|
|
||||||
#: forms.py:149
|
#: forms.py:146
|
||||||
msgid "Bills can't be null"
|
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:154
|
#: forms.py:151
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: forms.py:155
|
#: forms.py:152
|
||||||
msgid "Weight"
|
msgid "Weight"
|
||||||
msgstr "Parts"
|
msgstr "Parts"
|
||||||
|
|
||||||
#: forms.py:155 templates/forms.html:95
|
#: forms.py:153 templates/forms.html:123
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr "Ajouter"
|
msgstr "Ajouter"
|
||||||
|
|
||||||
#: forms.py:163
|
#: forms.py:162
|
||||||
msgid "User name incorrect"
|
msgid "User name incorrect"
|
||||||
msgstr "Nom d'utilisateur incorrect"
|
msgstr "Nom d’utilisateur incorrect"
|
||||||
|
|
||||||
#: forms.py:167
|
#: forms.py:167
|
||||||
msgid "This project already have this member"
|
msgid "This project already have this member"
|
||||||
msgstr "Ce membre existe déjà pour ce projet"
|
msgstr "Ce membre existe déjà pour ce projet"
|
||||||
|
|
||||||
#: forms.py:178
|
#: forms.py:183
|
||||||
msgid "People to notify"
|
msgid "People to notify"
|
||||||
msgstr "Personnes à prévenir"
|
msgstr "Personnes à prévenir"
|
||||||
|
|
||||||
#: forms.py:179
|
#: forms.py:184
|
||||||
msgid "Send invites"
|
msgid "Send invites"
|
||||||
msgstr "Envoyer les invitations"
|
msgstr "Envoyer les invitations"
|
||||||
|
|
||||||
#: forms.py:185
|
#: forms.py:190
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The email %(email)s is not valid"
|
msgid "The email %(email)s is not valid"
|
||||||
msgstr "L'email %(email)s est invalide"
|
msgstr "L’email %(email)s est invalide"
|
||||||
|
|
||||||
#: forms.py:191
|
#: forms.py:196
|
||||||
msgid "Start date"
|
|
||||||
msgstr "Date de départ"
|
|
||||||
|
|
||||||
#: forms.py:192
|
|
||||||
msgid "End date"
|
|
||||||
msgstr "Date de fin"
|
|
||||||
|
|
||||||
#: forms.py:202
|
|
||||||
msgid "What do you want to download ?"
|
msgid "What do you want to download ?"
|
||||||
msgstr "Que voulez-vous télécharger ?"
|
msgstr "Que voulez-vous télécharger ?"
|
||||||
|
|
||||||
#: forms.py:205
|
#: forms.py:199
|
||||||
msgid "bills"
|
msgid "bills"
|
||||||
msgstr "factures"
|
msgstr "factures"
|
||||||
|
|
||||||
#: forms.py:205
|
#: forms.py:199
|
||||||
msgid "transactions"
|
msgid "transactions"
|
||||||
msgstr "remboursements"
|
msgstr "remboursements"
|
||||||
|
|
||||||
#: forms.py:206
|
#: forms.py:201
|
||||||
msgid "Export file format"
|
msgid "Export file format"
|
||||||
msgstr "Format du fichier d'export"
|
msgstr "Format du fichier d’export"
|
||||||
|
|
||||||
#: web.py:95
|
#: web.py:129
|
||||||
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
|
|
||||||
msgid "Too many failed login attempts, please retry later."
|
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: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
|
#, python-format
|
||||||
msgid "You have just created '%(project)s' to share your expenses"
|
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:165
|
#: web.py:260
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(msg_compl)sThe project identifier is %(project)s"
|
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:185
|
#: web.py:281
|
||||||
msgid "A link to reset your password has been sent to your email."
|
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."
|
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 "Aucun token n’a été fourni"
|
||||||
|
|
||||||
|
#: 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"
|
msgid "Project successfully deleted"
|
||||||
msgstr "Projet supprimé"
|
msgstr "Projet supprimé"
|
||||||
|
|
||||||
#: web.py:254
|
#: web.py:401
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You have been invited to share your expenses for %(project)s"
|
msgid "You have been invited to share your expenses for %(project)s"
|
||||||
msgstr "Vous avez été invité à partager vos dépenses pour %(project)s"
|
msgstr "Vous avez été invité à partager vos dépenses pour %(project)s"
|
||||||
|
|
||||||
#: web.py:259
|
#: web.py:408
|
||||||
#, 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
|
|
||||||
msgid "Your invitations have been sent"
|
msgid "Your invitations have been sent"
|
||||||
msgstr "Vos invitations ont bien été envoyées"
|
msgstr "Vos invitations ont bien été envoyées"
|
||||||
|
|
||||||
#: web.py:290
|
#: web.py:439
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(member)s had been added"
|
msgid "%(member)s had been added"
|
||||||
msgstr "%(member)s a bien été ajouté"
|
msgstr "%(member)s a bien été ajouté"
|
||||||
|
|
||||||
#: web.py:303
|
#: web.py:452
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(name)s is part of this project again"
|
msgid "%(name)s is part of this project again"
|
||||||
msgstr "%(name)s a rejoint le projet"
|
msgstr "%(name)s a rejoint le projet"
|
||||||
|
|
||||||
#: web.py:312
|
#: web.py:461
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "User '%(name)s' has been deactivated"
|
msgid ""
|
||||||
msgstr "Le membre '%(name)s' a été désactivé"
|
"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 son solde soit nul."
|
||||||
|
|
||||||
#: web.py:314
|
#: web.py:465
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "User '%(name)s' has been deactivated. It will still appear in the users list until its balance becomes zero."
|
msgid "User '%(name)s' has been removed"
|
||||||
msgstr "Le membre '%(name)s' a été désactivé. Il continuera d'apparaître jusqu'à ce que sa balance devienne égale à zéro."
|
msgstr "Le membre « %(name)s » a été supprimé"
|
||||||
|
|
||||||
#: web.py:331
|
#: web.py:480
|
||||||
|
#, python-format
|
||||||
|
msgid "User '%(name)s' has been edited"
|
||||||
|
msgstr "Le membre « %(name)s » a été édité"
|
||||||
|
|
||||||
|
#: web.py:500
|
||||||
msgid "The bill has been added"
|
msgid "The bill has been added"
|
||||||
msgstr "La facture a bien été ajoutée"
|
msgstr "La facture a bien été ajoutée"
|
||||||
|
|
||||||
#: web.py:351
|
#: web.py:520
|
||||||
msgid "The bill has been deleted"
|
msgid "The bill has been deleted"
|
||||||
msgstr "La facture a été supprimée"
|
msgstr "La facture a été supprimée"
|
||||||
|
|
||||||
#: web.py:369
|
#: web.py:538
|
||||||
msgid "The bill has been modified"
|
msgid "The bill has been modified"
|
||||||
msgstr "La facture a été modifiée"
|
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"
|
msgid "Back to the list"
|
||||||
msgstr "Retourner à la liste"
|
msgstr "Retourner à la liste"
|
||||||
|
|
||||||
#: templates/authenticate.html:6
|
#: templates/admin.html:10
|
||||||
msgid ""
|
msgid "Administration tasks are currently disabled."
|
||||||
"The project you are trying to access do not exist, do you want to"
|
msgstr "Les tâches d’administration sont actuellement désactivées."
|
||||||
msgstr "Le projet auquel vous essayez d'acceder n'existe pas. Souhaitez vous"
|
|
||||||
|
|
||||||
#: templates/authenticate.html:7
|
#: 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’accéder n’existe pas, souhaitez vous"
|
||||||
|
|
||||||
|
#: templates/authenticate.html:8
|
||||||
msgid "create it"
|
msgid "create it"
|
||||||
msgstr "le créer"
|
msgstr "le créer"
|
||||||
|
|
||||||
#: templates/authenticate.html:7
|
#: templates/authenticate.html:8
|
||||||
msgid "?"
|
msgid "?"
|
||||||
msgstr " ?"
|
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
|
#: templates/create_project.html:4
|
||||||
msgid "Create a new project"
|
msgid "Create a new project"
|
||||||
|
@ -314,72 +310,85 @@ msgstr "Facture la plus récente"
|
||||||
msgid "Oldest bill"
|
msgid "Oldest bill"
|
||||||
msgstr "Facture la plus ancienne"
|
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
|
#: templates/dashboard.html:25
|
||||||
msgid "The Dashboard is currently deactivated."
|
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
|
#: templates/edit_project.html:6 templates/list_bills.html:24
|
||||||
msgid "you sure?"
|
msgid "you sure?"
|
||||||
msgstr "c'est sûr ?"
|
msgstr "c’est sûr ?"
|
||||||
|
|
||||||
#: templates/edit_project.html:11
|
#: templates/edit_project.html:11
|
||||||
msgid "Edit this project"
|
msgid "Edit this project"
|
||||||
msgstr "Éditer ce projet"
|
msgstr "Éditer ce projet"
|
||||||
|
|
||||||
#: templates/forms.html:23
|
#: templates/edit_project.html:15
|
||||||
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
|
|
||||||
msgid "Download this project's data"
|
msgid "Download this project's data"
|
||||||
msgstr "Télécharger les données de ce projet"
|
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"
|
msgid "Download"
|
||||||
msgstr "Télécharger"
|
msgstr "Télécharger"
|
||||||
|
|
||||||
#: templates/home.html:8
|
#: templates/home.html:7
|
||||||
msgid "Manage your shared <br>expenses, easily"
|
msgid "Manage your shared <br>expenses, easily"
|
||||||
msgstr "Gérez vos dépenses <br>partagées, facilement"
|
msgstr "Gérez vos dépenses <br>partagées, facilement"
|
||||||
|
|
||||||
|
@ -387,199 +396,230 @@ msgstr "Gérez vos dépenses<br> partagées, facilement"
|
||||||
msgid "Try out the demo"
|
msgid "Try out the demo"
|
||||||
msgstr "Essayez la démo"
|
msgstr "Essayez la démo"
|
||||||
|
|
||||||
#: templates/home.html:12
|
#: templates/home.html:13
|
||||||
msgid "You're sharing a house?"
|
msgid "You're sharing a house?"
|
||||||
msgstr "Vous êtes en colocation ?"
|
msgstr "Vous êtes en colocation ?"
|
||||||
|
|
||||||
#: templates/home.html:12
|
#: templates/home.html:13
|
||||||
msgid "Going on holidays with friends?"
|
msgid "Going on holidays with friends?"
|
||||||
msgstr "Partez en vacances avec des amis ?"
|
msgstr "Partez en vacances avec des amis ?"
|
||||||
|
|
||||||
#: templates/home.html:12
|
#: templates/home.html:13
|
||||||
msgid "Simply sharing money with others?"
|
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:12
|
#: templates/home.html:13
|
||||||
msgid "We can help!"
|
msgid "We can help!"
|
||||||
msgstr "On peut vous aider !"
|
msgstr "On peut vous aider !"
|
||||||
|
|
||||||
#: templates/home.html:24
|
#: templates/home.html:21
|
||||||
msgid "Log to an existing project"
|
msgid "Log to an existing project"
|
||||||
msgstr "Se connecter à un projet existant"
|
msgstr "Se connecter à un projet existant"
|
||||||
|
|
||||||
#: templates/home.html:28
|
#: templates/home.html:25
|
||||||
msgid "log in"
|
msgid "log in"
|
||||||
msgstr "se connecter"
|
msgstr "se connecter"
|
||||||
|
|
||||||
#: templates/home.html:29
|
#: templates/home.html:26
|
||||||
msgid "can't remember your password?"
|
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:36
|
#: templates/home.html:34 templates/home.html:42
|
||||||
msgid "or create a new one"
|
msgid "or create a new one"
|
||||||
msgstr "ou créez en un nouveau"
|
msgstr "ou créez en un nouveau"
|
||||||
|
|
||||||
#: templates/home.html:40
|
#: templates/home.html:38
|
||||||
msgid "let's get started"
|
msgid "let's get started"
|
||||||
msgstr "c'est parti !"
|
msgstr "c’est parti !"
|
||||||
|
|
||||||
#: templates/home.html:51
|
#: templates/home.html:51
|
||||||
msgid ""
|
msgid ""
|
||||||
"This access code will be sent to your friends. It is stored as-is by the "
|
"This access code will be sent to your friends. It is stored as-is by the "
|
||||||
"server, so don\\'t reuse a personal password!"
|
"server, so don\\'t reuse a personal password!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Ce code d\\'accès va être envoyé à vos amis et stocké en clair sur le "
|
"Ce code d’accès va être envoyé à vos amis et stocké en clair sur le serveur. "
|
||||||
"serveur.N\\'utilisez pas un mot de passe personnel !"
|
"N’utilisez pas un mot de passe personnel !"
|
||||||
|
|
||||||
#: templates/layout.html:5
|
#: templates/layout.html:5
|
||||||
msgid "Account manager"
|
msgid "Account manager"
|
||||||
msgstr "Gestion de comptes"
|
msgstr "Gestion de comptes"
|
||||||
|
|
||||||
#: templates/layout.html:45 templates/settle_bills.html:4
|
#: templates/layout.html:39
|
||||||
msgid "Bills"
|
msgid "Bills"
|
||||||
msgstr "Factures"
|
msgstr "Factures"
|
||||||
|
|
||||||
#: templates/layout.html:46 templates/settle_bills.html:5
|
#: templates/layout.html:40
|
||||||
msgid "Settle"
|
msgid "Settle"
|
||||||
msgstr "Remboursements"
|
msgstr "Remboursements"
|
||||||
|
|
||||||
#: templates/layout.html:50
|
#: templates/layout.html:41
|
||||||
msgid "Statistics"
|
msgid "Statistics"
|
||||||
msgstr "Statistiques"
|
msgstr "Statistiques"
|
||||||
|
|
||||||
#: templates/layout.html:53
|
#: templates/layout.html:48
|
||||||
msgid "options"
|
msgid "options"
|
||||||
msgstr "options"
|
msgstr "options"
|
||||||
|
|
||||||
#: templates/layout.html:55
|
#: templates/layout.html:50
|
||||||
msgid "Project settings"
|
msgid "Project settings"
|
||||||
msgstr "Options du projet"
|
msgstr "Options du projet"
|
||||||
|
|
||||||
#: templates/layout.html:59
|
#: templates/layout.html:54
|
||||||
msgid "switch to"
|
msgid "switch to"
|
||||||
msgstr "aller à"
|
msgstr "aller à"
|
||||||
|
|
||||||
#: templates/layout.html:62
|
#: templates/layout.html:57
|
||||||
msgid "Start a new project"
|
msgid "Start a new project"
|
||||||
msgstr "Nouveau projet"
|
msgstr "Nouveau projet"
|
||||||
|
|
||||||
#: templates/layout.html:64
|
#: templates/layout.html:59
|
||||||
msgid "Logout"
|
msgid "Logout"
|
||||||
msgstr "Se déconnecter"
|
msgstr "Se déconnecter"
|
||||||
|
|
||||||
#: templates/layout.html:92
|
#: templates/layout.html:66
|
||||||
|
msgid "Dashboard"
|
||||||
|
msgstr "Tableau de bord"
|
||||||
|
|
||||||
|
#: templates/layout.html:89
|
||||||
msgid "This is a free software"
|
msgid "This is a free software"
|
||||||
msgstr "Ceci est un logiciel libre"
|
msgstr "Ceci est un logiciel libre"
|
||||||
|
|
||||||
#: templates/layout.html:92
|
#: templates/layout.html:89
|
||||||
msgid "you can contribute and improve it!"
|
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:74
|
#: templates/list_bills.html:63
|
||||||
|
msgid "deactivate"
|
||||||
|
msgstr "désactiver"
|
||||||
|
|
||||||
|
#: templates/list_bills.html:70
|
||||||
msgid "reactivate"
|
msgid "reactivate"
|
||||||
msgstr "ré-activer"
|
msgstr "ré-activer"
|
||||||
|
|
||||||
#: templates/list_bills.html:88
|
#: templates/list_bills.html:82
|
||||||
msgid "Invite"
|
|
||||||
msgstr "Invitez"
|
|
||||||
|
|
||||||
#: templates/list_bills.html:88
|
|
||||||
msgid "Invite people to join this project!"
|
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:89
|
#: templates/list_bills.html:83
|
||||||
msgid "Add a new bill"
|
msgid "Add a new bill"
|
||||||
msgstr "Nouvelle facture"
|
msgstr "Nouvelle facture"
|
||||||
|
|
||||||
#: templates/list_bills.html:103
|
#: templates/list_bills.html:101
|
||||||
msgid "When?"
|
msgid "When?"
|
||||||
msgstr "Quand ?"
|
msgstr "Quand ?"
|
||||||
|
|
||||||
#: templates/list_bills.html:103
|
#: templates/list_bills.html:101
|
||||||
msgid "Who paid?"
|
msgid "Who paid?"
|
||||||
msgstr "Qui a payé ?"
|
msgstr "Qui a payé ?"
|
||||||
|
|
||||||
#: templates/list_bills.html:103
|
#: templates/list_bills.html:101
|
||||||
msgid "For what?"
|
msgid "For what?"
|
||||||
msgstr "Pour quoi ?"
|
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?"
|
msgid "How much?"
|
||||||
msgstr "Combien ?"
|
msgstr "Combien ?"
|
||||||
|
|
||||||
#: templates/list_bills.html:103
|
#: templates/list_bills.html:109
|
||||||
msgid "Actions"
|
|
||||||
msgstr "Actions"
|
|
||||||
|
|
||||||
#: templates/list_bills.html:111
|
|
||||||
msgid "each"
|
msgid "each"
|
||||||
msgstr "chacun"
|
msgstr "chacun"
|
||||||
|
|
||||||
#: templates/list_bills.html:113
|
#: templates/list_bills.html:120
|
||||||
msgid "edit"
|
|
||||||
msgstr "éditer"
|
|
||||||
|
|
||||||
#: templates/list_bills.html:122
|
|
||||||
msgid "Nothing to list yet. You probably want to"
|
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:122
|
#: templates/list_bills.html:120
|
||||||
msgid "add a bill"
|
msgid "add a bill"
|
||||||
msgstr "ajouter une facture"
|
msgstr "ajouter une facture"
|
||||||
|
|
||||||
#: templates/password_reminder.html:4
|
#: templates/password_reminder.html:4
|
||||||
msgid "Password reminder"
|
msgid "Password reminder"
|
||||||
msgstr "Rappel du code d'accès"
|
msgstr "Rappel du code d’accès"
|
||||||
|
|
||||||
#: templates/recent_projects.html:2
|
#: templates/recent_projects.html:2
|
||||||
msgid "Your projects"
|
msgid "Your projects"
|
||||||
msgstr "Vos projets"
|
msgstr "Vos projets"
|
||||||
|
|
||||||
#: templates/reset_password.html:2
|
#: templates/reset_password.html:7
|
||||||
msgid "Reset your password"
|
msgid "Reset your password"
|
||||||
msgstr "Changez votre mot de passe"
|
msgstr "Changez votre mot de passe"
|
||||||
|
|
||||||
#: templates/send_invites.html:11
|
#: templates/send_invites.html:4
|
||||||
msgid "Invite people to join this project"
|
msgid "Invite people to join this project"
|
||||||
msgstr "Invitez des personnes à rejoindre ce projet"
|
msgstr "Invitez des personnes à rejoindre ce projet"
|
||||||
|
|
||||||
#: templates/send_invites.html:12
|
#: templates/send_invites.html:5
|
||||||
msgid ""
|
msgid ""
|
||||||
"Specify a (comma separated) list of email adresses you want to notify "
|
"Specify a (comma separated) list of email adresses you want to notify "
|
||||||
"about the\n"
|
"about the\n"
|
||||||
"creation of this budget management project and we will send them an email"
|
"creation of this budget management project and we will send them an email"
|
||||||
" for you."
|
" for you."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Entrez les addresses des personnes que vous souhaitez inviter, séparées "
|
"Entrez les adresses des personnes que vous souhaitez inviter,\n"
|
||||||
"par des virgules. On s'occupe de leur envoyer un email."
|
"séparées par des virgules, on s’occupe de leur envoyer un email."
|
||||||
|
|
||||||
#: templates/send_invites.html:14
|
#: templates/send_invites.html:7
|
||||||
msgid "If you prefer, you can share the project identifier and the shared\n"
|
msgid ""
|
||||||
"password by other communication means. Or even directly share the following link:"
|
"If you prefer, you can share the project identifier and the shared\n"
|
||||||
msgstr "Si vous préférez vous pouvez partager l'identifiant du projet et son mot "
|
"password by other communication means. Or even directly share the "
|
||||||
"de passe par un autre moyen de communication. Ou directement partager le lien "
|
"following link:"
|
||||||
"suivant :"
|
msgstr ""
|
||||||
|
"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:31
|
#: templates/settle_bills.html:22
|
||||||
msgid "Who pays?"
|
msgid "Who pays?"
|
||||||
msgstr "Qui doit payer ?"
|
msgstr "Qui doit payer ?"
|
||||||
|
|
||||||
#: templates/settle_bills.html:31
|
#: templates/settle_bills.html:22
|
||||||
msgid "To whom?"
|
msgid "To whom?"
|
||||||
msgstr "Pour qui ?"
|
msgstr "Pour qui ?"
|
||||||
|
|
||||||
#: templates/statistics.html:22
|
#: templates/statistics.html:21
|
||||||
msgid "Who?"
|
msgid "Who?"
|
||||||
msgstr "Qui ?"
|
msgstr "Qui ?"
|
||||||
|
|
||||||
#: templates/statistics.html:22
|
#: templates/statistics.html:21
|
||||||
msgid "Paid"
|
msgid "Paid"
|
||||||
msgstr "A payé"
|
msgstr "A payé"
|
||||||
|
|
||||||
#: templates/statistics.html:22
|
#: templates/statistics.html:21
|
||||||
msgid "Spent"
|
msgid "Spent"
|
||||||
msgstr "A dépensé"
|
msgstr "A dépensé"
|
||||||
|
|
||||||
#: templates/statistics.html:22
|
#: templates/statistics.html:21
|
||||||
msgid "Balance"
|
msgid "Balance"
|
||||||
msgstr "Solde"
|
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"
|
||||||
|
|
|
@ -3,7 +3,7 @@ import re
|
||||||
|
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
import jinja2
|
import jinja2
|
||||||
from json import dumps
|
from json import dumps, JSONEncoder
|
||||||
from flask import redirect
|
from flask import redirect
|
||||||
from werkzeug.routing import HTTPException, RoutingException
|
from werkzeug.routing import HTTPException, RoutingException
|
||||||
import six
|
import six
|
||||||
|
@ -185,3 +185,28 @@ def create_jinja_env(folder, strict_rendering=False):
|
||||||
if strict_rendering:
|
if strict_rendering:
|
||||||
kwargs['undefined'] = jinja2.StrictUndefined
|
kwargs['undefined'] = jinja2.StrictUndefined
|
||||||
return jinja2.Environment(**kwargs)
|
return jinja2.Environment(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
|
@ -566,21 +566,9 @@ def settle_bill():
|
||||||
@main.route("/<project_id>/statistics")
|
@main.route("/<project_id>/statistics")
|
||||||
def statistics():
|
def statistics():
|
||||||
"""Compute what each member has paid and spent and display it"""
|
"""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])
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"statistics.html",
|
"statistics.html",
|
||||||
members=members,
|
members_stats=g.project.members_stats,
|
||||||
balance=balance,
|
|
||||||
paid=paid,
|
|
||||||
spent=spent,
|
|
||||||
current_view='statistics',
|
current_view='statistics',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ flask-mail>=0.8
|
||||||
Flask-Migrate>=1.8.0
|
Flask-Migrate>=1.8.0
|
||||||
Flask-script
|
Flask-script
|
||||||
flask-babel
|
flask-babel
|
||||||
flask-rest>=1.3
|
flask-restful>=0.3.6
|
||||||
jinja2>=2.6
|
jinja2>=2.6
|
||||||
raven
|
raven
|
||||||
blinker
|
blinker
|
||||||
|
|
21
setup.py
21
setup.py
|
@ -2,16 +2,6 @@
|
||||||
import codecs
|
import codecs
|
||||||
import os
|
import os
|
||||||
from setuptools import setup, find_packages
|
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__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
@ -23,6 +13,13 @@ def read_file(filename):
|
||||||
return content
|
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')
|
README = read_file('README.rst')
|
||||||
CHANGELOG = read_file('CHANGELOG.rst')
|
CHANGELOG = read_file('CHANGELOG.rst')
|
||||||
|
|
||||||
|
@ -37,7 +34,7 @@ ENTRY_POINTS = {
|
||||||
|
|
||||||
|
|
||||||
setup(name='ihatemoney',
|
setup(name='ihatemoney',
|
||||||
version='2.1.dev0',
|
version='2.1.1.dev0',
|
||||||
description='A simple shared budget manager web application.',
|
description='A simple shared budget manager web application.',
|
||||||
long_description="{}\n\n{}".format(README.encode('utf-8'), CHANGELOG.encode('utf-8')),
|
long_description="{}\n\n{}".format(README.encode('utf-8'), CHANGELOG.encode('utf-8')),
|
||||||
license='Custom BSD Beerware',
|
license='Custom BSD Beerware',
|
||||||
|
@ -59,5 +56,5 @@ setup(name='ihatemoney',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=install_requires,
|
install_requires=parse_requirements('requirements.txt'),
|
||||||
entry_points=ENTRY_POINTS)
|
entry_points=ENTRY_POINTS)
|
||||||
|
|
Loading…
Reference in a new issue