Opened 15 years ago
Closed 15 years ago
#14931 closed (invalid)
models.DateTimeField hard to inherit from if auto_add is used
| Reported by: | jtheoof | Owned by: | nobody |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 1.2 |
| Severity: | Keywords: | ||
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
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?
Note:
See TracTickets
for help on using tickets.
I don't know. What's up with that?
Closing invalid. Wishing I could close incoherent.
Seriously, I have no idea what you're reporting here, other than "my code doesn't work". If that's what you're reporting, please post to django-users.
If you're reporting a specific problem with the way fields are initialized, or a problem with the way auto_now etc are handled, you need to give specifics as to what you consider the problem to be, explaining the sequence of events that don't make sense, and providing a *simple* example.