#30114: Strange ValidationError issue
-------------------------------------+-------------------------------------
Reporter: Jerry | Owner: nobody
Vinokurov |
Type: Bug | Status: new
Component: Database | Version: 2.1
layer (models, ORM) | Keywords: validationerror,
Severity: Normal | uuid
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
Our project uses UUIDs as primary keys on a number of models. This works
as expected for the majority of the time, but at some point, we run into
the following error:
{{{
ValidationError: ["'f0c9eb37-a073-47b5-bbb1-589920939c5e' is not a valid
UUID."]
}}}
This is in fact a valid UUID and also a UUID in our system. We use Sentry
for our error reporting, which gives the entire stack trace:
{{{
ValidationError: ["'670e5eaf-d231-305e-bb5b-8c8fb0262574' is not a valid
UUID."]
File "django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "django/core/handlers/base.py", line 126, in _get_response
response = self.process_exception_by_middleware(e, request)
File "django/core/handlers/base.py", line 124, in _get_response
response = wrapped_callback(request, *callback_args,
**callback_kwargs)
File "python3.6/contextlib.py", line 52, in inner
return func(*args, **kwds)
File "django/contrib/admin/options.py", line 604, in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
File "django/utils/decorators.py", line 142, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "django/views/decorators/cache.py", line 44, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File "django/contrib/admin/sites.py", line 223, in inner
return view(request, *args, **kwargs)
File "django/utils/decorators.py", line 45, in _wrapper
return bound_method(*args, **kwargs)
File "django/utils/decorators.py", line 142, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "django/contrib/admin/options.py", line 1792, in changelist_view
'selection_note': _('0 of %(cnt)s selected') % {'cnt':
len(cl.result_list)},
File "django/db/models/query.py", line 250, in __len__
self._fetch_all()
File "django/db/models/query.py", line 1188, in _fetch_all
self._prefetch_related_objects()
File "django/db/models/query.py", line 723, in _prefetch_related_objects
prefetch_related_objects(self._result_cache,
*self._prefetch_related_lookups)
File "django/db/models/query.py", line 1569, in prefetch_related_objects
obj_list, additional_lookups = prefetch_one_level(obj_list,
prefetcher, lookup, level)
File "django/db/models/query.py", line 1682, in prefetch_one_level
prefetcher.get_prefetch_queryset(instances,
lookup.get_current_queryset(level)))
File "django/contrib/contenttypes/fields.py", line 194, in
get_prefetch_queryset
ret_val.extend(ct.get_all_objects_for_this_type(pk__in=fkeys))
File "django/db/models/query.py", line 268, in __iter__
self._fetch_all()
File "django/db/models/query.py", line 1186, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "django/db/models/query.py", line 54, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch,
chunk_size=self.chunk_size)
File "django/db/models/sql/compiler.py", line 1052, in execute_sql
sql, params = self.as_sql()
File "django/db/models/sql/compiler.py", line 464, in as_sql
where, w_params = self.compile(self.where) if self.where is not None
else ("", [])
File "django/db/models/sql/compiler.py", line 390, in compile
sql, params = node.as_sql(self, self.connection)
File "django/db/models/sql/where.py", line 81, in as_sql
sql, params = compiler.compile(child)
File "django/db/models/sql/compiler.py", line 390, in compile
sql, params = node.as_sql(self, self.connection)
File "django/db/models/lookups.py", line 355, in as_sql
return super().as_sql(compiler, connection)
File "django/db/models/lookups.py", line 163, in as_sql
rhs_sql, rhs_params = self.process_rhs(compiler, connection)
File "django/db/models/lookups.py", line 339, in process_rhs
sqls, sqls_params = self.batch_process_rhs(compiler, connection, rhs)
File "django/db/models/lookups.py", line 231, in batch_process_rhs
pre_processed = super().batch_process_rhs(compiler, connection, rhs)
File "django/db/models/lookups.py", line 51, in batch_process_rhs
_, params = self.get_db_prep_lookup(rhs, connection)
File "django/db/models/lookups.py", line 186, in get_db_prep_lookup
if self.get_db_prep_lookup_value_is_iterable else
File "django/db/models/lookups.py", line 185, in <listcomp>
[get_db_prep_value(v, connection, prepared=True) for v in value]
File "django/db/models/fields/__init__.py", line 2316, in
get_db_prep_value
value = self.to_python(value)
File "django/db/models/fields/__init__.py", line 2330, in to_python
params={'value': value},
}}}
Ok, so what's going on near line 2330 in
`django/db/models/fields/__init__.py` in the `to_python` function?
{{{
2325: return uuid.UUID(value)
2326: except (AttributeError, ValueError):
2327: raise exceptions.ValidationError(
2328: self.error_messages['invalid'],
2329: code='invalid',
2330: params={'value': value},
2331: )
2332: return value
2333: def formfield(self, **kwargs):
2334: return super().formfield(**{
}}}
And what's going on one frame before that, near line 2316 in
`django/db/models/fields/__init__.py` in `get_db_prep_value`:
{{{
2312: def get_db_prep_value(self, value, connection, prepared=False):
2313: if value is None:
2314: return None
2315: if not isinstance(value, uuid.UUID):
2316: value = self.to_python(value)
2317: if connection.features.has_native_uuid_field:
2318: return value
2319: return value.hex
}}}
Ok, so what's happening is that if the value that we're trying to convert
is not an instance of `uuid.UUID`, then we convert it to a Python value,
which calls `to_python`. This makes sense if `value` is a string, but...
Sentry is telling me that `value` ''is already'' a UUID:
`value UUID('f0c9eb37-a073-47b5-bbb1-589920939c5e')`
The check on line 2315 in `get_db_prep_value` should catch the fact that
this is a UUID, but it doesn't. I don't understand why at all. Sentry is
telling me that this is a UUID but it's failing the instance check; as a
result line 2325 in `to_python` attempts to convert the value to a UUID,
but `value` is already a UUID, so it throws a `ValidationError`.
That's the details of the error. The very weird thing about it is that
this error is totally inconsistent in when it shows up. We'll be running
for days in a production environment without any problems and then all of
a sudden this error will crop up. Once that happens, any attempt to
deserialize a UUID fails with this error.
We are running our application on an EC2 instance in AWS managed by
Elastic Beanstalk. A deployment of the application, which restarts it,
makes this error go away. But sooner or later it crops up again and brings
everything down. The worst thing is that I can't seem to replicate this
error in local development. Our DB runs on an RDS instance and I can point
my local development environment to that DB but I can't replicate the
error.
I'm truly baffled by this. Does anyone have any idea how this can happen?
It very much ''seems'' like a bug to me, hence this report, but I honestly
don't know. I'm just hoping to get some guidance or ideas about what is
going on here.
--
Ticket URL: <https://code.djangoproject.com/ticket/30114>
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/054.6a36cfa5fcc6c4d84a21574360893b5a%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.