Django Admin - 特定用户(admin)内容

Thi*_*lem 48 django permissions admin django-admin

我开始组织一个新项目,让我说我会有几个模型,如产品目录.

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

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

现在我想要这个新的管理员(谁不是网站管理员,但是商店管理员 - 可能是用户组)只能查看和编辑与他的商店链接的目录.

那可能吗?

我应该在Django Admin中执行此操作还是应该创建一个新的"shop admin"应用程序?

Chr*_*att 59

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

现在,如果您仍想继续,您可以通过简单地不将这些权限分配给用户来限制除了商店之外的所有商品.您必须让所有店主有权编辑他们需要访问的任何商店模型,但其他所有商品都应该从他们的权限列表中删除.

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

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)
Run Code Online (Sandbox Code Playgroud)

然后,您还需要将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)
Run Code Online (Sandbox Code Playgroud)

但是,这只会限制列出的内容,用户仍然可以使用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)
Run Code Online (Sandbox Code Playgroud)

更新06/05/12

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

  • `queryset`方法在较新版本的Django(idk哪个版本)上更改为`get_queryset` (4认同)
  • 对于更改和删除视图,当他们获取他们使用的对象时:"self.queryset().get(pk = pk)"因此如果用户无法查看该项,它将返回错误. (2认同)

cha*_*uur 12

我只是在这里发帖,因为最高评论不再是最新的答案.我正在使用Django 1.9,我不确定这是什么时候发生了变化.

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

class Venue(models.Model):
    user = models.ForeignKey(User)
    venue_name = models.CharField(max_length=255)
    area = models.CharField(max_length=255)
Run Code Online (Sandbox Code Playgroud)

现在,如果用户允许通过django管理面板登录,则该用户的员工状态必须为true.

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
Run Code Online (Sandbox Code Playgroud)

函数名称已从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)
Run Code Online (Sandbox Code Playgroud)

此外,我们还可以过滤内容,使关系更加深入.

class VenueDetails(models.Model):
    venue = models.ForeignKey(Venue)
    details = models.TextField()
Run Code Online (Sandbox Code Playgroud)

现在,如果我想过滤这个以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)
Run Code Online (Sandbox Code Playgroud)

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

以下是上述官方文档的链接.