Skip to content

Commit 67a8fca

Browse files
committed
Correct payment_per_time field definition - see HEA-572
payment_per_time was defined as a PositiveSmallIntegerField, but some data contains values to large for a small integer, and it is possible that for OtherCashIncome the payment_per_time could be a non-integer. Therefore, change to a FloatField with a validator to ensure the value is positive.
1 parent c934b0a commit 67a8fca

File tree

4 files changed

+96
-6
lines changed

4 files changed

+96
-6
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Generated by Django 5.1.1 on 2024-11-22 21:21
2+
3+
from django.db import migrations, models
4+
5+
import common.models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("baseline", "0017_alter_livelihoodactivity_percentage_kcals"),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name="livelihoodactivity",
17+
name="price",
18+
field=models.FloatField(
19+
blank=True,
20+
help_text="Price per unit",
21+
null=True,
22+
validators=[common.models.validate_positive],
23+
verbose_name="Price",
24+
),
25+
),
26+
migrations.AlterField(
27+
model_name="othercashincome",
28+
name="payment_per_time",
29+
field=models.FloatField(
30+
blank=True,
31+
help_text="Amount of money received each time the labor is performed",
32+
null=True,
33+
validators=[common.models.validate_positive],
34+
verbose_name="Payment per time",
35+
),
36+
),
37+
migrations.AlterField(
38+
model_name="paymentinkind",
39+
name="payment_per_time",
40+
field=models.FloatField(
41+
blank=True,
42+
help_text="Amount of item received each time the labor is performed",
43+
null=True,
44+
validators=[common.models.validate_positive],
45+
verbose_name="Payment per time",
46+
),
47+
),
48+
]

apps/baseline/models.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,13 @@ class LivelihoodActivity(common_models.Model):
11021102
# but there are exceptions, such as MilkProduction, where there is also an amount used for ButterProduction
11031103
quantity_consumed = models.PositiveIntegerField(blank=True, null=True, verbose_name=_("Quantity Consumed"))
11041104

1105-
price = models.FloatField(blank=True, null=True, verbose_name=_("Price"), help_text=_("Price per unit"))
1105+
price = models.FloatField(
1106+
blank=True,
1107+
null=True,
1108+
validators=[common_models.validate_positive],
1109+
verbose_name=_("Price"),
1110+
help_text=_("Price per unit"),
1111+
)
11061112
# Can be calculated / validated as `quantity_sold * price` for livelihood strategies that involve the sale of
11071113
# a proportion of the household's own production.
11081114
income = models.FloatField(blank=True, null=True, help_text=_("Income"))
@@ -1584,8 +1590,12 @@ class PaymentInKind(LivelihoodActivity):
15841590
"""
15851591

15861592
# Production calculation/validation is `people_per_household * times_per_month * months_per_year`
1587-
payment_per_time = models.PositiveSmallIntegerField(
1588-
verbose_name=_("Payment per time"), help_text=_("Amount of item received each time the labor is performed")
1593+
payment_per_time = models.FloatField(
1594+
blank=True,
1595+
null=True, # Not required if people_per_household or times_per_month is null
1596+
validators=[common_models.validate_positive],
1597+
verbose_name=_("Payment per time"),
1598+
help_text=_("Amount of item received each time the labor is performed"),
15891599
)
15901600
people_per_household = models.PositiveSmallIntegerField(
15911601
verbose_name=_("People per household"), help_text=_("Number of household members who perform the labor")
@@ -1601,6 +1611,14 @@ class PaymentInKind(LivelihoodActivity):
16011611
help_text=_("Number of times in a year that the labor is performed"),
16021612
)
16031613

1614+
def clean(self):
1615+
# payment_per_time is only required if people_per_household and times_per_month are provided
1616+
if self.people_per_household and self.times_per_month and not self.payment_per_time:
1617+
raise ValidationError(
1618+
_("Payment per time is required if people per household and times per month are provided")
1619+
)
1620+
super().clean()
1621+
16041622
def validate_quantity_produced(self):
16051623
if (
16061624
self.quantity_produced
@@ -1711,8 +1729,12 @@ class OtherCashIncome(LivelihoodActivity):
17111729
# However, some other income (e.g. Remittances) just has a number of times per year and is not calculated from
17121730
# people_per_household, etc. Therefore those fields must be nullable, and we must store the total number of times
17131731
# per year as a separate field
1714-
payment_per_time = models.PositiveSmallIntegerField(
1715-
verbose_name=_("Payment per time"), help_text=_("Amount of money received each time the labor is performed")
1732+
payment_per_time = models.FloatField(
1733+
blank=True,
1734+
null=True, # Not required if people_per_household or times_per_month is null
1735+
validators=[common_models.validate_positive],
1736+
verbose_name=_("Payment per time"),
1737+
help_text=_("Amount of money received each time the labor is performed"),
17161738
)
17171739
people_per_household = models.PositiveSmallIntegerField(
17181740
verbose_name=_("People per household"),
@@ -1734,6 +1756,14 @@ class OtherCashIncome(LivelihoodActivity):
17341756
help_text=_("Number of times in a year that the income is received"),
17351757
)
17361758

1759+
def clean(self):
1760+
# payment_per_time is only required if people_per_household and times_per_month are provided
1761+
if self.people_per_household and self.times_per_month and not self.payment_per_time:
1762+
raise ValidationError(
1763+
_("Payment per time is required if people per household and times per month are provided")
1764+
)
1765+
super().clean()
1766+
17371767
def validate_income(self):
17381768
if (
17391769
self.people_per_household

apps/common/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@
3232
logger = logging.getLogger(__name__)
3333

3434

35+
def validate_positive(value):
36+
"""
37+
Validator to ensure that a value is positive.
38+
39+
The normal models.validators.MinValueValidator(0) isn't suitable because it allows zero,
40+
which isn't appropriate for some attributes, such as price.
41+
"""
42+
if value < 0:
43+
raise ValidationError(_("Value must be greater than 0."))
44+
45+
3546
class ShowQueryVariablesMixin(object):
3647
"""
3748
Mixin for models.Manager classes that shows the query arguments when a get() query fails

pipelines/assets/livelihood_activity.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,8 @@ def get_instances_from_dataframe(
520520
# that is embedded in the ButterProduction calculations in current BSSs
521521
livelihood_activity["type_of_milk_consumed"] = MilkProduction.MilkType.WHOLE
522522

523-
# Add the `times_per_year` to FoodPurchase, because it is not present in any current BSS
523+
# Add the `times_per_year` to FoodPurchase, PaymentInKind and OtherCashIncome,
524+
# because it is not in the current BSSs
524525
if (
525526
"times_per_month" in livelihood_strategy["attribute_rows"]
526527
and "months_per_year" in livelihood_strategy["attribute_rows"]

0 commit comments

Comments
 (0)