I don't know if it's a recurring theme, that of knowing when to work with the Django authentication scheme, what is the best way to use it and extend it or if it is replaced by a custom one, but always with the idea of preserving the mechanisms and functionalities of authentication.
Due to the above, I want to share this concern that I have about some probable alternatives or design approaches that I have thought about the following situation, with the hope that they can help me analyze/propose or choose the best one:
I am building an application where I have three different types of users:
- Medical
- Patient
- Physiotherapist
All three users must be able to log in to the system and there would be relationships (from the context of the business object of the application or requirements) between the Medical users with the patient users and the Physiotherapist users with the Patient users, understanding that:
- A patient is treated by a doctor
- A patient is treated by a therapist
- A patient could have these three roles in the system (I assume this would not be a problem)
My initial idea is to use Django's default authentication scheme (django.contrib.auth)
I initially thought of the following entity schema, in which the User model/table is equivalent to the auth_user table that Django automatically generates in our database when we apply our first migration, and in which Django users are stored:
In this User table I have three boolean fields that I have added which are:
is_patient
, is_medical
andis_physiotherapist
The previous one, I think it is an approach that would allow me to do what I am looking for, only that there is a detail and that is that Django's default authentication model cannot be modified, it is immutable, I would think that to modify it you would have to modify the core of Django and this is something more expensive or it would be necessary to do it well or to know what is being done.
is_patient
For this reason, the , is_medical
and fields is_physiotherapist
cannot be added to the User table as I intend.
A classic recommendation that Django gives us in its documentation is to extend the User model with a OneToOne relationship to another model that contains the additional data that we want to add to the users. A basic example of what I say is this following figure:
This is how I can get the users I have in Django (managed by Django's default authentication scheme django.contrib.auth
) to have additional attributes such as a photo, date of birth, among others that we want.
In relation to the latter, the scheme that I present below, could be useful to manage the different user roles that I need?, that is, patients, doctors and physiotherapists
I must have relationships between these tables:
- Medical Users and Patient Users
- Physiotherapist Users and Patient Users and so between them and other tables
With this approach, these relationships will not be affected?
The three different types of users, their attributes would be stored between the UserProfile and User tables.
In itself, the UserProfile table would have all the fields of the patient, doctor and therapist users.
Is this a good practice in terms of scalability? I know that they would not be normalized of course.
Because another alternative is to think about my initial approach, but without the boolean fields is_patient
, is_medical
and is_physiotherapist
then by transitivity work with the different user roles, that is, like this:
In addition to the above, some alternatives are also presented such as:
- Create a table/Model named Role
If I have a separate or independent role model/table, and this would be related to the Django User model then I could say that a User can have multiple roles. This approach can be useful if I want to store unique information about a particular role.
Django Permissions and Authorization At the moment I don't know the degree of granularity (to what extent it would allow me to work with the models and their instances in terms of operations with them).
I have skimmed that the authorization and permissions system would allow me to work with the create, edit and delete operations
You could also look at the creation of groups, for example, a group called Doctors where medical users belong to it and that this group (and therefore the users that comprise it) can have certain permissions to certain models and operations. And so with the other types of users.
Is this another good alternative?
- AUTH_USER_MODEL Creating a Custom User model
Another alternative that is presented for when Django's default user management scheme does not satisfy the requirements of user authentication or management in a project, is to replace or customize the user model, but I do not know if this would have its disadvantages in terms of not preserving the authentication mechanisms and more functionalities that Django provides us with its User model.
Does my requirement to have a patient user, a medical user and a physical therapist user require building a custom Users model?
Everything that Django shows us about managing users and the multiple things that they allow us to do with this theme is not something that is complicated in itself at first, but from what I have seen and from my point of view I dare to affirm that it is a somewhat extensive topic, and for the same reason, I feel somewhat confused to make the best decision according to what I need.
If anyone has done something similar or has any suggestions for guidance (I know it may be a very particular case to post it in a free community), I would be very grateful.
Thank you very much for your time and apologies for the long post.
I took the trouble to create a project to be able to shape your case which, by the way, I find quite interesting since it is possible that I will come across something similar in a few months for a project.
I created the project
hospital
(it was the first thing that came to mind) using Django 1.9. Now yes, as Jack the Ripper said, let's go by parts .Regarding your comment:
This, in my opinion, makes things a bit easier for you, you forget about the tedious registrations, you will create your superuser (with
createsuperuser
) and then create the necessary system users and, most importantly, you (I am assuming that you are the system administrator ) you will be the one controlling who can do what. I also assume that you do not want to be bothered every time they want to create a user, so you will give the tools to other users with less privileges to be able to create new patients and even doctors and physiotherapists, something like a "super doctor".Remember that authentication (users, passwords) and authorization (groups, permissions) are problems that we have to attack separately.
Having said that, we can then start with the problem of multiple users. I would be inclined to work with your original idea, extend a
User
to have the booleans and handle three types of profiles for each one, I think this would be easier to handle when you want to enter the authorization part and add for example a a user to the groupdoctor
,fisioterapeuta
orpaciente
.Each group would have its privileges and it would be possible for a user to belong to one or more groups, so you could have a doctor who is also a patient for example.
Now yes, let's go to some code. I'll start by contradicting your statement:
Well, it's not immutable. You can choose between using AbstractBaseUser or
AbstractUser
, the difference is that itAbstractBaseUser
is a user without the Django fields, but it contains all the authentication logic, while itAbstractUser
has all the fields that Django gives you. What we will do then is extend the user since we don't want to create our own user model but we want to add some boolean fields.First, we tell Django that we want to use our own user model (I've created a single app called
usuario
):The customized model. Not to feel too bad, I'm using the same table name that Django uses (
auth_user
):Then, Django itself suggests that the first thing you do when extending the user is to create the migrations, so we run the command and also create a superuser in passing:
With a quick query with
shell
we can see the new fields:As you just saw, it is perfectly possible to extend the user. Now we need the profiles. As the profiles are only a relation
OneToOne
to our user, we can create all the profiles that we want (I will only create a couple of fields for each one):Again, we create the migration and migrate:
Done, we already replicated your original idea!
Authentication
Let's see if it is possible to authenticate the user. I am not going to create a complete login, we will only use
authenticate()
to demonstrate it. Let's do it from theshell
:Perfect, it works. One less headache, we already know that even having created our own user model inherited from
User
Django's, the authentication process works as it should. How you do authentication is ultimately up to you (using the Django authentication system, OAuth2, Social Auth, etc.).Authorization
As I mentioned earlier, it will suffice for now to create three groups:
doctor
,fisioterapeuta
andpaciente
. Before continuing, we can create some functions in our modelUser
to obtain the profiles according to the type of user.This will help us, because when you use the relation
OneToOneField
and this relation does not exist, it gives you the errorRelatedObjectDoesNotExist
:But, if we use our new functions, it will not generate an error because we are previously validating that the attribute exists:
Very well, then these functions will serve us well. As an example, in some of your views you could use them in this way to obtain the profiles according to the logged in user:
Bien, para ver el tema de los grupos, creemos solo un doctor y un paciente: "Dr. House" y "Jorge Enfermizo". No quiero llenar esto de capturas de pantalla ya que esto es fácil de hacer desde el admin, pero hagámoslo desde el
shell
:Listo, ahora creemos los grupos de los usuarios:
Agregamos los usuarios a su grupo correspondiente, digamos que Dr. House también puede ser un paciente:
Ahora, solo bastará con preguntar para saber si un usuario pertenece a cierto grupo, en algunas de tus vistas:
Finalmente, solo quedaría que a los grupos creados, les agregues los permisos que quieres que tenga cada uno y en las vistas uses tus mecanismos para comprobar que el usuario pueda hacer solo lo que su grupo le permite.
Notas finales
Siempre lo digo, no reinventes la rueda a menos que sea por temas didácticos.
Si quieres autenticar usuarios:
Si quieres validar la parte de autorización:
Si se te antoja usar registro de usuarios:
Si quieres un template gratuito, actualizado y muy bueno:
Es muy interesante tu caso de uso, probé una solución un poco mas sencilla, no estoy seguro si cumple a cabalidad con tus requerimientos, en esta solución se puede usar
django.contrib
sin complicaciones para login y demás.Eso sí agregando cada usuario a un grupo de usuarios de Django para posteriores filtros en los views, un usuario puede pertenecer a N grupos sin problemas, desde el sitio de administración puedes asignar a los usuarios a determinados grupos.
Perdón por la informalidad del código, pero es una vista global de como se puede construir la base de datos, los atributos son valores de pruebas.
models.py
Se me viene a la cabeza que la clase
PacienteEspecializado
puede ser una historia clínica o cualquier otra cosa.Al registrarlos en
admin.py
, el código sobra pero bueno.admin.py
Si tienes alguna pregunta o la solución tiene algún error házmelo saber.
Un saludo amigo. Yo uso un enfoque similar al tuyo, creando grupos de usuarios en Groups de Django. Extendiendo el modelo con un modelo "Usuario" y un FK hacia User (Todos mis roles requieren los mismos campos).
Las ultimas funciones aseguran que la creación de un "Usuario" se relacione directamente con la creación de un "User". Lo hice siguiendo este tutorial: https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html#onetoone
Otra recomendación es que protejas los templates. No sé que tan recomendable sea puesto que no he hecho protección desde las vistas. Yo he agregado un templatetag en la app usuarios:
Este es el código de user_tags.py:
Luego simplemente la cargas con "load" en tu template. https://docs.djangoproject.com/en/2.1/howto/custom-template-tags/
Ahora, tu app es requerida para un sólo un hospital. Mi problema surgió cuando un usuario tiene varios roles en varias empresas, es decir, es técnico en una y cliente en otra, en ese caso se actualizó el fk hacia empresa, y ahora tiene las reparaciones de la empresa en la que es cliente.
Estoy en este dilema, necesitaría extender la tabla
y añadir empresa_id. Así podría seleccionar a que empresa pertenece el rol en el que se está desempeñando, y al estar los templates protegidos no tendría acceso a ciertas vistas. Espero te sirva y te dejo el enfoque, por si tu app crece y la requieras utilizar en múltiples hospitales, y te evites este quebradero de cabeza en el que estoy.
Check out this tutorial if it helps: https://simpleisbetterthancomplex.com/tutorial/2018/01/18/how-to-implement-multiple-user-types-with-django.html
Good luck and programming!