Login

Feet and Inches FormField/Widget

Author:
btaylordesign
Posted:
January 18, 2011
Language:
Python
Version:
1.2
Score:
0 (after 0 ratings)

Feet and Inches Widget/Field allows users to enter a value in feet, inches and fractional inches and have that value stored as a decimal in your database of choice.

I have this code broken out into separate files, but you can always combine to suit your needs. I have designated file delineation with a single line comment and the name of the file as referenced in the import statements.

The INCH_DECIMALS and INCH_FRACTION constants may be modified to allow smaller increments. My needs for this implementation only required accuracy to 1/16.

Hope this helps someone learn how to use the MultiWidget and MultiValueFields!

Happy Coding.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#constants.py
#set up conversions dictionary
INCH_DECIMALS = [str(float(i) / float(16)) for i in range(0, 16)]
INCH_FRACTIONS = ['---', '1/16', '1/8', '3/16', '1/4', '5/16', '3/8',
'7/16', '1/2', '9/16', '5/8', '11/16',
'3/4', '13/16', '7/8', '15/16']
conversions = zip(INCH_DECIMALS, INCH_FRACTIONS)
INCH_CONVERSIONS = tuple(conversions)
INCH_CONVERSIONS_DICT = dict(conversions)


#utils.py
from decimal import Decimal
from math import floor

from app.constants import INCH_CONVERSIONS_DICT


def inches_to_feet_and_inches(value_in_inches):
    if value_in_inches:
        value_in_inches = float(value_in_inches)
        feet = int(round(value_in_inches, 0) / 12)
        inches = int(floor(value_in_inches) % 12)
        fractional_inches = value_in_inches % floor(value_in_inches)
        return feet, inches, fractional_inches
    return 0, 0, 0


def sum_feet_inches_fractional_inches(feet, inches, fractional_inches):
    return Decimal(str(((float(feet) * 12) + float(inches)) + fractional_inches))


def format_inches_to_feet_and_inches(value_in_inches):
    feet, inches, fractional_inches = inches_to_feet_and_inches(value_in_inches)
    if value_in_inches > 11.9375:
        value = "%s' %s" % (feet, inches)
    else:
        value = "%s" % inches

    if fractional_inches > 0:
        value = '%s %s' % (value, INCH_CONVERSIONS_DICT[str(fractional_inches)])

    return value + '"'



#custom_fields.py
from django.core.exceptions import ValidationError
from django.forms.fields import MultiValueField, IntegerField, FloatField

from app.custom_widgets import FeetAndInchesWidget
from app.utils import sum_feet_inches_fractional_inches


class FeetAndInchesField(MultiValueField):
    widget = FeetAndInchesWidget

    def __init__(self, *args, **kwargs):
        errors = self.default_error_messages.copy()
        if 'error_messages' in kwargs:
            errors.update(kwargs['error_messages'])
        localize = kwargs.get('localize', False)
        fields = (
            IntegerField(min_value=0, required=False, localize=localize),
            IntegerField(min_value=0, localize=localize),
            FloatField()
        )
        super(FeetAndInchesField, self).__init__(fields, *args, **kwargs)

    def compress(self, data_list):
        if data_list:
            feet = data_list[0]
            inches = data_list[1]
            fractional_inches = data_list[2]
            if feet == inches == fractional_inches == 0:
                raise ValidationError(u'Please specify a value for feet or inches')
            return sum_feet_inches_fractional_inches(feet, inches, fractional_inches)
        return None


#custom_widgets.py
from django import forms
from django.template.defaultfilters import mark_safe

from app.constants import INCH_FRACTIONS, INCH_CONVERSIONS, INCH_CONVERSIONS_DICT
from app.utils import inches_to_feet_and_inches


class FeetAndInchesWidget(forms.MultiWidget):
    """
    A widget that splits foot / inch and fraction text input into a decimal value
    """

    def __init__(self, attrs=None):
        self.attrs = attrs or {}
        self.inch_conversions = INCH_CONVERSIONS
        widgets = (
                   forms.TextInput(attrs={'size' : '3'}),
                   forms.TextInput(attrs={'size' : '3'}),
                   forms.Select(attrs=attrs, choices=self.inch_conversions)
        )
        super(FeetAndInchesWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            feet, inches, fractional_inches = inches_to_feet_and_inches(value)
            return [feet, inches, fractional_inches]
        return [0, 0, 0]

    def format_output(self, rendered_widgets):
        return u'%s Feet  %s %s Inches' % \
            (rendered_widgets[0], rendered_widgets[1], rendered_widgets[2])


"""
Sample Usage and accompanying form/admin classes
"""

#models.py
from django.db import models

from app.constants import INCH_CONVERSIONS_DICT
from app.custom_fields import FeetAndInchesField
from app.utils import inches_to_feet_and_inches, \
    format_inches_to_feet_and_inches

class Cut(models.Model):
    roll = models.ForeignKey(Roll)
    date_cut = models.DateField(auto_now_add=True)
    job_number = models.CharField(max_length=50)
    qty = models.DecimalField(max_digits=9, decimal_places=4, default=0)

    def __unicode__(self):
        return '%s - Job #: %s - Qty: %s' % (self.date_cut, self.job_number, self.length_in_feet_and_inches)

    @property
    def length_in_feet_and_inches(self):
        return format_inches_to_feet_and_inches(self.qty)


#forms.py
from django import forms

from app.custom_fields import FeetAndInchesField
from app.custom_widgets import FeetAndInchesWidget
from warehouse.models import Cut, Roll

class CutForm(forms.ModelForm):
    class Meta:
        model = Cut

    qty = FeetAndInchesField()


class CutInlineFormset(forms.models.BaseInlineFormSet):
    def clean(self):
        for form in self.forms:
            try:
                if form.cleaned_data:
                    delete = form.cleaned_data.get('DELETE')
                    if not delete:
                        roll = form.cleaned_data.get('roll', None)
                        qty = form.cleaned_data.get('qty', 0)
                        check_remaining_roll_length = False
                        cut = form.save(commit=False)
                        try:
                            original_cut = self.model.objects.get(pk=cut.id)
                            if original_cut.qty != cut.qty:
                                check_remaining_roll_length = True
                        except self.model.DoesNotExist:
                            #A new Cut object is being created so 
                            check_remaining_roll_length = True

                        if check_remaining_roll_length and roll and qty > 0:
                            if qty > roll.remaining_qty:
                                raise forms.ValidationError(u'Cut length exceeds remaining length of roll')

            except AttributeError:
                pass


#admin.py
from django.contrib import admin

from warehouse.forms import CutForm, CutInlineFormset
from warehouse.models import Roll, Cut

class CutInline(admin.TabularInline):
    model = Cut
    form = CutForm
    formset = CutInlineFormset
    extra = 0


class RollAdmin(admin.ModelAdmin):
    form = RollForm
    inlines = [CutInline]

admin.site.register(Roll, RollAdmin)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

Please login first before commenting.