Всем привет. При разработке API для очередного веб-портала я взял свой привычный стек: Но в этот раз стояла довольно непривычная задача — сделать одну User модель, которая может иметь несколько разных профилей (Исполнитель, Заказчик). И наличие каждого из профилей дает разные полномочия на работу с одними и теми же ресурсами. Такой подход позволяет пользователям не заводить несколько учетных записей для каждой роли, что зачастую было бы невозможно, ввиду ограничений на модель: уникальный email или номер телефона. Итак, опишем возникшие перед нами проблемы: Ниже я приведу свой способ решения этой задачи, который сложился из уже наработанных привычек по организации Django-проекта, а также попыток придумать наиболее гибкое и масштабируемое решение. Над этой проблемой я почти не думал и шел по опыту предыдущих проектов, да и в документации фреймворка этот способ описан, как предпочтительный (https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#extending-the-existing-user-model) Таким образом каждый пользователь может иметь от 0 до 2-х профилей (профиль Исполнителя, профиль Заказчика). Данную организацию моделей я принял как исходную точку для разработки решения и считал, что здесь ошибок допущено не было. Эту часть задачи я также решал по опыту предыдущих проектов, использовав Custom Permissions от drf (https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions). В этом решении мне нравится его простота и наглядность. Но допускаю, что данную задачу можно было решить и через джанго пермишны. Для действий, которые доступны пользователям с любым из профилей, можно сделать общий permission У такого подхода есть минус: если пермишнов по профилям будет больше 2-х, то множество таких сочетаний будет сильно расти. Но если вы можете с достаточной уверенностью утверждать, что пермишны не будут расширяться, то код останется довольно лаконичным. В django-rest-framework пермишны, которые применяются к конкретной вью, указываются в атрибуте permissions_class, которые накладывается по принципу AND.  В этом примере доступ ко вью получат только пользователи, имеющие оба профиля. Именно в таком случае нам приходится придумывать фокусы с составными пермишнами EmployeeAndCompanyManagerOnly и использовать их в качестве одного общего пермишн класса. С версии 3.9 в drf появилась возможность конструировать различные условия из пермишнов, используя синтаксис битовых операций Синтаксис не самый очевидный и симпатичный, но может быть удобнее, чем городить кучу составных пермишн классов. В текущей версии проекта я использовал версию drf 3.11, поэтому решил использовать эту новую возможность. Это добавляет некоторой гибкости в масштабировании проекта, не так уж сильно усложняя чтение и понимание кода. P.S. В дальнейшем у меня добавились еще пермишны для CompanyOwnerOnly и CompanyMemberOnly, и мне не пришлось ничего дописывать помимо этих классов, что хорошо. Мы дошли до самого интересного и неочевидного места.  Когда-то давно, потратив несколько дней на изучение вопросов (Как же все-таки правильно писать REST API? Обязательно ли апи должно быть RESTful? Да и вообще как делать правильно? Должны же быть какие-то best practice?), я понял, что мнений много, а четких правил нет. Но все мнения сходятся в одном: не бойтесь делать то, что удобно в вашем случае. И для себя я вывел следующие тезисы: В своих проектах я использую viewsets от drf, которые из коробки генерят необходимые нам REST окончания для наших моделей. И в случае необходимости разбавляю его некоторыми экшнами.  Итак, опишем ресурс, с которым взаимодействуют различные пользователи: Cодержание экшнов не имеет значения ("create", "update", "partial_update" — название дефолтных экшнов drf), поэтому не буду приводить их описания. Вот как будет выглядеть апи от имени Исполнителя (для Заказчика аналогично, только со своими методами):  И все бы было хорошо, если бы не тот факт, что один и тот же пользователь, может иметь сразу оба профиля, а значит сваггер нарисует нам следующую картинку:  Тут и становится понятно, что у нас есть некоторые проблемы: И решить их навскидку можно 4-мя способами: Написать разные TaskViewSet для Исполнителя и Заказчика. Использовать кастомный хэдер в реквесте для определения, в качестве кого пользователь хочет запросить ресурс. Использовать query параметры в урле. HATEOAS (расскажу подробнее в следующем посте!) По этому решению я не могу описать плюсы-минусы, так как никогда не пытался внедрять в свои проекты. Читал, что это не всегда удобно и бывает не просто поддерживать.
Но достоверность этой информации подтвердить не могу.
Так же есть подозрение, что подход не решает проблему №1. Я выбрал третье решение, так как в нем меньше всего минусов. Написал специальный сериалайзер и подключаю его к спецификации тех экшнов, которые должны содержать этот query параметр.  Получилось довольно симпатично и последним штрихом осталось определить queryset во вью в зависимости от выбранного mode. Итак, мы решили проблему №1, теперь мы точно знаем, от какой роли запрашиваем задачи. Следующим этапом надо было придумать, как ограничить действия на объекты, которые мы можем получить, например, как Исполнитель, но не можем редактировать как Заказчик (так как мы не ее автор, хоть у нас и есть профиль Заказчика). И тут мне помогло то, как организован метод self.get_object() в drf view. Он ищет объект в queryset = self.filter_queryset(self.get_queryset()), который мы определили до этого, что в общем-то логично, так как другой связи с моделью у вью нет.  Соответственно небольшой модификацией кода мы решили и вторую проблему: Технически проблема решена: при попытке изменить задачу, автором которой я не являюсь, мне вернется ошибка 404 NotFound, т.к. queryset для данного пользователя сформированный по ветке MODE.COMPANY просто не будет содержать этот ресурс. Дальше я попытался навести красоту: мне не нравилось то, что в if мы мешаем и mode из урла, и проверку по экшнам. А также при добавлении нового экшна надо не забыть его здесь дописать. После небольшого рефакторинга стало лучше: Но от чего хочется избавиться так это от того, что при добавлении экшна я должен скролить до словаря и вписывать туда дефолтный мод. По-питонячи было бы использовать какой-нибудь декоратор и пометить экшн сразу, но эту доработку я оставлю на потом. В первую очередь я набирал этот текст, чтобы попытаться логически выстроить свое решение и объяснить каждый следующую шаг. В итоге, как мне кажется, получилось довольно лаконичное решение, которое хорошо масштабируется на произвольное количество профилей. При этом сложность кода почти не возрастает, и мы смогли удачно разобраться с пермишнами на объекты. На хабр я решил запостить эту статью, т.к. пока решал данную задачу, долго искал что-то похожее в интернете, но нашел довольно мало материалов на тему "один пользователь — много ролей". Хочется узнать не изобретал ли я велосипед в стеке Django, drf. А если это действительно нужный кейс, то можно даже обернуть это в небольшой плагинчик для джанго. Всем спасибо! Разработчик