#34205: Arrayfield constraint validation crash in 4.1
-------------------------------------+-------------------------------------
     Reporter:  James Gillard        |                    Owner:  nobody
         Type:  Uncategorized        |                   Status:  new
    Component:  Database layer       |                  Version:  4.1
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by James Gillard:

Old description:

> I'd already posted this in the django-users groups and someone had
> suggested this might be a bug, so am reposting here:
>
> I can't yet work out whether this is a Django bug or how I'm using model
> constraints... Just upgraded from 4.0.8 to 4.1.4 and have hit this issue
> when saving this model in the admin. I'd read in the release notes that
> these constraints would be validated on model save, and that's the code
> that's leading to this exception. If it's not something I've done, it
> seems ArrayField isn't working with this new validation of my condition.
> The same happens for Q(phone_numbers__len__gte=0), and the error
> disappears if I comment out this condition. It seems the generated code
> might be wrong, as I see 12 "%s" and only 11 elements in params. All it's
> trying to do is ensure that an empty list isn't considered unique.
>
> When hitting save I now get {{{ IndexError: tuple index out of range }}}
>
> Here's the failing model:
>
> {{{
> phone_numbers = ArrayField(models.CharField(max_length=200),
> default=list, blank=True)
>
> class Meta:
>     constraints = [
>         models.UniqueConstraint(
>            fields=['phone_numbers'],
>            condition=~Q(phone_numbers__len=0),
>            name='unique_email_phones',
>         ),
>     ]
> )
> }}}
>
> And the full stack trace:
> {{{
> Traceback (most recent call last):
>   File ".../django/core/handlers/wsgi.py", line 131, in __call__
>     response = self.get_response(request)
>   File ".../django/core/handlers/base.py", line 140, in get_response
>     response = self._middleware_chain(request)
>   File ".../django/core/handlers/exception.py", line 57, in inner
>     response = response_for_exception(request, exc)
>   File ".../django/core/handlers/exception.py", line 140, in
> response_for_exception
>     response = handle_uncaught_exception(
>   File ".../django/core/handlers/exception.py", line 181, in
> handle_uncaught_exception
>     return debug.technical_500_response(request, *exc_info)
>   File ".../django_extensions/management/technical_response.py", line 40,
> in null_technical_500_response
>     raise exc_value.with_traceback(tb)
>   File ".../django/core/handlers/exception.py", line 55, in inner
>     response = get_response(request)
>   File ".../django/core/handlers/base.py", line 197, in _get_response
>     response = wrapped_callback(request, *callback_args,
> **callback_kwargs)
>   File ".../django/contrib/admin/options.py", line 686, in wrapper
>     return self.admin_site.admin_view(view)(*args, **kwargs)
>   File ".../django/utils/decorators.py", line 133, in _wrapped_view
>     response = view_func(request, *args, **kwargs)
>   File ".../django/views/decorators/cache.py", line 62, in
> _wrapped_view_func
>     response = view_func(request, *args, **kwargs)
>   File ".../django/contrib/admin/sites.py", line 242, in inner
>     return view(request, *args, **kwargs)
>   File "/Users/james/journee-git/journee-
> django-2/apps/catalogue/admin.py", line 1175, in change_view
>     return super().change_view(
>   File ".../django_object_actions/utils.py", line 57, in change_view
>     return super(BaseDjangoObjectActions, self).change_view(
>   File ".../django/contrib/admin/options.py", line 1893, in change_view
>     return self.changeform_view(request, object_id, form_url,
> extra_context)
>   File ".../django/utils/decorators.py", line 46, in _wrapper
>     return bound_method(*args, **kwargs)
>   File ".../django/utils/decorators.py", line 133, in _wrapped_view
>     response = view_func(request, *args, **kwargs)
>   File ".../django/contrib/admin/options.py", line 1750, in
> changeform_view
>     return self._changeform_view(request, object_id, form_url,
> extra_context)
>   File ".../django/contrib/admin/options.py", line 1796, in
> _changeform_view
>     form_validated = form.is_valid()
>   File ".../django/forms/forms.py", line 205, in is_valid
>     return self.is_bound and not self.errors
>   File ".../django/forms/forms.py", line 200, in errors
>     self.full_clean()
>   File ".../django/forms/forms.py", line 439, in full_clean
>     self._post_clean()
>   File ".../django/forms/models.py", line 492, in _post_clean
>     self.instance.full_clean(exclude=exclude, validate_unique=False)
>   File ".../django/db/models/base.py", line 1472, in full_clean
>     self.validate_constraints(exclude=exclude)
>   File ".../django/db/models/base.py", line 1423, in validate_constraints
>     constraint.validate(model_class, self, exclude=exclude, using=using)
>   File ".../django/db/models/constraints.py", line 361, in validate
>     if (self.condition & Exists(queryset.filter(self.condition))).check(
>   File ".../django/db/models/query_utils.py", line 141, in check
>     return compiler.execute_sql(SINGLE) is not None
>   File ".../django/db/models/sql/compiler.py", line 1398, in execute_sql
>     cursor.execute(sql, params)
>   File ".../debug_toolbar/panels/sql/tracking.py", line 230, in execute
>     return self._record(self.cursor.execute, sql, params)
>   File ".../debug_toolbar/panels/sql/tracking.py", line 154, in _record
>     return method(sql, params)
>   File ".../django/db/backends/utils.py", line 103, in execute
>     return super().execute(sql, params)
>   File ".../django/db/backends/utils.py", line 67, in execute
>     return self._execute_with_wrappers(
>   File ".../django/db/backends/utils.py", line 80, in
> _execute_with_wrappers
>     return executor(sql, params, many, context)
>   File ".../django/db/backends/utils.py", line 89, in _execute
>     return self.cursor.execute(sql, params)
> IndexError: tuple index out of range
> }}}

New description:

 I'd already posted this in the django-users groups and someone had
 suggested this might be a bug, so am reposting here:

 I can't yet work out whether this is a Django bug or how I'm using model
 constraints... Just upgraded from 4.0.8 to 4.1.4 and have hit this issue
 when saving this model in the admin. I'd read in the release notes that
 these constraints would be validated on model save, and that's the code
 that's leading to this exception. If it's not something I've done, it
 seems ArrayField isn't working with this new validation of my condition.
 The same happens for Q(phone_numbers__len__gte=0), and the error
 disappears if I comment out this condition. It seems the generated code
 might be wrong, as I see 12 "%s" and only 11 elements in params. All it's
 trying to do is ensure that an empty list isn't considered unique.

 When hitting save I now get {{{ IndexError: tuple index out of range }}}

 Here's the failing model:

 {{{
 phone_numbers = ArrayField(models.CharField(max_length=200), default=list,
 blank=True)

 class Meta:
     constraints = [
         models.UniqueConstraint(
            fields=['phone_numbers'],
            condition=~Q(phone_numbers__len=0),
            name='unique_email_phones',
         ),
     ]
 )
 }}}

 And the full stack trace:
 {{{
 Traceback (most recent call last):
   File ".../django/core/handlers/wsgi.py", line 131, in __call__
     response = self.get_response(request)
   File ".../django/core/handlers/base.py", line 140, in get_response
     response = self._middleware_chain(request)
   File ".../django/core/handlers/exception.py", line 57, in inner
     response = response_for_exception(request, exc)
   File ".../django/core/handlers/exception.py", line 140, in
 response_for_exception
     response = handle_uncaught_exception(
   File ".../django/core/handlers/exception.py", line 181, in
 handle_uncaught_exception
     return debug.technical_500_response(request, *exc_info)
   File ".../django_extensions/management/technical_response.py", line 40,
 in null_technical_500_response
     raise exc_value.with_traceback(tb)
   File ".../django/core/handlers/exception.py", line 55, in inner
     response = get_response(request)
   File ".../django/core/handlers/base.py", line 197, in _get_response
     response = wrapped_callback(request, *callback_args,
 **callback_kwargs)
   File ".../django/contrib/admin/options.py", line 686, in wrapper
     return self.admin_site.admin_view(view)(*args, **kwargs)
   File ".../django/utils/decorators.py", line 133, in _wrapped_view
     response = view_func(request, *args, **kwargs)
   File ".../django/views/decorators/cache.py", line 62, in
 _wrapped_view_func
     response = view_func(request, *args, **kwargs)
   File ".../django/contrib/admin/sites.py", line 242, in inner
     return view(request, *args, **kwargs)
   File "apps/catalogue/admin.py", line 1175, in change_view
     return super().change_view(
   File ".../django_object_actions/utils.py", line 57, in change_view
     return super(BaseDjangoObjectActions, self).change_view(
   File ".../django/contrib/admin/options.py", line 1893, in change_view
     return self.changeform_view(request, object_id, form_url,
 extra_context)
   File ".../django/utils/decorators.py", line 46, in _wrapper
     return bound_method(*args, **kwargs)
   File ".../django/utils/decorators.py", line 133, in _wrapped_view
     response = view_func(request, *args, **kwargs)
   File ".../django/contrib/admin/options.py", line 1750, in
 changeform_view
     return self._changeform_view(request, object_id, form_url,
 extra_context)
   File ".../django/contrib/admin/options.py", line 1796, in
 _changeform_view
     form_validated = form.is_valid()
   File ".../django/forms/forms.py", line 205, in is_valid
     return self.is_bound and not self.errors
   File ".../django/forms/forms.py", line 200, in errors
     self.full_clean()
   File ".../django/forms/forms.py", line 439, in full_clean
     self._post_clean()
   File ".../django/forms/models.py", line 492, in _post_clean
     self.instance.full_clean(exclude=exclude, validate_unique=False)
   File ".../django/db/models/base.py", line 1472, in full_clean
     self.validate_constraints(exclude=exclude)
   File ".../django/db/models/base.py", line 1423, in validate_constraints
     constraint.validate(model_class, self, exclude=exclude, using=using)
   File ".../django/db/models/constraints.py", line 361, in validate
     if (self.condition & Exists(queryset.filter(self.condition))).check(
   File ".../django/db/models/query_utils.py", line 141, in check
     return compiler.execute_sql(SINGLE) is not None
   File ".../django/db/models/sql/compiler.py", line 1398, in execute_sql
     cursor.execute(sql, params)
   File ".../debug_toolbar/panels/sql/tracking.py", line 230, in execute
     return self._record(self.cursor.execute, sql, params)
   File ".../debug_toolbar/panels/sql/tracking.py", line 154, in _record
     return method(sql, params)
   File ".../django/db/backends/utils.py", line 103, in execute
     return super().execute(sql, params)
   File ".../django/db/backends/utils.py", line 67, in execute
     return self._execute_with_wrappers(
   File ".../django/db/backends/utils.py", line 80, in
 _execute_with_wrappers
     return executor(sql, params, many, context)
   File ".../django/db/backends/utils.py", line 89, in _execute
     return self.cursor.execute(sql, params)
 IndexError: tuple index out of range
 }}}

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/34205#comment:2>
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 view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/01070184f6642309-1b7b1f00-3fe1-4530-aa61-9cc76573ab63-000000%40eu-central-1.amazonses.com.

Reply via email to