J. Bestoso
J. Bestoso

Reputation: 31

Annotate Django queryset with a datetime

So, I have a queryset that I need to annotate with some additional fields, including integers, booleans, and datetimes. Something like this:

def get_appointments_with_overrides(override_price, override_start_time, override_advance_booking_needed):
    return (Appointments.objects.annotate(override_price=Value(override_price, IntegerField())).
             annotate(override_start_time=Value(override_start_time, DateTimeField())).
              annotate(override_advance_booking_needed=Value(override_advance_booking_needed, BooleanField())))

Where override_price etc. are all properties (but not Django fields) in the Appointments model.

When I try to use this (specifically, when I call first() or last() on the annotated queryset), I get the following error.

AttributeError: 'DateTimeField' object has no attribute 'model'

If I remove the datetime annotation, I don’t get any errors and the other two annotations work exactly as I expect them to. So what, if anything, am I doing wrong with this datetime annotation?

Edited to add an Appointments model:

class Appointments(model):

   price = models.IntegerField(null=False)
   start_time = models.DateTimeField(null=False)
   advance_booking_needed = models.BooleanField(null=False, default=True)

   def __init__(self):
      super(Appointments, self).__init__(*args, **kwargs)
      self.__override_price = None
      self.__override_start_time = None
      self.__override_advance_booking_needed = None

   @property
   def override_price(self):
      return self.__override_price

   @override_price.setter
   def override_price(self, value):
      self.__override_price = value

   @property
   def override_start_time(self):
      return self.__override_start_time

   @override_start_time.setter
   def override_start_time(self, value):
      self.__override_start_time = value

   @property
   def override_advance_booking_needed(self):
      return self.__override_advance_booking_needed

   @override_advance_booking_needed.setter
   def override_advance_booking_needed(self, value):
      self.__override_advance_booking_needed = value

Edited again to add a stack trace:

  File "/project_dir/appointments/tests/test_overrides.py", line 232, in test_override_values:
    overriden_appointments.last()
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/query.py", line 573, in last
    objects = list((self.reverse() if self.ordered else self.order_by('-pk'))[:1])
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/query.py", line 250, in __iter__
    self._fetch_all()
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/query.py", line 1118, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/query.py", line 53, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch)
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 876, in execute_sql
    sql, params = self.as_sql()
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 428, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup()
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 46, in pre_sql_setup
    self.setup_query()
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 37, in setup_query
    self.select, self.klass_info, self.annotation_col_map = self.get_select()
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 227, in get_select
    sql, params = self.compile(col, select_format=True)
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 373, in compile
    sql, params = node.as_sql(self, self.connection)
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/expressions.py", line 616, in as_sql
    val = self.output_field.get_db_prep_value(val, connection=connection)
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 1459, in get_db_prep_value
    value = self.get_prep_value(value)
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 1438, in get_prep_value
    value = super(DateTimeField, self).get_prep_value(value)
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 1296, in get_prep_value
    return self.to_python(value)
  File "/project_dir/env/fake_name/lib/python2.7/site-packages/django/db/models/fields/__init__.py", line 1392, in to_python
    (self.model.__name__, self.name, value),
AttributeError: 'DateTimeField' object has no attribute 'model'

Upvotes: 0

Views: 3248

Answers (3)

Adam Barnes
Adam Barnes

Reputation: 3232

Remove the second arguments to Value(). If you hand it True or False, it's smart enough to know it's a boolean value and use the appropriate type. Similar for integers and the built in datetime.date[time] classes.

Upvotes: 0

J. Bestoso
J. Bestoso

Reputation: 31

Well, I have a workaround, at least: I can convert the datetime to a string, and store that instead

def get_appointments_with_overrides(override_price, override_start_time, override_advance_booking_needed):
    return (Appointments.objects.annotate(override_price=Value(override_price, IntegerField())).
             annotate(override_start_time=Value(override_start_time.strftime('%d-%m-%Y %H:%M:%S.%f'), CharField())).
              annotate(override_advance_booking_needed=Value(override_advance_booking_needed, BooleanField())))

And then I can convert it back to a datetime later.

This works, but it's stupid, and I'd really like to know why I can't just annotate with the datetime directly.

Upvotes: 0

Raydel Miranda
Raydel Miranda

Reputation: 14360

Plane and simple, you can't use properties for ORM lookups, it is not supported.

You could though look into Custom Look Ups and implement your own for your use case.

On the other hand:

In your model definition, you're declaring __override_price etc ... as class attributes, you might want to be sure you know the difference: Class vs instance attributes.

Upvotes: 1

Related Questions