Как заставить Django Rest Framework работать с Django Tenants и React?
Вот моя установка:
settings.py
SHARED_APPS = (
'django_tenants',
'main',
other apps...
)
TENANT_APPS = (
'rest_framework',
'company',
)
MIDDLEWARE = [
'django_tenants.middleware.main.TenantMainMiddleware',
other middleware...
]
DATABASE_ROUTERS = (
'django_tenants.routers.TenantSyncRouter',
)
urls.py
from django.urls import include, path
from rest_framework import routers
# other imports
from main.api.v1 import projects
router = routers.DefaultRouter()
router.register(r'api/v1/project', projects.ProjectViewSet)
urlpatterns = [
-- other paths --
path('', include(router.urls)),
]
api/v1/project.py
# other imports
from company.models import Project
from rest_framework import serializers
from rest_framework import viewsets
from rest_framework import permissions
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Project
fields = ['url', 'name', 'keycode']
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all().order_by('id')
serializer_class = ProjectSerializer
permission_classes = [permissions.AllowAny]
main.models.py
from django.contrib.auth.models import User as AuthUser
from django_tenants.models import TenantMixin, DomainMixin
# note, definition of custom "User" model which has an AuthUser 1 to 1 relationship
class Company(TenantMixin):
name = models.CharField(max_length=100)
subdomain = models.CharField(max_length=32)
employees = models.ManyToManyField(User, related_name='companies')
migration_id = models.IntegerField(null=True)
class Domain(DomainMixin):
pass
company.models.py
from django.db import models
class Project(models.Model):
name = models.CharField(max_length=100)
keycode = models.CharField(max_length=8)
И последняя деталь - я использую не Django frontend, а созданный в React. Запрос, который идет к бэкенду, является обычным запросом, однако он идет с поддомена и включает JWT-токен (но я не знаю, имеет ли это значение), вот сокращенная версия заголовков запроса:
Request URL: http://localhost:8000/api/v1/project/
Request Method: GET
Authorization: Bearer <token here>
Origin: http://cbd.localhost:3000
Ошибка, которую я вижу на бэкенде, выглядит следующим образом:
взаимосвязь "компания_проект" не существует
Мое предположение, что это происходит из-за того, что когда этот запрос создается в ProjectViewSet:
queryset = Project.objects.all().order_by('id')
Запрос не выполняется в контексте арендатора. Но мой вопрос в том, как именно это можно сделать. Я вижу в Django Tenants, что есть конструкция, которая выглядит следующим образом:
with tenant_context(tenant):
# All commands here are ran under the schema from the `tenant` object
Но я не представляю, как мне получить параметр 'tenant' в определении класса, где он, по-видимому, необходим.
Есть идеи?
Что ж, я придумал один способ сделать это, но он уродлив.
в моем файле settings.py я добавил новую константу:
BASE_TENANT = "cbd_co"
Я установил это для моего первого арендатора (в моей системе будет схема арендатора по умолчанию, которая на самом деле не принадлежит клиенту, а является своего рода шаблоном)
Затем, в объявлении набора представлений моей модели я сделал следующее:
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.raw('SELECT * FROM "' + settings.BASE_TENANT + '"."company_project";')
serializer_class = ProjectSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self):
schema = self.request.META['HTTP_ORIGIN'].split("//")[1].split('.')[0] + '_co'
qs = Project.objects.raw('SELECT * FROM "' + schema + '"."company_project" ORDER BY "company_project"."id" ASC;')
return qs
Я все еще надеюсь, что кто-то предложит лучшее решение...
Примечания к этому решению. В своем нынешнем виде оно совершенно небезопасно. На самом деле мне придется получить заголовок авторизации из запроса, проверить токен JWT, получить пользователя, получить все допустимые компании пользователей (что также определяет допустимые поддомены и, следовательно, допустимые схемы. и только после этого я верну набор запросов.