Django 编辑页面过滤

作者 耿嘉豪 日期 2019-05-26 阅读量
Django 编辑页面过滤

Django 编辑页面过滤

在 Django Admin 中遇到了一个根据用户过滤的问题。

如果只需要在列表页中对当前用户的内容进行过滤, 只需要重写 get_queryset 方法即可。

但是上面的方法在编辑页面并没有实现过滤, 举个例子, 当用户要发一篇文章时, 需要选择文章的分类, 分类应该只显示该用户自己创建的分类, 而在文章编辑页面, 分类列表会显示当前数据库中的所有分类。

使用 formfield_for_foreignkey/manytomany

官方文档中, 提供了 formfield_for_foreignkeyformfield_for_manytomany 方法, 分别针对一对多和多对多的关系, 示例如下:

class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

class MyModelAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "cars":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super().formfield_for_manytomany(db_field, request, **kwargs)

示例中, 通过判断当前字段的 name 属性, 对相应的字段进行过滤, 实现了对用户的内容过滤。

更优雅的实现方案

官方给出的示例解决了问题, 但是这样的话得判断 db_field.name 再去找相应的 model, 需要对特定的字段名称进行判断, 没有普适性, 还有如果需要过滤的字段有很多呢?那就需要更多的条件判断, 有没有更优雅的实现方法, 把方法抽象出来直接找到 model 让所有 admin 都适用?

这个问题困扰了我一个, 文档和 Google 上面都搜不到, 所有的文章和回答给出的代码都是官方文档的例子, 最终是通过阅读源码解决的。

通过 db_field.remote_field.model 就能取到 model, 这样就抽象出来放到基类了。代码如下:

class MyModelAdmin(admin.ModelAdmin):
...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
kwargs['queryset'] = db_field.remote_field.model.objects.filter(owner=request.user)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def formfield_for_manytomany(self, db_field, request, **kwargs):
kwargs['queryset'] = db_field.remote_field.models.objects.filter(owner=request.user)
return super().formfield_for_manytomany(db_field, request, **kwargs)

这样的话, 只需要把 models 继承这个基类就解决问题了。

改进

上面的代码还存在两个问题:

  1. 针对 models 中没有用户这一字段的情况没有进行处理

  2. 不能直接依赖 Django 暴露出的 api, 应该自己抽象一次

对上述代码进行改进, 最终结果如下:

class MyModelAdmin(admin.ModelAdmin):
...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
qs = self.queryset_property(db_field, request)
if qs is not None:
kwargs['queryset'] = qs
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def formfield_for_manytomany(self, db_field, request, **kwargs):
qs = self.queryset_property(db_field, request)
if qs is not None:
kwargs['queryset'] = qs
return super().formfield_for_manytomany(db_field, request, **kwargs)

@staticmethod
def queryset_property(db_field, request):
obj = db_field.remote_field.model
fields = obj._meta.fields
field_names = [field.name for field in fields]
if 'owner' in field_names:
return obj.objects.filter(owner=request.user)
return None