I would like to implement different systems of measurement in my Django project, so that users can choose wether they want to use metric or imperial units. However, I don't know what is the correct approach to do this.
Currently my models don't have measurement unit aware fields (they're integer/decimal fields) and I would prefer that I don't have to change my fields directly.
Since my current database values already represent metric values, I am planning to keep it that way, which means that I will have to handle user input / value output conversion. Pint seems to be a great library for this purpose.
Is this possible without editing fields in app, but using registration patterns or something else instead? What I would like to achieve is something like this:
This kind of solution would make it easier to plug it to existing apps since no fields and forms would be changed directly in the app. Basic examples using registration patterns would be helpful.
Any relevant information is welcome, including general ideas and patterns how this is done in non-Django projects.
A module is a single Python file, while a package is a directory of such modules. A package contains an additional __init__.py file to distinguish itself from a mere directory that includes some Python scripts. In this article, you will find 30 Python built-in modules you should be using in your project.
In the model view controller (MVC) architecture, the view component deals with how data is presented to users for consumption and viewing. In the Django framework, views are Python functions or classes that receive a web request and return a web response.
GeoDjango is an included contrib module for Django that turns it into a world-class geographic web framework. GeoDjango strives to make it as simple as possible to create geographic web applications, like location-based services. Its features include: Django model fields for OGC geometries and raster data.
Since you want to continue to store in metric then the only time you are going to possibly convert is:
For user input you can use a custom MultiValueField and a custom MultiWidget which will output a metric value.
from django import forms
from django.forms import widgets,Form, CharField, MultiValueField, ChoiceField, DecimalField
imperial = "imp"
imperial_display = "Miles"
metric = "met"
metric_display = "Kilometers"
unit_types = ((imperial, imperial_display), (metric, metric_display))
#You would use that library instead of this, and maybe not floats
def to_imperial(val):
return float(val) * 1.60934
def to_metric(val):
return float(val) / 0.62137
class NumberUnitWidget(widgets.MultiWidget):
def __init__(self, default_unit=None, attrs=None):
#I feel like this default_unit thing I'm doing is ugly
#but I couldn't think of another way to get the decompress
#to know which unit to convert to
self.default_unit = default_unit
_widgets = [
widgets.NumberInput(attrs=attrs),
widgets.Select(attrs=attrs, choices=unit_types),
]
super(NumberUnitWidget, self).__init__(_widgets, attrs)
#convert a single value to list for the form
def decompress(self, value):
if value:
if self.default_unit == imperial:
return [to_imperial(value), self.default_unit]
#value is the correct format
return [value, self.default_unit]
return [0, self.default_unit]
class NumberUnitField(MultiValueField):
def __init__(self, *args, **kwargs):
_widget = NumberUnitWidget(default_unit=kwargs['initial'][0])
fields = [
DecimalField(label="Number!"),
ChoiceField(choices=unit_types)
]
super(NumberUnitField, self).__init__(fields=fields, widget = _widget, *args, **kwargs)
def compress(self, values):
if values[1] == imperial:
#They inputed using imperial, convert to kilo
return to_metric(values[0])
return values[0] #You dont need to convert because its in the format you want
class TestForm(Form):
name = CharField()
num = NumberUnitField(initial=[None, metric])
The default setting of metric for num
in TestForm
can be overwritten when you instantiate the form using initial={'num':[None,'imp']}
so that is where you could insert the users preference from their profile.
Using the above form when you see that the form is_valid()
the data the form will return to you will be in metric.
When the user sees the form it will appear as a Django NumberInput followed by a select with the default setting already selected.
For displaying data you could use a template filter. Here is a simple one to give you an idea
from django import template
register = template.Library()
@register.filter
def convert_units(value, user_pref):
#You probably want to use that library you found here to do conversions
#instead of what i've used.
if user_pref == "met":
return str(value) + " Kilometers"
else:
return str(float(value)/ 1.60934) + " Miles"
Then in the template it would look something like:
{{a_value|convert_units:user_pref}}
Of course all of this is just converting Miles/Kilometers. You'd have to duplicate and change or subclass the widget/field and probably have some more template filters to cover other things like pounds/kilograms.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With