views:

419

answers:

2

Hello...

A common occurrence I have with one particular project is that it requires the user to enter dimensions (for width/depth/height) in Feet and Inches. Calculations are needed to be performed on that dimension, so I've been working on a custom field type that takes in a dimension in Feet/Inches (eg. 1'-10") and saves it to the database as a decimal number using a regex to parse the input. The field displays to the end-user as feet-inches at all times (with the eventual goal of writing a method to be able to optionally display in metric, and interact with measure.py, and geodjango stuff). What I have so far is definitely not DRY, but aside from that, I'm having trouble with validation at the form level. The custom model field itself works properly (from what I've seen), and I've written a form field clean method which should work to validate the field. My question is how to hook that form field back into my model form to work for all the width/depth/height fields. I'm thinking maybe an override of the init on the modelform (a la self.fields['depth']...) , but I'm not quite sure where to go from here...

DCML_PATTERN = re.compile(r'^(?P<feet>\d+)(?P<dec_inch>\.?\d*)\'?$')
FTIN_PATTERN = re.compile(r'^(?P<feet>\d+)\'?\s*-?\s*(?P<inch>[0-9]|10|11)?\"?$')

class FtInField(models.Field):
        __metaclass__ = models.SubfieldBase

        empty_strings_allowed = False

        def db_type(self):
                return 'double'

        def get_internal_type(self):
                return "FtInField"

        def to_python(self,value):
                if value is u'' or value is None:
                        return None
                if isinstance(value, float):
                        m = FTDCML_PATTERN.match(str(value))
                        if m is None:
                                raise Exception('Must be an integer or decimal number')
                        feet = int(m.group('feet'))
                        dec_inch = float(m.group('dec_inch') or 0)
                        inch = dec_inch * 12
                        return "%d\'-%.0f\"" % (feet,inch)
                return value

        def get_db_prep_value(self,value):
                if value is u'' or value is None:
                        return None
                m = FTIN_PATTERN.match(value)
                if m is None:
                        raise Exception('Must be in X\'-Y" Format')
                feet = int(m.group('feet'))
                inch = int(m.group('inch') or 0)
                return (feet + (inch/float(12)))

class FtInField(forms.Field):
        def clean(self,value):
                super(FtInField, self).clean(value)
                if value is u'' or value is None:
                        raise forms.ValidationError('Enter a dimension in X\'-Y" format')
                m = FTIN_PATTERN.match(value)
                if m is None:
                        raise forms.ValidationError('Must be in X\'-Y" Format')
                feet = int(m.group('feet'))
                inch = int(m.group('inch') or 0)
                value = '%d\'-%.0f"' % (feet,inch)
                return value

class ProductClass(models.Model):
        productname = models.CharField('Product Name', max_length=60,blank=True)
        depth = FtInField('Depth (Feet/Inches)')
        width = FtInField('Width (Feet/Inches)')
        height = FtInField('Height (Feet/Inches)')

class ProductClassForm(forms.ModelForm):
        depth = FtInField()
        width = FtInField()
        height = FtInField()

        class Meta:
                model = ProductClass

class ProductClassAdmin(admin.ModelAdmin):
        form = ProductClassForm
A: 

To avoid duplication you should probably implement a data type class that handles the parsing of feets and inches for you, it should greatly simplify the other code.

Then you should create a model field and form field, keeping in mind that these are two COMPLETELY SEPARATE components. (which you more or less have already done, but this is just for completeness)

Now, if I'm reading the question right, you want to set the default form field for your model field. To facilitate this you want to implement the formfield() function on your model field class. Ref: the django docs

Daniel Bruce
A: 

Thank you, thank you to both of you. This is what I came up with (based on both of your advise). I'll work on defining a data type to make it better in terms of repetition, but in the meantime, this works...(I was so close, yet so far away...) You guys are amazing. Thanks.

DCML_PATTERN = re.compile(r'^(?P<feet>\d+)(?P<dec_inch>\.?\d*)\'?$')
FTIN_PATTERN = re.compile(r'^(?P<feet>\d+)\'?\s*-?\s*(?P<inch>[0-9]|10|11)?\"?$')

class FtInFormField(forms.Field):
        def clean(self,value):
                super(FtInFormField, self).clean(value)
                if value is u'' or value is None:
                        raise forms.ValidationError('Enter a dimension in X\'-Y" format')
                m = FTIN_PATTERN.match(value)
                if m is None:
                        raise forms.ValidationError('Must be in X\'-Y" Format')
                feet = int(m.group('feet'))
                inch = int(m.group('inch') or 0)
                value = '%d\'-%.0f"' % (feet,inch)
                return value

class FtInField(models.Field):
        __metaclass__ = models.SubfieldBase

        empty_strings_allowed = False

        def db_type(self):
                return 'double'

        def get_internal_type(self):
                return "FtInField"

        def to_python(self,value):
                if value is u'' or value is None:
                        return None
                if isinstance(value, float):
                        m = FTDCML_PATTERN.match(str(value))
                        if m is None:
                                raise Exception('Must be an integer or decimal number')
                        feet = int(m.group('feet'))
                        dec_inch = float(m.group('dec_inch') or 0)
                        inch = dec_inch * 12
                        return "%d\'-%.0f\"" % (feet,inch)
                return value

        def get_db_prep_value(self,value):
                if value is u'' or value is None:
                        return None
                m = FTIN_PATTERN.match(value)
                if m is None:
                        raise Exception('Must be in X\'-Y" Format')
                feet = int(m.group('feet'))
                inch = int(m.group('inch') or 0)
                return (feet + (inch/float(12)))

        def formfield(self, **kwargs):
                defaults = {'form_class': FtInFormField}
                defaults.update(kwargs)
                return super(FtInField,self).formfield(**defaults)

class ProductClass(models.Model):
        productname = models.CharField('Product Name', max_length=60,blank=True)
        depth = FtInField('Depth (Feet/Inches)')
        width = FtInField('Width (Feet/Inches)')
        height = FtInField('Height (Feet/Inches)')

class ProductClassForm(forms.ModelForm):

        class Meta:
                model = ProductClass

class ProductClassAdmin(admin.ModelAdmin):
        form = ProductClassForm
bkev