Лучшая практика проектирования базы данных в проекте django rest?

У меня есть проект, в котором пользователи имеют несколько разрешений на основе назначенной роли.

Админ может назначить права (например, can_view, can_approve) в некотором меню некоторой роли и так далее.

Таким образом, может быть очень много bool-флагов, и некоторые из них будут ненужными для некоторых меню.

Если я использую модель Django Group и базу разрешений, то разрешений может быть очень много. Например: (может создавать меню, может обновлять меню...)

ИЛИ любым другим способом, который будет уместен?

class User():
   id
   name
   role_id(fk)

class DashboardMenu():
   id
   name
   codename(unique)
   url
   parent_id

class Role():
   id
   name(unique)
   desc
   menus(menu_ids (many to many))
   

class RoleMenuRight():
   id
   role_id(fk)
   menu_id(fk)
   can_create
   can_delete
   can_update
   can_view
   can_approve
   can_reject
   #other bool flags

Другой подход

class User():
   id
   name
   role_id(fk)

class Permission():
   id
   name
   codename(unique)
  

class Role():
   id
   name(unique)
   desc
   permissions(permission_ids (many to many))
   

Я бы предложил использовать что-то вроде django contrib auth django.contrib.auth Вы можете создать разрешения, как у вас, и вместо использования роли - создать группу с разрешениями для типов пользователей

После 1 часа борьбы я получил это. Надеюсь, это поможет вам.

class User():
   id
   name
   role (fk)
    
class Role():
   id
   name(unique)
   desc
   menus(many_to_many, thorough="RoleMenuMapping")
  
class RoleMenuMapping():
  role(fk)
  dash_board_menu(fk)
  #below fk make each menu related with role has one permission, 
  #if you relate permission m2m with role or menus, you will confuse when get privileges. 
  #Because, they will be multiple when you get.
  privilege(fk)
  class Meta:
    unique_togeter = [(role, dash_board_menu)] 

class Privilege():
   id
   can_create (bool)
   can_delete (bool)
   can_update (bool)
   can_view (bool)
   can_approve (bool)
   #other bool flags
    
 class DashboardMenu():
   id
   name
   codename(unique)
   parent_id

  [
{"id":1,
 "menu_name":"name",
 "user_can_create":true,
 "user_can_update":false
  .....
},
 {"id":2,
 "menu_name":"name",
 "user_can_create":true,
 "user_can_update":false
  .....
}

can get by 
    
user = User.objects.filter(id==100).prefetch_related('role__rolemenumapping_set__privilege','role__rolemenumapping_set__menu')
   
   return [ {
      "id":row.menu.id,
      "name":row.menu.name,
      ....
      "user_can_create": row.privilege.can_create,
      "user_can_update":row.privilege.can_create,
      
     } for row in user.role.rolemenumapping_set
  ]
  
 
  {"role_id":1,
    "menus":[  
{"menu_id":1,"can_create":true,"can_update":true,"can_view":true,"can_delete":false},
         {"menu_id":2,"can_create":false,"can_update":true,"can_view":true,"can_delete":false},
         {"menu_id":3,"can_create":true,"can_update":false,"can_view":true,"can_delete":false}]}
    
This request can be done by below:

role= Role.objects.get(pk=role_id)

for menu in menus:
    menu_id = menu.pop("menu_id")
    #below should return single privilege row!
    privilege = Privilege.objects.filter(**menu)
    role_menu_map = RoleMenuMapping.objects(role=role,menu_id=menu_id)
    role_menu_map.privilege = privilege
    role_menu_map.save()

# I know upper code hit db a lot. 
# This code comes from your request. 
# If this kind of request is frequent, you'd better change each permissions to one privilege_id.

Прежде всего, лучший дизайн базы данных для django-rest-framework - это просто лучшие практики дизайна базы данных с использованием django ORM. А django ORM основан на SQL & лучшие практики реляционных БД в уме.

Чтобы помочь вам с вашим запросом о лучшей практике работы с базами данных с django ORM для REST API, я бы предложил следовать этой странице & packages, если вы думаете об использовании DRF https://www. django-rest-framework.org/api-guide/permissions/#:~:text=The%20DRY%20Rest%20Permissions%20package%20provides%20the%20ability,to%20a%20client%20app%20through%20the%20API%27s%20serializer.

Вы также можете выбрать любой тип разрешения на основе ролей из сторонних пакетов из django eco system, чтобы настроить их так, как вам нужно. но вы должны сначала расширить свое понимание моделей django ORM & contrib.auth permissions basic, чтобы расширить его дальше для ваших нужд или понять другие сторонние реализации. это мое общее руководство для новых разработчиков django ORM.

Эта статья очень старая, но поможет вам понять, как работает django ORM гораздо лучше https://schinckel.net/2014/08/26/avoiding-sql-antipatterns-using-django-(and-postgres)/ .

Второй способ более правильный.

Превосходство моделей Django в том, что они обрабатывают таблицы "многие-ко-многим" и другие SQL вещи под капотом. Плохо то, что документацию по Django IMO очень сложно использовать, если вы еще не очень хорошо представляете, что вы ищете.

Поэтому я бы выбрал второй способ. Вот моя реализация. Вы заметите, что на самом деле вам не нужно определять один или одну сторону множества в one-to-many или many-to-many. Вот как я понимаю реализацию второго способа с учетом того, как я понимаю лучшую практику*.

На самом деле я не запускал/тестировал ни один из этих кодов, поэтому относитесь к ним как к псевдокоду.

from django.db import models
class User(model.Models):
    name = models.CharField(max_length=255)

Почему я не упомянул id (он же первичный ключ, или "pk"), или не упомянул role_id? Потому что нам не нужно говорить о role_id здесь и мы можем позволить магии django справиться с pk. Возможно, вы делаете что-то очень необычное или неправильное с pk.

В Django вам даже не понадобится "role_id"! Это абстракция. Если вы запутались, продолжайте, потому что именно здесь появляется related_name через поля ForeignKey и ManyToMany.

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

class Role():
  name = models.CharField(max_length=255,unique=True)
  desc = models.CharField(max_length=1023)
  user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='roles')
  

Теперь мы можем получить доступ к роли (ролям) пользователя через user.roles. Django просто волшебным образом добавил поле "roles" в users через related_name. Теперь здесь происходит еще больше магии - мы создаем множество разрешений для множества ролей здесь:

class Permission(model.Models):
    name = models.CharField(max_length=255)
    codename = models.CharField(max_length=255,unique=True)
    roles = models.ManyToManyField("Role",related_name="permissions")

Не уверен, что вам действительно нужно кодовое имя, но привет.

Теперь посмотрите на роль. Здесь вы создаете множество разрешений для множества ролей. Теперь вы можете получить доступ к разрешениям, связанным с ролью, через role.permissions или к роли, связанной с разрешением, через permission.roles. Связанное имя позволяет этому атрибуту называться "разрешениями" или "ролями"

Бонусное чтение:

*За исключением того, что я использую "related_name" для простоты использования и читабельности вместо странного способа по умолчанию. Риск использования related_name заключается в том, что они должны быть уникальными, иначе могут произойти плохие вещи. Существует имя по умолчанию, с помощью которого можно добиться того же результата, поскольку связанное имя необязательно, но я никогда не использовал его.

Строка с именем класса в качестве первого аргумента в ManyToMany и ForeignKey должна быть именем класса на другой стороне отношения. Они не являются произвольными. Связанные имена могут быть произвольными, по соглашению я считаю, что они должны быть множественными.

Так что да, второй способ был более правильным - вам не нужны "полезные таблицы" или как там правильно называются join-таблицы в SQL в Django- это делается под капотом Django.

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

Импорт:

from django.conf import settings

Теперь внутри класса

user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,related_name='this_associated_with_built_in_users')

Фишка django в том, что все части того, что вам нужно знать, уже есть, но разбросаны в документации.

Полезные ссылки:

https://docs.djangoproject.com/en/3.2/topics/db/models/#relationships

https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_many/#

https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_one/

Связанное название объясняется здесь: https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ForeignKey

Также, если вы хотите знать, как сделать это без связанного имени, я думаю, вот как это сделать. У меня никогда не было: https://docs.djangoproject.com/en/3.2/topics/db/models/#abstract-related-name

Вернуться на верх