Django 编辑页面过滤
在 Django Admin 中遇到了一个根据用户过滤的问题。
如果只需要在列表页中对当前用户的内容进行过滤, 只需要重写 get_queryset 方法即可。
但是上面的方法在编辑页面并没有实现过滤, 举个例子, 当用户要发一篇文章时, 需要选择文章的分类, 分类应该只显示该用户自己创建的分类, 而在文章编辑页面, 分类列表会显示当前数据库中的所有分类。
在官方文档中, 提供了 formfield_for_foreignkey 和 formfield_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 继承这个基类就解决问题了。
改进
上面的代码还存在两个问题:
针对 models 中没有用户这一字段的情况没有进行处理
不能直接依赖 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
|