Django:实现多个用户级别/角色/类型

Posted

技术标签:

【中文标题】Django:实现多个用户级别/角色/类型【英文标题】:Django: implement multiple user levels / roles / types 【发布时间】:2020-07-17 07:15:52 【问题描述】:

我使用 Django 已经有一段时间了,但直到现在我才想到这一点。

目前,我有一个包含不同用户级别的项目。通常,根据我过去的经验,我只使用 Django 开发系统,只有两个用户级别,即超级用户和普通/普通用户。所以我的问题是在模型/数据库中呈现这些不同用户级别的有效方法是什么?这里,我将以一个学校系统为例,并提供一些我的初步想法实施它。

用户等级:

    管理员(超级用户和员工) 校长 老师 学生

方法#1:根据每个用户级别添加新表

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)

方法#2:在用户模型中添加额外的用户类型属性

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
'''

我的想法:

在方法 #1 中,由于内置的​​ User 模型有两个字段,is_staff 和 is_superuser,是否可以像上面的示例一样将字段实现/更改为 SuperUser/Admin 表?这意味着当我创建一个管理员/超级用户时,我希望它在 Admin 表中添加一个新行,而不是添加一个新用户并将用户的 is_superuser 和 is_staff 字段更新为内置用户模型中的 1。

在方法 #2 中,问题在于具有不同访问权限的表直接连接到它。例如,Salary 模型(Student 用户无法访问)与 User 模型(包含 Student 用户)有直接链接。

我希望我能够获得一些见解以及适当有效的实施方式,以防止将来出现任何实施不便和错误。非常感谢。

【问题讨论】:

组不能满足您的需要吗? docs.djangoproject.com/en/3.0/topics/auth/default/#groups @IainShelvington 组不是用于根据访问权限对用户进行分类吗? 【参考方案1】:

我认为您使用方法#2 走在正确的道路上。它更轻,更直接。

我不会为每个权限级别使用自定义的“类用户”模型。过于复杂,无法扩展和增加查询数量,对您的问题没有太大好处。 不是您的 UML 架构,而是其内容必须保证您的权限要求

如果权限级别不是互斥的:

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),
    )

但你需要有更广泛的反思。


我认为您在谈论两个不同的问题:多态性权限

多态性

多态性是对象具有多种形式的能力。对于 Django 模型,可以使用多种策略来完成:OneToOneField - 如您所述 - 多表继承抽象模型代理模型

非常好的资源:这个article,以及关于model inheritance 的Django 文档

这个很复杂的问题都是指:你的同一个实体的几种形式有多少相似,或者不同。以及哪些操作特别相似或不同(数据形状、查询、权限等)

权限设计:

您可以在多种模式中进行选择

面向模型权限:授予用户对Model 的“添加”、“查看”、“编辑”或“删除”权限。 这是在 Django 中使用内置 Permission 模型完成的,该模型具有指向 ContentType 的 ForeignKey 面向对象权限:用户被授予每个Model实例的“添加”、“查看”、“编辑”或“删除”权限。一些包提供了这个能力,例如django-guardian面向规则的权限:通过自定义逻辑而不是 M2M 表授予用户对模型实例的权限。 django rules 包提供了这种架构。

【讨论】:

如果不同的用户级别有不同的功能(字段)呢?例如,仅需要 Student 用户级模型来上传文件(通过使用 FileField)。因此,在这种情况下,方法#1 不是更可取吗? 在方法#2中,如果你想在做某事之前检查用户权限,检查request.user.status会很容易。根据该值,您是否引发错误,是否执行操作,...【参考方案2】:

您可以从 AbstractUser(完整的 User model,包含字段,包括 is_superuser 和 is_staff)创建个人资料,然后,一旦您有了个人资料,就可以让用户有机会创建其他类型的个人资料(学生、教师或原则),它可以有自己的功能。

例如,在你的 models.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')

您可以向该模型添加类方法,例如

def is_teacher(self):
    if self.teacher:
        return True
    else:
        return False

那么,您的教师模型可能如下所示

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)

【讨论】:

很好,但是外键字段不应该在教师类(用户类型类)中吗? 当然,刚刚添加了@Kyle_397 在此解决方案中,您必须在每次要添加另一个用户状态时添加一个新表。它根本无法扩展。而且您在这许多模型之间进行额外的查询,但没有什么好处 您在哪里看到@TimothéDelion 的状态?这就像创建一家公司并拥有与该公司相关的某些功能。 我的回答是同时是老师和学生,@Kyle_397 的方法#2 也是可以的。更多表 = 更多请求或至少更多连接:这是昂贵的。这是我关于多态性的圣经,如果你也觉得有趣,请告诉我:realpython.com/modeling-polymorphism-django-python【参考方案3】:

我在几个项目中使用的方法之一是这样的(伪代码):

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_lvl

然后您可以使用此逻辑编写包装函数以检查操作级别是否大于用户级别,例如:如果有人想更改超级用户密码,他/她应该使用 level-0-user 登录但要更改普通用户的密码应该是0、1级。这个逻辑也可以应用于类、函数等动作。

创建基类,然后将 lvl_decorator 添加到它,然后从它继承 => 这可以保持您的代码超级干净并防止进一步复制粘贴。

我的意思的例子:

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')

在任何你想要的地方使用这个基类。

【讨论】:

这个想法看起来很有趣,带有包装函数。您能否提供一些有关如何使用它的参考或文档,因为我对此不太熟悉:-( @Kyle_397 我自己提出了这个想法,我没有从任何地方复制它,所以没有参考!但它就像魅力一样! @Kyle_397 我也添加了示例希望您现在可以使用它:D.

以上是关于Django:实现多个用户级别/角色/类型的主要内容,如果未能解决你的问题,请参考以下文章

Django 上的用户角色模式

SQL SERVER 2008 服务器登录名角色数据库用户角色架构的关系

如何实现基于组和角色的权限系统?

oracle 中如何创建一个角色并把角色赋予指定账户

django的权限6表和 content_type

如何根据用户在 Laravel 5 中的角色选择用户?