Django Admin - 特定用户(管理员)内容

Posted

技术标签:

【中文标题】Django Admin - 特定用户(管理员)内容【英文标题】:Django Admin - Specific user (admin) content 【发布时间】:2011-09-12 18:00:21 【问题描述】:

我开始组织一个新项目,假设我将有一些模型,例如 productscatalogs

我将允许我的客户(不是访客,只有特定客户)登录 Django 管理站点来创建、编辑和删除他们自己的目录。

假设我创建了一个名为 “Shop” 的模型,创建每个商店(名称、地址、徽标、联系信息等)并创建一个绑定到该商店的管理员用户。

现在我希望这位新管理员(不是站点管理员,而是商店管理员——可能是用户组)查看和编辑仅与他的商店链接的目录强>.

这可能吗?

我应该在 Django Admin 中执行此操作,还是应该创建一个新的“商店管理员”应用程序?

【问题讨论】:

【参考方案1】:

首先,警告性警告:Django 管理员设计理念是任何有权访问管理员 (is_staff==True) 的用户都是受信任的 用户,例如一名员工,因此甚至可以访问管理员的“员工”称号。虽然您可以自定义管理员以限制区域,但允许您组织内的任何人访问您的管理员被认为是有风险的,并且 Django 在这一点上不保证任何类型的安全性。

现在,如果您仍想继续,您可以通过不将这些权限分配给用户来立即限制除商店以外的大多数内容。您必须授予所有店主编辑他们需要访问的任何商店模型的权限,但其他所有内容都应从他们的权限列表中删除。

然后,对于需要仅限所有者查看的每个模型,您需要添加一个字段来存储“所有者”或用户允许访问它。您可以使用ModelAdmin 上的save_model 方法来执行此操作,该方法可以访问请求对象:

class MyModelAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        obj.user = request.user
        super(MyModelAdmin, self).save_model(request, obj, form, change)

然后您还需要将 ModelAdmin 的查询集限制为仅由当前用户拥有的那些项目:

class MyModelAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super(MyModelAdmin, self).get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(owner=request.user)

但是,这只会限制列出的内容,用户仍然可以使用 URL 来访问他们无权访问的其他对象,因此您需要覆盖 ModelAdmin 的每个易受攻击的视图以重定向,如果用户不是所有者:

from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse

class MyModelAdmin(admin.ModelAdmin):
    def change_view(self, request, object_id, form_url='', extra_context=None):
        if not self.queryset(request).filter(id=object_id).exists():
            return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))

        return super(MyModelAdmin, self).change_view(request, object_id, form_url, extra_context)

    def delete_view(self, request, object_id, extra_context=None):
        if not self.queryset(request).filter(id=object_id).exists():
            return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))

        return super(MyModelAdmin, self).delete_view(request, object_id, extra_context)

    def history_view(self, request, object_id, extra_context=None):
        if not self.queryset(request).filter(id=object_id).exists():
            return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))

        return super(MyModelAdmin, self).history_view(request, object_id, extra_context)

2012 年 6 月 5 日更新

感谢@christophe31 指出由于ModelAdmin 的查询集已经受到用户的限制,您可以在更改、删除和历史视图中使用self.queryset()。这很好地抽象了模型类名,使代码不那么脆弱。我也改为使用filterexists,而不是try...exceptget。这种方式更加精简,而且实际上也产生了更简单的查询。

【讨论】:

我查看了 django 管理代码,它使用 queryset 方法的 queryset 对象,所以你的代码的第二部分不是强制性的。 (我看过 django 1.4 源代码) @christophe31:这是在 Django 1.3 之前编写的,因此可能发生了一些变化。但是,我不知道有什么可以否定仍然限制更改、删除和历史视图的必要性。请提供一些细节。 用于更改和删除视图,当他们获得他们使用的对象时:“self.queryset().get(pk=pk)”所以如果用户无法查看该项目,它将返回错误. queryset 方法在较新版本的 Django(idk 哪个版本)上更改为 get_queryset @ChrisPratt 感谢您的警告。这仍然适用于 django 2.0+ 吗?如果是这样,将is_staff=True 设置为“非员工”然后限制权限的最佳选择是什么?是否可以接受建议的第二个管理站点,例如here?【参考方案2】:

对不起,我知道已经晚了,但也许它会帮助其他人。我猜django-permission 应用程序可以帮助实现这一目的。

【讨论】:

【参考方案3】:

我只是在这里发布这个,因为顶部评论不再是最新的答案。我正在使用 Django 1.9,我不确定这是什么时候发生的。

例如,您有不同的场地和与每个场地关联的不同用户,模型将如下所示:

class Venue(models.Model):
    user = models.ForeignKey(User)
    venue_name = models.CharField(max_length=255)
    area = models.CharField(max_length=255)

现在,如果用户允许通过 django 管理面板登录,他的员工状态必须为真。

admin.py 看起来像:

class FilterUserAdmin(admin.ModelAdmin): 
    def save_model(self, request, obj, form, change):
        if getattr(obj, 'user', None) is None:  
            obj.user = request.user
        obj.save()
    def get_queryset(self, request):
        qs = super(FilterUserAdmin, self).queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(user=request.user)
    def has_change_permission(self, request, obj=None):
        if not obj:
            return True 
        return obj.user == request.user or request.user.is_superuser


@admin.register(Venue)
class VenueAdmin(admin.ModelAdmin):
    pass

函数名称已从 queryset 更改为 get_queryset。

编辑:我想扩展我的答案。还有另一种方法可以在不使用 queryset 函数的情况下返回过滤后的对象。我确实想强调一下,我不知道这种方法效率更高还是效率更低。

get_queryset 方法的另一种实现如下:

def get_queryset(self, request):
    if request.user.is_superuser:
        return Venue.objects.all()
    else:
        return Venue.objects.filter(user=request.user)

此外,如果关系更深,我们还可以过滤内容。

class VenueDetails(models.Model):
    venue = models.ForeignKey(Venue)
    details = models.TextField()

现在,如果我想过滤这个有 Venue 作为外键但没有 User 的模型,我的查询更改如下:

def get_queryset(self, request):
    if request.user.is_superuser:
        return VenueDetails.objects.all()
    else:
        return VenueDetails.objects.filter(venue__user=request.user)

Django ORM 允许我们访问不同类型的关系,这些关系可以通过 '__' 访问任意深度

Here's 上述官方文档的链接。

【讨论】:

感谢您花时间更新此内容。这对我来说非常有用,我可以确认它适用于 Django 1.9(至少)。 很好的答案chatuur,一条评论。您不想使用备用 get_queryset 函数,因为它会在返回结果时忽略任何管理员过滤器。最好在您覆盖的函数中调用 super,这样 django 其他地方提供的任何功能(如过滤器)都不会丢失。【参考方案4】:

我认为 RelatedOnlyFieldListFilter 应该对您有所帮助。 这里是 Django Doc 的链接:RelatedOnlyFieldListFilter

list_filter 可以是:一个元组,其中第一个元素是一个字段 name 和第二个元素是一个类继承自 django.contrib.admin.FieldListFilter,例如:

class PersonAdmin(admin.ModelAdmin):
    list_filter = (
        ('is_staff', admin.BooleanFieldListFilter),
    )

您可以将相关模型的选择限制到对象 使用 RelatedOnlyFieldListFilter 参与该关系:(Vous pouvez limiter les choix d'un modèle lié aux objets concernés par la 关系 en utilisant RelatedOnlyFieldListFilter:)

  class BookAdmin(admin.ModelAdmin):
      list_filter = (
           ('author', admin.RelatedOnlyFieldListFilter),
      ) 

假设 author 是 User 模型的 ForeignKey,这会将 list_filter 的选择限制为写过书的用户 而不是列出所有用户。 (En supposant que author est une clé ForeignKey vers un modèle User, cela va limiter les choix de list_filter aux utilisateurs qui ont écrit un livre au lieu d'énumérer tous les utilisateurs。)

【讨论】:

以上是关于Django Admin - 特定用户(管理员)内容的主要内容,如果未能解决你的问题,请参考以下文章

Django-admin实现管理员或特定组或人员可访问数据

django之admin站点

Django Admin显示/隐藏字段如果在下拉菜单中选择了特定值

django基础知识之后台管理Admin站点:

django-admin后台管理

Django admin:代理模型的用户权限