如何在 Django 中编写自己的装饰器?

Posted

技术标签:

【中文标题】如何在 Django 中编写自己的装饰器?【英文标题】:How can I write my own decorator in Django? 【发布时间】:2018-12-10 04:09:39 【问题描述】:

我的models.py文件如下:

from django.contrib.auth.models import User

class Shopkeeper(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
    # ...


class Customer(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
# ...

我有一些视图,只有客户在登录后才能访问,但店主不能。反之亦然。 我怎样才能为这样的任务编写装饰器?

【问题讨论】:

【参考方案1】:

装饰器并没有什么神奇之处,它是一个将要装饰的函数(或类)作为输入并对其进行一些更改的函数。如果我们查看login_required decorator [GitHub],我们会看到:

def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
    """
    Decorator for views that checks that the user is logged in, redirecting
    to the log-in page if necessary.
    """
    actual_decorator = user_passes_test(
        lambda u: u.is_authenticated,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    return actual_decorator

因此,我们实际上可以简单地制作user_passes_test 装饰器的特殊情况:

from django.contrib.auth.decorators import user_passes_test

def shopkeeper_required(function=None):
    def is_shopkeeper(u):
        return Shopkeeper.objects.filter(user=u).exists()
    actual_decorator = user_passes_test(is_shopkeeper)
    if function:
        return actual_decorator(function)
    else:
        return actual_decorator

def customer_required(function=None):
    def is_customer(u):
        return Customer.objects.filter(user=u).exists()
    actual_decorator = user_passes_test(is_customer)
    if function:
        return actual_decorator(function)
    else:
        return actual_decorator

然后您可以将其实现为:

@login_required
@shopkeeper_required
def some_shopkeeper_view(request):
    # ...
    pass

@login_required
@customer_required
def some_customer_view(request):
    # ...
    pass

请注意,@shopkeeper_required 确实确实强制用户登录,尽管在许多情况下确实如此。

编辑

我们可以将其与@login_required 合并(通过添加一个参数作为开关来打开或关闭此行为,默认情况下为打开),例如:

from django.contrib.auth.decorators import user_passes_test

def shopkeeper_required(function=None, login_required=True, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
    def is_shopkeeper(u):
        if login_required and not u.is_authenticated:
            return False
        return Shopkeeper.objects.filter(user=u).exists()
    actual_decorator = user_passes_test(
        is_shopkeeper,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    else:
        return actual_decorator

def customer_required(function=None, login_required=True, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
    def is_customer(u):
        if login_required and not u.is_authenticated:
            return False
        return Customer.objects.filter(user=u).exists()
    actual_decorator = user_passes_test(
        is_customer,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    else:
        return actual_decorator

【讨论】:

如何将@login_required 与此自定义装饰器合并? actual_decorator = user_passes_test(u.is_authenticated and is_shopkeeper) 会起作用吗? 不!因为那时没有uu 存在是因为lambda 表达式,所以应该是user_passes_test(lambda u: u.is_authenticated and is_shopkeeper(u)) 'User' object has no attribute 'login_required' 当客户已经登录并且通过了对店主可以访问的视图的请求时,您编辑的代码会给出上面给出的错误。 @AbidMehmood:嗨,我打错了。现在已修复:)。

以上是关于如何在 Django 中编写自己的装饰器?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 user_passes_test 装饰器可调用函数中传递 Django 请求对象

如何从django中具有@property装饰器的模型类序列化函数

如何在 Python 中编写有效的类装饰器?

使用 djangorestframework 装饰器时如何调试 django ajax 函数?

Django中基于类的视图上带有参数的函数装饰器

Django 的 @login_required 装饰器在人们未注册时将他们重定向到 /accounts/login。如何更改此网址?