I'm building a single database/shared schema multi-tenant application using Django 2.2 and Python 3.7.
I'm attempting to use the new contextvars api to share the tenant state (an Organization) between views.
I'm setting the state in a custom middleware like this:
# tenant_middleware.py
from organization.models import Organization
import contextvars
import tenant.models as tenant_model
tenant = contextvars.ContextVar('tenant', default=None)
class TenantMiddleware:
   def __init__(self, get_response):
        self.get_response = get_response
    def __call__(self, request):
        response = self.get_response(request)
        user = request.user
        if user.is_authenticated:
            organization = Organization.objects.get(organizationuser__is_current_organization=True, organizationuser__user=user)
            tenant_object = tenant_model.Tenant.objects.get(organization=organization)
            tenant.set(tenant_object)
        return response
I'm using this state by having my app's models inherit from a TenantAwareModel like this:
# tenant_models.py
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from organization.models import Organization
from tenant_middleware import tenant
User = get_user_model()
class TenantManager(models.Manager):
    def get_queryset(self, *args, **kwargs):
        tenant_object = tenant.get()
        if tenant_object:
            return super(TenantManager, self).get_queryset(*args, **kwargs).filter(tenant=tenant_object)
        else:
            return None
    @receiver(pre_save)
    def pre_save_callback(sender, instance, **kwargs):
        tenant_object = tenant.get()
        instance.tenant = tenant_object
class Tenant(models.Model):
    organization = models.ForeignKey(Organization, null=False, on_delete=models.CASCADE)
    def __str__(self):
        return self.organization.name
class TenantAwareModel(models.Model):
    tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, related_name='%(app_label)s_%(class)s_related', related_query_name='%(app_label)s_%(class)ss')
    objects = models.Manager()
    tenant_objects = TenantManager()
    class Meta:
        abstract = True
In my application the business logic can then retrieve querysets using .tenant_objects... on a model class rather than .objects...
The problem I'm having is that it doesn't always work - specifically in these cases:
In my login view after
login()is called, the middleware runs and I can see the tenant is set correctly. When I redirect from my login view to my home view, however, the state is (initially) empty again and seems to get set properly after the home view executes. If I reload the home view, everything works fine.If I logout and then login again as a different user, the state from the previous user is retained, again until a do a reload of the page. This seems related to the previous issue, as it almost seems like the state is lagging (for lack of a better word).
I use Celery to spin off
shared_tasksfor processing. I have to manually pass the tenant to these, as they don't pick up the context.
Questions:
Am I doing this correctly?
Do I need to manually reload the state somehow in each module?
Frustrated, as I can find almost no examples of doing this and very little discussion of contextvars. I'm trying to avoid passing the tenant around manually everywhere or using thread.locals.
Thanks.