Django: реализовать несколько пользовательских уровней / ролей / типов - PullRequest
3 голосов
/ 05 апреля 2020

Я давно пользуюсь Django, но до сих пор никогда не думал об этом.

В настоящее время у меня есть проект, который содержит разные уровни пользователя. Обычно, в моем прошлом опыте, я только разрабатывал системы, использующие Django только с двумя пользовательскими уровнями: суперпользовательский и обычный / обычный пользователь. Итак, мой вопрос Каковы эффективные способы представления этих различных уровней пользователей в модели / базе данных? Здесь я собираюсь использовать школьную систему в качестве примера, а также изложу некоторые из моих начальных мыслей о

Уровни пользователя:

  1. Администратор (администратор и персонал)
  2. Директор
  3. Учитель
  4. Ученики

Метод № 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, как в примере выше? Это означает, что когда я создаю администратора / суперпользователя, я хочу, чтобы он добавил новую строку в таблицу администраторов вместо добавления нового пользователя и обновления пользовательских полей is_superuser и is_staff до 1 во встроенной модели User.

В методе № 2 проблема заключается в том, что таблицы с разными правами доступа напрямую связаны с ним. Например, модель «Зарплата» (к которой пользователь Студента не может получить доступ) имеет прямую связь с моделью «Пользователь» (содержит пользователя «Студент»).

Я надеюсь, что мне удастся получить некоторые идеи, а также правильный эффективный способ реализовать это так, чтобы предотвратить любые неудобства реализации и ошибки в будущем. Большое спасибо.

Ответы [ 3 ]

2 голосов
/ 05 апреля 2020

Я думаю, что вы на правильном пути с методом # 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 - как вы упомянули - наследование нескольких таблиц , абстрактные модели или прокси-модели .

Очень хорошие ресурсы: эта статья и Django do c о модели наследования

Это очень сложная проблема все относятся к: насколько ваши несколько форм одного и того же лица похожи или различны. И какие операции особенно похожи или различны (форма данных, запросы, разрешения, ... и т. Д. c)

  • Разрешения Дизайн:

Вы можете выбрать один из нескольких шаблонов

  • Разрешение на основе модели : пользователю предоставляется разрешение «добавить», «просмотреть», «изменить» или «удалить» до Model. Это делается в Django со встроенной моделью Permission, которая имеет ForeignKey до ContentType
  • Объектно-ориентированное разрешение: пользователь предоставляется разрешение «добавить», «просмотреть», «изменить» или «удалить» для каждого Model экземпляра. Некоторые пакеты предоставляют такую ​​возможность, например, django-guardian.
  • Правило , ориентированное на правила: пользователю предоставляется разрешение на экземпляр модели через пользовательский лог c вместо таблицы M2M. Пакет django rules обеспечивает такую ​​архитектуру.
2 голосов
/ 05 апреля 2020

Вы можете создать из AbstractUser (полная Модель пользователя , в комплекте с полями, включая 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)
1 голос
/ 05 апреля 2020

Один из методов, который я использовал в нескольких проектах, таков (псевдокод):

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

Затем вы можете написать функцию-обертку с такими логами c для любой проверки действия, если уровень действия больше уровня пользователя, например: если кто-то хочет изменить пароль суперпользователя, он / она должен войти в систему с пользователем уровня 0, но для изменения пароля обычного пользователя он должен быть уровня 0, 1. Этот лог c также может быть применяется к классу, функциям и т. д. c действия.

Создайте базовый класс, а затем добавьте к нему 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')

Используйте этот базовый класс в любом месте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...