diff --git a/docs/user/api/v3.rst b/docs/user/api/v3.rst index 39f82a07e00..942d891ef72 100644 --- a/docs/user/api/v3.rst +++ b/docs/user/api/v3.rst @@ -186,6 +186,8 @@ Projects list }] } + :query string name: return projects with matching name + :query string slug: return projects with matching slug :query string language: language code as ``en``, ``es``, ``ru``, etc. :query string programming_language: programming language code as ``py``, ``js``, etc. diff --git a/readthedocs/api/v3/filters.py b/readthedocs/api/v3/filters.py index 1f210b486ec..d54a8116f9b 100644 --- a/readthedocs/api/v3/filters.py +++ b/readthedocs/api/v3/filters.py @@ -8,11 +8,20 @@ class ProjectFilter(filters.FilterSet): + # TODO this is copying the patterns from other filter sets, where the fields + # are all ``icontains`` lookups by default. We discussed reversing this + # pattern in the future though, see: + # https://github.com/readthedocs/readthedocs.org/issues/9862 + name = filters.CharFilter(lookup_expr="icontains") + slug = filters.CharFilter(lookup_expr="icontains") + class Meta: model = Project fields = [ - 'language', - 'programming_language', + "name", + "slug", + "language", + "programming_language", ] diff --git a/readthedocs/api/v3/tests/responses/projects-list-empty.json b/readthedocs/api/v3/tests/responses/projects-list-empty.json new file mode 100644 index 00000000000..a07f3f09fac --- /dev/null +++ b/readthedocs/api/v3/tests/responses/projects-list-empty.json @@ -0,0 +1,6 @@ +{ + "count": 0, + "next": null, + "previous": null, + "results": [] +} diff --git a/readthedocs/api/v3/tests/test_projects.py b/readthedocs/api/v3/tests/test_projects.py index 9bea87fc407..5c35ef5c0f6 100644 --- a/readthedocs/api/v3/tests/test_projects.py +++ b/readthedocs/api/v3/tests/test_projects.py @@ -23,6 +23,48 @@ def test_projects_list(self): self._get_response_dict('projects-list'), ) + def test_projects_list_filter_full_hit(self): + self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.token.key}") + response = self.client.get( + reverse("projects-list"), + data={ + "name": self.project.name, + }, + ) + self.assertEqual(response.status_code, 200) + self.assertDictEqual( + response.json(), + self._get_response_dict("projects-list"), + ) + + def test_projects_list_filter_partial_hit(self): + self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.token.key}") + response = self.client.get( + reverse("projects-list"), + data={ + "name": self.project.name[0:3], + }, + ) + self.assertEqual(response.status_code, 200) + self.assertDictEqual( + response.json(), + self._get_response_dict("projects-list"), + ) + + def test_projects_list_filter_miss(self): + self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.token.key}") + response = self.client.get( + reverse("projects-list"), + data={ + "name": "63dadecd5323d789cafe09f01cda85fd", + }, + ) + self.assertEqual(response.status_code, 200) + self.assertDictEqual( + response.json(), + self._get_response_dict("projects-list-empty"), + ) + def test_own_projects_detail(self): self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') response = self.client.get(