#23420: Custom lookup transformers can't create DateTimeFields when USE_TZ=True
-------------------------------------+-------------------------------------
     Reporter:  chosak               |      Owner:  nobody
         Type:  Bug                  |     Status:  new
    Component:  Database layer       |    Version:  1.7
  (models, ORM)                      |   Keywords:  lookup, transform,
     Severity:  Normal               |  datetimefield
 Triage Stage:  Unreviewed           |  Has patch:  1
Easy pickings:  0                    |      UI/UX:  0
-------------------------------------+-------------------------------------
 If you define a custom lookup transformer that converts to a
 `DateTimeField`, and `USE_TZ=True`, then making a query with the transform
 fails with a cryptic error.

 Consider the case where you have a model that stores Unix timestamps
 (seconds since 1/1/1970) as positive integers. (Granted, Django supports
 DateTimeFields, but there may be other reasons why you want to store the
 raw timestamp.) It might be useful to define a
 [https://docs.djangoproject.com/en/dev/howto/custom-lookups/#a-simple-
 transformer-example custom lookup transform] that makes use of MySQL's
 [http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html
 #function_from-unixtime FROM_UNIXTIME] to convert to DateTime
 representation when queried.

 Take for example this model:

 {{{
 #!python
 from django.db import models

 class UnixTimestamp(models.Model):
     ts = models.PositiveIntegerField()
 }}}

 It'd be nice to be able to do something like:

 {{{
 #!python
 from datetime import datetime, timedelta

 one_week_ago = datetime.utcnow() - timedelta(days=7)
 recent_ts = UnixTimestamp.objects.filter(ts__as_datetime__gt=one_week_ago)
 }}}

 You should be able to do this:

 {{{
 #!python
 class PositiveIntegerDateTimeTransform(models.Transform):
     lookup_name = 'as_datetime'

     @property
     def output_field(self):
         return models.DateTimeField()

     def as_sql(self, qn, connection):
         lhs, params = qn.compile(self.lhs)
         return 'from_unixtime({})'.format(lhs), params

 models.PositiveIntegerField.register_lookup(PositiveIntegerDateTimeTransform)
 }}}

 But in practice the above produces output like this (see attached unit
 test):

 {{{
 Traceback (most recent call last):
   File "/dev/django/tests/custom_lookups/tests.py", line 253, in
 test_datetime_output_field
     UnixTimestamp.objects.filter(ts__as_datetime__gt=year_one),
   File "/dev/django/django/db/models/manager.py", line 80, in
 manager_method
     return getattr(self.get_queryset(), name)(*args, **kwargs)
   File "/dev/django/django/db/models/query.py", line 702, in filter
     return self._filter_or_exclude(False, *args, **kwargs)
   File "/dev/django/django/db/models/query.py", line 720, in
 _filter_or_exclude
     clone.query.add_q(Q(*args, **kwargs))
   File "/dev/django/django/db/models/sql/query.py", line 1319, in add_q
     clause, require_inner = self._add_q(where_part, self.used_aliases)
   File "/dev/django/django/db/models/sql/query.py", line 1346, in _add_q
     current_negated=current_negated, connector=connector)
   File "/dev/django/django/db/models/sql/query.py", line 1218, in
 build_filter
     condition = self.build_lookup(lookups, col, value)
   File "/dev/django/django/db/models/sql/query.py", line 1123, in
 build_lookup
     return final_lookup(lhs, rhs)
   File "/dev/django/django/db/models/lookups.py", line 82, in __init__
     self.rhs = self.get_prep_lookup()
   File "/dev/django/django/db/models/lookups.py", line 85, in
 get_prep_lookup
     return self.lhs.output_field.get_prep_lookup(self.lookup_name,
 self.rhs)
   File "/dev/django/django/db/models/fields/__init__.py", line 1249, in
 get_prep_lookup
     return super(DateField, self).get_prep_lookup(lookup_type, value)
   File "/dev/django/django/db/models/fields/__init__.py", line 651, in
 get_prep_lookup
     return self.get_prep_value(value)
   File "/dev/django/django/db/models/fields/__init__.py", line 1405, in
 get_prep_value
     (self.model.__name__, self.name, value),
 AttributeError: 'DateTimeField' object has no attribute 'model'
 }}}

 This error is caused by a `DateTimeField` naive timezone warning (code
 
[https://github.com/django/django/blob/1.7/django/db/models/fields/__init__.py#L1275
 here]) assuming that the field instance is bound to a model, which it is
 not in this case.

 3 attachments to this ticket:

 1. output_field_docs.patch : clarifies documentation of `output_field` in
 `django.db.models.Transform`, which is currently wrong
 2. datetime_unbound_warning.patch : modifies `DateTimeField` to warn about
 naive datetimes with unbound `Field` instances.
 3. datetime_custom_lookup_test.patch : test that demonstrates the above-
 described scenario (MySQL only)

--
Ticket URL: <https://code.djangoproject.com/ticket/23420>
Django <https://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 unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/049.f64070ab5f8b161e346b032f9f32d129%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to