Calling defer() with the column name of a foreign key ('user_id') instead of the field name ('user') will raise an AttributeError: 'ForeignKey' object has no attribute 'field'
I believe this is either a regression in 4.2 or an undocumented change of behavior. Generally speaking, I'd prefer this new requirement to always use the field name as it reduces ambiguity/improves refactoring etc. However, it needs documentation and a better error message.
Here is an example traceback:
In [11]: User.objects.defer('organization_id')
Out[11]: ---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/IPython/core/formatters.py:708, in PlainTextFormatter.__call__(self, obj)
701 stream = StringIO()
702 printer = pretty.RepresentationPrinter(stream, self.verbose,
703 self.max_width, self.newline,
704 max_seq_length=self.max_seq_length,
705 singleton_pprinters=self.singleton_printers,
706 type_pprinters=self.type_printers,
707 deferred_pprinters=self.deferred_printers)
--> 708 printer.pretty(obj)
709 printer.flush()
710 return stream.getvalue()
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/IPython/lib/pretty.py:410, in RepresentationPrinter.pretty(self, obj)
407 return meth(obj, self, cycle)
408 if cls is not object \
409 and callable(cls.__dict__.get('__repr__')):
--> 410 return _repr_pprint(obj, self, cycle)
412 return _default_pprint(obj, self, cycle)
413 finally:
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/IPython/lib/pretty.py:778, in _repr_pprint(obj, p, cycle)
776 """A pprint that just redirects to the normal repr function."""
777 # Find newlines and replace them with p.break_()
--> 778 output = repr(obj)
779 lines = output.splitlines()
780 with p.group():
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/query.py:374, in QuerySet.__repr__(self)
373 def __repr__(self):
--> 374 data = list(self[: REPR_OUTPUT_SIZE + 1])
375 if len(data) > REPR_OUTPUT_SIZE:
376 data[-1] = "...(remaining elements truncated)..."
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/query.py:398, in QuerySet.__iter__(self)
383 def __iter__(self):
384 """
385 The queryset iterator protocol uses three nested iterators in the
386 default case:
(...)
396 - Responsible for turning the rows into model objects.
397 """
--> 398 self._fetch_all()
399 return iter(self._result_cache)
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/query.py:1881, in QuerySet._fetch_all(self)
1879 def _fetch_all(self):
1880 if self._result_cache is None:
-> 1881 self._result_cache = list(self._iterable_class(self))
1882 if self._prefetch_related_lookups and not self._prefetch_done:
1883 self._prefetch_related_objects()
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/query.py:91, in ModelIterable.__iter__(self)
88 compiler = queryset.query.get_compiler(using=db)
89 # Execute the query. This will also fill compiler.select, klass_info,
90 # and annotations.
---> 91 results = compiler.execute_sql(
92 chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
93 )
94 select, klass_info, annotation_col_map = (
95 compiler.select,
96 compiler.klass_info,
97 compiler.annotation_col_map,
98 )
99 model_cls = klass_info["model"]
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/sql/compiler.py:1547, in SQLCompiler.execute_sql(self, result_type, chunked_fetch, chunk_size)
1545 result_type = result_type or NO_RESULTS
1546 try:
-> 1547 sql, params = self.as_sql()
1548 if not sql:
1549 raise EmptyResultSet
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/sql/compiler.py:734, in SQLCompiler.as_sql(self, with_limits, with_col_aliases)
732 try:
733 combinator = self.query.combinator
--> 734 extra_select, order_by, group_by = self.pre_sql_setup(
735 with_col_aliases=with_col_aliases or bool(combinator),
736 )
737 for_update_part = None
738 # Is a LIMIT/OFFSET clause needed?
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/sql/compiler.py:84, in SQLCompiler.pre_sql_setup(self, with_col_aliases)
78 def pre_sql_setup(self, with_col_aliases=False):
79 """
80 Do any necessary class setup immediately prior to producing SQL. This
81 is for things that can't necessarily be done in __init__ because we
82 might not have all the pieces in place at that time.
83 """
---> 84 self.setup_query(with_col_aliases=with_col_aliases)
85 order_by = self.get_order_by()
86 self.where, self.having, self.qualify = self.query.where.split_having_qualify(
87 must_group_by=self.query.group_by is not None
88 )
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/sql/compiler.py:73, in SQLCompiler.setup_query(self, with_col_aliases)
71 if all(self.query.alias_refcount[a] == 0 for a in self.query.alias_map):
72 self.query.get_initial_alias()
---> 73 self.select, self.klass_info, self.annotation_col_map = self.get_select(
74 with_col_aliases=with_col_aliases,
75 )
76 self.col_count = len(self.select)
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/sql/compiler.py:256, in SQLCompiler.get_select(self, with_col_aliases)
254 select_idx += 1
255 assert not (self.query.select and self.query.default_cols)
--> 256 select_mask = self.query.get_select_mask()
257 if self.query.default_cols:
258 cols = self.get_default_columns(select_mask)
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/sql/query.py:768, in Query.get_select_mask(self)
766 opts = self.get_meta()
767 if defer:
--> 768 return self._get_defer_select_mask(opts, mask)
769 return self._get_only_select_mask(opts, mask)
File ~/.local/share/virtualenvs/axi-v3J2FyDc/lib/python3.11/site-packages/django/db/models/sql/query.py:724, in Query._get_defer_select_mask(self, opts, mask, select_mask)
722 field = relation.field
723 else:
--> 724 field = opts.get_field(field_name).field
725 field_select_mask = select_mask.setdefault(field, {})
726 related_model = field.model._meta.concrete_model
AttributeError: 'ForeignKey' object has no attribute 'field'
Thanks for the report 👍
It's at least inconsistent with the behaviour of
only()
which does allow the column name.