Opened 6 years ago

Closed 5 years ago

Last modified 3 years ago

#28442 closed Bug (fixed)

Error creating queryset with nested OuterRefs on a foreign key

Reported by: Abram Booth Owned by: Oliver Sauder
Component: Database layer (models, ORM) Version: 1.11
Severity: Normal Keywords:
Cc: Oliver Sauder Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Given a model like:

class Agent(models.Model):
    agency = models.ForeignKey('Agency')
    ...

Trying to make a queryset for use in a nested subquery will fail (traceback+error below):

q = Agent.objects.filter(agency_id=OuterRef(OuterRef('id')))

Tested in 1.11.1 and 1.11.3. Nested OuterRefs work fine with other types of fields.

Traceback:

[...]/django/db/models/manager.py in manager_method(self, *args, **kwargs)                                                                                                           
     83         def create_method(name, method):
     84             def manager_method(self, *args, **kwargs):                                                                                                                       
---> 85                 return getattr(self.get_queryset(), name)(*args, **kwargs)                                                                                                   
     86             manager_method.__name__ = method.__name__                                                                                                                        
     87             manager_method.__doc__ = method.__doc__                                                                                                                          

[...]/django/db/models/query.py in filter(self, *args, **kwargs)                                                                                                                     
    782         set.                                                                                                                                                                 
    783         """
--> 784         return self._filter_or_exclude(False, *args, **kwargs)
    785 
    786     def exclude(self, *args, **kwargs):                                                                                                                                      
    
[...]/django/db/models/query.py in _filter_or_exclude(self, negate, *args, **kwargs)                                                                                                 
    800             clone.query.add_q(~Q(*args, **kwargs))
    801         else:                                                                                                                                                                
--> 802             clone.query.add_q(Q(*args, **kwargs))                                                                                                                            
    803         return clone
    804                       
                                                                                                                                                                                     
[...]/django/db/models/sql/query.py in add_q(self, q_object)                                                                                                                         
   1248         existing_inner = set(                                                                                                                                                
   1249             (a for a in self.alias_map if self.alias_map[a].join_type == INNER))                                                                                             
-> 1250         clause, _ = self._add_q(q_object, self.used_aliases)                                                                                                                 
   1251         if clause:                                                                                                                                                           
   1252             self.where.add(clause, AND)
        
[...]/django/db/models/sql/query.py in _add_q(self, q_object, used_aliases, branch_negated, current_negated, allow_joins, split_subq)                                                
   1274                     child, can_reuse=used_aliases, branch_negated=branch_negated,
   1275                     current_negated=current_negated, connector=connector,                                                                                                    
-> 1276                     allow_joins=allow_joins, split_subq=split_subq,                                                                                                          
   1277                 )
   1278                 joinpromoter.add_votes(needed_inner)
                
[...]/django/db/models/sql/query.py in build_filter(self, filter_expr, branch_negated, current_negated, can_reuse, connector, allow_joins, split_subq)                               
   1204             else:
   1205                 lhs = MultiColSource(alias, targets, sources, field)                                                                                                         
-> 1206             condition = lookup_class(lhs, value)                                                                                                                             
   1207             lookup_type = lookup_class.lookup_name                                                                                                                           
   1208         else:                                                                                                                                                                
                                                                                                                                                                                     
[...]/django/db/models/lookups.py in __init__(self, lhs, rhs)                                                                                                                        
     22     def __init__(self, lhs, rhs):                                                                                                                                            
     23         self.lhs, self.rhs = lhs, rhs
---> 24         self.rhs = self.get_prep_lookup()
     25         if hasattr(self.lhs, 'get_bilateral_transforms'):
     26             bilateral_transforms = self.lhs.get_bilateral_transforms()
            
[...]/django/db/models/fields/related_lookups.py in get_prep_lookup(self)
    110                 # as we don't get to the direct value branch otherwise.                                                                                                      
    111                 target_field = self.lhs.output_field.get_path_info()[-1].target_fields[-1]                                                                                   
--> 112                 self.rhs = target_field.get_prep_value(self.rhs)
    113         
    114         return super(RelatedLookupMixin, self).get_prep_lookup()
                
[...]/django/db/models/fields/__init__.py in get_prep_value(self, value)                                                                                                             
    964         if value is None:                                                                                                                                                    
    965             return None
--> 966         return int(value)
    967     
    968     def contribute_to_class(self, cls, name, **kwargs):
            
TypeError: int() argument must be a string, a bytes-like object or a number, not 'OuterRef'

Change History (5)

comment:1 Changed 6 years ago by Tim Graham

Triage Stage: UnreviewedAccepted

comment:3 Changed 5 years ago by Oliver Sauder

Cc: Oliver Sauder added
Has patch: set
Last edited 5 years ago by Tim Graham (previous) (diff)

comment:6 Changed 5 years ago by Oliver Sauder

Owner: set to Oliver Sauder
Status: newassigned

comment:7 Changed 5 years ago by Tim Graham <timograham@…>

Resolution: fixed
Status: assignedclosed

In 6f0b8c1c:

Fixed #28442 -- Fixed crash with nested OuterRefs that reference AutoField.

comment:8 Changed 3 years ago by Mariusz Felisiak <felisiak.mariusz@…>

In 600628f8:

Refs #28442 -- Adjusted related lookups handling of expression rhs.

Expressions should never be prepared as other Lookup.get_prep_lookup
implementations hint at by returning early on the presence of the
resolve_expression attribute.

The previous solution was only handling lookups against related fields
pointing at AutoFields and would break for foreign keys to other fields.

It was also causing bidirectional coupling between model fields and
expressions which the method level import of OuterRef was a symptom of.

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