#14931: models.DateTimeField hard to inherit from if auto_add is used
------------------------------------------+---------------------------------
 Reporter:  jtheoof                       |       Owner:  nobody    
   Status:  new                           |   Milestone:            
Component:  Database layer (models, ORM)  |     Version:  1.2       
 Keywords:                                |       Stage:  Unreviewed
Has_patch:  0                             |  
------------------------------------------+---------------------------------
 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?

-- 
Ticket URL: <http://code.djangoproject.com/ticket/14931>
Django <http://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-upda...@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to