Wagtail API按author_name与author_status过滤失败求助
我用Wagtail搭建了API接口127.0.0.1:8000/api/v2/stories,返回结果包含blog_authors嵌套数据,里面有author_name和author_status字段。尝试用?author_name=Test、?blog_authors__author_name=Test、?author__name=Test这些URL参数过滤时,都返回错误:"query parameter is not an operation or a recognised field: author_name"。我已经把相关字段加入known_query_parameters,也试过DjangoFilterBackend,但问题还是没解决,求问怎么实现按author_name和author_status过滤API数据。
api.py
class ProdPagesAPIViewSet(BaseAPIViewSet): renderer_classes = [JSONRenderer] pagination_class = CustomPagination filter_backends = [FieldsFilter, ChildOfFilter, AncestorOfFilter, DescendantOfFilter, OrderingFilter, TranslationOfFilter, LocaleFilter, SearchFilter,] known_query_parameters = frozenset( [ "limit", "offset", "fields", "order", "search", "search_operator", # Used by jQuery for cache-busting. See #1671 "_", # Required by BrowsableAPIRenderer "format", "page","author_name", ] ) meta_fields = ["type","seo_title","search_description","first_published_at"] body_fields = ["id","type","seo_title","search_description","first_published_at","title"] listing_default_fields = ["type","seo_title","search_description","first_published_at","id","title","alternative_title","news_slug","blog_image","video_thumbnail","categories","blog_authors","excerpt","content","content2","tags","story_type"] nested_default_fields = [] def get_queryset(self): return super().get_queryset().filter(story_type='Story').order_by('-first_published_at') name = "stories" model = AddStory api_router.register_endpoint("stories", ProdPagesAPIViewSet)
models.py(blog_authors相关)
class BlogAuthorsOrderable(Orderable): """This allows us to select one or more blog authors from Snippets.""" page = ParentalKey("blog.AddStory", related_name="blog_authors") author = models.ForeignKey( "blog.BlogAuthor", on_delete=models.CASCADE, ) panels = [ # Use a SnippetChooserPanel because blog.BlogAuthor is registered as a snippet FieldPanel("author"), ] @property def author_name(self): return self.author.name @property def author_website(self): return self.author.website @property def author_image(self): return self.author.image @property def author_status(self): return self.author.status api_fields = [ APIField("author_name"), APIField("author_website"), APIField("author_status"), # This is using a custom django rest framework serializer APIField("author_image", serializer=ImageSerializedField()), # The below APIField is using a Wagtail-built DRF Serializer that supports # custom image rendition sizes APIField( "image", serializer=ImageRenditionField("fill-200x250|format-webp", source="author_image" ) ), ] @register_snippet class BlogAuthor(models.Model): """Blog author for snippets.""" name = models.CharField(max_length=100) status = models.BooleanField(default=False, verbose_name="Special Author") website = models.URLField(blank=True, null=True) image = models.ForeignKey( "wagtailimages.Image", on_delete=models.SET_NULL, null=True, blank=False, related_name="+", ) panels = [ MultiFieldPanel( [ FieldPanel("name"), # Use an ImageChooserPanel because wagtailimages.Image (image property) # is a ForeignKey to an Image FieldPanel("image"), FieldPanel("status"), ], heading="Name, Image and Status", ), MultiFieldPanel( [ FieldPanel("website"), ], heading="Links" ) ] def __str__(self): """String repr of this class.""" return self.name class Meta: # noqa verbose_name = "Blog Author" verbose_name_plural = "Blog Authors"
AddStory模型中调用blog_authors
class AddStory(Page): """Blog detail page.""" content_panels = Page.content_panels + [ MultiFieldPanel( [ InlinePanel("blog_authors", label="Author", min_num=1, max_num=4) ], heading="Author(s)" ), ] api_fields = [ APIField("blog_authors"), ]
解决方案
1. 自定义过滤后端处理嵌套字段查询
Wagtail默认的FieldsFilter不支持跨关联模型的嵌套过滤,需要自定义过滤后端来解析这类参数:
from wagtail.api.v2.filters import FieldsFilter class NestedFieldsFilter(FieldsFilter): def filter_queryset(self, request, queryset, view): for param_name, value in request.query_params.items(): # 处理blog_authors__author_name和blog_authors__author_status参数 if param_name.startswith('blog_authors__author_'): # 转换为Django ORM可识别的关联查询路径 orm_param = param_name.replace('blog_authors__author_', 'blog_authors__author__') queryset = queryset.filter(**{orm_param: value}) return super().filter_queryset(request, queryset, view)
2. 更新API视图集配置
- 替换原有的
FieldsFilter为自定义的NestedFieldsFilter - 在
known_query_parameters中添加允许的过滤参数
修改后的api.py关键部分:
# 导入自定义过滤后端(根据实际路径调整) from .filters import NestedFieldsFilter class ProdPagesAPIViewSet(BaseAPIViewSet): renderer_classes = [JSONRenderer] pagination_class = CustomPagination filter_backends = [NestedFieldsFilter, ChildOfFilter, AncestorOfFilter, DescendantOfFilter, OrderingFilter, TranslationOfFilter, LocaleFilter, SearchFilter,] known_query_parameters = frozenset( [ "limit", "offset", "fields", "order", "search", "search_operator", "_", "format", "page", "blog_authors__author_name", "blog_authors__author_status", ] ) # 其余代码保持不变
3. 正确调用API进行过滤
现在可以使用以下参数实现过滤:
- 按作者名称过滤:
127.0.0.1:8000/api/v2/stories?blog_authors__author_name=Test - 按作者特殊状态过滤:
127.0.0.1:8000/api/v2/stories?blog_authors__author_status=True
核心原理
自定义过滤后端将API传入的blog_authors__author_name参数转换为Django ORM能识别的blog_authors__author__name——因为BlogAuthorsOrderable模型中的author是关联BlogAuthor的外键,name是BlogAuthor的原生字段,这样ORM就能正确关联查询。同时必须将这些参数加入known_query_parameters,否则Wagtail会判定为非法参数返回错误。
内容的提问来源于stack exchange,提问作者Mohammad Fathi Rahman

