From 57c4c8229ad5391d40f542f06405146a134b5907 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 14 Sep 2023 12:31:37 +0200 Subject: [PATCH 1/3] fix offset conversations i.e. temperature conversations, see https://pint.readthedocs.io/en/stable/user/nonmult.html closes #90 --- AUTHORS.rst | 1 + src/quantityfield/fields.py | 6 +-- tests/dummyapp/admin.py | 1 + .../0002_offsetunitfloatfieldsavemodel.py | 38 +++++++++++++++++++ tests/dummyapp/models.py | 4 ++ tests/test_field.py | 36 ++++++++++++++++++ 6 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 tests/dummyapp/migrations/0002_offsetunitfloatfieldsavemodel.py diff --git a/AUTHORS.rst b/AUTHORS.rst index b1c93cb..36b8f69 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -9,3 +9,4 @@ Contributors * Alex Bhandari * Jonas Haag * Igor Kozyrenko +* Samuel Scott Jennings diff --git a/src/quantityfield/fields.py b/src/quantityfield/fields.py index f0456b2..dbb0f1d 100644 --- a/src/quantityfield/fields.py +++ b/src/quantityfield/fields.py @@ -170,7 +170,7 @@ def value_to_string(self, obj) -> str: def from_db_value(self, value: Any, *args, **kwargs) -> Optional[Quantity]: if value is None: return None - return self.ureg.Quantity(value * getattr(self.ureg, self.base_units)) + return self.ureg.Quantity(value, getattr(self.ureg, self.base_units)) def to_python(self, value) -> Optional[Quantity]: if isinstance(value, Quantity): @@ -187,7 +187,7 @@ def to_python(self, value) -> Optional[Quantity]: value = cast(NUMBER_TYPE, to_number(value)) - return self.ureg.Quantity(value * getattr(self.ureg, self.base_units)) + return self.ureg.Quantity(value, getattr(self.ureg, self.base_units)) def clean(self, value, model_instance) -> Quantity: """ @@ -331,7 +331,7 @@ def clean(self, value): except (ValueError, TypeError): raise ValidationError(self.error_messages["invalid"], code="invalid") - val = self.ureg.Quantity(val * getattr(self.ureg, units)).to(self.base_units) + val = self.ureg.Quantity(val, getattr(self.ureg, units)).to(self.base_units) self.validate(val.magnitude) self.run_validators(val.magnitude) return val diff --git a/tests/dummyapp/admin.py b/tests/dummyapp/admin.py index 328c768..7a982e8 100644 --- a/tests/dummyapp/admin.py +++ b/tests/dummyapp/admin.py @@ -23,3 +23,4 @@ def get_readonly_fields(self, request, obj=None): admin.site.register(FloatFieldSaveModel, ReadOnlyEditing) admin.site.register(HayBale, ReadOnlyEditing) admin.site.register(IntFieldSaveModel, ReadOnlyEditing) +admin.site.register(OffsetUnitFloatFieldSaveModel, ReadOnlyEditing) diff --git a/tests/dummyapp/migrations/0002_offsetunitfloatfieldsavemodel.py b/tests/dummyapp/migrations/0002_offsetunitfloatfieldsavemodel.py new file mode 100644 index 0000000..9e1791e --- /dev/null +++ b/tests/dummyapp/migrations/0002_offsetunitfloatfieldsavemodel.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.5 on 2023-09-25 08:24 + +from django.db import migrations, models + +import quantityfield.fields + + +class Migration(migrations.Migration): + dependencies = [ + ("dummyapp", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="OffsetUnitFloatFieldSaveModel", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=20)), + ( + "weight", + quantityfield.fields.QuantityField( + base_units="degC", unit_choices=["degC"] + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/tests/dummyapp/models.py b/tests/dummyapp/models.py index 544e92b..ac6fbec 100644 --- a/tests/dummyapp/models.py +++ b/tests/dummyapp/models.py @@ -85,3 +85,7 @@ class ChoicesDefinedInModel(models.Model): class ChoicesDefinedInModelInt(models.Model): weight = IntegerQuantityField("kilogram", unit_choices=["milligram", "pounds"]) + + +class OffsetUnitFloatFieldSaveModel(FieldSaveModel): + weight = QuantityField("degC") diff --git a/tests/test_field.py b/tests/test_field.py index 8d0c402..8620258 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -35,6 +35,7 @@ FieldSaveModel, FloatFieldSaveModel, IntFieldSaveModel, + OffsetUnitFloatFieldSaveModel, ) Quantity = ureg.Quantity @@ -475,3 +476,38 @@ class TestIntFieldSave(IntLikeFieldSaveTestBase, TestCase): class TestBigIntFieldSave(IntLikeFieldSaveTestBase, TestCase): MODEL = BigIntFieldSaveModel + + +class TestOffsetUnitFieldSaveTestBase(FloatLikeFieldSaveTestBase, TestCase): + MODEL = OffsetUnitFloatFieldSaveModel + DEFAULT_WEIGHT_QUANTITY_STR = "100.0 degree_Celsius" + HEAVIEST = 1000 + LIGHTEST = 1 + FAHRENHEIT_VALUE = 212 # 100 celsius = 212 kelvin + COMPARE_QUANTITY = Quantity(100, ureg.fahrenheit) + + def test_value_conversion(self): + obj = self.MODEL.objects.first() + degF = obj.weight.to(ureg.fahrenheit) # weight is in celsius + self.assertAlmostEqual(degF.magnitude, self.FAHRENHEIT_VALUE) + self.assertEqual(degF.units, ureg.fahrenheit) + + def test_value_stored_as_quantity(self): + obj = self.MODEL.objects.first() + self.assertIsInstance(obj.weight, Quantity) + self.assertEqual(str(obj.weight), self.DEFAULT_WEIGHT_QUANTITY_STR) + + def test_stores_value_in_base_units(self): + self.MODEL.objects.create(weight=self.FAHRENHEIT_VALUE, name="fahrenheit") + item = self.MODEL.objects.get(name="fahrenheit") + self.assertEqual(item.weight.units, "degree_Celsius") + self.assertAlmostEqual(item.weight.magnitude, self.FAHRENHEIT_VALUE) + + def test_comparison_with_quantity(self): + weight = Quantity(20, ureg.celsius) + qs = self.MODEL.objects.filter(weight__gt=weight) + self.assertNotIn(self.lightest, qs) + + def test_comparison_with_quantity_respects_units(self): + qs = self.MODEL.objects.filter(weight__gt=self.COMPARE_QUANTITY) + self.assertNotIn(self.lightest, qs) From 726cddb4972adfb8e6e9b9a588b17f5d2df7f154 Mon Sep 17 00:00:00 2001 From: Carli* Freudenberg Date: Sun, 1 Oct 2023 23:55:29 +0200 Subject: [PATCH 2/3] tox: be verbose when testing --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5264dd7..a03889d 100644 --- a/tox.ini +++ b/tox.ini @@ -57,4 +57,4 @@ docker= postgres commands = - pytest -x {posargs} + pytest -vv {posargs} From 344207dd8a5cf84f9a95d20b7beac06a4d1ecddf Mon Sep 17 00:00:00 2001 From: Carli* Freudenberg Date: Sun, 1 Oct 2023 23:57:26 +0200 Subject: [PATCH 3/3] add/fix some comments --- tests/dummyapp/models.py | 2 ++ tests/test_field.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/dummyapp/models.py b/tests/dummyapp/models.py index ac6fbec..84ad15a 100644 --- a/tests/dummyapp/models.py +++ b/tests/dummyapp/models.py @@ -88,4 +88,6 @@ class ChoicesDefinedInModelInt(models.Model): class OffsetUnitFloatFieldSaveModel(FieldSaveModel): + # Note: This is a temperature not a weight. + # We wanted to reuse existing test cases inheritance weight = QuantityField("degC") diff --git a/tests/test_field.py b/tests/test_field.py index 8620258..39e584b 100644 --- a/tests/test_field.py +++ b/tests/test_field.py @@ -483,9 +483,12 @@ class TestOffsetUnitFieldSaveTestBase(FloatLikeFieldSaveTestBase, TestCase): DEFAULT_WEIGHT_QUANTITY_STR = "100.0 degree_Celsius" HEAVIEST = 1000 LIGHTEST = 1 - FAHRENHEIT_VALUE = 212 # 100 celsius = 212 kelvin + FAHRENHEIT_VALUE = 212 # 100 celsius = 212 fahrenheit COMPARE_QUANTITY = Quantity(100, ureg.fahrenheit) + # Note: weight here is a temperature, + # reused the field name to allow inheritance of float test cae + def test_value_conversion(self): obj = self.MODEL.objects.first() degF = obj.weight.to(ureg.fahrenheit) # weight is in celsius