Package: django-pint-field
django-pint-fieldUsepintwith Django's ORM.If you want to store quantities (1 gram, 3 miles, 8.120391 angstroms, etc) in a model, edit them in forms, and have the ability to convert to other quantities in your django projects, this is the package for you!This package is modified from the fantasticdjango-pintwith different goals. Unlike django-pint, in this project we use a composite Postgres field to store both the magnitude and the user's desired units, along with the equivalent value in base units. This third piece of date - the base units - makes it possible to conduct lookups comparing one instance that might be specified in "grams" with another that may be specified in "pounds", but display each instance in the units that the user desires. The units your users want to use are the units they see, while still allowing accurate comparisons of one quantity to another.For this reason, the project only works with Postgresql databases.Installpip install django_pint_fieldAdd"django_pint_field",to your list of installed apps.Runpython manage.py migrate django_pint_fieldUsageAssuming we are starting with the following model:fromdecimalimportDecimalfromdjango.dbimportmodelsfromdjango.db.modelsimportDecimalFieldfromdjango_pint_field.modelsimportIntegerPintFieldclassIntegerPintFieldSaveModel(models.Model):name=models.CharField(max_length=20)weight=IntegerPintField("gram")def__str__(self):returnstr(self.name)We can do the following:fromdecimalimportDecimalfromdjango_pint_field.unitsimporturegfrom.modelsimportIntegerPintFieldSaveModelQuantity=ureg.Quantity# Start by creating a few Pint Quantity objectsextra_small=Quantity(1*ureg.gram)small=Quantity(10*ureg.gram)medium=Quantity(100*ureg.gram)large=Quantity(1000*ureg.gram)extra_large=Quantity(10000*ureg.gram)# Create a couple objectsIntegerPintFieldSaveModel.objects.create(name="small",weight=small)IntegerPintFieldSaveModel.objects.create(name="large",weight=large)In[1]:IntegerPintFieldSaveModel.objects.first()Out[1]:<IntegerPintFieldSaveModel:small>In[2]:IntegerPintFieldSaveModel.objects.first().weightOut[2]:10<Unit('gram')>In[3]:IntegerPintFieldSaveModel.objects.first().weight.magnitudeOut[3]:10In[4]:IntegerPintFieldSaveModel.objects.first().weight.unitsOut[4]:<Unit('gram')>Valid LookupsOther lookups will be added in the future. Currently available are:exactgtgteltlterangeisnull# Perform some queriesIntegerPintFieldSaveModel.objects.filter(weight__gt=medium)<QuerySet[<IntegerPintFieldSaveModel:large>]>IntegerPintFieldSaveModel.objects.filter(weight__gt=extra_small)<QuerySet[<IntegerPintFieldSaveModel:small>,<IntegerPintFieldSaveModel:large>]>IntegerPintFieldSaveModel.objects.filter(weight__gte=small)<QuerySet[<IntegerPintFieldSaveModel:small>,<IntegerPintFieldSaveModel:large>]>IntegerPintFieldSaveModel.objects.filter(weight__range=(small,medium))<QuerySet[<IntegerPintFieldSaveModel:small>]>AggregatesA number of aggregates have been implemented for the Django Pint Fields. Functionally they perform for Pint Fields the same way django's default aggregates work for other field types, and each is prepended with "Pint". The aggregates include:PintAvgPintCountPintMaxPintMinPintStdDevPintSumPintVariancefromdjango_pint_field.aggregatesimportPintAvg,PintCount,PintMax,PintMin,PintStdDev,PintSum,PintVariance# Perform some queriesIntegerPintFieldSaveModel.objects.aggregate(PintAvg('weight')){'weight__pintavg':Decimal('0.50500000000000000000')<Unit('kilogram')>}IntegerPintFieldSaveModel.objects.aggregate(PintCount('weight')){'weight__pintcount':2}IntegerPintFieldSaveModel.objects.aggregate(PintMax('weight')){'weight__pintmax':Decimal('1.0')<Unit('kilogram')>}IntegerPintFieldSaveModel.objects.aggregate(PintMin('weight')){'weight__pintmin':Decimal('0.01')<Unit('kilogram')>}IntegerPintFieldSaveModel.objects.aggregate(PintStdDev('weight')){'weight__pintstddev':Decimal('0.49500000000000000000')<Unit('kilogram')>}IntegerPintFieldSaveModel.objects.aggregate(PintSum('weight')){'weight__pintsum':Decimal('1.01')<Unit('kilogram')>}IntegerPintFieldSaveModel.objects.aggregate(PintVariance('weight')){'weight__pintvariance':Decimal('0.24502500000000000000')<Unit('kilogram')>}Use with Django Rest Frameworkfromdjango_pint_field.restimportIntegerPintRestFieldclassDjangoPintFieldSerializer(serializers.ModelSerializer):# Let DRF know which type of serializer field to useweight=IntegerPintRestField()classMeta:model=IntegerPintFieldSaveModelfields=["name","weight"]Creating your own unitsYou cancreate your own pint unitsif thedefault unitsin pint are not sufficient.Anywhere within your project (ideally in settings or a file adjacent to settings), define the custom unit registry by importing Pint's default UnitRegistry and extending it:frompintimportUnitRegistrycustom_ureg=UnitRegistry()custom_ureg.define("custom = [custom]")custom_ureg.define("kilocustom = 1000 * custom")Then add the custom registry to your app's settings.py:DJANGO_PINT_FIELD_UNIT_REGISTER = custom_uregModel FieldsIntegerPintField: Stores a pint measurement as an integer (-2147483648 to 2147483647).BigIntegerPintField: Stores a pint measurement as a big integer (-9223372036854775808 to 9223372036854775807).DecimalPintField: Stores a pint measurement as a decimal. Like Django's DecimalField, DecimalPintField takes requiredmax_digitsanddecimal_placesparameters.Form FieldsIntegerPintFormField: Used in forms with IntegerPintField and BigIntegerPintField.DecimalPintFormField: Used in forms with DecimalPintField.WidgetsPintFieldWidget: Default widget for all django pint field types.TabledPintFieldWidget: Provides a table showing conversion to each of theunit_choices.Example usage:classWeightModel(models.Model):decimal_weight=DecimalPintField("gram",blank=True,null=True,max_digits=10,decimal_places=2,unit_choices=["kilogram","milligram","pounds"],)classTabledWeightForm(forms.ModelForm):classMeta:model=WeightModelfields="__all__"def__init__(self,*args,**kwargs):super().__init__(*args,**kwargs)# If we want to use the tabled widget, we need to pass the#   default_unit and unit_choices to the new widgetdefault_unit=self.fields["decimal_weight"].default_unitunit_choices=self.fields["decimal_weight"].unit_choicesself.fields["decimal_weight"].widget=TabledPintFieldWidget(default_unit=default_unit,unit_choices=unit_choices)The template for this widget is located athttps://github.com/jacklinke/django-pint-field/blob/main/django_pint_field/templates/tabled_django_pint_field_widget.htmlIf you want to override the template, add your own template in your project at: "templates/django_pint_field/tabled_django_pint_field_widget.html"DRF Serializer FieldsIntegerPintRestField: Used in DRF with IntegerPintField and BigIntegerPintField.DecimalPintRestField: Used in DRF with DecimalPintField.SettingsDJANGO_PINT_FIELD_DECIMAL_PRECISIONDetermines whether django_pint_field should automatically set the python decimal precision for the project. If an integer greater than 0 is provided, the decimal context precision for the entire project will be set to that value. Otherwise, the precision remains at the default (usually 28).* Type: int* Default: 0DJANGO_PINT_FIELD_UNIT_REGISTERThe Unit Registry to use in the project. Defaults to pint.UnitRegistry.* Type: int* Default: 0Rounding modes (upcoming feature)decimal.ROUND_CEILINGRound towards Infinity.decimal.ROUND_DOWNRound towards zero.decimal.ROUND_FLOORRound towards -Infinity.decimal.ROUND_HALF_DOWNRound to nearest with ties going towards zero.decimal.ROUND_HALF_EVENRound to nearest with ties going to nearest even integer.decimal.ROUND_HALF_UPRound to nearest with ties going away from zero.decimal.ROUND_UPRound away from zero.decimal.ROUND_05UPRound away from zero if last digit after rounding towards zero would have been 0 or 5; otherwise round towards zero.Read more about rounding modes for decimals at thedecimal docsUse the test app with docker composeSet up a virtual environment (recommended)Make a virtual environment named.venv:python3 -m venv .venvActivate the new environment:source .venv/bin/activateInstall dependenciesRequirespoetry. If you do not yet have it installed, runpip install poetry.poetry install --with devBuild and bring updocker compose build
docker compose run django python manage.py migrate
docker compose run django python manage.py createsuperuser
docker compose up -dNavigate to127.0.0.1:8000Test (assuming you have already performedbuild)docker compose run django python manage.py testRun psql on the Postgres databasedocker compose exec postgres psql -U postgresToDos:Implement rounding modesThink through what it would take to build range types for these fieldsAdd extensible widget template