djangodjango-admindjango-database

Django admin prefetch content_type model


I used the django debug toolbar to analyse why the calls to my usermodel were so painfully slow within the django admin. There I saw that I had hundreds of duplicate calls to the content_type model:

SELECT ••• FROM "django_content_type" WHERE "django_content_type"."id" = 1 LIMIT 21 362 similar queries. Duplicated 4 times.

To be honest, I do not understand where these calls come from in the first place but I wanted to pre_fetch the model. However, this seems not to be possible in the normal way because there is actually no ForeignKey or any other kind of direct relationship between the models. How could I reduce those 362 content_type calls?

This is the usermodel in question:

class User(AbstractBaseUser, PermissionsMixin):
    """
    Base model for the user application
    """
    USERNAME_FIELD = "email"
    objects = UserManager()

    username_validator = None
    username = None
    email = models.EmailField(_("email address"), unique=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)    
    first_name = models.CharField(max_length=150, blank=True)
    last_name = models.CharField(max_length=150, blank=True)
    title_of_person = models.ForeignKey(
        TitleOfPerson, on_delete=models.CASCADE, blank=True, null=True
    )

    is_verified = models.BooleanField(default=False)
    language = models.ForeignKey(
        Language, blank=True, null=True, on_delete=models.SET_NULL
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = _("User")
        verbose_name_plural = _("Users")

    def __str__(self) -> str:
        return self.email

Thanks


Solution

  • In your UserAdmin class, add a UserCreationForm for your User model. Like,

    from django.contrib.auth.admin import (
        UserAdmin as BaseUserAdmin, UserChangeForm as BaseUserChangeForm,
        UserCreationForm as BaseUserCreationForm
    )
    
    class UserChangeForm(BaseUserChangeForm):
        class Meta:
            model = User
            fields = '__all__'
    
        def clean_email(self):
            data = self.cleaned_data.get('email')
            return data.lower()
    
    class UserAdmin(BaseUserAdmin):
        form = UserChangeForm
        ... other fields...
    

    This should fix your query problem.

    Actual part that fixes the problem:

    In the default UserChangeForm, inside the __init__ method, the user_permissions queryset is being initilised with select_related optimization.

    class UserChangeForm(forms.ModelForm):
        password = ReadOnlyPasswordHashField(
            label=_("Password"),
            help_text=_(
              "Raw passwords are not stored, so there is no way to see this "
              "user’s password, but you can change the password using "
              '<a href="{}">this form</a>.'
            ),
        )
    
        class Meta:
            model = User
            fields = "__all__"
            field_classes = {"username": UsernameField}
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            password = self.fields.get("password")
            if password:
                password.help_text = password.help_text.format("../password/")
            user_permissions = self.fields.get("user_permissions")
            if user_permissions:
                user_permissions.queryset = 
                  user_permissions.queryset.select_related(
                  "content_type"
                )