He estado usando Django durante bastante tiempo, pero nunca había pensado en esto hasta ahora.
Actualmente, tengo un proyecto que contiene diferentes niveles de usuario. Por lo general, en mi experiencia pasada, solo desarrollé sistemas usando Django con solo dos niveles de usuario que son superusuario y usuario normal/regular. Entonces, mi pregunta es ¿cuáles son las formas efectivas de presentar estos diferentes niveles de usuario en el modelo/base de datos? Aquí, usaré un sistema escolar como ejemplo y también proporcionaré algunos de mis pensamientos iniciales sobre su implementación.
Niveles de usuario:
Método n.º 1: agregar nuevas tablas en función de cada nivel de usuario
from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): user = models.CharfieldField(max_length = 10, unique = True) class Admin(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) class Pricipal(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) class Teacher(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) class Student(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)Método n.º 2: agregue atributos de tipos de usuario adicionales en el modelo de usuario
from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): user = models.CharfieldField(max_length = 10, unique = True) is_superuser = models.BooleanField(default = False) is_staff = models.BooleanField(default = False) is_principal = models.BooleanField(default = False) is_teacher = models.BooleanField(default = False) is_student = models.BooleanField(default = False ''' User table in DB: user | is_superuser | is_staff | is_principal | is_teacher | is_student '''Mis pensamientos:
En el método n.º 1, dado que el modelo de usuario integrado tiene dos campos, is_staff y is_superuser, ¿es posible implementar/cambiar los campos en una tabla de superusuario/administrador como en el ejemplo anterior? Esto significa que cuando creo un administrador/superusuario, quiero que agregue una nueva fila en la tabla de administración, en lugar de agregar un nuevo usuario y actualizar los campos is_superuser y is_staff del usuario en 1 en el modelo de usuario integrado.
En el Método #2, el problema es que las tablas con diferentes privilegios de acceso están directamente conectadas a él. Por ejemplo, el modelo Salario (al que no puede acceder el usuario Estudiante) tiene un vínculo directo con el modelo Usuario (contiene el usuario Estudiante).
Espero poder obtener algunas ideas y también una forma adecuada y efectiva de implementar esto para evitar cualquier inconveniente y error de implementación en el futuro. Muchas gracias.
Uno de los métodos que utilicé en varios proyectos es este (pseudocódigo):
class User(AbstractUser): ADMIN = 0 PRINCIPLE = 1 TEACHER = 2 STUDENT = 3 USER_LEVEL_CHOICES = ( (ADMIN, "Admin"), (PRINCIPLE, "Principle"), (TEACHER, "Teacher"), (STUDENT, "Student"), ) user_level = models.IntgerField(choices=USER_LEVEL_CHOICES) def lvl_decorator(): def check_lvl(func): def function_wrapper(self, actor, action_on, *args, **kwargs): if 'action_lvl' not in action_on: # then action_on is user if actor.user_lvl < action_on.user_lvl: return True return False else: # then action_on is action of some kind for that user (you can add action_lvl to ... and pas them to this wapper) if actor.user_lvl < action_on.action_lvl: return True return False return function_wrapper return check_lvlLuego, puede escribir la función contenedora con esta lógica para cualquier acción, verifique si el nivel de acción es mayor que el nivel de usuario, por ejemplo: si alguien quiere cambiar la contraseña de superusuario, debe iniciar sesión con el usuario de nivel 0, pero para cambiar la contraseña del usuario normal. debe ser de nivel 0, 1. Esta lógica también se puede aplicar a acciones de clase, funciones, etc.
Cree una clase base y luego agréguele lvl_decorator y luego inherente a ella => esto mantiene su código súper limpio y evita que se copie y pegue más.
ejemplo de lo que quiero decir:
def lvl_decorator(): def check_lvl(func): def function_wrapper(self, actor, action_on, *args, **kwargs): if 'action_lvl' not in action_on: # then action_on is user if actor.user_lvl < action_on.user_lvl: return True return False else: if actor.user_lvl < action_on.action_lvl: return True return False return function_wrapper return check_lvl class BaseClass(type): def __new__(cls, name, bases, local): for attr in local: value = local[attr] if callable(value): local[attr] = lvl_decorator() return type.__new__(cls, name, bases, local) # in other locations like views.py use this sample class FooViewDjango(object, ApiView): # don't remove object or this won't work, you can use any Django stuff you need to inherent. __metaclass__ = BaseClass def baz(self): print('hora hora')Utilice esta clase base en cualquier lugar que desee.
Creo que estás en el camino correcto con el método #2. Es más ligero y más sencillo.
No usaría un modelo personalizado "similar al usuario" para cada nivel de permiso. Demasiado complicado, no escala y multiplica el número de consultas, sin ningún beneficio para su problema. No es su esquema UML sino su contenido el que debe garantizar sus requisitos de permisos .
Si los niveles de permiso no son mutuamente excluyentes:
from django.db import models from django.contrib.postgres.fields import ArrayField class User(AbstractUser): ADMIN = 0 PRINCIPLE = 1 TEACHER = 2 STUDENT = 3 USER_LEVEL_CHOICES = ( (ADMIN, "Admin"), (PRINCIPLE, "Principle"), (TEACHER, "Teacher"), (STUDENT, "Student"), ) status = ArrayField( models.IntegerField(choices=USER_LEVEL_CHOICES, blank=True, default=STUDENT), )Pero hay que tener una reflexión más amplia.
Creo que estás hablando de dos problemas separados: polimorfismo y permisos .
El polimorfismo es la capacidad de un objeto para adoptar muchas formas. Para un modelo Django, se puede hacer con muchas estrategias: OneToOneField -como mencionaste- herencia de tablas múltiples , modelos abstractos o modelos proxy .
Muy buenos recursos: este artículo y el documento de Django sobre la herencia del modelo .
Este problema muy complejo se refiere a: en qué medida sus diversas formas de una misma entidad son similares o diferentes. Y qué operaciones son particularmente similares o diferentes (forma de datos, consultas, permisos, etc.)
Puedes elegir entre varios estampados
Model . Esto se hace en Django con el modelo de Permission incorporado, que tiene una ForeignKey para ContentTypeModel . Algunos paquetes proporcionan esta capacidad, por ejemplo, django-guardian .rules de Django proporciona este tipo de arquitectura.Puede crear desde AbstractUser (un modelo de usuario completo, completo con campos, incluidos is_superuser y is_staff) un perfil y luego, una vez que tenga el perfil, dar la oportunidad a los usuarios de crear otro tipo de perfil (Estudiante, Profesor o Principio) que podría tener funcionalidades propias.
Por ejemplo, en sus modelos.py
class Profiles(AbstractUser): date_of_birth = models.DateField(max_length=128, blank=True, null=True, default=None, verbose_name=_(u'Date of birth')) principle = models.OneToOneField(Principles, null=True, blank=True, verbose_name=_(u'Principles'), on_delete=models.CASCADE) teacher = models.OneToOneField(Teachers, null=True, blank=True, verbose_name=_(u'Teachers'), on_delete=models.CASCADE) student = models.OneToOneField(Students, null=True, blank=True, verbose_name=_(u'Students'), on_delete=models.CASCADE) class Meta: db_table = 'profiles' verbose_name = _('Profile') verbose_name_plural = _('Profiles')A ese modelo puede agregar métodos de clase, como
def is_teacher(self): if self.teacher: return True else: return FalseEntonces, su modelo de Profesores podría verse así
class Teachers(models.Model): image = models.FileField(upload_to=UploadToPathAndRename(settings.TEACHERS_IMAGES_DIR), blank=True, null=True, verbose_name=_('Teacher logo')) name = models.CharField(blank=False, null=False, default=None, max_length=255, validators=[MaxLengthValidator(255)], verbose_name=_('Name')) street = models.CharField( max_length=128, blank=False, null=True, default=None, verbose_name=_('Street')) created_by = models.ForeignKey('Profiles', null=True, blank=True, on_delete=models.SET_NULL)