barbacompta/eo_gestion/eo_facture/fields.py

147 lines
4.8 KiB
Python

# barbacompta - invoicing for dummies
# Copyright (C) 2019-2020 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from decimal import Decimal, InvalidOperation
from django import forms
from django.core import validators
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.six import text_type
from django.utils.translation import ugettext_lazy as _
class PercentagePerYear(list):
def __init__(self, sequence):
super().__init__(sequence)
def __str__(self):
return ','.join(map(lambda p: ':'.join(map(str, [p[0], int(100 * p[1])])), self))
class EuroField(models.DecimalField):
def __init__(self, *args, **kwargs):
kwargs["max_digits"] = 8
kwargs["decimal_places"] = 2
super().__init__(*args, **kwargs)
def assertion_error_to_validation_error(fun):
def f(*args, **kwargs):
try:
return fun(*args, **kwargs)
except (AssertionError, ValueError) as e:
raise ValidationError(*e.args)
return f
@assertion_error_to_validation_error
def check_percentage_per_year(value):
years = [a for a, b in value]
percentages = [b for a, b in value]
# ordered
assert years == sorted(years), "years are not ordered"
# sum equals 100
assert sum(percentages) == 1, "percentage does not sum to 100"
# no duplicate year
assert len(years) == len(set(years)), "years are not unique"
# consecutive
assert years == list(range(years[0], years[0] + len(years))), 'years are not consecutive'
def parse_percentage_per_year(value):
msg = _("field must be numeric values separated by commas")
values = value.split(",")
decimals = []
for value in values:
try:
year, decimal = value.split(":")
except ValueError:
raise ValidationError(msg)
try:
year = int(year.strip())
except ValueError:
raise ValidationError(msg)
try:
decimal = Decimal(decimal.strip()) / 100
except InvalidOperation:
raise ValidationError(msg)
decimals.append((year, decimal))
return PercentagePerYear(decimals)
class PercentagePerYearFormField(forms.Field):
default_error_messages = {
'invalid': _('field must be numeric values separated by commas'),
}
def to_python(self, value):
if value in validators.EMPTY_VALUES:
return None
if isinstance(PercentagePerYear, value):
return value
if not isinstance(text_type, value):
raise ValidationError(self.default_error_messages["invalid"])
return parse_percentage_per_year(value)
class PercentagePerYearField(models.Field):
default_validators = [check_percentage_per_year]
def __init__(self, *args, **kwargs):
kwargs["max_length"] = 64
super().__init__(*args, **kwargs)
def from_db_value(self, value, expression, connection, context):
return self.to_python(value)
def to_python(self, value):
if isinstance(value, PercentagePerYear):
return value
if value is not None:
percentage_per_year = []
pairs = value.split(",")
for pair in pairs:
try:
year, percentage = map(value.__class__.strip, pair.split(":"))
except ValueError:
raise ValidationError(PercentagePerYearFormField.default_error_messages["invalid"])
year = int(year)
percentage = Decimal(percentage) / Decimal(100)
percentage_per_year.append((year, percentage))
try:
return PercentagePerYear(percentage_per_year)
except forms.ValidationError:
return value
return value
def get_prep_value(self, value):
if isinstance(value, PercentagePerYear):
return text_type(value)
elif value is not None:
return text_type(parse_percentage_per_year(value))
return value
def get_internal_type(self):
return "CharField"
def formfield(self, **kwargs):
defaults = {"form_class": PercentagePerYearFormField}
defaults.update(kwargs)
return super().formfield(**kwargs)