Skip to content

Commit

Permalink
Add an initial resync_versions API to v3 (#11484)
Browse files Browse the repository at this point in the history
* Add an initial resync_versions API to v3

This will be used in the frontend,
but also available as an API.

Mostly curious if this is a good approach,
and I can get some tests together for it.

Refs #6090

* Add test

* Fix test

* Add default serializer

* Add to serializer

* Update readthedocs/api/v3/views.py

Co-authored-by: Santos Gallegos <stsewd@proton.me>

* Update sync_versions endpoint

* Fix tests

* Add docs

* Add missing test

---------

Co-authored-by: Santos Gallegos <stsewd@proton.me>
  • Loading branch information
ericholscher and stsewd committed Jul 29, 2024
1 parent d281701 commit 6528d5d
Show file tree
Hide file tree
Showing 15 changed files with 110 additions and 0 deletions.
33 changes: 33 additions & 0 deletions docs/user/api/v3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,39 @@ Project update
:statuscode 204: Updated successfully


Project versions sync
+++++++++++++++++++++

.. http:post:: /api/v3/projects/(string:project_slug)/sync-versions/
Trigger a background task to sync the versions of the project.

**Example request**:

.. tabs::

.. code-tab:: bash

$ curl \
-X POST \
-H "Authorization: Token <token>" \
https://readthedocs.org/api/v3/projects/pip/sync-versions/

.. code-tab:: python

import requests
URL = 'https://readthedocs.org/api/v3/projects/pip/sync-versions/'
TOKEN = '<token>'
HEADERS = {'Authorization': f'token {TOKEN}'}
response = requests.post(
URL,
headers=HEADERS,
)
print(response.json())

:statuscode 202: Task created successfully
:statuscode 400: Bad request, task not created

Versions
~~~~~~~~

Expand Down
10 changes: 10 additions & 0 deletions readthedocs/api/v3/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ class ProjectLinksSerializer(BaseLinksSerializer):
superproject = serializers.SerializerMethodField()
translations = serializers.SerializerMethodField()
notifications = serializers.SerializerMethodField()
sync_versions = serializers.SerializerMethodField()

def get__self(self, obj):
path = reverse("projects-detail", kwargs={"project_slug": obj.slug})
Expand Down Expand Up @@ -558,6 +559,15 @@ def get_superproject(self, obj):
)
return self._absolute_url(path)

def get_sync_versions(self, obj):
path = reverse(
"projects-sync-versions",
kwargs={
"project_slug": obj.slug,
},
)
return self._absolute_url(path)

def get_translations(self, obj):
path = reverse(
"projects-translations-list",
Expand Down
1 change: 1 addition & 0 deletions readthedocs/api/v3/tests/responses/projects-detail.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/project/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/project/translations/",
"versions": "https://readthedocs.org/api/v3/projects/project/versions/"
},
Expand Down
1 change: 1 addition & 0 deletions readthedocs/api/v3/tests/responses/projects-list.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/project/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/project/translations/"
},
"privacy_level": "public",
Expand Down
1 change: 1 addition & 0 deletions readthedocs/api/v3/tests/responses/projects-list_POST.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"redirects": "https://readthedocs.org/api/v3/projects/test-project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/test-project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/test-project/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/test-project/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/test-project/translations/",
"versions": "https://readthedocs.org/api/v3/projects/test-project/versions/"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"redirects": "https://readthedocs.org/api/v3/projects/subproject/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/subproject/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/subproject/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/subproject/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/subproject/translations/",
"versions": "https://readthedocs.org/api/v3/projects/subproject/versions/"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"redirects": "https://readthedocs.org/api/v3/projects/subproject/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/subproject/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/subproject/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/subproject/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/subproject/translations/",
"versions": "https://readthedocs.org/api/v3/projects/subproject/versions/"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"redirects": "https://readthedocs.org/api/v3/projects/new-project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/new-project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/new-project/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/new-project/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/new-project/translations/",
"versions": "https://readthedocs.org/api/v3/projects/new-project/versions/"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/project/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/project/translations/",
"versions": "https://readthedocs.org/api/v3/projects/project/versions/"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"triggered": true}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"notifications": "https://readthedocs.org/api/v3/projects/project/notifications/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/project/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/project/translations/",
"versions": "https://readthedocs.org/api/v3/projects/project/versions/"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
"sync_versions": "https://readthedocs.org/api/v3/projects/project/sync-versions/",
"translations": "https://readthedocs.org/api/v3/projects/project/translations/",
"versions": "https://readthedocs.org/api/v3/projects/project/versions/"
},
Expand Down
31 changes: 31 additions & 0 deletions readthedocs/api/v3/tests/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,37 @@ def test_projects_superproject(self):
self._get_response_dict("projects-superproject"),
)

def test_projects_sync_versions(self):
# Ensure a default version exists to sync
self.project.update_latest_version()

url = reverse(
"projects-sync-versions",
kwargs={
"project_slug": self.project.slug,
},
)

self.client.logout()
response = self.client.get(url)
self.assertEqual(response.status_code, 401)
response = self.client.post(url)
self.assertEqual(response.status_code, 401)

# Test with a user that is not the owner
self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.others_token.key}")
response = self.client.post(url)
self.assertEqual(response.status_code, 403)

self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.token.key}")
response = self.client.post(url)
self.assertEqual(response.status_code, 202)

self.assertDictEqual(
response.json(),
self._get_response_dict("projects-sync-versions"),
)

def test_others_projects_builds_list(self):
self.client.credentials(HTTP_AUTHORIZATION=f"Token {self.token.key}")
response = self.client.get(
Expand Down
1 change: 1 addition & 0 deletions readthedocs/api/v3/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# allows /api/v3/projects/
# allows /api/v3/projects/pip/
# allows /api/v3/projects/pip/superproject/
# allows /api/v3/projects/pip/sync-versions/
projects = router.register(
r"projects",
ProjectsViewSet,
Expand Down
25 changes: 25 additions & 0 deletions readthedocs/api/v3/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from readthedocs.builds.models import Build, Version
from readthedocs.core.utils import trigger_build
from readthedocs.core.utils.extend import SettingsOverrideObject
from readthedocs.core.views.hooks import trigger_sync_versions
from readthedocs.notifications.models import Notification
from readthedocs.oauth.models import (
RemoteOrganization,
Expand Down Expand Up @@ -161,6 +162,9 @@ def get_serializer_class(self):
if self.action in ("update", "partial_update"):
return ProjectUpdateSerializer

# Default serializer so that sync_versions works with the BrowseableAPI
return ProjectSerializer

def get_queryset(self):
# Allow hitting ``/api/v3/projects/`` to list their own projects
if self.basename == "projects" and self.action == "list":
Expand Down Expand Up @@ -218,6 +222,27 @@ def superproject(self, request, project_slug):
except Exception:
return Response(status=status.HTTP_404_NOT_FOUND)

@action(detail=True, methods=["post"], url_path="sync-versions")
def sync_versions(self, request, project_slug):
"""
Kick off a task to sync versions for a project.
POST to this endpoint to trigger a task that syncs versions for the project.
This will be used in a button in the frontend,
but also can be used to trigger a sync from the API.
"""
project = self.get_object()
triggered = trigger_sync_versions(project)
data = {}
if triggered:
data.update({"triggered": True})
code = status.HTTP_202_ACCEPTED
else:
data.update({"triggered": False})
code = status.HTTP_400_BAD_REQUEST
return Response(data=data, status=code)


class ProjectsViewSet(SettingsOverrideObject):
_default_class = ProjectsViewSetBase
Expand Down

0 comments on commit 6528d5d

Please sign in to comment.