"object has no attribute" in custom Django model field
Question: I am trying to create a Django model field that represents a duration with
days, hours, minutes and seconds text input fields in the HTML, and stores the
duration in the db using the ical format (RFC5545).

(this is related to my question on [How to create an ical duration field in
Django?](http://stackoverflow.com/questions/13597063/how-to-create-an-ical-
duration-field-in-django))

Here is my approach:

Thanks bakkal and Pol. Below is what I came up with.

    
    
    from django.db import models
    from icalendar.prop import vDuration
    from django.forms.widgets import MultiWidget
    from django.forms import TextInput, IntegerField
    from django.forms.util import flatatt
    from django.forms.fields import MultiValueField
    from django.utils.encoding import force_unicode
    from django.utils.safestring import mark_safe
    from django.utils.text import capfirst
    from django.utils.translation import ugettext_lazy as _
    from django.core import validators
    from datetime import timedelta
    
    def is_int(s):
        try: 
            int(s)
            return True
        except ValueError:
            return False
    
    class Widget_LabelInputField(TextInput):
        """
        Input widget with label
        """
        input_type="numbers"
        def __init__(self, labelCaption, attrs=None):
            self.labelCaption = labelCaption
        super(Widget_LabelInputField, self).__init__(attrs)
    
        def _format_value(self, value):
            if is_int(value):
                return value
            return '0'
    
        def render(self, name, value, attrs=None):
            if value is None:
                value = '0'
            final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
            if value != '':
                # Only add the 'value' attribute if a value is non-empty.
                final_attrs['value'] = force_unicode(self._format_value(value))
            if (self.labelCaption):
            typeString = self.labelCaption + ': '
            else:
                typeString = ''           
            return mark_safe(u'' + typeString + '<input%s style=\'width: 30px; margin-right: 20px\'/>' % flatatt(final_attrs))
    
    
    
    class Widget_DurationField(MultiWidget):
        """
        A Widget that splits duration input into two <input type="text"> boxes.
        """
    
        def __init__(self, attrs=None):
            widgets = (Widget_LabelInputField(labelCaption='days', attrs=attrs),
                       Widget_LabelInputField(labelCaption='hours', attrs=attrs),
                       Widget_LabelInputField(labelCaption='minutes', attrs=attrs),
                       Widget_LabelInputField(labelCaption='seconds', attrs=attrs)
                       )
            super(Widget_DurationField, self).__init__(widgets, attrs)
    
        def decompress(self, value):
            if value:
                duration = vDuration.from_ical(value)
                return [str(duration.days), str(duration.seconds // 3600), str(duration.seconds % 3600 // 60), str(duration.seconds % 60)]
            return [None, None, None, None]
    
    
    
    class Forms_DurationField(MultiValueField):
        widget = Widget_DurationField
        default_error_messages = {
            'invalid_day': _(u'Enter a valid day.'),
            'invalid_hour': _(u'Enter a valid hour.'),
            'invalid_minute': _(u'Enter a valid minute.'),
            'invalid_second': _(u'Enter a valid second.')
        }
    
        def __init__(self, *args, **kwargs):
            errors = self.default_error_messages.copy()
            if 'error_messages' in kwargs:
                errors.update(kwargs['error_messages'])
            fields = (
                IntegerField(min_value=-9999, max_value=9999,
                          error_messages={'invalid': errors['invalid_day']},),
                IntegerField(min_value=-9999, max_value=9999,
                          error_messages={'invalid': errors['invalid_hour']},),
                IntegerField(min_value=-9999, max_value=9999,
                          error_messages={'invalid': errors['invalid_minute']},),
                IntegerField(min_value=-9999, max_value=9999,
                          error_messages={'invalid': errors['invalid_second']},),
            )
            super(Forms_DurationField, self).__init__(fields, *args, **kwargs)
    
        def compress(self, data_list):
            if data_list:
                if data_list[0] in validators.EMPTY_VALUES:
                    raise ValidationError(self.error_messages['invalid_day'])
                if data_list[1] in validators.EMPTY_VALUES:
                    raise ValidationError(self.error_messages['invalid_hour'])
                if data_list[2] in validators.EMPTY_VALUES:
                    raise ValidationError(self.error_messages['invalid_minute'])
                if data_list[3] in validators.EMPTY_VALUES:
                    raise ValidationError(self.error_messages['invalid_second'])
    
                return vDuration(timedelta(days=data_list[0],hours=data_list[1],minutes=data_list[2],seconds=data_list[3]))
            return None
    
    
    
    
    class Model_DurationField(models.Field):
        description = "Duration"
    
        def __init__(self, *args, **kwargs):
            super(Model_DurationField, self).__init__(*args, **kwargs)
    
        def db_type(self, connection):
            return 'varchar(255)'
    
        def get_internal_type(self):
            return "Model_DurationField"
    
        def to_python(self, value):
            if isinstance(value, vDuration) or value is None:
                return value
    
            return vDuration.from_ical(value) 
    
        def get_prep_value(self, value):
            return value.to_ical() 
    
        def formfield(self, **kwargs):
            defaults = {
                'form_class': Forms_DurationField,
                'required': not self.blank,
                'label': capfirst(self.verbose_name),
                'help_text': self.help_text}
            defaults.update(kwargs)
            return super(Model_DurationField, self).formfield(**defaults)
    

It works in the following Model:

    
    
    class TestModel(models.Model):
        ID = models.CharField(max_length=255)
        start = models.DateTimeField(null=True)
        #duration = models.CharField(max_length=255,null=True) commented out
        otherDuration = duration.Model_DurationField(null=True)
    

but not in this one:

    
    
    class TestModel(models.Model):
        ID = models.CharField(max_length=255)
        start = models.DateTimeField(null=True)
        duration = models.CharField(max_length=255,null=True)  # not commented out
        otherDuration = duration.Model_DurationField(null=True)
    

I get the following error:

    
    
    File "/somepath/models.py", line 5, in TestModel
        otherDuration = duration.Model_DurationField(null=True)
    AttributeError: 'CharField' object has no attribute 'Model_DurationField'
    

That puzzles me... it seems that python considers my field to be an attribute
of the previous field, but only if it is a CharField. Any ideas?


Answer: I was stupid. The problem was that I named the file where the model was
defined duration.py, so there was a naming conflict with the "duration" field.
I renamed the file and it worked.

