Reputation: 31
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
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
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
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