From 65eb48698bfe455b9ef34d753d3d372da92f6ed9 Mon Sep 17 00:00:00 2001 From: Maximilian Friedersdorff Date: Wed, 19 Mar 2025 10:10:21 +0000 Subject: [PATCH 1/6] Document the intrinsic units of the database field --- docs/topics/storing.rst | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/docs/topics/storing.rst b/docs/topics/storing.rst index 43b9db8..913fa0f 100644 --- a/docs/topics/storing.rst +++ b/docs/topics/storing.rst @@ -53,4 +53,34 @@ that the unit is abstracted to the measure's standard unit for storage and compa How is this data stored? ------------------------ -Since django-measurement v2.0 there value will be stored in a single float field. +Since django-measurement v2.0 the value will be stored in a single float field. +This is of course unit less, but the value stored will be interpreted to be +in the ``STANDARD_UNIT`` of the ``measurement.measures`` class. + +For example, for a measure that looks like so:: + + class Production(MeasureBase): + STANDARD_UNIT = "g" + UNITS = { + "g": 1.0, + "t": 1000000.0, + "lb": 453.59237, + "cwt": 45359.237, + } + ALIAS = { + "gram": "g", + "metric tonne": "t", + "metric ton": "t", + "tonne": "t", + "pound": "lb", + "hundredweight": "cwt", + "short hundredweight": "cwt", + } + SI_UNITS = ["g"] + +the value will be stored in the database in grams, because that is the +``STANDARD_UNIT``. + +For a ``BidimensionalMeasure`` the value in the database is stored in units +that correspond to the ``STANDARD_UNIT`` of the ``PRIMARY_DIMENSION`` over the +``STANDARD_UNIT`` of the ``REFERENCE_DIMENSION``. From 50e1bb4be28ec3aeeacceff794e5f409ed660e83 Mon Sep 17 00:00:00 2001 From: Maximilian Friedersdorff Date: Wed, 19 Mar 2025 10:11:39 +0000 Subject: [PATCH 2/6] Remove python2 print statements --- docs/topics/storing.rst | 10 +++++----- docs/topics/use.rst | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/topics/storing.rst b/docs/topics/storing.rst index 913fa0f..717fe65 100644 --- a/docs/topics/storing.rst +++ b/docs/topics/storing.rst @@ -28,7 +28,7 @@ you'd add it to your log like so:: beer.volume = Volume(us_pint=1) beer.save() - print beer # '1 us_pint of Total Domination' + print(beer) # '1 us_pint of Total Domination' Perhaps you next recklessly dove into your stash of terrible, but nostalgia-inducing Russian beer and had a half-liter of @@ -40,14 +40,14 @@ you'd add it to your log like so:: another_beer.volume = Volume(l=0.5) another_beer.save() - print beer # '0.5 l of #9' + print(beer) # '0.5 l of #9' Note that although the original unit specified is stored for display, that the unit is abstracted to the measure's standard unit for storage and comparison:: - print beer.volume # '1 us_pint' - print another_beer.volume # '0.5 l' - print beer.volume > another_beer.volume # False + print(beer.volume) # '1 us_pint' + print(another_beer.volume) # '0.5 l' + print(beer.volume > another_beer.volume) # False How is this data stored? diff --git a/docs/topics/use.rst b/docs/topics/use.rst index cb0bb7a..3b3d76c 100644 --- a/docs/topics/use.rst +++ b/docs/topics/use.rst @@ -8,8 +8,8 @@ and use it for easily handling measurements like so:: from measurement.measures import Weight w = Weight(lb=135) # Represents 135lbs - print w # '135.0 lb' - print w.kg # '61.234919999999995' + print(w) # '135.0 lb' + print(w.kg) # '61.234919999999995' See `Python-measurement's documentation `_ for more information about interacting with measurements. From 8e30ef2aa8a025eedd820b36177cdadea5e974b5 Mon Sep 17 00:00:00 2001 From: Maximilian Friedersdorff Date: Wed, 19 Mar 2025 10:23:11 +0000 Subject: [PATCH 3/6] Document converting to MeasurementField --- docs/converting.rst | 90 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 docs/converting.rst diff --git a/docs/converting.rst b/docs/converting.rst new file mode 100644 index 0000000..33e2f41 --- /dev/null +++ b/docs/converting.rst @@ -0,0 +1,90 @@ +Converting existing Fields to MeasurementField +============================================== + +Field is already in the 'Standard' Unit +--------------------------------------- + +This is the trivial case. We can just replace the existing field with a +measurement field:: + + from django_measurement.models import MeasurementField + from measurement.measures import Volume + from django.db import models + + class BeerConsumptionLogEntry(models.Model): + name = models.CharField(max_length=255) + volume = models.FloatField( + help_text="Volume of beer consumed in litres" + ) # Field to be converted + + def __str__(self): + return '%s of %s' % (self.name, self.volume) + +goes to:: + + from django_measurement.models import MeasurementField + from measurement.measures import Volume + from django.db import models + + class BeerConsumptionLogEntry(models.Model): + name = models.CharField(max_length=255) + volume = MeasurementField(measurement=Volume) + + def __str__(self): + return '%s of %s' % (self.name, self.volume) + +Because ``Volume`` uses the litre as its ``STANDARD_UNIT`` everything is happy. +Run ``makemigrations`` and everyone is happy. + +Field in non 'Standard' Units +----------------------------- + +In this case it's necessary to first add a data migration that converts the +field into standard units. Say we have a model like this:: + + from django_measurement.models import MeasurementField + from measurement.measures import Volume + from django.db import models + + class BeerConsumptionLogEntry(models.Model): + name = models.CharField(max_length=255) + volume = models.FloatField( + help_text="Volume of beer consumed in hectolitres" + ) # Field to be converted + + def __str__(self): + return '%s of %s' % (self.name, self.volume) + +We first need to convert the volume field to liters like so:: + + litres_per_hectolitre = 100 + + def convert_field(apps, _): + BeerConsumptionLogEntry = apps.get_model("beer", "BeerConsumptionLogEntry") + + BeerConsumptionLogEntry.all_objects.update( + volume=Subquery( + BeerConsumptionLogEntry.objects.filter(id=OuterRef("id")) + .annotate(converted_volume=F("volume") * litres_per_hectolitre) + .values("converted_volume")[:1] + ) + ) + + + def convert_field_reverse(apps, _): + BeerConsumptionLogEntry = apps.get_model("beer", "BeerConsumptionLogEntry") + + BeerConsumptionLogEntry.all_objects.update( + volume=Subquery( + BeerConsumptionLogEntry.objects.filter(id=OuterRef("id")) + .annotate(converted_volume=F("volume") / litres_per_hectolitre) + .values("converted_volume")[:1] + ) + ) + +And then hook those two functions up into a RunPython migration as per usual. +Having an actual reverse migration here is critical. It is also be critical +that it is legitimately the inverse. Multiplying and or dividing by the exact +same constant ensures this. + +Now you can just convert the field to a measurement field as above. From 9a84e0f0adea0711215d1600af396a7e7b901373 Mon Sep 17 00:00:00 2001 From: Maximilian Friedersdorff Date: Wed, 19 Mar 2025 10:26:10 +0000 Subject: [PATCH 4/6] Move converting.rst to topics --- docs/{ => topics}/converting.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{ => topics}/converting.rst (100%) diff --git a/docs/converting.rst b/docs/topics/converting.rst similarity index 100% rename from docs/converting.rst rename to docs/topics/converting.rst From 7d0999929bace99925c68437e7a8f975519912c8 Mon Sep 17 00:00:00 2001 From: Maximilian Friedersdorff Date: Wed, 19 Mar 2025 10:27:35 +0000 Subject: [PATCH 5/6] Mark doc as not executable (!) --- docs/topics/forms.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 docs/topics/forms.rst diff --git a/docs/topics/forms.rst b/docs/topics/forms.rst old mode 100755 new mode 100644 From 7c9324bb9537461491a5bcce938541a74714720a Mon Sep 17 00:00:00 2001 From: Maximilian Friedersdorff Date: Wed, 19 Mar 2025 10:29:58 +0000 Subject: [PATCH 6/6] Name topics so index order is explicit --- docs/topics/{installation.rst => 00-installation.rst} | 0 docs/topics/{forms.rst => 01-forms.rst} | 0 docs/topics/{measures.rst => 02-measures.rst} | 0 docs/topics/{settings.rst => 03-settings.rst} | 0 docs/topics/{storing.rst => 04-storing.rst} | 0 docs/topics/{converting.rst => 05-converting.rst} | 0 docs/topics/{use.rst => 06-use.rst} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename docs/topics/{installation.rst => 00-installation.rst} (100%) rename docs/topics/{forms.rst => 01-forms.rst} (100%) rename docs/topics/{measures.rst => 02-measures.rst} (100%) rename docs/topics/{settings.rst => 03-settings.rst} (100%) rename docs/topics/{storing.rst => 04-storing.rst} (100%) rename docs/topics/{converting.rst => 05-converting.rst} (100%) rename docs/topics/{use.rst => 06-use.rst} (100%) diff --git a/docs/topics/installation.rst b/docs/topics/00-installation.rst similarity index 100% rename from docs/topics/installation.rst rename to docs/topics/00-installation.rst diff --git a/docs/topics/forms.rst b/docs/topics/01-forms.rst similarity index 100% rename from docs/topics/forms.rst rename to docs/topics/01-forms.rst diff --git a/docs/topics/measures.rst b/docs/topics/02-measures.rst similarity index 100% rename from docs/topics/measures.rst rename to docs/topics/02-measures.rst diff --git a/docs/topics/settings.rst b/docs/topics/03-settings.rst similarity index 100% rename from docs/topics/settings.rst rename to docs/topics/03-settings.rst diff --git a/docs/topics/storing.rst b/docs/topics/04-storing.rst similarity index 100% rename from docs/topics/storing.rst rename to docs/topics/04-storing.rst diff --git a/docs/topics/converting.rst b/docs/topics/05-converting.rst similarity index 100% rename from docs/topics/converting.rst rename to docs/topics/05-converting.rst diff --git a/docs/topics/use.rst b/docs/topics/06-use.rst similarity index 100% rename from docs/topics/use.rst rename to docs/topics/06-use.rst