首页 > 解决方案 > 使用 Python 3.7 contextvars 在 Django 视图之间传递状态

问题描述

我正在使用 Django 2.2 和 Python 3.7 构建单个数据库/共享模式多租户应用程序。

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

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

# 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

.tenant_objects...在我的应用程序中,业务逻辑可以使用模型类而不是检索查询集.objects...

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

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

  2. 如果我注销然后以其他用户身份再次登录,则会保留前一个用户的状态,直到重新加载页面。这似乎与上一期有关,因为状态似乎落后了(因为没有更好的词)。

  3. 我使用 Celeryshared_tasks进行加工。我必须手动将租户传递给这些,因为它们不会获取上下文。

问题:

  1. 我这样做正确吗?

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

沮丧,因为我几乎找不到这样做的例子,也很少讨论contextvars. 我试图避免到处手动传递租户或使用thread.locals.

谢谢。

标签: pythondjangopython-3.7django-2.2

解决方案


您只是在生成响应后设置上下文。这意味着它总是会滞后。您可能想在之前设置它,然后检查用户是否已更改。

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


推荐阅读