Лучшая практика проектирования базы данных в проекте 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