4.0.9 -> 4.1.0 Django upgrade causes AttributeError in pytests

I have a codebase running Django 4.0.9 that passes all pytest unit tests. I am using python 3.9.6. Regardless of whether I use python 3.9, 3.10 or 3.11, when I upgrade the Django version from 4.0.9 to 4.1.0, about 70 of my pytest unit tests fail with the following error:

FAILED server/apps/task/tests/models/test_task_queryset.py::TestStuckTasks::test_stuck - AttributeError: 'NoneType' object has no attribute 'startswith'

When I downgrade back to Django 4.0.9, all the tests pass again. The behavior is very consistent.
Given below is detailed stack trace. I noticed at least one model with a missing column field definition that was present in the database. My guess is that this mismatch causes the “name” parameter to quote_name() to be None and that causes this error. Why is this a problem only with Django 4.1.0 and not with Django 4.0.9? How can I fix it without touching any of the models? I am able to upgrade to Django 4.0.10 without issues. This error is not seen with Django 4.0.9 or Django 4.0.10

Here is the detailed stack trace:

server/apps/task/tests/models/test_task_queryset.py:18: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/base.py:40: in __call__
    return cls.create(**kwargs)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/base.py:528: in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/django.py:117: in _generate
    return super()._generate(strategy, params)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/base.py:465: in _generate
    return step.build()
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/builder.py:258: in build
    step.resolve(pre)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/builder.py:199: in resolve
    self.attributes[field_name] = getattr(self.stub, field_name)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/builder.py:344: in __getattr__
    value = value.evaluate_pre(
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/declarations.py:48: in evaluate_pre
    return self.evaluate(instance, step, context)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/declarations.py:395: in evaluate
    return step.recurse(subfactory, extra, force_sequence=force_sequence)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/builder.py:216: in recurse
    return builder.build(parent_step=self, force_sequence=force_sequence)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/builder.py:262: in build
    instance = self.factory_meta.instantiate(
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/base.py:317: in instantiate
    return self.factory._create(model, *args, **kwargs)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/django.py:163: in _create
    return cls._get_or_create(model_class, *args, **kwargs)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/factory/django.py:140: in _get_or_create
    instance, _created = manager.get_or_create(*args, **key_fields)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/manager.py:85: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/query.py:928: in get_or_create
    return self.get(**kwargs), False
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/query.py:646: in get
    num = len(clone)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/query.py:376: in __len__
    self._fetch_all()
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/query.py:1866: in _fetch_all
    self._result_cache = list(self._iterable_class(self))
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/query.py:87: in __iter__
    results = compiler.execute_sql(
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/sql/compiler.py:1382: in execute_sql
    sql, params = self.as_sql()
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/sql/compiler.py:615: in as_sql
    self.compile(self.where) if self.where is not None else ("", [])
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/sql/compiler.py:503: in compile
    sql, params = node.as_sql(self, self.connection)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/sql/where.py:112: in as_sql
    sql, params = compiler.compile(child)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/sql/compiler.py:503: in compile
    sql, params = node.as_sql(self, self.connection)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/lookups.py:357: in as_sql
    return super().as_sql(compiler, connection)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/lookups.py:224: in as_sql
    lhs_sql, params = self.process_lhs(compiler, connection)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/lookups.py:214: in process_lhs
    lhs_sql, params = super().process_lhs(compiler, connection, lhs)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/lookups.py:102: in process_lhs
    sql, params = compiler.compile(lhs)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/sql/compiler.py:503: in compile
    sql, params = node.as_sql(self, self.connection)
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/expressions.py:1109: in as_sql
    sql = ".".join(map(compiler.quote_name_unless_alias, identifiers))
../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/models/sql/compiler.py:494: in quote_name_unless_alias
    r = self.connection.ops.quote_name(name)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.backends.postgresql.operations.DatabaseOperations object at 0x108aca6e0>, name = None

    def quote_name(self, name):
>       if name.startswith('"') and name.endswith('"'):
E       AttributeError: 'NoneType' object has no attribute 'startswith'

../../../.pyenv/versions/python3-10/lib/python3.10/site-packages/django/db/backends/postgresql/operations.py:163: AttributeError

I also tried updating other packages like factory-boy, pytest-django, pytest, pytest-mock. The upgrade of those other packages I tried had no effect. The only package that causes this error is Django==4.1.0

Hey! This might have been a bug in Django 4.1.0 that was fixed in later Django 4.1 versions. Try upgrading to 4.1.10 and report back if the problem persists.

I just tried upgrading to 4.1.10 and the result is the same. I get exactly the same errors with my unit tests as I do after upgrading to 4.1.0.

It’s a bummer that the issue still reproduces on the latest version :frowning_face:

I don’t know what changed between Django 4.0 and 4.1 that could have caused this. Your best bet on getting help is to share a way for others to reproduce your issue. Try to isolate the problem by checking if the issue replicates without factory-boy or if you can isolate the problematic field or verify if there is something in your custom queryset that might be causing this.

If this is a bug in Django, the core devs will need a way to reproduce the problem before they can fix it in any case.

I was running into this exact issue going from v3.2 to v4.2 and after an extensive deep dive I found that one of the models had a ForeignObject field with an on_delete of SET_NULL. Changing this to anything but SET_NULL or SET_DEFAULT fixes this issue.