Back
Feb 18, 2010

User profiles with inheritance in Django

Usually users' profiles are stored in single model. When there are multiple user types, separation is made by some field like user_type.

Situation is a little more complicated when different data is needed for each user type.

Of course it's better when all users have same type and are granted different permissions. But since we got task to have separate user types - we'll have to solve it.

Simple solution

The most obvious solution is to make base profile with type identifier, which will pull needed model instance:

request.user.get_profile().get_final_profile()

But I don't really like this solution. Firstly, get_final_profile method is basically a big switch-case. Secondly - we must not forget to call extra method.

I wanted to simply write request.user.profile ans get needed profile instance. This is possible by combining two known tricks - Upcast and AutoOneToOneField.

Upcast

Allows to get sublass instance from base class instance.

AutoOneToOneField

Allows creating related objects on request. This field is not needed per se, only idea. Taking AutoOneToOneField as a base, it's easy to create a new one that calls upcast when related objects is requested.

Solution

class UpCastModel(models.Model):
    """
    Base class for models that are ment to be inherited.
    Introduces upcast method that returns child instance.
    """
    final_type = models.ForeignKey(ContentType, editable=False)

    def save(self, args, **kwargs):
        if not self.pk:
            self.final_type = ContentType.objects.get_for_model(type(self))
        super(UpCastModel, self).save(args, **kwargs)

    def upcast(self):
        if not hasattr(self, '_upcast'):
            if self.final_type.model_class == self.class:
                self._upcast = self
            else:
                self._upcast = self.final_type.get_object_for_this_type(id=self.pk)
        return self._upcast

    class Meta:
        abstract = True


class UpCastSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):

    def get(self, instance, instance_type=None):
        parent = super(
            UpCastSingleRelatedObjectDescriptor, self
        ).get(instance, instance_type)
        return parent.upcast()


class UpCastOneToOneField(models.OneToOneField):
    """
    UpCastOneToOneField gets child of related object.
    """
    def contribute_to_related_class(self, cls, related):
        setattr(
            cls, related.get_accessor_name(),
            UpCastSingleRelatedObjectDescriptor(related)
        )

Now profile looks like this:

class BaseProfile(UpcastModel):
    user = UpCastOneToOneField(User)


class BoyProfile(BaseProfile):
    pass


class GirlProfile(CitizenProfile):
    pass

In order to get users, profile, we just need to write requst.user.profile.

Subscribe for the news and updates

More thoughts
Apr 15, 2024Technology
Lazy Promises in Node.js

Promise is a powerful tool in asynchronous programming that allows developers to call a time-consuming function and proceed with program execution without waiting for the function result.

Jul 13, 2022Technology
Prosemirror: Render node as react component

In this article I’m going to show how to declare custom prosemirror node, how to render it with toDom method and how improve that with custom NodeView using React component.

Aug 27, 2020Technology
5 tips for designing database architecture

Designing database architecture is a challenging task, and it gets even more difficult when your app keeps getting bigger. Here are several tips on how to manage your data structure in a more efficient way.

Mar 3, 2017Technology
Flask vs Django. Which Is Better for Your Web App?

There are two most popular web frameworks in Python. There is the Django with lots of intelligent defaults and the Flask micro framework with complete freedom in the choice of modules. Let’s see, what django vs flask is in 2017.

Jan 12, 2017Technology
Making Custom Report Tables Using AngularJS and Django

In this article I will tell you how to create an interactive interface with a widely customized visual look and different filtering to view reports.

Feb 18, 2010Technology
Absolute urls in models

Everybody knows about permalink, but it's usually used only in get_absolute_url. I prefer to use it for all related model urls.class Event(models.Model):# ...@models.permalinkdef edit_url(self):return ('event_edit', (self.pk, ))And then in template:<a href="{{ event.edit_url }}">Редактировать событие</a>