﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
14931	models.DateTimeField hard to inherit from if auto_add is used	jtheoof	nobody	"Suppose I want to override models.DateTimeField with this model inspired from Snippet [388](http://djangosnippets.org/snippets/388)

{{{
import datetime
import logging
import warnings
import pytz

from django.conf import settings
from django.db import models
from django.dispatch import dispatcher
from django.db.models import signals

LOG = logging.getLogger(__name__)

try:
    pytz_exc = (pytz.UnknownTimeZoneError,)
except AttributeError:
    # older versions of pytz
    pytz_exc = (IOError, KeyError)

TIMEZONES = tuple((t, t) for t in pytz.all_timezones)

class UTCDateTimeField(models.DateTimeField):
    
    def __init__(self, *args, **kw):
        self.default_tz = kw.get('default_tz')
        if 'default_tz' in kw:
            del(kw['default_tz'])
        super(UTCDateTimeField, self).__init__(*args, **kw)
        self._name = None
        
    def get_internal_type(self):
        return 'DateTimeField'

    def _model_init(self, signal, sender, instance, **kw):
        setattr(instance, ""_%s"" % self._name, getattr(instance, self._name))
        setattr(instance, ""_%s_utc"" % self._name, getattr(instance, ""%s_utc"" % self._name))
        setattr(instance, ""_%s_tz"" % self._name, getattr(instance, ""%s_tz"" % self._name))
        
    def _model_pre_save(self, signal, sender, instance, **kw):
        dt = getattr(instance, self._name)
        utc = getattr(instance, ""%s_utc"" % self._name)
        tz = getattr(instance, ""%s_tz"" % self._name)
        if not tz:
            # try to get the default
            if isinstance(self.default_tz, basestring):
                tz = getattr(instance, self.default_tz)
                if isinstance(tz, basestring):
                    # use it as a timezone
                    pass
                elif callable(tz):
                    self.default_tz = tz
                    tz = None
                else:
                    warnings.warn(""UTCDateTimeField cannot use default_tz %s in model %s"" % (self.default_tz, instance))
                    self.default_tz = lambda: getattr(settings, 'TIME_ZONE', 'UTC')
                    tz = None
            if callable(self.default_tz):
                tz = self.default_tz()
                if not tz or not isinstance(tz, basestring):
                    warnings.warn(""UTCDateTimeField cannot use default timezone %s, using Django default"" % tz)
                    tz = getattr(settings, 'TIME_ZONE', 'UTC')
            if not tz:
                tz = getattr(settings, 'TIME_ZONE', 'UTC')
            setattr(instance, ""%s_tz"" % self._name, tz)
        _dt = getattr(instance, ""_%s"" % self._name)
        _utc = getattr(instance, ""_%s_utc"" % self._name)
        _tz = getattr(instance, ""_%s_tz"" % self._name)
        if isinstance(tz, unicode):
            tz = tz.encode('utf8')
        try:
            timezone = pytz.timezone(tz)
        except pytz_exc, e:
            warnings.warn(""Error in timezone: %s"" % e)
            setattr(instance, ""%s_tz"" % self._name, 'UTC')
            timezone = pytz.UTC
        if not dt and not utc:
            # do nothing
            return
        elif dt and dt != _dt:
            utc = UTCDateTimeField.utc_from_localtime(dt, timezone)
            setattr(instance, ""%s_utc"" % self._name, utc)
        elif utc and (utc != _utc or tz != _tz):
            dt = UTCDateTimeField.localtime_from_utc(utc, timezone)
            setattr(instance, ""_%s"" % self._name, dt)
        elif not dt:
            dt = UTCDateTimeField.localtime_from_utc(utc, timezone)
            setattr(instance, ""_%s"" % self._name, dt)
        elif not utc:
            utc = UTCDateTimeField.utc_from_localtime(dt, timezone)
            setattr(instance, ""%s_utc"" % self._name, utc)
        if dt.tzinfo:
            dt = dt.replace(tzinfo=None)
        if utc.tzinfo:
            utc = utc.replace(tzinfo=None)
        setattr(instance, ""_%s"" % self._name, dt)
        setattr(instance, ""_%s_utc"" % self._name, utc)
        setattr(instance, ""_%s_tz"" % self._name, tz)
        setattr(instance, ""%s"" % self._name, dt)
        setattr(instance, ""%s_utc"" % self._name, utc)
        
    def _model_post_save(self, signal, sender, instance, **kw):
        setattr(instance, ""%s"" % self._name, getattr(instance, ""_%s"" % self._name))
        setattr(instance, ""%s_utc"" % self._name, getattr(instance, ""_%s_utc"" % self._name))
        
    
    def contribute_to_class(self, cls, name):
        self._name = name

        super(UTCDateTimeField, self).contribute_to_class(cls, name)
        self.utc_field = models.DateTimeField(null=True, editable=False)
        self.creation_counter = self.utc_field.creation_counter + 1
        models.DateTimeField.contribute_to_class(self.utc_field, cls, '%s_utc' % name)
        self.tz_field = models.CharField(max_length=38, editable=False, choices=TIMEZONES, default=getattr(settings, 'TIME_ZONE', 'UTC'))
        self.creation_counter = self.tz_field.creation_counter + 1
        models.CharField.contribute_to_class(self.tz_field, cls, '%s_tz' % name)
        
        # add the properties for offset-aware datetimes
        def get_timezone(s):
            tz = getattr(s, '%s_tz' % name)
            if isinstance(tz, unicode):
                tz = tz.encode('utf8')
            return pytz.timezone(tz)
        setattr(cls, ""%s_timezone"" % name, property(get_timezone))
        
        def get_dt_offset_aware(s):
            dt = getattr(s, ""%s_utc_offset_aware"" % name)
            tz = getattr(s, ""%s_timezone"" % name)
            return dt.astimezone(tz)
        setattr(cls, ""%s_offset_aware"" % name, property(get_dt_offset_aware))
        
        def get_utc_offset_aware(s):
            return getattr(s, '%s_utc' % name).replace(tzinfo=pytz.utc)
        setattr(cls, ""%s_utc_offset_aware"" % name, property(get_utc_offset_aware))
        
        signals.post_init.connect(self._model_init, sender=cls)
        signals.pre_save.connect(self._model_pre_save, sender=cls)
        signals.post_save.connect(self._model_post_save, sender=cls)

    @staticmethod
    def localtime_from_utc(utc, tz):
        dt = utc.replace(tzinfo=pytz.utc)
        return dt.astimezone(tz)
        
    @staticmethod
    def utc_from_localtime(dt, tz):
        return tz.normalize(tz.localize(dt)).astimezone(pytz.utc)
}}}

Now with the following model:

{{{
class SimpleModel(models.Model):
    
    date_created = UTCDateTimeField(auto_now_add=True)
    #date_created = UTCDateTimeField(default=datetime.datetime.now)
}}}
    
Doing a simple:

{{{
    SimpleModel.objects.create()
}}}
    
Will fail because `_model_pre_save` is called with empty: `getattr(instance, self._name)` so `dt` is `None`
It works if I use the commented line instead

Looking through django's code `django.db.models.fields.__init__.py` I founds this:

{{{
def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
    self.auto_now, self.auto_now_add = auto_now, auto_now_add
    #HACKs : auto_now_add/auto_now should be done as a default or a pre_save.
    if auto_now or auto_now_add:
        kwargs['editable'] = False
        kwargs['blank'] = True
    Field.__init__(self, verbose_name, name, **kwargs)
}}}
        
at initialization of DateField, what's up with that? "		closed	Database layer (models, ORM)	1.2		invalid			Unreviewed	0	0	0	0	0	0
