Skip to main content

Django model field to allow storage of a distance (with units).

Project description

Squarehost Logo Django Distance Field

92% Unit Testing Coverage https://bitbucket.org/squarehost/django-distance-field.git


(C) 2018 Squarehost Ltd. http://www.squarehost.co.uk

Ian Shurmer (ian@squarehost.co.uk)


What is it?

Django Distance Field is a simple extension to Django's GIS fields to allow registration of a distance/measurement field within a model. Data within this field can then be converted using Django's "D" objects, and filtered using standard Django queryset functionality.

It doesn't need a GIS enabled database or extension installed. It should work with Django version 1.7 and above.

What does that mean?

Put simply, you can add a distance field which allows the user to enter a measurement using a large variety of units. This distance will then be converted to a standard unit format (for example, metres), and stored in the database. The user's specified units are also stored, and when the measurement retrieved, automatically converted into these to be displayed.

Eh?

Well, how about some psuedo-code? Here's how you might use the API:

In [1]: from mymodels.model import ModelWithWidthHeight

In [2]: from distance import D

In [3]: inst = ModelWithWidthHeight( )

In [4]: inst.width = "10m"

In [5]: inst.width
Out[5]: D(m=10.0)

In [6]: inst.height = "10in"

In [7]: inst.height
Out[7]: D(inch=10.0)

In [8]: inst.width > inst.height
Out[8]: True

In [9]: inst.height.m
Out[9]: 0.254

Is that it? Yawn...

Wait, there's more! You can also filter querysets directly using either D objects or strings, with all your usual Django API niceness:

In [1]: from mymodels.model import ModelWithWidthHeight

In [2]: from distance import D

In [3]: ModelWithWidthHeight.objects.filter(width__lte="10m")
Out[3]: <QuerySet [<ModelWithWidthHeight: ModelWithWidthHeight object (1)>]>

In [4]: ModelWithWidthHeight.objects.filter(width__lt="10m")
Out[4]: <QuerySet []>

In [5]: ModelWithWidthHeight.objects.filter(width__lte="1000cm")
Out[5]: <QuerySet [<ModelWithWidthHeight: ModelWithWidthHeight object (1)>]>

In [6]: ModelWithWidthHeight.objects.filter(width__lte="999cm")
Out[6]: <QuerySet []>

In [7]: ModelWithWidthHeight.objects.filter(width__lte="1000cm", height__gt=D(inch=9))
Out[7]: <QuerySet [<ModelWithWidthHeight: ModelWithWidthHeight object (1)>]>

Okay, that looks a little more useful. What about forms and widgets?

Using a DistanceField automatically results in a standard CharField being included within the form, BUT with relevant validators applied to ensure that the system recognises the units, and throws a ValidationError if not.

The field data is then automatically parsed to the D object and stored in the database accordingly.

What units do you recognise?

Along with Django's default units we have a couple of others thrown in (for example, Rack Units/U), meaning current support is as follows:

  • Chain, Chain Benoit, Chain Sears, British Chain Benoit, British Chain Sears, British Chain Sears Truncated, Cm, British Ft, British Yd, Clarke Ft, Clarke Link, Fathom, Ft, German M, Gold Coast Ft, Indian Yd, Inch, Km, Link, Link Benoit, Link Sears, M, Mi, Mm, Nm, Nm Uk, Rod, Sears Yd, Survey Ft, Um, Yd, U

You can also add units if you so desire - simply call the distance.register_units method with your unit alias as keyword arguments, and the number of units in metres as the keyword value. For example:

In [1]: import distance

In [2]: distance.register_units(my_unit=0.5, my_other_unit=2)

In [3]: distance.D(my_unit=10).m
Out[3]: 5.0

In [4]: distance.D(my_unit=10).inch
Out[4]: 196.8503937007874

In [5]: distance.D(my_other_unit=10).m
Out[5]: 20.0

You should probably do that somewhere like your app ready methods.

What about aliases? You say "inch", I say '"'

We have a variety of aliases already specified within the D class:

Actual Unit Alias
british_chain_benoit British chain (Benoit 1895 B)
british_chain_sears_truncated British chain (Sears 1922 truncated)
british_chain_sears British chain (Sears 1922)
british_ft British foot (Sears 1922)
british_ft British foot
british_yd British yard (Sears 1922)
british_yd British yard
chain_benoit Chain (Benoit)
chain_sears Chain (Sears)
clarke_ft Clarke's Foot
clarke_link Clarke's link
cm centimeter
ft '
ft Foot (International)
ft foot
german_m German legal metre
gold_coast_ft Gold Coast foot
inch "
inch inches
inch in
indian_yd Indian yard
indian_yd Yard (Indian)
km kilometer
km kilometre
link_benoit Link (Benoit)
link_sears Link (Sears)
mi mile
mm millimeter
mm millimetre
m meter
m metre
nm_uk Nautical Mile (UK)
nm Nautical Mile
sears_yd Yard (Sears)
survey_ft U.S. Foot
survey_ft US survey foot
um micrometer
um micrometre
u ru
yd yard

As we're not too picky, the aliases are case-insensitive.

My alias isn't there?

Well, as with units, you can add your own. Keyword argument keys should be your new alias, with the value the existing units:

In [1]: import distance

In [2]: distance.register_aliases(feet="ft")

In [3]: distance.D(feet=10)
Out[3]: D(ft=10.0)

How does it work then?

When you create a DistanceField in your model, it also has a default unit (metres unless you change it through the unit kwarg). Any distances passed to this unit field are converted into this standard unit, before being saved into the database as a decimal in much the same way as a DecimalField.

But you said it will remember my units? How?

Glad you asked. Optionally, although if you don't add it we can't remember your units, for each DistanceField you add you should also add a DistanceUnitField, and tell your DistanceField about it. The DistanceUnitField is a simple CharField, which is automatically populated depending on the units specified in your DistanceField.

You don't need to worry about this field, simply creating it in your model and pointing the DistanceField to it is all you need to do. It's not editable, and you don't need to change it.

Okay, I'm sold. Well, sold enough to download and test an MIT-licensed library anyway. How do I use it?

You can install either directly via PyPi (pip install django-distance-field), or checkout from our Git repo: https://bitbucket.org/squarehost/django-distance-field.git

Usage is straightforward. As it's only a field, you don't need to add to your INSTALLED_APPS, although you can do so if you wish to run tests.

In your models.py file, create the DistanceField according to your requirements:

from distance import DistanceField, DistanceUnitField

class ModelWithWidthHeight(models.Model):
	width = DistanceField(unit='m', unit_field='width_units')
	height = DistanceField(unit='in', unit_field='height_units')

	width_units = DistanceUnitField( )
	height_units = DistanceUnitField( )

DistanceField will also respect standard Django DecimalField options, such as blank, null, max_digits, and decimal_places. Bear in mind that the field will actually be stored in the unit you specify, so you might want to change the max_digits and precision depending on your requirements.

Also, bearing in mind that your units may well be converted to all other weird and wonderful units, you may want a couple of extra decimal places to try and reduce the occurrence of any strange rounding errors.

Default unit is m (metres), max_digits is 14, and decimal_places is 6.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-distance-field-1.3.0.tar.gz (14.6 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page