使用 Python 3.7 contextvars 在 Django 视图之间传递状态

Using Python 3.7 contextvars to pass state between Django views

我正在使用 Django 2.2 和 Python 3.7 构建单个 database/shared 模式多租户应用程序。

我正在尝试使用新的 contextvars api 在视图之间共享租户状态(Organization)。

我正在像这样在自定义中间件中设置状态:

# 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

我通过让我的应用程序的模型继承自 TenantAwareModel 来使用此状态,如下所示:

# 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

在我的应用程序中,业务逻辑随后可以在模型 class 上使用 .tenant_objects... 而不是 .objects...

来检索查询集

我遇到的问题是它并不总是有效 - 特别是在这些情况下:

  1. 调用 login() 后,在我的登录视图中,中间件运行,我可以看到租户设置正确。然而,当我从我的登录视图重定向到我的主页视图时,状态(最初)再次为空并且似乎在主页视图执行后正确设置。如果我重新加载主页视图,一切正常。

  2. 如果我注销然后以不同的用户身份再次登录,前一个用户的状态将被保留,直到重新加载页面。这似乎与上一期有关,因为状态似乎滞后(找不到更好的词)。

  3. 我用Celery分拆shared_tasks进行处理。我必须手动将租户传递给它们,因为它们不获取上下文。

问题:

  1. 我这样做正确吗?

  2. 我是否需要在每个模块中以某种方式手动重新加载状态?

很沮丧,因为我几乎找不到这样做的例子,而且对 contextvars 的讨论也很少。我试图避免在任何地方手动传递租户或使用 thread.locals.

谢谢。

您仅在生成响应后设置上下文。这意味着它将永远滞后。您可能想先设置它,然后再检查用户是否已更改。

请注意,我不确定这是否会完全按照您的要求工作。上下文变量根据定义是本地的;但是在像 Django 这样的环境中,您永远无法保证来自同一用户的连续请求将由同一服务器进程提供服务,同样,一个进程可以为来自多个用户的请求提供服务。另外,正如您所指出的,Celery 又是另一个独立的进程,不会共享上下文。