Here is my setup:
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)
And the final detail is that I am not using a Django frontend but rather one created in React. The request which is going to the backend is just a standard request however it is coming from a subdomain and includes a JWT token (but I don't know if that is relevant), here is a shortened version of the request headers:
Request URL: http://localhost:8000/api/v1/project/
Request Method: GET
Authorization: Bearer <token here>
Origin: http://cbd.localhost:3000
The error that I am seeing on the backend is this:
relation "company_project" does not exist
My guess is that this is happening due to the fact that when this query is created in the ProjectViewSet:
queryset = Project.objects.all().order_by('id')
The request is not done within the context of a tenant. But my question is how exactly would that be done. I see in Django Tenants that there is a construct that looks like this:
with tenant_context(tenant):
# All commands here are ran under the schema from the `tenant` object
But I have no idea how I would go about getting the 'tenant' parameter in the class definition where it seems to be needed.
Any ideas?
Well, I figured out one way to do it, but it's ugly.
in my settings.py file I added a new constant:
BASE_TENANT = "cbd_co"
I set this to my first tenant (in my system there will be a default tenant schema which is not actually owned by a client but is a sort of template)
Then, in my model viewset declaration i did this:
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
I'm still hoping someone suggests a better solution though...
Notes on this solution. As it is, it is completely insecure. What I will actually have to do is get the authorization header from the request, verify the JWT token, get the user, get all the valid user companies (which also determines the valid subdomains and therefore the valid schemas. and only then would I return the queryset.