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.
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.
Allows to get sublass instance from base class instance.
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.
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
.