I have a user role schema (medical, patient, physiotherapist) that derives from the AbstractUser class to use it in requests as seen in the following models:
#models.py
from __future__ import unicode_literals
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.contrib.auth import get_user_model
from django.dispatch import receiver
from django.db.models.signals import post_save
class User(AbstractUser):
is_medical = models.BooleanField(default=False)
is_physiotherapist = models.BooleanField(default=False)
is_patient = models.BooleanField(default=False)
slug = models.SlugField(max_length=100, blank=True)
photo = models.ImageField(upload_to='avatars', null = True, blank = True)
def get_medical_profile(self):
medical_profile = None
if hasattr(self, 'medicalprofile'):
medical_profile=self.medicalprofile
return medical_profile
def get_patient_profile(self):
patient_profile = None
if hasattr(self, 'patientprofile'):
patient_profile = self.patientprofile
return patient_profile
def get_physiotherapist_profile(self):
physiotherapist_profile = None
if hasattr(self, 'physiotherapistprofile'):
physiotherapist_profile = self.physiotherapistprofile
return physiotherapist_profile
class Meta:
db_table = 'auth_user'
class MedicalProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=64)
class PatientProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=64)
class PhysiotherapistProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=64)
What I want is that when a new user is created, automatically, his profile is created depending on his field if he is a doctor, patient, or physiotherapist ( is_medical
, is_patient
, is_physiotherapist
)
For that I am using the signal in post_save()
the following way in the same models.py file where my models are:
In the signal I am sending three parameters plus the **kwargs:
sender
, which is my users modelcreated
, a boolean parameter that tells me that an instance of my user modelAUTH_USER_MODEL
has been createdinstance
, the user instance being created
To know that a user is about to be created, somehow I must find out if in that request that I make (create the user), that user or that instance that is about to be created goes, for which I douser = self.request.user
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_for_new_user(sender, created, instance, **kwargs):
# Pregunto si en el request va el user, aqui va mi inquietud
user = self.request.user
#user = get_user_model()
if created:
if user.is_medical:
profile=MedicalProfile(user=instance)
profile.save()
It's good logic, but it turns out that I haven't defined self and I (obviously) get this error
In the method in create_profile_for_new_user(...)
which I am applying the signal of post_save()
I only have four attributes, so if I add self as an attribute at the beginning
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_for_new_user(self,sender, created, instance, **kwargs):
would get this error:
TypeError: create_profile_for_new_user() missing 1 required positional argument: 'self'
127.0.0.1 - - [28/Dec/2015 22:57:04] "GET /admin/userprofile/user/add/?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
Another option that I was contemplating is to obtain the user that is being created with the function get_user_model()
as I do here (previously importing the function of course from django.contrib.auth import get_user_model
):
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_for_new_user(sender, created, instance, **kwargs):
# Pregunto si en el request va el user, aqui va mi inquietud
#user = self.request.user
user = get_user_model()
if created:
if user.is_medical:
profile=MedicalProfile(user=instance)
profile.save()
But when I do, I get this message, and it's logical, because my User object (I'm getting an instance of the original Django User model) doesn't have the attribute I'm asking for, in this caseis_medical
According to the above, I don't know how to ask or inquire about the user to examine their boolean attributes ( is_patient
, is_medical
, is_physiotherapist
) and create their respective profile accordingly.
By the way, I take advantage and another concern arises: Is it possible to use the signal post_save
for more than one purpose?
In this case, I want to use it both to create a profile of the user instance that is created and to give a value to a named field slug
based on its attribute first_name
. That is, could you apply it to the methods you need?
Remember that it
create_profile_for_new_user
is a function related to the model, it is not a view, therefore you do not have access to therequest
. It's also not a class, so you don't have access to itself
as you'd like.The parameter
created
is not going to work as you want since you are using it in the wrong parameter position, the first parameter of the functions that receive a signal is the model that sends the signal (sender
) and the second parameter is the instance of that model (instance
).The parameter
instance
is the one that contains the user that has been created, so the following is valid:Not even that is necessary, you could do it without the need to assign the variable
user
:If you need to assign the other fields of your model, you can also do it although I thought we had already solved this :
Note:
Notice that in none of the examples am I using the parameter
created
that you had initially defined as a parameter.Although it is possible to use the
created
del parameterpost_save
, note that this is the third parameter, not the second:Update:
The problem you have about the
IntegrityError
one you mention in your comments is because when you log in, the system is trying to save your last login date and when trying to do this, it calls againpost_save
and finally your functioncreate_profile_for_new_user
.The solution is to always use the parameter
created
:If you don't use this, the function will try to save the profile again and will generate a
IntegrityError
since the profile was created before when the user was initially created.In the settings.py file you have to specify the class that inherits from AbstractUser in the AUTH_USER_MODEL property. So it would be
This will take the instance of your class, not the default User class (auth.User)
Finally, I opted to work by overriding the
save()
class methodAbstractUser
instead of working by applying the signalpost_save()
to the function ,create_profile_for_new_user
so my models looked like this:User
MedicalProfile
PatientProfile
PhisiotherapystProfile
In this way, a user can be created that has all possible combinations of roles (doctor, patient, physiotherapist).
In any case, a signal
post_save()
applied in the function is still usedpost_save_user()
to enter a slug value to my fieldslug
in the modelUser
. This value is based on the attributeusername
Thank you very much for your constant guidance. :)