Search code examples
pythondjangodjango-admindjango-migrations

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


Scratching my head on this one. I've simply added a new field to a model.

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'

Then I ran python manage.py makemigrations followed by python manage.py migrate. This was successful and I can see the new field on the table in my database. The info from the django_migrations table looks correct.

Here is my migration file:

# 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),
        ),
    ]

Here is my Image Admin File:

from .base import BaseModelAdmin


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

Here is the BaseModelAdmin that ImageAdmin inherits from:

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

Here is the BaseModel that the Image model inherits from:

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

The problem I'm facing is when I visit that model page in the django admin, I get an internal server error with the following:

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'

I've tried reverting the migration and rerunning it many times. I've also looked at other similar questions but none of the solutions worked or addressed my specific issue.

Why is this happening?


Solution

  • I suspect that adding a new field isn't the cause of the problem - it's just coincidence that the problem arose at the same time as you made this change. The clue is in the traceback:

    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'
    

    Specifically, the error is coming from the contenttypes framework - i.e., your content_type field is broken on at least one existing model instance in the database. The model_class() method is returning a null value instead of the model class.

    The most likely cause of this is that you have deleted a model which was previously being referred to by your generic foreign key, or you have removed the app which provided that model from INSTALLED_APPS.

    You should be able to resolve the issue by running the remove_stale_contenttypes management command. If that doesn't work, then it's probably going to be a case of manually inspecting the database to find out where the problem is (emptying the whole database will also help verify whether this is the issue).

    The FieldDoesNotExist exception is not a problem. It would be properly handled by Django if there wasn't an issue with your content types.


    Side note: I don't think it has anything to do with your problem, but it's not a good idea to use names that clash with Python built-in functions as it can lead to confusing behaviour.