@@ -73,6 +73,106 @@ def _patched_json_default(self, o, _orig=DjangoJSONEncoder.default):
7373 except ImportError :
7474 pass
7575
76+ # Patch Wagtail API v2 page-ID filters to accept 24-char ObjectId hex
77+ # strings in addition to integers. All four filters call int(value)
78+ # and raise BadRequestError on ValueError, rejecting ObjectId strings.
79+ try :
80+ from bson import ObjectId
81+ from wagtail .api .v2 import filters as _api_filters
82+ from wagtail .api .v2 .utils import BadRequestError as _BadReq
83+ from wagtail .models import Page as _Page
84+
85+ def _parse_page_id (value ):
86+ try :
87+ n = int (value )
88+ if n < 0 :
89+ raise ValueError ()
90+ return n
91+ except ValueError :
92+ return ObjectId (value )
93+
94+ def _child_of_filter (self , request , queryset , view ):
95+ if "child_of" not in request .GET :
96+ return queryset
97+ value = request .GET ["child_of" ]
98+ if value == "root" :
99+ parent_page = view .get_root_page ()
100+ else :
101+ try :
102+ parent_page = view .get_base_queryset ().get (
103+ id = _parse_page_id (value )
104+ )
105+ except _Page .DoesNotExist as e :
106+ raise _BadReq ("parent page doesn't exist" ) from e
107+ except Exception as e :
108+ raise _BadReq ("child_of must be a positive integer" ) from e
109+ queryset = queryset .child_of (parent_page )
110+ queryset ._filtered_by_child_of = parent_page
111+ return queryset
112+
113+ def _ancestor_of_filter (self , request , queryset , view ):
114+ if "ancestor_of" not in request .GET :
115+ return queryset
116+ value = request .GET ["ancestor_of" ]
117+ try :
118+ descendant_page = view .get_base_queryset ().get (
119+ id = _parse_page_id (value )
120+ )
121+ except _Page .DoesNotExist as e :
122+ raise _BadReq ("descendant page doesn't exist" ) from e
123+ except Exception as e :
124+ raise _BadReq ("ancestor_of must be a positive integer" ) from e
125+ return queryset .ancestor_of (descendant_page )
126+
127+ def _descendant_of_filter (self , request , queryset , view ):
128+ if "descendant_of" not in request .GET :
129+ return queryset
130+ if hasattr (queryset , "_filtered_by_child_of" ):
131+ raise _BadReq (
132+ "filtering by descendant_of with child_of is not supported"
133+ )
134+ value = request .GET ["descendant_of" ]
135+ if value == "root" :
136+ parent_page = view .get_root_page ()
137+ else :
138+ try :
139+ parent_page = view .get_base_queryset ().get (
140+ id = _parse_page_id (value )
141+ )
142+ except _Page .DoesNotExist as e :
143+ raise _BadReq ("ancestor page doesn't exist" ) from e
144+ except Exception as e :
145+ raise _BadReq ("descendant_of must be a positive integer" ) from e
146+ return queryset .descendant_of (parent_page )
147+
148+ def _translation_of_filter (self , request , queryset , view ):
149+ if "translation_of" not in request .GET :
150+ return queryset
151+ value = request .GET ["translation_of" ]
152+ if value == "root" :
153+ page = view .get_root_page ()
154+ else :
155+ try :
156+ page = view .get_base_queryset ().get (id = _parse_page_id (value ))
157+ except _Page .DoesNotExist as e :
158+ raise _BadReq ("translation_of page doesn't exist" ) from e
159+ except Exception as e :
160+ raise _BadReq (
161+ "translation_of must be a positive integer"
162+ ) from e
163+ saved = getattr (queryset , "_filtered_by_child_of" , None )
164+ queryset = queryset .translation_of (page )
165+ if saved :
166+ queryset ._filtered_by_child_of = saved
167+ return queryset
168+
169+ _api_filters .ChildOfFilter .filter_queryset = _child_of_filter
170+ _api_filters .AncestorOfFilter .filter_queryset = _ancestor_of_filter
171+ _api_filters .DescendantOfFilter .filter_queryset = _descendant_of_filter
172+ _api_filters .TranslationOfFilter .filter_queryset = _translation_of_filter
173+ except ImportError :
174+ pass
175+
76176 # Patch ModelViewSet.pk_path_converter so viewsets whose model uses
77177 # ObjectIdAutoField get "object_id" URL converter instead of "int".
78178 # ObjectIdAutoField inherits AutoField → IntegerField, so Wagtail's
0 commit comments