【问题标题】:Override POST, PATCH & DELETE methods覆盖 POST、PATCH 和 DELETE 方法
【发布时间】:2016-10-20 07:56:52
【问题描述】:

如何覆盖多个 ViewSet 的 POST、PATCH 和 DELETE 方法以允许我添加强制后端参数?

逻辑。我正在构建一个多租户应用程序,该应用程序在所有相关表中都有一个“tenant_id”。此tenant_id 标识租户,因此所有请求都必须包含此父键,以避免用户看到/修改不属于他们的内容。

对于 Get 查询,我创建了一个自定义过滤器后端,它使我能够添加强制过滤器对象来限制用户可以获得的内容

class CustomFilterBackend(filters.BaseFilterBackend):
    """
    Filter that only allows users to see entries related to their tenant.
    """
    def filter_queryset(self, request, queryset, view):
        tenant_id = get_tenant_id_from_token(request)
        return queryset.filter(is_deleted=False, tenant_id=tenant_id)

然后我通过 filter_backends = () 选项将此过滤器添加到所有 ViewSet 类中

问题是,有没有办法为 POST、PATCH、DELETE 请求实现相同的功能?

我目前的想法是对所有模型都覆盖model.save 方法?但这不会处理 HTTP DELETE 方法。

透明的tenant_id

在模型中,tenant_id 当然是强制性的。但是,我不想强​​制 Web/移动客户端始终提供tenant_id,因为我可以从用户的 JWT 令牌中获取它。即tenant_id 应该对网络/移动应用程序是透明的。

编辑 2

问题是,我想在网络/移动应用程序不知情的情况下静默/安静/幕后添加tenant_id。这意味着我不希望使用 API 的应用程序发送 tenant_id JSON 密钥。

样本模型

class SampleModel(models.Model):
    """
    Sample model
    """
    title = models.CharField(max_length=100)
    tenant = models.ForeignKey(Tenant, on_delete=models.PROTECT)


class CustomFilterBackend(filters.BaseFilterBackend):
"""
Filter that only allows users to see entries related to their tenant.
"""
def filter_queryset(self, request, queryset, view):
    tenant_id = get_tenant_id_from_token(request)
    return queryset.filter(is_deleted=False, tenant_id=tenant_id)


class SampleViewSet(ListCreateRetrieveUpdateViewSet):
    """
    Sample viewset
    """
    serializer_class = SampleModelSerializer
    permission_classes = (HasPermission)
    queryset = SampleModel.objects.all()
    filter_backends = (CustomFilterBackend, )

通过将 filter_backends 添加到所有视图集,所有 GET 查询现在都包含一个tenant_id。所以我想要的是对所有其他 HTTP 方法,尤其是 POST 和 PATCH 实现相同的效果

从阅读看来我必须重写序列化程序?是否有可能以 DRY 方式进行?到现在还没想好怎么弄

在创建自定义 jwt_payload_handler 后,获取tenant_id 的 JWT 的 Payload 如下所示:

{
  "exp": 1477069682,
  "is_superuser": true,
  "email": "test-email@gmail.com",
  "tenant_id": 1,  #THE TENANT ID
  "user_id": 1,
  "username": "test-email@gmail.com"
}

【问题讨论】:

    标签: django django-views django-rest-framework


    【解决方案1】:

    我建议使用HiddenField 并创建一个类来获取租户,就像CurrentUserDefault 一样。

    例如,如果你想设置当前登录用户的租户(与他的身份验证方式、令牌、会话...无关):

    class CurrentTenantDefault(CurrentUserDefault):
        def __call__(self):
            current_user = super().__call__()
            return current_user.tenant
    

    如果你不使用 Django 身份验证(你应该!),看看CurrentUserDefault 的实现,看看如何从请求中获取租户(确保返回一个租户实例,而不是它的 id,这可能不起作用)。

    可能是这样的:

    class CurrentTenantDefault():
        def set_context(self, serializer_field):
            request = serializer_field.context['request']
            tenant_id = get_tenant_id_from_token(request)
            self.tenant = Tenant.objects.get(pk=tenant_id)
    
        def __call__(self):
            return self.tenant
    

    然后在序列化程序中,声明隐藏字段,如:

        tenant = serializers.HiddenField(default=CurrentTenantDefault())
    

    注意:你不应该在你的模型中声明 ForeignKey 名称以“_id”结尾,因为当你实例化你的模型时,instance.tenant 是一个租户实例,而不是租户 ID . Django 确实将外键存储在tenant_id 列中,但这是透明的,您不需要关心它。

    【讨论】:

    • 我正在使用 Django 的身份验证。此外,ForeignKey 没有使用_id 声明,我已经更正了我给出的示例模型。根据您的建议,我会将CurrentTenantDefault 放在哪里?此外,租户标识符将从 JWT 令牌的私有部分获取。我正在使用django-rest-framework-jwt 并在登录时将tenant_id 添加到令牌中。请参阅我添加到问题中的示例..
    • 我通常将它保存在serializers 模块中,或者当序列化程序被拆分为多个模块时,例如serializers.core。我已经使用您的 get_tenant_id_from_token 函数更新了答案,并以 CurrentTenantDefault 为例。
    • 谢谢先生。这可以按预期工作并且易于理解
    【解决方案2】:

    如果您想弄清楚如何将 Id 传递给 view,您的答案很简单。 Id is mostly passed to views through urls。尝试GET 请求您的查询集,ID 如下。

    api/url/endpoint/1
    

    并且 1 将从 urls 传递到 drf 路由器的视图。然后基于http method,在您的情况下选择 GET 适当的操作。例如,如果您使用 GET,PUT,PATCH or DELETE 方法调用上述 url,则 1 被视为 reference to Id of the object,然后执行 orm 查询,可以是

    model.objects.get(id=1) #if request.method is GET
    model.objects.get(id=1).delete() #if request.method is DELETE
    model.objects.get(id=1) #if request.method is PATCH or PUT and then the fields of the corresponding object will be updated.
    

    如果您将POST 请求传递给api 端点,通常场景模型名称将通过api 端点识别,然后将使用传递的参数创建一个新对象。

    现在回答另一部分,即返回什么。在大多数情况下,drf 返回一个object of class Response,它可以被浏览器和移动设备用作JSON 对象。但是在涉及 drf 视图集遵循mro 的场景中,这些响应不会从 get() 之类的方法返回。在这些情况下,通常是最后一个函数,在大多数情况下是 render_to_response() 将返回响应。

    HTH

    【讨论】:

    • 我不是在寻找如何传递 ID。我已经编辑了这个问题并提供了更多的清晰度。现在看看这个问题是否有意义。
    猜你喜欢
    • 2015-01-17
    • 2021-02-01
    • 1970-01-01
    • 2018-11-12
    • 1970-01-01
    • 2019-07-18
    • 2022-08-20
    • 1970-01-01
    • 2013-10-03
    相关资源
    最近更新 更多