django-measurement-3.1.1/0000755000372000037200000000000013355617427016156 5ustar travistravis00000000000000django-measurement-3.1.1/django_measurement/0000755000372000037200000000000013355617427022025 5ustar travistravis00000000000000django-measurement-3.1.1/django_measurement/__init__.py0000644000372000037200000000000013355617275024125 0ustar travistravis00000000000000django-measurement-3.1.1/django_measurement/conf.py0000644000372000037200000000057213355617275023331 0ustar travistravis00000000000000"""Settings for django-measurement.""" from appconf import AppConf from django.conf import settings __all__ = ('settings',) class DjangoMeasurementConf(AppConf): """Settings for django-measurement.""" BIDIMENSIONAL_SEPARATOR = '/' """ For measurement classes subclassing a BidimensionalMeasure, this . """ class Meta: prefix = 'measurement' django-measurement-3.1.1/django_measurement/forms.py0000755000372000037200000001073013355617275023532 0ustar travistravis00000000000000from itertools import product from django import forms from django.core.validators import MaxValueValidator, MinValueValidator from measurement.base import BidimensionalMeasure, MeasureBase from django_measurement import utils from django_measurement.conf import settings class MeasurementWidget(forms.MultiWidget): def __init__(self, attrs=None, float_widget=None, unit_choices_widget=None, unit_choices=None, *args, **kwargs): self.unit_choices = unit_choices if not float_widget: float_widget = forms.TextInput(attrs=attrs) if not unit_choices_widget: unit_choices_widget = forms.Select( attrs=attrs, choices=unit_choices ) widgets = (float_widget, unit_choices_widget) super(MeasurementWidget, self).__init__(widgets, attrs) def decompress(self, value): if value: choice_units = set([ u for u, n in self.unit_choices ]) unit = value.STANDARD_UNIT if unit not in choice_units: unit = choice_units.pop() magnitude = getattr(value, unit) return [magnitude, unit] return [None, None] class MeasurementField(forms.MultiValueField): def __init__(self, measurement, max_value=None, min_value=None, unit_choices=None, validators=None, bidimensional_separator=settings.MEASUREMENT_BIDIMENSIONAL_SEPARATOR, *args, **kwargs): if not issubclass(measurement, (MeasureBase, BidimensionalMeasure)): raise ValueError( "%s must be a subclass of MeasureBase" % measurement ) self.measurement_class = measurement if not unit_choices: if issubclass(measurement, BidimensionalMeasure): assert isinstance(bidimensional_separator, str), \ "Supplied bidimensional_separator for %s must be of string/unicode type;" \ " Instead got type %s" % (measurement, str(type(bidimensional_separator)),) unit_choices = tuple(( ( '{0}__{1}'.format(primary, reference), '{0}{1}{2}'.format( getattr(measurement.PRIMARY_DIMENSION, 'LABELS', {}).get( primary, primary), bidimensional_separator, getattr(measurement.REFERENCE_DIMENSION, 'LABELS', {}).get( reference, reference)), ) for primary, reference in product( measurement.PRIMARY_DIMENSION.get_units(), measurement.REFERENCE_DIMENSION.get_units(), ) )) else: unit_choices = tuple(( (u, getattr(measurement, 'LABELS', {}).get(u, u)) for u in measurement.get_units() )) if validators is None: validators = [] if min_value is not None: if not isinstance(min_value, MeasureBase): msg = '"min_value" must be a measure, got %s' % type(min_value) raise ValueError(msg) validators += [MinValueValidator(min_value)] if max_value is not None: if not isinstance(max_value, MeasureBase): msg = '"max_value" must be a measure, got %s' % type(max_value) raise ValueError(msg) validators += [MaxValueValidator(max_value)] float_field = forms.FloatField(*args, **kwargs) choice_field = forms.ChoiceField(choices=unit_choices) defaults = { 'widget': MeasurementWidget( float_widget=float_field.widget, unit_choices_widget=choice_field.widget, unit_choices=unit_choices ), } defaults.update(kwargs) fields = (float_field, choice_field) super(MeasurementField, self).__init__(fields, validators=validators, *args, **defaults) def compress(self, data_list): if not data_list: return None value, unit = data_list if value in self.empty_values: return None return utils.get_measurement( self.measurement_class, value, unit ) django-measurement-3.1.1/django_measurement/models.py0000644000372000037200000000746713355617275023701 0ustar travistravis00000000000000import logging import warnings from django.db.models import FloatField from django.utils.translation import ugettext_lazy as _ from measurement import measures from measurement.base import BidimensionalMeasure, MeasureBase from . import forms from .utils import get_measurement logger = logging.getLogger('django_measurement') class MeasurementField(FloatField): description = "Easily store, retrieve, and convert python measures." empty_strings_allowed = False MEASURE_BASES = ( BidimensionalMeasure, MeasureBase, ) default_error_messages = { 'invalid_type': _( "'%(value)s' (%(type_given)s) value" " must be of type %(type_wanted)s." ), } def __init__(self, verbose_name=None, name=None, measurement=None, measurement_class=None, unit_choices=None, *args, **kwargs): if not measurement and measurement_class is not None: warnings.warn( "\"measurement_class\" will be removed in version 4.0", DeprecationWarning ) measurement = getattr(measures, measurement_class) if not measurement: raise TypeError('MeasurementField() takes a measurement' ' keyword argument. None given.') if not issubclass(measurement, self.MEASURE_BASES): raise TypeError( 'MeasurementField() takes a measurement keyword argument.' ' It has to be a valid MeasureBase subclass.' ) self.measurement = measurement self.widget_args = { 'measurement': measurement, 'unit_choices': unit_choices, } super(MeasurementField, self).__init__(verbose_name, name, *args, **kwargs) def deconstruct(self): name, path, args, kwargs = super(MeasurementField, self).deconstruct() kwargs['measurement'] = self.measurement return name, path, args, kwargs def get_prep_value(self, value): if value is None: return None elif isinstance(value, self.MEASURE_BASES): # sometimes we get sympy.core.numbers.Float, which the # database does not understand, so explicitely convert to # float return float(value.standard) else: return super(MeasurementField, self).get_prep_value(value) def get_default_unit(self): unit_choices = self.widget_args['unit_choices'] if unit_choices: return unit_choices[0][0] return self.measurement.STANDARD_UNIT def from_db_value(self, value, *args, **kwargs): if value is None: return None return get_measurement( measure=self.measurement, value=value, original_unit=self.get_default_unit(), ) def to_python(self, value): if value is None: return value elif isinstance(value, self.MEASURE_BASES): return value value = super(MeasurementField, self).to_python(value) return_unit = self.get_default_unit() msg = "You assigned a %s instead of %s to %s.%s.%s, unit was guessed to be \"%s\"." % ( type(value).__name__, str(self.measurement.__name__), self.model.__module__, self.model.__name__, self.name, return_unit, ) logger.warning(msg) return get_measurement( measure=self.measurement, value=value, unit=return_unit, ) def formfield(self, **kwargs): defaults = {'form_class': forms.MeasurementField} defaults.update(kwargs) defaults.update(self.widget_args) return super(MeasurementField, self).formfield(**defaults) django-measurement-3.1.1/django_measurement/utils.py0000644000372000037200000000053513355617275023543 0ustar travistravis00000000000000from measurement.base import BidimensionalMeasure def get_measurement(measure, value, unit=None, original_unit=None): unit = unit or measure.STANDARD_UNIT m = measure( **{unit: value} ) if original_unit: m.unit = original_unit if isinstance(m, BidimensionalMeasure): m.reference.value = 1 return m django-measurement-3.1.1/django_measurement.egg-info/0000755000372000037200000000000013355617427023517 5ustar travistravis00000000000000django-measurement-3.1.1/django_measurement.egg-info/PKG-INFO0000644000372000037200000000720313355617426024615 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: django-measurement Version: 3.1.1 Summary: Convenient fields and classes for handling measurements Home-page: https://github.com/coddingtonbear/django-measurement Author: Adam Coddington Author-email: me@adamcoddington.net License: MIT Description: |version| |ci| |coverage| |license| Django Measurement ================== Easily use, manipulate, and store unit-aware measurement objects using Python and Django. `django.contrib.gis.measure `_ has these wonderful 'Distance' objects that can be used not only for storing a unit-aware distance measurement, but also for converting between different units and adding/subtracting these objects from one another. This module provides for a django model field and admin interface for storing any measurements provided by `python-measurement`_. Example use with a model: .. code-block:: python 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 __unicode__(self): return u"%s of %s" % (self.name, self.volume) entry = BeerConsumptionLogEntry() entry.name = 'Bear Republic Racer 5' entry.volume = Volume(us_pint=1) entry.save() These stored measurement objects can be used in all of the usual ways supported by `python-measurement`_ too: .. code-block:: python >>> from measurement.measures import Weight >>> weight_1 = Weight(lb=125) >>> weight_2 = Weight(kg=40) >>> added_together = weight_1 + weight_2 >>> added_together Weight(lb=213.184976807) >>> added_together.kg # Maybe I actually need this value in kg? 96.699 - Documentation for django-measurement is available via `Read the Docs`_. - Please post issues on GitHub_. .. _Read the Docs: https://django-measurement.readthedocs.io/ .. _GitHub: https://github.com/coddingtonbear/django-measurement/issues .. _python-measurement: https://github.com/coddingtonbear/python-measurement .. |version| image:: https://img.shields.io/pypi/v/django-measurement.svg :target: https://pypi.python.org/pypi/django-measurement .. |ci| image:: https://api.travis-ci.org/coddingtonbear/django-measurement.svg?branch=master :target: https://travis-ci.org/coddingtonbear/django-measurement .. |coverage| image:: https://codecov.io/gh/coddingtonbear/django-measurement/branch/master/graph/badge.svg :target: https://codecov.io/gh/coddingtonbear/django-measurement .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg :target: LICENSE Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Environment :: Web Environment Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Framework :: Django django-measurement-3.1.1/django_measurement.egg-info/SOURCES.txt0000644000372000037200000000145713355617427025412 0ustar travistravis00000000000000.travis.yml LICENSE README.rst requirements-dev.txt requirements.txt setup.cfg setup.py tox.ini django_measurement/__init__.py django_measurement/conf.py django_measurement/forms.py django_measurement/models.py django_measurement/utils.py django_measurement.egg-info/PKG-INFO django_measurement.egg-info/SOURCES.txt django_measurement.egg-info/dependency_links.txt django_measurement.egg-info/not-zip-safe django_measurement.egg-info/pbr.json django_measurement.egg-info/requires.txt django_measurement.egg-info/top_level.txt docs/conf.py docs/index.rst docs/topics/forms.rst docs/topics/installation.rst docs/topics/measures.rst docs/topics/settings.rst docs/topics/storing.rst docs/topics/use.rst tests/__init__.py tests/custom_measure_base.py tests/forms.py tests/models.py tests/settings.py tests/test_fields.pydjango-measurement-3.1.1/django_measurement.egg-info/dependency_links.txt0000644000372000037200000000000113355617426027564 0ustar travistravis00000000000000 django-measurement-3.1.1/django_measurement.egg-info/not-zip-safe0000644000372000037200000000000113355617426025744 0ustar travistravis00000000000000 django-measurement-3.1.1/django_measurement.egg-info/pbr.json0000644000372000037200000000005713355617426025176 0ustar travistravis00000000000000{"git_version": "a91945e", "is_release": false}django-measurement-3.1.1/django_measurement.egg-info/requires.txt0000644000372000037200000000005613355617426026117 0ustar travistravis00000000000000django django-appconf>=1.0.2 measurement>=1.6 django-measurement-3.1.1/django_measurement.egg-info/top_level.txt0000644000372000037200000000002313355617426026243 0ustar travistravis00000000000000django_measurement django-measurement-3.1.1/docs/0000755000372000037200000000000013355617427017106 5ustar travistravis00000000000000django-measurement-3.1.1/docs/topics/0000755000372000037200000000000013355617427020407 5ustar travistravis00000000000000django-measurement-3.1.1/docs/topics/forms.rst0000755000372000037200000000333613355617275022300 0ustar travistravis00000000000000 Using Measurement Objects in Forms ================================== This is an example for a simple form field usage:: from django import forms from django_measurement.forms import MeasurementField class BeerForm(forms.Form): volume = MeasurementField(Volume) You can limit the units in the select field by using the 'unit_choices' keyword argument. To limit the value choices of the MeasurementField uses the regular 'choices' keyword argument:: class BeerForm(forms.Form): volume = MeasurementField( measurement=Volume, unit_choices=(("l","l"), ("oz","oz")), choices=((1.0, 'one'), (2.0, 'two')) ) If unicode symbols are needed in the labels for a MeasurementField, define a LABELS dictionary for your subclassed MeasureBase object:: # -*- coding: utf-8 -*- from sympy import S, Symbol class Temperature(MeasureBase): SU = Symbol('kelvin') STANDARD_UNIT = 'k' UNITS = { 'c': SU - S(273.15), 'f': (SU - S(273.15)) * S('9/5') + 32, 'k': 1.0 } LABELS = { 'c':u'°C', 'f':u'°F', 'k':u'°K', } For a `MeasurementField` that represents a `BidimensionalMeasure`, you can set the separator either in settings.py (`MEASUREMENT_BIDIMENSIONAL_SEPARATOR is '/'` by default, add setting to override for all BiDimensionalMeasure subclasses) or override for an individual field with the kwarg bidimensional_separator:: speed = MeasurementField( measurement=Speed, bidimensional_separator=' per ' ) # Rendered option labels will now be in the format "ft per s", "m per hr", etc django-measurement-3.1.1/docs/topics/installation.rst0000644000372000037200000000053713355617275023650 0ustar travistravis00000000000000 Installation ============ You can either install from pip:: pip install django-measurement *or* checkout and install the source from the `github repository `_:: git clone https://github.com/coddingtonbear/django-measurement.git cd django-measurement python setup.py install django-measurement-3.1.1/docs/topics/measures.rst0000644000372000037200000000027513355617275022772 0ustar travistravis00000000000000 Measures ======== See `python-measurement's documentation `_ for information about what measures are available. django-measurement-3.1.1/docs/topics/settings.rst0000644000372000037200000000055113355617275023003 0ustar travistravis00000000000000 Settings ======== ``MEASUREMENT_BIDIMENSIONAL_SEPARATOR`` --------------------------------------- For any BidimensionalMeasure, what is placed between the primary and reference dimensions on rendered label MEASUREMENT_BIDIMENSIONAL_SEPARATOR = " per " Defaults to "/". Can be overriden as kwarg `bidimensional_separator` for a given MeasurementField. django-measurement-3.1.1/docs/topics/storing.rst0000644000372000037200000000343213355617275022631 0ustar travistravis00000000000000 Storing Measurement Objects =========================== Suppose you were trying to cut back on drinking, and needed to store a log of how much beer you drink day-to-day; you might (naively) create a model like such:: 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) and assume you had a pint of `Ninkasi's Total Domination `_; you'd add it to your log like so:: from measurement.measures import Volume beer = BeerConsumptionLogEntry() beer.name = 'Total Domination' beer.volume = Volume(us_pint=1) beer.save() 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 `Baltika's #9 `_; you'd add it to your log like so:: another_beer = BeerConsumptionLogEntry() another_beer.name = '#9' another_beer.volume = Volume(l=0.5) another_beer.save() 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 How is this data stored? ------------------------ Since django-measurement v2.0 there value will be stored in a single float field. django-measurement-3.1.1/docs/topics/use.rst0000644000372000037200000000101113355617275021727 0ustar travistravis00000000000000 Using Measurement Objects ========================= You can import any of the above measures from `measurement.measures` 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' See `Python-measurement's documentation `_ for more information about interacting with measurements. django-measurement-3.1.1/docs/conf.py0000644000372000037200000000002513355617275020403 0ustar travistravis00000000000000master_doc = 'index' django-measurement-3.1.1/docs/index.rst0000644000372000037200000000515513355617275020756 0ustar travistravis00000000000000Django Measurement ================== |version| |ci| |coverage| |license| Easily use, manipulate, and store unit-aware measurement objects using Python and Django. Contents: --------- .. toctree:: :maxdepth: 2 :glob: topics/* Summary: -------- `django.contrib.gis.measure `_ has these wonderful 'Distance' objects that can be used not only for storing a unit-aware distance measurement, but also for converting between different units and adding/subtracting these objects from one another. This module provides for a django model field and admin interface for storing any measurements provided by `python-measurement`_. Example use with a model: .. code-block:: python 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 __unicode__(self): return u"%s of %s" % (self.name, self.volume) entry = BeerConsumptionLogEntry() entry.name = 'Bear Republic Racer 5' entry.volume = Volume(us_pint=1) entry.save() These stored measurement objects can be used in all of the usual ways supported by `python-measurement`_ too: .. code-block:: python >>> from measurement.measures import Weight >>> weight_1 = Weight(lb=125) >>> weight_2 = Weight(kg=40) >>> added_together = weight_1 + weight_2 >>> added_together Weight(lb=213.184976807) >>> added_together.kg # Maybe I actually need this value in kg? 96.699 - Documentation for django-measurement is available via `Read the Docs`_. - Please post issues on GitHub_. .. _Read the Docs: https://django-measurement.readthedocs.io/ .. _GitHub: https://github.com/coddingtonbear/django-measurement/issues .. _python-measurement: https://github.com/coddingtonbear/python-measurement .. |version| image:: https://img.shields.io/pypi/v/django-measurement.svg :target: https://pypi.python.org/pypi/django-measurement .. |ci| image:: https://api.travis-ci.org/coddingtonbear/django-measurement.svg?branch=master :target: https://travis-ci.org/coddingtonbear/django-measurement .. |coverage| image:: https://codecov.io/gh/coddingtonbear/django-measurement/branch/master/graph/badge.svg :target: https://codecov.io/gh/coddingtonbear/django-measurement .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg :target: LICENSE Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` django-measurement-3.1.1/tests/0000755000372000037200000000000013355617427017320 5ustar travistravis00000000000000django-measurement-3.1.1/tests/__init__.py0000644000372000037200000000000013355617275021420 0ustar travistravis00000000000000django-measurement-3.1.1/tests/custom_measure_base.py0000644000372000037200000000122213355617275023715 0ustar travistravis00000000000000from measurement.base import BidimensionalMeasure, MeasureBase from sympy import S, Symbol __all__ = ( 'Temperature', 'Time', 'DegreePerTime', ) class Temperature(MeasureBase): SU = Symbol('kelvin') STANDARD_UNIT = 'k' UNITS = { 'c': SU - S(273.15), 'f': (SU - S(273.15)) * S('9/5') + 32, 'k': 1.0 } LABELS = { 'c': u'°C', 'f': u'°F', 'k': u'°K', } class Time(MeasureBase): UNITS = { 's': 3600.0, 'h': 1.0, } SI_UNITS = ['s'] class DegreePerTime(BidimensionalMeasure): PRIMARY_DIMENSION = Temperature REFERENCE_DIMENSION = Time django-measurement-3.1.1/tests/forms.py0000644000372000037200000000105413355617275021021 0ustar travistravis00000000000000from django import forms from django_measurement.forms import MeasurementField from tests.custom_measure_base import DegreePerTime, Temperature, Time from tests.models import MeasurementTestModel class MeasurementTestForm(forms.ModelForm): class Meta: model = MeasurementTestModel exclude = [] class LabelTestForm(forms.Form): simple = MeasurementField(Temperature) class SITestForm(forms.Form): simple = MeasurementField(Time) class BiDimensionalLabelTestForm(forms.Form): simple = MeasurementField(DegreePerTime) django-measurement-3.1.1/tests/models.py0000644000372000037200000000517413355617275021165 0ustar travistravis00000000000000from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from measurement import measures from django_measurement.models import MeasurementField from tests.custom_measure_base import DegreePerTime, Temperature, Time class MeasurementTestModel(models.Model): measurement_distance = MeasurementField( measurement=measures.Distance, validators=[ MinValueValidator(measures.Distance(mi=1.0)), MaxValueValidator(measures.Distance(mi=3.0)) ], blank=True, null=True, ) measurement_distance_km = MeasurementField( measurement=measures.Distance, unit_choices=(('km', 'km'),), validators=[ MinValueValidator(measures.Distance(km=1.0)), MaxValueValidator(measures.Distance(km=3.0)) ], blank=True, null=True, ) measurement_weight = MeasurementField( measurement=measures.Weight, validators=[ MinValueValidator(measures.Weight(kg=1.0)), MaxValueValidator(measures.Weight(kg=3.0)) ], blank=True, null=True, ) measurement_speed = MeasurementField( measurement=measures.Speed, validators=[ MinValueValidator(measures.Speed(mph=1.0)), MaxValueValidator(measures.Speed(mph=3.0)) ], blank=True, null=True, ) measurement_temperature = MeasurementField( measurement=measures.Temperature, validators=[ MinValueValidator(measures.Temperature(1.0)), MaxValueValidator(measures.Temperature(3.0)) ], blank=True, null=True, ) measurement_temperature2 = MeasurementField( measurement_class='Temperature', validators=[ MinValueValidator(measures.Temperature(1.0)), MaxValueValidator(measures.Temperature(3.0)) ], blank=True, null=True, ) measurement_speed_mph = MeasurementField( measurement=measures.Speed, unit_choices=(('mi__hr', 'mph'),), validators=[ MinValueValidator(measures.Speed(mph=1.0)), MaxValueValidator(measures.Speed(mph=3.0)) ], blank=True, null=True, ) measurement_custom_degree_per_time = MeasurementField( measurement=DegreePerTime, blank=True, null=True, ) measurement_custom_temperature = MeasurementField( measurement=Temperature, blank=True, null=True, ) measurement_custom_time = MeasurementField( measurement=Time, blank=True, null=True, ) def __str__(self): return self.measurement django-measurement-3.1.1/tests/settings.py0000644000372000037200000000047513355617275021541 0ustar travistravis00000000000000DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', } } INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django_measurement', 'tests' ) SITE_ID = 1 ROOT_URLCONF = 'core.urls' SECRET_KEY = 'foobar' USE_L10N = True django-measurement-3.1.1/tests/test_fields.py0000644000372000037200000002205513355617275022204 0ustar travistravis00000000000000# -*- coding: utf-8 -*- import pytest from django.core.exceptions import ValidationError from django.utils import module_loading from measurement import measures from measurement.measures import Distance from django_measurement.forms import MeasurementField from tests.custom_measure_base import DegreePerTime, Temperature, Time from tests.forms import ( BiDimensionalLabelTestForm, LabelTestForm, MeasurementTestForm, SITestForm ) from tests.models import MeasurementTestModel pytestmark = [ pytest.mark.django_db, ] class TestMeasurementField: def test_storage_of_standard_measurement(self): measurement = measures.Weight(g=20) instance = MeasurementTestModel() instance.measurement_weight = measurement instance.save() retrieved = MeasurementTestModel.objects.get(pk=instance.pk) assert retrieved.measurement_weight == measurement def test_storage_of_temperature(self): measurement = measures.Temperature(c=20) instance = MeasurementTestModel() instance.measurement_temperature = measurement instance.save() retrieved = MeasurementTestModel.objects.get(pk=instance.pk) assert retrieved.measurement_temperature == measurement def test_storage_of_string_value(self): instance = MeasurementTestModel() instance.measurement_weight = "21.4" instance.save() retrieved = MeasurementTestModel.objects.get(pk=instance.pk) assert retrieved.measurement_weight == measures.Weight(g=21.4) def test_storage_of_float_value(self): instance = MeasurementTestModel() instance.measurement_weight = 21.4 instance.save() retrieved = MeasurementTestModel.objects.get(pk=instance.pk) assert retrieved.measurement_weight == measures.Weight(g=21.4) def test_storage_of_bidimensional_measurement(self): measurement = measures.Speed(mph=20) instance = MeasurementTestModel() instance.measurement_speed = measurement instance.save() retrieved = MeasurementTestModel.objects.get(pk=instance.pk) assert retrieved.measurement_speed == measurement def test_storage_and_retrieval_of_bidimensional_measurement(self): original_value = measures.Speed(mph=65) MeasurementTestModel.objects.create( measurement_speed=original_value, ) retrieved = MeasurementTestModel.objects.get() new_value = retrieved.measurement_speed assert new_value == original_value assert type(new_value) == type(original_value) assert new_value.unit == original_value.STANDARD_UNIT def test_storage_and_retrieval_of_bidimensional_measurement_choice(self): original_value = measures.Speed(mph=65) MeasurementTestModel.objects.create( measurement_speed_mph=original_value, ) retrieved = MeasurementTestModel.objects.get() new_value = retrieved.measurement_speed_mph assert new_value == original_value assert type(new_value) == type(original_value) assert new_value.unit == original_value.unit def test_storage_and_retrieval_of_measurement(self): original_value = measures.Weight(lb=124) MeasurementTestModel.objects.create( measurement_weight=original_value, ) retrieved = MeasurementTestModel.objects.get() new_value = retrieved.measurement_weight assert new_value == original_value assert type(new_value) == type(original_value) assert new_value.unit == original_value.STANDARD_UNIT def test_storage_and_retrieval_of_measurement_choice(self): original_value = measures.Distance(km=100) MeasurementTestModel.objects.create( measurement_distance_km=original_value, ) retrieved = MeasurementTestModel.objects.get() new_value = retrieved.measurement_distance_km assert new_value == original_value assert type(new_value) == type(original_value) assert new_value.unit == original_value.unit @pytest.mark.parametrize('fieldname, measure_cls', [ ('measurement_weight', measures.Weight), ('measurement_distance', measures.Distance), ('measurement_distance_km', measures.Distance), ('measurement_speed', measures.Speed), ('measurement_temperature', measures.Temperature), ('measurement_speed_mph', measures.Speed), ('measurement_custom_degree_per_time', DegreePerTime), ('measurement_custom_temperature', Temperature), ('measurement_custom_time', Time), ]) class TestDeconstruct: def test_deconstruct(self, fieldname, measure_cls): field = MeasurementTestModel._meta.get_field(fieldname) name, path, args, kwargs = field.deconstruct() assert name == fieldname assert path == 'django_measurement.models.MeasurementField' assert args == [] assert kwargs['blank'] == field.blank assert kwargs['null'] == field.null if 'measurement_class' in kwargs: assert kwargs['measurement_class'] == measure_cls.__name__ assert kwargs['measurement_class'] == field.measurement_class else: assert 'measurement' in kwargs new_cls = module_loading.import_string(path) new_field = new_cls(name=name, *args, **kwargs) assert type(field) == type(new_field) assert field.deconstruct() == ( name, path, args, kwargs ) class TestMeasurementFormField: def test_max_value(self): valid_form = MeasurementTestForm({ 'measurement_distance_0': 2.0, 'measurement_distance_1': 'mi', }) invalid_form = MeasurementTestForm({ 'measurement_distance_0': 4.0, 'measurement_distance_1': 'mi', }) assert valid_form.is_valid() assert not invalid_form.is_valid() field = MeasurementField(measures.Distance, max_value=measures.Distance(mi=1)) field.clean([0.5, 'mi']) with pytest.raises(ValidationError) as e: field.clean([2.0, 'mi']) assert 'Ensure this value is less than or equal to 1.0 mi.' in str(e) with pytest.raises(ValueError) as e: MeasurementField(measures.Distance, max_value=1.0) assert bytes(e) == '"max_value" must be a measure, got float' def test_form_storage(self): form = MeasurementTestForm({ 'measurement_distance_0': 2.0, 'measurement_distance_1': 'mi', }) assert form.is_valid() obj = form.save() assert obj.measurement_distance == measures.Distance(mi=2) def test_form_bidir(self): form = MeasurementTestForm({ 'measurement_speed_0': 2.0, 'measurement_speed_1': 'mi__hr', }) assert form.is_valid() obj = form.save() assert obj.measurement_speed == measures.Speed(mi__hr=2) def test_min_value(self): field = MeasurementField(measures.Distance, min_value=measures.Distance(mi=1.0)) field.clean([2.0, 'mi']) with pytest.raises(ValidationError) as e: field.clean([0.5, 'mi']) assert 'Ensure this value is greater than or equal to 1.0 mi.' in str(e) with pytest.raises(ValueError) as e: MeasurementField(measures.Distance, min_value=1.0) assert str(e) == '"min_value" must be a measure, got float' def test_float_casting(self, caplog): m = MeasurementTestModel( measurement_distance=float(2000), measurement_distance_km=2, ) m.full_clean() assert m.measurement_distance.value == 2000 assert m.measurement_distance.unit == Distance.STANDARD_UNIT assert m.measurement_distance_km.value == 2 assert m.measurement_distance_km.unit == 'km' assert m.measurement_distance_km == Distance(km=2) m.measurement_distance_km = Distance(km=1) m.full_clean() assert m.measurement_distance_km.value == 1 assert m.measurement_distance_km.unit == 'km' assert m.measurement_distance_km == Distance(km=1) record = caplog.records[0] assert record.levelname == 'WARNING' assert record.message == ('You assigned a float instead of Distance to' ' tests.models.MeasurementTestModel.measurement_distance,' ' unit was guessed to be "m".') def test_unicode_labels(self): form = LabelTestForm() assert ('c', u'°C') in form.fields['simple'].fields[1].choices assert ('f', u'°F') in form.fields['simple'].fields[1].choices assert ('k', u'°K') in form.fields['simple'].fields[1].choices si_form = SITestForm() assert ('ms', 'ms') in si_form.fields['simple'].fields[1].choices assert ('Ps', 'Ps') in si_form.fields['simple'].fields[1].choices bi_dim_form = BiDimensionalLabelTestForm() assert ('c__ms', u'°C/ms') in bi_dim_form.fields['simple'].fields[1].choices assert ('c__Ps', u'°C/Ps') in bi_dim_form.fields['simple'].fields[1].choices django-measurement-3.1.1/.travis.yml0000644000372000037200000000150513355617275020271 0ustar travistravis00000000000000language: python sudo: false cache: pip python: 3.6 env: matrix: - DJANGO=111 - DJANGO=20 - DJANGO=master - TOXENV=qa - TOXENV=docs matrix: fast_finish: true allow_failures: - env: DJANGO=master install: - pip install --upgrade pip tox - pip install -U codecov before_script: - | if [[ -z $TOXENV ]]; then export TOXENV=py$(echo $TRAVIS_PYTHON_VERSION | sed -e 's/\.//g')-dj$DJANGO fi - echo $TOXENV script: - tox -e $TOXENV after_success: - codecov deploy: provider: pypi user: codingjoe password: secure: a5tlCZBkW+ZaTq0ZAJX5nrucGbzV3TCPxY5wWBTrcFDynGVhSEkN/xG3obxi9dNxRP3Y/iTDZp+CUJraUxHehbsFZ7vtRWXWOnhWeJrlVrc0/KPmMWpHOAbTZ/UIZZfn8q362HAb8c6BcrAWOK00Bpt3DBIzzGXvsg2sdcrpW4k= on: tags: true distributions: sdist bdist_wheel repo: coddingtonbear/django-measurement branch: master django-measurement-3.1.1/LICENSE0000644000372000037200000000207213355617275017165 0ustar travistravis00000000000000The MIT License (MIT) Copyright (c) 2014 Adam Coddington Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. django-measurement-3.1.1/README.rst0000644000372000037200000000466513355617275017661 0ustar travistravis00000000000000|version| |ci| |coverage| |license| Django Measurement ================== Easily use, manipulate, and store unit-aware measurement objects using Python and Django. `django.contrib.gis.measure `_ has these wonderful 'Distance' objects that can be used not only for storing a unit-aware distance measurement, but also for converting between different units and adding/subtracting these objects from one another. This module provides for a django model field and admin interface for storing any measurements provided by `python-measurement`_. Example use with a model: .. code-block:: python 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 __unicode__(self): return u"%s of %s" % (self.name, self.volume) entry = BeerConsumptionLogEntry() entry.name = 'Bear Republic Racer 5' entry.volume = Volume(us_pint=1) entry.save() These stored measurement objects can be used in all of the usual ways supported by `python-measurement`_ too: .. code-block:: python >>> from measurement.measures import Weight >>> weight_1 = Weight(lb=125) >>> weight_2 = Weight(kg=40) >>> added_together = weight_1 + weight_2 >>> added_together Weight(lb=213.184976807) >>> added_together.kg # Maybe I actually need this value in kg? 96.699 - Documentation for django-measurement is available via `Read the Docs`_. - Please post issues on GitHub_. .. _Read the Docs: https://django-measurement.readthedocs.io/ .. _GitHub: https://github.com/coddingtonbear/django-measurement/issues .. _python-measurement: https://github.com/coddingtonbear/python-measurement .. |version| image:: https://img.shields.io/pypi/v/django-measurement.svg :target: https://pypi.python.org/pypi/django-measurement .. |ci| image:: https://api.travis-ci.org/coddingtonbear/django-measurement.svg?branch=master :target: https://travis-ci.org/coddingtonbear/django-measurement .. |coverage| image:: https://codecov.io/gh/coddingtonbear/django-measurement/branch/master/graph/badge.svg :target: https://codecov.io/gh/coddingtonbear/django-measurement .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg :target: LICENSEdjango-measurement-3.1.1/requirements-dev.txt0000644000372000037200000000010713355617275022215 0ustar travistravis00000000000000-e . coverage flake8 isort pydocstyle pep8-naming pytest pytest-django django-measurement-3.1.1/requirements.txt0000644000372000037200000000005613355617275021444 0ustar travistravis00000000000000django django-appconf>=1.0.2 measurement>=1.6 django-measurement-3.1.1/setup.cfg0000644000372000037200000000246113355617427020002 0ustar travistravis00000000000000[metadata] name = django-measurement author = Adam Coddington author-email = me@adamcoddington.net summary = Convenient fields and classes for handling measurements description-file = README.rst description-content-type = text/x-rst; charset=UTF-8 home-page = https://github.com/coddingtonbear/django-measurement license = MIT classifier = Development Status :: 5 - Production/Stable Framework :: Django Intended Audience :: Developers License :: OSI Approved :: MIT License Operating System :: OS Independent Environment :: Web Environment Programming Language :: Python Programming Language :: Python :: 3 Framework :: Django [files] packages = django_measurement [pbr] skip_authors = true skip_changelog = true [build_sphinx] source-dir = docs build-dir = docs/_build warning-is-error = 1 [tool:pytest] norecursedirs = env docs .tox .eggs django_settings_module = tests.settings addopts = --tb=short -rxs [flake8] max-line-length = 99 max-complexity = 10 statistics = true show-source = true exclude = */migrations/*,docs/*,env/*,.tox/*,.eggs/* [pydocstyle] add_ignore = D1 [isort] atomic = true multi_line_output = 5 line_length = 79 combine_as_imports = true skip = wsgi.py,docs,env,.tox,.eggs known_first_party = django_measurement,tests known_third_party = django [egg_info] tag_build = tag_date = 0 django-measurement-3.1.1/setup.py0000755000372000037200000000014713355617275017676 0ustar travistravis00000000000000#!/usr/bin/env python from setuptools import setup setup( setup_requires=['pbr'], pbr=True, ) django-measurement-3.1.1/tox.ini0000644000372000037200000000140313355617275017470 0ustar travistravis00000000000000[tox] envlist = py{36}-dj{111,20,master},qa,docs [testenv] deps= -rrequirements-dev.txt dj111: https://github.com/django/django/archive/stable/1.11.x.tar.gz#egg=django dj20: https://github.com/django/django/archive/stable/2.0.x.tar.gz#egg=django djmaster: https://github.com/django/django/archive/master.tar.gz#egg=django commands= coverage run --source=django_measurement -m 'pytest' \ --basetemp={envtmpdir} \ --ignore=.tox \ {posargs} [testenv:qa] changedir={toxinidir} deps= -rrequirements-dev.txt commands= isort --check-only --recursive --diff {posargs} flake8 --jobs=2 {posargs} pydocstyle --verbose --explain --source --count {posargs} [testenv:docs] deps=sphinx commands=python setup.py build_sphinx django-measurement-3.1.1/PKG-INFO0000644000372000037200000000720313355617427017255 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: django-measurement Version: 3.1.1 Summary: Convenient fields and classes for handling measurements Home-page: https://github.com/coddingtonbear/django-measurement Author: Adam Coddington Author-email: me@adamcoddington.net License: MIT Description: |version| |ci| |coverage| |license| Django Measurement ================== Easily use, manipulate, and store unit-aware measurement objects using Python and Django. `django.contrib.gis.measure `_ has these wonderful 'Distance' objects that can be used not only for storing a unit-aware distance measurement, but also for converting between different units and adding/subtracting these objects from one another. This module provides for a django model field and admin interface for storing any measurements provided by `python-measurement`_. Example use with a model: .. code-block:: python 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 __unicode__(self): return u"%s of %s" % (self.name, self.volume) entry = BeerConsumptionLogEntry() entry.name = 'Bear Republic Racer 5' entry.volume = Volume(us_pint=1) entry.save() These stored measurement objects can be used in all of the usual ways supported by `python-measurement`_ too: .. code-block:: python >>> from measurement.measures import Weight >>> weight_1 = Weight(lb=125) >>> weight_2 = Weight(kg=40) >>> added_together = weight_1 + weight_2 >>> added_together Weight(lb=213.184976807) >>> added_together.kg # Maybe I actually need this value in kg? 96.699 - Documentation for django-measurement is available via `Read the Docs`_. - Please post issues on GitHub_. .. _Read the Docs: https://django-measurement.readthedocs.io/ .. _GitHub: https://github.com/coddingtonbear/django-measurement/issues .. _python-measurement: https://github.com/coddingtonbear/python-measurement .. |version| image:: https://img.shields.io/pypi/v/django-measurement.svg :target: https://pypi.python.org/pypi/django-measurement .. |ci| image:: https://api.travis-ci.org/coddingtonbear/django-measurement.svg?branch=master :target: https://travis-ci.org/coddingtonbear/django-measurement .. |coverage| image:: https://codecov.io/gh/coddingtonbear/django-measurement/branch/master/graph/badge.svg :target: https://codecov.io/gh/coddingtonbear/django-measurement .. |license| image:: https://img.shields.io/badge/license-MIT-blue.svg :target: LICENSE Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Environment :: Web Environment Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Framework :: Django