Search code examples
pythondjangoproxy-classes

Extending the methods available to the Django User model via a proxy


I'm integrating an app which in the past has had a custom user model to replace auth.User and added custom methods and relationships to other models.

I'd like to make it more of a reusable app, so I'm looking to add a proxy model to give the User the same methods so that things like request.user.myfunc() still work, and creating a OneToOneField relationship to the User for extra fields.

class ConsolesPermissionMixin(object):
    @property
    def is_admin(self):
        return self.is_superuser or self.is_staff

    _consoles = []

    @property
    def all_consoles(self):
        if not self._consoles:
            self._consoles = self.consoles.all()
        return self._consoles


class ProxyUser(ConsolesPermissionMixin, User):
    class Meta:
        proxy = True


class ConsoleUser(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    participant = models.OneToOneField(
        'consoles.Participant',
        blank=True,
        null=True
    )
    consoles = models.ManyToManyField(
        'consoles.Console',
        null=True,
        blank=True,
    )
    objects = UserManager()

    class Meta:
        app_label = 'consoles'
        verbose_name = _('Console User')
        verbose_name_plural = _('Console Users')

So in order to implement this I've set AUTH_USER_MODEL = 'consoles.ProxyUser' in settings. But this results in a TypeError: ProxyUser cannot proxy the swapped model 'consoles.ProxyUser'.

Surely for the proxy model to be used, it has to be set to AUTH_USER_MODEL, but in turn does that mean that AUTH_USER_MODEL can't be a proxy model?


Solution

  • Proxy models are used to modify Python behaviour of a model by extending a model class. Database tables aren't generated for proxy models. So you can't use a proxy model for AUTH_USER_MODEL. In fact, you can't use a proxy model if you want a db table for that model.

    To understand proxy models better, consider this:

    class Badge(models.Model):
        name = ...
        color = ... # gold/silver
    
    
    class GoldBadge(Badge)
        class Meta:
            proxy = True
    
        def award(self, user):
            # award a gold badge to user
    
    class SilverBadge(Badge):
        class Meta:
            proxy = True
    
        def award(self, user):
            # award a silver badge to user
    

    Tables are generated for only Badge model whereas GoldBadge and SilverBadge are proxy models so no tables are generated for them. They are just extending the functionality (i.e. changing Python behaviour) of Badge model.

    See docs on proxy models