#34570 closed Bug (fixed)
QuerySet.defer() raises an AttributeError when the field is ManyToManyField or GenericForeignKey
| Reported by: | Paco Martínez | Owned by: | Simon Charette | 
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 4.2 | 
| Severity: | Release blocker | Keywords: | contenttypes, defer, manytomanyfield, genericforeignkey | 
| Cc: | Simon Charette | Triage Stage: | Ready for checkin | 
| Has patch: | yes | Needs documentation: | no | 
| Needs tests: | no | Patch needs improvement: | no | 
| Easy pickings: | no | UI/UX: | no | 
Description
In Version 4.2.1
Hello, when I try to defer a field that is a ManyToManyField or a GenericForeignKey it raises an AttributteError.
Having these three models:
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, ContentType
class ModelA(models.Model):
    name = models.CharField(max_length=100)
  
class ModelB(models.Model):
    name = models.CharField(max_length=100)
    model_a = models.ManyToManyField('ModelA')
    
class ModelC(models.Model):
    name = models.CharField(max_length=100)
    content_type = models.ForeignKey(ContentType, null=True, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField(null=True)
    generic_model = GenericForeignKey('content_type', 'object_id')
Traceback from a ManyToManyField:
>>> ModelB.objects.defer('model_a')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 374, in __repr__
    data = list(self[: REPR_OUTPUT_SIZE + 1])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 398, in __iter__
    self._fetch_all()
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 1881, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 91, in __iter__
    results = compiler.execute_sql(
              ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 1547, in execute_sql
    sql, params = self.as_sql()
                  ^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 734, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup(
                                       ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 84, in pre_sql_setup
    self.setup_query(with_col_aliases=with_col_aliases)
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 73, in setup_query
    self.select, self.klass_info, self.annotation_col_map = self.get_select(
                                                            ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 256, in get_select
    select_mask = self.query.get_select_mask()
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 772, in get_select_mask
    return self._get_defer_select_mask(opts, mask)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 728, in _get_defer_select_mask
    field = opts.get_field(field_name).field
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'ManyToManyField' object has no attribute 'field'
Traceback from defering a GenericForeignKey:
>>> ModelC.objects.defer('generic_model')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 374, in __repr__
    data = list(self[: REPR_OUTPUT_SIZE + 1])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 398, in __iter__
    self._fetch_all()
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 1881, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 91, in __iter__
    results = compiler.execute_sql(
              ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 1547, in execute_sql
    sql, params = self.as_sql()
                  ^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 734, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup(
                                       ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 84, in pre_sql_setup
    self.setup_query(with_col_aliases=with_col_aliases)
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 73, in setup_query
    self.select, self.klass_info, self.annotation_col_map = self.get_select(
                                                            ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 256, in get_select
    select_mask = self.query.get_select_mask()
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 772, in get_select_mask
    return self._get_defer_select_mask(opts, mask)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/query.py", line 728, in _get_defer_select_mask
    field = opts.get_field(field_name).field
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'GenericForeignKey' object has no attribute 'field'
PD:
In version 4.1.9 it is working without any error, but in versions 4.2.0 and 4.2.1 raises the AttributeError 
Change History (8)
comment:1 by , 2 years ago
| Cc: | added | 
|---|---|
| Severity: | Normal → Release blocker | 
| Triage Stage: | Unreviewed → Accepted | 
follow-up: 7 comment:2 by , 2 years ago
| Owner: | changed from to | 
|---|---|
| Status: | new → assigned | 
In version 4.1.9 it is working without any error, but in versions 4.2.0 and 4.2.1 raises the AttributeError
Could you elaborate on that? Many-to-many fields and generic foreign keys are not deferrable (they are virtual and do not map to columns) so the previous code was simply swallowing invalid values passed to defer.
I guess that raising a more detailed exception would still require a deprecation period and that it would be better to perform this validation at defer/only instead of compilation time anyway so our only option here is to silence cases where opts.get_field(field_name) returns something that doesn't have a .field property.
comment:3 by , 2 years ago
| Has patch: | set | 
|---|
comment:4 by , 2 years ago
| Triage Stage: | Accepted → Ready for checkin | 
|---|
comment:7 by , 2 years ago
Replying to Simon Charette:
In version 4.1.9 it is working without any error, but in versions 4.2.0 and 4.2.1 raises the AttributeError
Could you elaborate on that? Many-to-many fields and generic foreign keys are not deferrable (they are virtual and do not map to columns) so the previous code was simply swallowing invalid values passed to
defer.
I guess that raising a more detailed exception would still require a deprecation period and that it would be better to perform this validation at
defer/onlyinstead of compilation time anyway so our only option here is to silence cases whereopts.get_field(field_name)returns something that doesn't have a.fieldproperty.
Well, the more I read about defer() and what it was for, the more I wondered that we were deferring ManyToMany and GenericForeignKey fields. Thanks for confirming that it was useless and that it just swallowed it without taking any action.
Thank you very much to both of you for your answers and for your quickness in solving it.
Thanks for the report!
Regression in b3db6c8dcb5145f7d45eff517bcd96460475c879.
Reproduced at 0ec60661e61b153e6bcec64649b1b7f524eb3e18.