Opened 14 years ago
Closed 14 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.