March 16, 2022

Get yourself a better Django proxy experience

Ah, Django proxy models and the power they hold! Yet, the implementation aftermath can highlight a number of unwanted side-effects. Here’s a couple of tips when working with Django proxies that will make end-users grateful and developers sigh in relief.

No more toe-curling results from ORM queries

Initialize discriminating proxy model fields to their expected values. You’ll see the benefits of this tip when unit-testing proxy instances already, where you should be using bulk_create() to speed up your tests and straight proxy model queries to shorten your ORM statements.

Start with overriding the main queryset method in managers.py:

from django.db.models import manager

class ParentProxyModelManager(manager.Manager):
    """The parent proxy model manager."""

    def get_queryset(self):
        """Return the queryset."""
        return super().get_queryset().filter(
            discriminating_field=self.model.DISCRIMINATING_FIELD
        )

In models.py:

from django.db import Model

class ParentModel(models.Model):
    """The parent model."""

    # Add your proxy-discriminating field(s) here

class ParentProxyModel(ParentModel):
    """The parent proxy model."""

    DISCRIMINATING_FIELD = None

    objects = ParentProxyModelManager()
    
    class Meta:
        """Model options."""
    
        proxy = True
    
    def __init__(self, *args, **kwargs):
        """Initialize proxy instance."""
        super().__init__(*args, **kwargs)
        self.discriminating_field = self.DISCRIMINATING_FIELD

Sensible link behaviour in the parent changelist

Sometimes you want to keep showing the proxy parent model admin for listing all the results. One issue with this is that every instance link in the changelist still points to its non-proxified version. You will see how problematic this ends up being if not corrected as your proxy modeladmins grow in behavioral diversity. What we can do is add a method to our model returning the right proxy and override change_view() in the proxy parent admin model.

In models.py:

from django.db import Model
class ParentModel(models.Model):
    """The parent model."""

    ...

    def get_proxy_model(self):
        """Return the proxy model."""
        # Check those fields' values discriminating your proxy model
        # and return the latter

In admin.py:

from django.contrib import admin
from django.http import HttpResponseRedirect

@admin.register(ParentModel)
class ParentModelAdmin(admin.ModelAdmin):
    """The parent model admin."""

    fields = ("discriminating_field",)

    def change_view(
        self,
        request,
        object_id,
        form_url="", 
        extra_context=None
    ):
        """Return the change view."""
        if self.model.__name__ == "ParentModel":
            obj = self.get_object(request, object_id)
            proxy = obj.get_proxy_model()
            if proxy._meta.model.__name__ != "Operation":
                redirect_url = reverse(
                    (
                        f"admin:{proxy._meta.app_label}_"
                        f"{proxy._meta.model_name}_change"
                    ),
                        kwargs={"object_id": obj.pk},
                )
                return HttpResponseRedirect(redirect_url)
         return super().change_view(
             request, object_id, form_url, extra_context
         )

As you can see, it doesn’t take that much to make Django proxy models better. Find here the official documentation on the general subject.

Copyright © 2001-2024 Niccolò Mineo
Some rights reserved: CC BY-NC 4.0