0

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'
5
  • Can you include your model Appointments so we can see the definition of the fields and properties? Commented Jan 23, 2019 at 19:42
  • @Ralf I've added one Commented Jan 23, 2019 at 19:48
  • Can you add the full traceback for the error? Commented Jan 23, 2019 at 20:01
  • I can't make sense of your Appointments class. What is the purpose of the __ class attributes and the properties? Only model.Field class properties will actually be represented as columns in the database. Properties only exist in the python object. They are not used by the django ORM. And annotations must be calculated from values in the database. Commented Jan 23, 2019 at 20:11
  • @HåkenLid Please don't get too hung up on the class definition. It's something I made up to try to demonstrate the problem I'm encountering because I don't want to post my company's actual code on the internet. I am well aware that the properties won't actually be represented in the database. Commented Jan 23, 2019 at 20:28

3 Answers 3

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

So why is it working for the integer and boolean properties, but not the datetime one?
In fact, you're right, you're annotating not filtering, could you post the stack trace of the error?
Sorry, they're instance attributes in the actual model. I'll update the one I have here and add the stack trace.
0

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.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.