为什么 Django Admin 模型页面在成功迁移后引发 FieldDoesNotExist 异常?

Why does Django Admin model page raise FieldDoesNotExist exception after a successful migration?

这个让我摸不着头脑。我只是向模型添加了一个新字段。

class Image(BaseModel):
    url = models.URLField(max_length=1000, null=True)
    content_type = models.ForeignKey(ContentType, on_delete=models.SET_NULL, null=True)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()
    
    # new field added below
    class Type(models.TextChoices):
        HEADER = 'HEADER', _('Header')

    type = models.CharField(
        max_length=20,
        choices=Type.choices,
        null=True,
        blank=True
    )

    class Meta:
        db_table = 'Image'

然后我 运行 python manage.py makemigrations 然后是 python manage.py migrate。这是成功的,我可以在我的数据库中的 table 上看到新字段。来自 django_migrations table 的信息看起来是正确的。

这是我的迁移文件:

# Generated by Django 3.0.5 on 2022-03-23 12:46

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('example', '0082_auto_20220322_1937'),
    ]

    operations = [
        migrations.AddField(
            model_name='image',
            name='type',
            field=models.CharField(blank=True, choices=[('HEADER', 'Header')], max_length=20, null=True),
        ),
    ]

这是我的图像管理文件:

from .base import BaseModelAdmin


class ImageAdmin(BaseModelAdmin):
    list_display = ('id', 'url', 'content_type', 'content_object', 'type', 'is_deleted')
    list_filter = ('is_deleted',)

这是 ImageAdmin 继承自的 BaseModelAdmin:

from django.contrib.contenttypes.admin import GenericTabularInline
from django.contrib import admin


class BaseModelAdmin(admin.ModelAdmin):

    def get_actions(self, request):
        actions = super(BaseModelAdmin, self).get_actions(request)

        if 'delete_selected' in actions:
            del actions['delete_selected']

        return actions

    def has_delete_permission(self, request, obj=None):
        return False


class BaseStackedInline(admin.StackedInline):

    def get_actions(self, request):
        actions = super(BaseModelAdmin, self).get_actions(request)

        if 'delete_selected' in actions:
            del actions['delete_selected']

        return actions

    def has_delete_permission(self, request, obj=None):
        return False


class BaseGenericTabularInline(GenericTabularInline):

    def get_actions(self, request):
        actions = super(BaseModelAdmin, self).get_actions(request)

        if 'delete_selected' in actions:
            del actions['delete_selected']

        return actions

    def has_delete_permission(self, request, obj=None):
        return False

这里是Image模型继承自的BaseModel:

from django.db import models


class BaseManager(models.Manager):
    def get_or_none(self, *args, **kwargs):
        try:
            return self.get(*args, **kwargs)
        except self.model.DoesNotExist:
            return None


class BaseModel(models.Model):
    is_deleted = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = BaseManager()

    def __str__(self):
        return F"ID: {self.id}"

    class Meta:
        abstract = True

我面临的问题是,当我在 django 管理中访问该模型页面时,出现内部服务器错误,内容如下:

Traceback (most recent call last):
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/admin/utils.py", line 262, in lookup_field
example_app  |     f = _get_non_gfk_field(opts, name)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/admin/utils.py", line 297, in _get_non_gfk_field
example_app  |     raise FieldDoesNotExist()
example_app  | django.core.exceptions.FieldDoesNotExist
example_app  |
example_app  | During handling of the above exception, another exception occurred:
example_app  |
example_app  | Traceback (most recent call last):
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 34, in inner
example_app  |     response = get_response(request)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py", line 145, in _get_response
example_app  |     response = self.process_exception_by_middleware(e, request)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py", line 143, in _get_response
example_app  |     response = response.render()
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/response.py", line 105, in render
example_app  |     self.content = self.rendered_content
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/response.py", line 83, in rendered_content
example_app  |     return template.render(context, self._request)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/backends/django.py", line 61, in render
example_app  |     return self.template.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 171, in render
example_app  |     return self._render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 163, in _render
example_app  |     return self.nodelist.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 936, in render
example_app  |     bit = node.render_annotated(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 903, in render_annotated
example_app  |     return self.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/loader_tags.py", line 150, in render
example_app  |     return compiled_parent._render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 163, in _render
example_app  |     return self.nodelist.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 936, in render
example_app  |     bit = node.render_annotated(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 903, in render_annotated
example_app  |     return self.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/loader_tags.py", line 150, in render
example_app  |     return compiled_parent._render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 163, in _render
example_app  |     return self.nodelist.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 936, in render
example_app  |     bit = node.render_annotated(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 903, in render_annotated
example_app  |     return self.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/loader_tags.py", line 62, in render
example_app  |     result = block.nodelist.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 936, in render
example_app  |     bit = node.render_annotated(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 903, in render_annotated
example_app  |     return self.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/loader_tags.py", line 62, in render
example_app  |     result = block.nodelist.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 936, in render
example_app  |     bit = node.render_annotated(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/base.py", line 903, in render_annotated
example_app  |     return self.render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/admin/templatetags/base.py", line 33, in render
example_app  |     return super().render(context)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/template/library.py", line 214, in render
example_app  |     _dict = self.func(*resolved_args, **resolved_kwargs)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/admin/templatetags/admin_list.py", line 342, in result_list
example_app  |     'results': list(results(cl)),
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/admin/templatetags/admin_list.py", line 318, in results
example_app  |     yield ResultList(None, items_for_result(cl, res, None))
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/admin/templatetags/admin_list.py", line 309, in __init__
example_app  |     super().__init__(*items)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/admin/templatetags/admin_list.py", line 232, in items_for_result
example_app  |     f, attr, value = lookup_field(field_name, result, cl.model_admin)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/admin/utils.py", line 273, in lookup_field
example_app  |     attr = getattr(obj, name)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/contenttypes/fields.py", line 243, in __get__
example_app  |     rel_obj = ct.get_object_for_this_type(pk=pk_val)
example_app  |   File "/usr/local/lib/python3.9/site-packages/django/contrib/contenttypes/models.py", line 175, in get_object_for_this_type
example_app  |     return self.model_class()._base_manager.using(self._state.db).get(**kwargs)
example_app  | AttributeError: 'NoneType' object has no attribute '_base_manager'

我已经多次尝试还原迁移并重新运行它。我也看过其他类似的问题,但 none 的解决方案有效或解决了我的具体问题。

为什么会这样?

我怀疑添加新字段不是问题的原因 - 在您进行此更改的同时出现问题只是巧合。线索在回溯中:

File "/usr/local/lib/python3.9/site-packages/django/contrib/contenttypes/fields.py", line 243, in __get__
rel_obj = ct.get_object_for_this_type(pk=pk_val)
File "/usr/local/lib/python3.9/site-packages/django/contrib/contenttypes/models.py", line 175, in get_object_for_this_type
return self.model_class()._base_manager.using(self._state.db).get(**kwargs)
AttributeError: 'NoneType' object has no attribute '_base_manager'

具体来说,错误来自 contenttypes 框架 - 即,您的 content_type 字段在数据库中至少一个现有模型实例上被破坏。 model_class() 方法返回空值而不是模型 class。

最可能的原因是您删除了之前由您的通用外键引用的模型,或者您从 INSTALLED_APPS 中删除了提供该模型的应用程序。

您应该能够通过 运行 remove_stale_contenttypes 管理命令解决问题。如果这不起作用,那么可能需要手动检查数据库以找出问题所在(清空整个数据库也有助于验证是否是问题所在)。

FieldDoesNotExist异常不是问题。如果您的内容类型没有问题,它将是 Django properly handled


旁注:我认为这与您的问题没有任何关系,但使用与 Python built-in functions 冲突的名称不是一个好主意,因为它会导致混淆行为。