Opened 6 years ago

Closed 6 years ago

Last modified 5 years ago

#29142 closed Bug (fixed)

QuerySet crashes when OuterRef is combined with an operator

Reported by: Michael Barr Owned by: Matthew Schinckel
Component: Database layer (models, ORM) Version: 2.0
Severity: Normal Keywords:
Cc: Matthew Schinckel Triage Stage: Ready for checkin
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

According to the documentation on models.OuterRef:

"It acts like an F expression except that the check to see if it refers to a valid field isn’t made until the outer queryset is resolved."

I am experiencing an issue with this using the following example:

class ExampleModel(models.Model):
    date = models.DateField()
    value = models.IntegerField()


subquery = ExampleModel.objects.filter(
    date__gte=models.OuterRef('date') - timedelta(days=2),
    date__lte=models.OuterRef('date') + timedelta(days=2),
    value__lte=5
)
queryset = ExampleModel.objects.annotate(
    value_is_lte_five=models.Exists(subquery)
)


The result that I am getting is:

AttributeError: 'ResolvedOuterRef' object has no attribute 'relabeled_clone'

From my understanding, if this is similar to an F Expression, you should be able to perform the timedelta on the OuterRef. I also found someone else on StackOverflow with the same issue.

What I would like to know is if this is a bug (e.g. should it be supported), or a documentation issue.

Change History (12)

comment:1 by Tim Graham, 6 years ago

Cc: Matthew Schinckel added
Triage Stage: UnreviewedAccepted

comment:2 by Matthew Schinckel, 6 years ago

Yeah, I think this is a bug.

I'm not sure where F gets it's relabeled_clone from, because OuterRef inherits directly from F.

comment:3 by Matthew Schinckel, 6 years ago

Ah, is it that F gets turned into a Ref?

comment:4 by Matthew Schinckel, 6 years ago

Owner: changed from nobody to Matthew Schinckel
Status: newassigned

comment:6 by Tim Graham, 6 years ago

Summary: OuterRef not being treated as an F ExpressionQuerySet crashes when OuterRef is combined with an operator
Triage Stage: AcceptedReady for checkin

comment:7 by Tim Graham <timograham@…>, 6 years ago

Resolution: fixed
Status: assignedclosed

In c412926a:

Fixed #29142 -- Fixed crash when OuterRef is used with an operator.

comment:8 by nirmalraghavan, 6 years ago

Resolution: fixed
Status: closednew

Issue still exists in django 2.0.5. Performing arithmetic operation on OuterRef returns the following.
'ResolvedOuterRef' object has no attribute 'relabeled_clone'

comment:9 by Tim Graham, 6 years ago

Resolution: fixed
Status: newclosed

The patch doesn't qualify for a backport to the stable/2.0.x branch based on our supported versions policy.

comment:10 by Brenton Partridge, 6 years ago

For anyone needing a backport, it's relatively easy to patch in your userland code.

# Patch from https://github.com/django/django/commit/c412926a2e359afb40738d8177c9f3bef80ee04e
# https://code.djangoproject.com/ticket/29142
django.db.models.F.relabeled_clone = lambda self, relabels: self

comment:11 by Dmitry Mugtasimov, 5 years ago

The backport does not seem to work for me

class CustomResolvedOuterRef(ResolvedOuterRef):
    contains_aggregate = False

    def relabeled_clone(self, relabels):
        return self


class CustomOuterRef(OuterRef):
    def resolve_expression(self, query=None, allow_joins=True, reuse=None,
                           summarize=False, for_save=False, simple_col=False):
        if isinstance(self.name, self.__class__):
            return self.name
        return CustomResolvedOuterRef(self.name)


queryset.annotate(
                title_vector=SearchVector(
                    'title', config=SIMPLE_PG_SEARCH_CONFIGURATION)
            ).annotate(
                has_role=Exists(
                    JobTitle.objects.annotate(
                        title_vector=CustomOuterRef('title_vector')
                    ).filter(
                        title_vector=SearchQuery(
                            F('name'), config=SIMPLE_PG_SEARCH_CONFIGURATION)
                    )
                )
            )

get this:

Environment:


Request Method: GET
Request URL: http://127.0.0.1:8000/admin/jobs/job/

Django Version: 2.2.2
Python Version: 3.7.3
Installed Applications:
['core',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.sites',
 'django.contrib.postgres',
 'allauth',
 'allauth.account',
 'drf_yasg',
 'rest_auth',
 'rest_auth.registration',
 'rest_framework',
 'rest_framework.authtoken',
 'colorful',
 'gm2m',
 'explorer',
 'allauth.socialaccount',
 'accounts',
 'questions',
 'colleges',
 'geo',
 'jobs',
 'advices',
 'referral']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback:

File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  115.                 response = self.process_exception_by_middleware(e, request)

File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  113.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/usr/local/lib/python3.7/contextlib.py" in inner
  74.                 return func(*args, **kwds)

File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/options.py" in wrapper
  606.                 return self.admin_site.admin_view(view)(*args, **kwargs)

File "/usr/local/lib/python3.7/site-packages/django/utils/decorators.py" in _wrapped_view
  142.                     response = view_func(request, *args, **kwargs)

File "/usr/local/lib/python3.7/site-packages/django/views/decorators/cache.py" in _wrapped_view_func
  44.         response = view_func(request, *args, **kwargs)

File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/sites.py" in inner
  223.             return view(request, *args, **kwargs)

File "/usr/local/lib/python3.7/site-packages/django/utils/decorators.py" in _wrapper
  45.         return bound_method(*args, **kwargs)

File "/usr/local/lib/python3.7/site-packages/django/utils/decorators.py" in _wrapped_view
  142.                     response = view_func(request, *args, **kwargs)

File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/options.py" in changelist_view
  1672.             cl = self.get_changelist_instance(request)

File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/options.py" in get_changelist_instance
  744.             sortable_by,

File "/usr/local/lib/python3.7/site-packages/django/contrib/admin/views/main.py" in __init__
  45.         self.root_queryset = model_admin.get_queryset(request)

File "/code/jobs/admin.py" in get_queryset
  76.         return queryset.annotate_with_has_role()

File "/code/jobs/models/job.py" in annotate_with_has_role
  88.                             F('name'), config=SIMPLE_PG_SEARCH_CONFIGURATION)

File "/usr/local/lib/python3.7/site-packages/django/db/models/query.py" in filter
  892.         return self._filter_or_exclude(False, *args, **kwargs)

File "/usr/local/lib/python3.7/site-packages/django/db/models/query.py" in _filter_or_exclude
  910.             clone.query.add_q(Q(*args, **kwargs))

File "/usr/local/lib/python3.7/site-packages/django/db/models/sql/query.py" in add_q
  1290.         clause, _ = self._add_q(q_object, self.used_aliases)

File "/usr/local/lib/python3.7/site-packages/django/db/models/sql/query.py" in _add_q
  1318.                     split_subq=split_subq, simple_col=simple_col,

File "/usr/local/lib/python3.7/site-packages/django/db/models/sql/query.py" in build_filter
  1207.             condition = self.build_lookup(lookups, reffed_expression, value)

File "/usr/local/lib/python3.7/site-packages/django/db/models/sql/query.py" in build_lookup
  1104.         lookup_class = lhs.get_lookup(lookup_name)

Exception Type: AttributeError at /admin/jobs/job/
Exception Value: 'CustomResolvedOuterRef' object has no attribute 'get_lookup'

Last edited 5 years ago by Dmitry Mugtasimov (previous) (diff)

in reply to:  11 comment:12 by Matthew Schinckel, 5 years ago

Replying to Dmitry Mugtasimov:

The backport does not seem to work for me

Does the backport work if you just use the standard OuterRef class? I think perhaps it's something else, as this issue was about the missing 'relabeled_clone', not a missing 'get_lookup'.

Perhaps this is a different issue entirely?

Note: See TracTickets for help on using tickets.
Back to Top