#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)