diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a1d8b751..37d7dcbc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,10 +8,16 @@ This document describes changes between each past release. Added ===== + - Add CORS headers in the API (#407) - Document database migrations (#390) - Allow basic math operations in amount field (#413) +Fixed +===== + +- Do not allow negative weights on users (#366) + 3.0 (2018-11-25) ---------------- diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index 5374fd9f..be04c8f8 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -2,7 +2,7 @@ from flask_wtf.form import FlaskForm from wtforms.fields.core import SelectField, SelectMultipleField from wtforms.fields.html5 import DateField, DecimalField from wtforms.fields.simple import PasswordField, SubmitField, TextAreaField, StringField -from wtforms.validators import Email, Required, ValidationError, EqualTo +from wtforms.validators import Email, Required, ValidationError, EqualTo, NumberRange from flask_babel import lazy_gettext as _ from flask import request from werkzeug.security import generate_password_hash @@ -174,9 +174,11 @@ class BillForm(FlaskForm): class MemberForm(FlaskForm): - name = StringField(_("Name"), validators=[Required()]) - weight = CommaDecimalField(_("Weight"), default=1) + + weight_validators = [NumberRange(min=0.1, message=_("Weights should be positive"))] + weight = CommaDecimalField(_("Weight"), default=1, + validators=weight_validators) submit = SubmitField(_("Add")) def __init__(self, project, edit=False, *args, **kwargs): diff --git a/ihatemoney/messages.pot b/ihatemoney/messages.pot index ba81d225..fb69539e 100644 --- a/ihatemoney/messages.pot +++ b/ihatemoney/messages.pot @@ -79,6 +79,9 @@ msgstr "" msgid "Weight" msgstr "" +msgid "Weights should be positive" +msgstr "" + msgid "Add" msgstr "" diff --git a/ihatemoney/migrations/versions/a67119aa3ee5_migrate_negative_weights.py b/ihatemoney/migrations/versions/a67119aa3ee5_migrate_negative_weights.py new file mode 100644 index 00000000..ec234706 --- /dev/null +++ b/ihatemoney/migrations/versions/a67119aa3ee5_migrate_negative_weights.py @@ -0,0 +1,38 @@ +"""Migrate negative weights + +Revision ID: a67119aa3ee5 +Revises: afbf27e6ef20 +Create Date: 2018-12-25 18:34:20.220844 + +""" + +# revision identifiers, used by Alembic. +revision = 'a67119aa3ee5' +down_revision = 'afbf27e6ef20' + +from alembic import op +import sqlalchemy as sa +# Snapshot of the person table +person_helper = sa.Table( + 'person', sa.MetaData(), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('project_id', sa.String(length=64), nullable=True), + sa.Column('name', sa.UnicodeText(), nullable=True), + sa.Column('activated', sa.Boolean(), nullable=True), + sa.Column('weight', sa.Float(), nullable=True), + sa.ForeignKeyConstraint(['project_id'], ['project.id'], ), + sa.PrimaryKeyConstraint('id') +) + + +def upgrade(): + op.execute( + person_helper.update() + .where(person_helper.c.weight <= 0) + .values(weight=1) + ) + + +def downgrade(): + # Downgrade path is not possible, because information has been lost. + pass diff --git a/ihatemoney/models.py b/ihatemoney/models.py index 3c36e76c..b9cff4f8 100644 --- a/ihatemoney/models.py +++ b/ihatemoney/models.py @@ -376,8 +376,9 @@ class Bill(db.Model): def pay_each(self): """Compute what each share has to pay""" if self.owers: - # FIXME: SQL might dot that more efficiently - return self.amount / sum(i.weight for i in self.owers) + # FIXME: SQL might do that more efficiently + weights = sum(i.weight for i in self.owers) + return self.amount / weights else: return 0 diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index 9f9d8fab..d29ec628 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -621,6 +621,18 @@ class BudgetTestCase(IhatemoneyTestCase): resp = self.client.get("/raclette/") self.assertNotIn('extra-info', resp.data.decode('utf-8')) + def test_negative_weight(self): + self.post_project("raclette") + + # Add one user and edit it to have a negative share + self.client.post("/raclette/members/add", data={'name': 'alexis'}) + resp = self.client.post("/raclette/members/1/edit", data={'name': 'alexis', 'weight': -1}) + + # An error should be generated, and its weight should still be 1. + self.assertIn('

', resp.data.decode('utf-8')) + self.assertEqual(len(models.Project.query.get('raclette').members), 1) + self.assertEqual(models.Project.query.get('raclette').members[0].weight, 1) + def test_rounding(self): self.post_project("raclette") diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.po b/ihatemoney/translations/fr/LC_MESSAGES/messages.po index 2516d568..d610145f 100644 --- a/ihatemoney/translations/fr/LC_MESSAGES/messages.po +++ b/ihatemoney/translations/fr/LC_MESSAGES/messages.po @@ -105,6 +105,9 @@ msgstr "Nom" msgid "Weight" msgstr "Parts" +msgid "Weights should be positive" +msgstr "Les parts doivent ĂȘtre positives" + msgid "Add" msgstr "Ajouter"