Why is pytest throwing an "AttributeError: 'NoneType' object has no attribute '_meta'" error when testing model creation?

Why is pytest throwing an "AttributeError: 'NoneType' object has no attribute '_meta'" error when testing model creation?

我正在使用 Django 2 并尝试为我的模型编写一些单元测试。我有这些模型...

class CoopTypeManager(models.Manager):

    def get_by_natural_key(self, name):
        return self.get_or_create(name=name)[0]


class CoopType(models.Model):
    name = models.CharField(max_length=200, null=False, unique=True)

    objects = CoopTypeManager()


class CoopManager(models.Manager):
    # Look up by coop type
    def get_by_type(self, type):
        qset = Coop.objects.filter(type__name=type,
                                   enabled=True)
        return qset

class Coop(models.Model):
    objects = CoopManager()
    name = models.CharField(max_length=250, null=False)
    types = models.ManyToManyField(CoopType)
    address = AddressField(on_delete=models.CASCADE)
    enabled = models.BooleanField(default=True, null=False)
    phone = PhoneNumberField(null=True)
    email = models.EmailField(null=True)
    web_site = models.TextField()

我创建了以下工厂来自动生成这些模型...

import factory
from .models import CoopType, Coop


class CoopTypeFactory(factory.DjangoModelFactory):
    """
        Define Coop Type Factory
    """
    class Meta:
        model = CoopType


class CoopFactory(factory.DjangoModelFactory):
    """
        Define Coop Factory
    """
    class Meta:
        model = Coop

    coop_type = factory.SubFactory(CoopTypeFactory)

然后我创建了这个简单的测试...

import pytest
from django.test import TestCase
from .tests.factories import CoopTypeFactory, CoopFactory


class ModelTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        print("setUpTestData: Run once to set up non-modified data for all class methods.")
        pass

    def setUp(self):
        print("setUp: Run once for every test method to setup clean data.")
        pass

    @pytest.mark.django_db
    def test_coop_type_model():
        """ Test coop type model """
        # create coop type model instance
        coop_type = CoopTypeFactory(name="Test Coop Type Name")
        assert coop_type.name == "Test Coop Type Name"

但是当我 运行 测试时,我得到一个错误,"AttributeError: 'NoneType' object has no attribute '_meta'"

(venv) localhost:web davea$ python manage.py test
Creating test database for alias 'default'...
Got an error creating the test database: (1007, "Can't create database 'test_maps_data'; database exists")
Type 'yes' if you would like to try deleting the test database 'test_maps_data', or 'no' to cancel: yes
Destroying old test database for alias 'default'...
Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 371, in execute_from_command_line
    utility.execute()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/__init__.py", line 365, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/commands/test.py", line 26, in run_from_argv
    super().run_from_argv(argv)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/base.py", line 335, in execute
    output = self.handle(*args, **options)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/management/commands/test.py", line 59, in handle
    failures = test_runner.run_tests(test_labels)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/test/runner.py", line 601, in run_tests
    old_config = self.setup_databases()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/test/runner.py", line 548, in setup_databases
    self.parallel, **kwargs
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/test/utils.py", line 176, in setup_databases
    serialize=connection.settings_dict.get('TEST', {}).get('SERIALIZE', True),
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/backends/base/creation.py", line 76, in create_test_db
    self.connection._test_serialized_contents = self.serialize_db_to_string()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/backends/base/creation.py", line 119, in serialize_db_to_string
    serializers.serialize("json", get_objects(), indent=None, stream=out)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/serializers/__init__.py", line 128, in serialize
    s.serialize(queryset, **options)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/core/serializers/base.py", line 80, in serialize
    for count, obj in enumerate(queryset, start=1):
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/backends/base/creation.py", line 116, in get_objects
    yield from queryset.iterator()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/query.py", line 336, in _iterator
    yield from self._iterable_class(self, chunked_fetch=use_chunked_fetch, chunk_size=chunk_size)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/query.py", line 54, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1050, in execute_sql
    sql, params = self.as_sql()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 445, in as_sql
    extra_select, order_by, group_by = self.pre_sql_setup()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 50, in pre_sql_setup
    self.setup_query()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 40, in setup_query
    self.query.get_initial_alias()
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/query.py", line 886, in get_initial_alias
    alias = self.join(BaseTable(self.get_meta().db_table, None))
  File "/Users/davea/Documents/workspace/chicommons/maps/web/venv/lib/python3.7/site-packages/django/db/models/sql/query.py", line 284, in get_meta
    return self.model._meta
AttributeError: 'NoneType' object has no attribute '_meta'

编辑: 如果您想尝试重现该问题,请点击此处 github link。项目在 "web" -- https://github.com/chicommons/maps

简单的解决方案是,在测试期间忽略迁移 运行。为此,为测试环境创建一个单独的设置模块

# /web/maps/test_settings.py
from .settings import *


class DisableMigrations(object):
    # ref: 

    def __contains__(self, item):
        return True

    def __getitem__(self, item):
        return None


MIGRATION_MODULES = DisableMigrations()

然后运行测试为,

python manage.py test <b>--settings=maps.test_settings</b>

  • Screenshot

注意:我对存储库进行了其他小改动以完成它,但我希望这些事情不是什么大问题。 (反正我给你做了a PR here

好的,我在 github 上查看了您的代码,我的第一个警告是:不要对其他包的模型进行 monkeypatch,如果可以的话,请子类化

你对 django-address 的使用有点乱,你在使用模型并修改它们的行为,但它很危险。

罪名是

setattr(State._meta, 'default_manager', StateCustomManager())
setattr(Locality._meta, 'default_manager', LocalityCustomManager())

这些属性是私有的(因此 _ 前缀 _meta 不应被混淆。我会改用 State.add_to_class('objects', StateCustomManager())

第二个警告是:我真的不认为你使用 get_by_natural_key() 和自动创建对象是非常明智的,它可能会减慢你的对象访问速度。

您没有在任何一个上调用 Supper class 构造函数 你派生的 classes。当你写一个派生的class时,一定要调用 子 class:

上的超级 class 构造函数
class SubClass(SupperClass):
    def __init__(self, *args, **kwargs):
        super(SubClass, self).__init__(*args, **kwargs)