Skip to content

Add List API Endpoint for RemoteRepository and RemoteOrganization #7510

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions docs/api/v3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,149 @@ Organization projects list
}


Remote Organizations
~~~~~~~~~~~~~~~~~~~~

Remote Organizations are the importable organizations connected via
``GitHub``, ``GitLab`` and ``BitBucket``.


Remote Organization listing
+++++++++++++++++++++++++++


.. http:get:: /api/v3/remote/organizations/

Retrieve a list of all Remote Organizations for the authenticated user.

**Example request**:

.. tabs::

.. code-tab:: bash

$ curl -H "Authorization: Token <token>" https://readthedocs.org/api/v3/remote/organizations/

.. code-tab:: python

import requests
URL = 'https://readthedocs.org/api/v3/remote/organizations/'
TOKEN = '<token>'
HEADERS = {'Authorization': f'token {TOKEN}'}
response = requests.get(URL, headers=HEADERS)
print(response.json())

**Example response**:

.. sourcecode:: json

{
"count": 20,
"next": "api/v3/remote/organizations/?limit=10&offset=10",
"previous": null,
"results": [
{
"avatar_url": "https://avatars.githubusercontent.com/u/12345?v=4",
"created": "2019-04-29T10:00:00Z",
"modified": "2019-04-29T12:00:00Z",
"name": "Organization Name",
"pk": 1,
"slug": "organization",
"url": "https://github.com/organization",
"vcs_provider": "github"
}
]
}


The ``results`` in response is an array of remote organizations data.

:query string name: return remote organizations with matching name
:query string vcs_provider: return remote organizations for specific vcs provider (``github``, ``gitlab`` or ``bitbucket``)

:requestheader Authorization: token to authenticate.


Remote Repositories
~~~~~~~~~~~~~~~~~~~

Remote Repositories are the importable repositories connected via
``GitHub``, ``GitLab`` and ``BitBucket``.


Remote Repository listing
+++++++++++++++++++++++++


.. http:get:: /api/v3/remote/repositories/

Retrieve a list of all Remote Repositories for the authenticated user.

**Example request**:

.. tabs::

.. code-tab:: bash

$ curl -H "Authorization: Token <token>" https://readthedocs.org/api/v3/remote/repositories/

.. code-tab:: python

import requests
URL = 'https://readthedocs.org/api/v3/remote/repositories/'
TOKEN = '<token>'
HEADERS = {'Authorization': f'token {TOKEN}'}
response = requests.get(URL, headers=HEADERS)
print(response.json())

**Example response**:

.. sourcecode:: json

{
"count": 20,
"next": "api/v3/remote/repositories/?limit=10&offset=10",
"previous": null,
"results": [
{
"organization": {
"avatar_url": "https://avatars.githubusercontent.com/u/12345?v=4",
"created": "2019-04-29T10:00:00Z",
"modified": "2019-04-29T12:00:00Z",
"name": "Organization Name",
"pk": 1,
"slug": "organization",
"url": "https://github.com/organization",
"vcs_provider": "github"
},
"avatar_url": "https://avatars3.githubusercontent.com/u/test-rtd?v=4",
"clone_url": "https://github.com/rtd/project.git",
"created": "2019-04-29T10:00:00Z",
"description": "This is a test project.",
"full_name": "rtd/project",
"html_url": "https://github.com/rtd/project",
"modified": "2019-04-29T12:00:00Z",
"name": "project",
"pk": 1,
"ssh_url": "[email protected]:rtd/project.git",
"vcs": "git",
"vcs_provider": "github",
"admin": true
}
]
}


The ``results`` in response is an array of remote repositories data.

:query string name: return remote repositories with matching name
:query string vcs: return remote repositories for specific vcs (``git``, ``svn``, ``hg`` or ``bzr``)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this one. Is there a good use case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, vcs_provider should be enough, but I saw it in the API v2, so I added it as I did not know the specific requirements for the API v3. :)

:query string vcs_provider: return remote repositories for specific vcs provider (``github``, ``gitlab`` or ``bitbucket``)
:query string organization: return remote repositories for specific remote organization (Using remote organization ``pk``)

:requestheader Authorization: token to authenticate.


Additional APIs
---------------

Expand Down
25 changes: 25 additions & 0 deletions readthedocs/api/v3/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from readthedocs.builds.constants import BUILD_STATE_FINISHED
from readthedocs.builds.models import Build, Version
from readthedocs.oauth.models import RemoteRepository, RemoteOrganization
from readthedocs.projects.models import Project


Expand Down Expand Up @@ -47,3 +48,27 @@ def get_running(self, queryset, name, value):
return queryset.exclude(state=BUILD_STATE_FINISHED)

return queryset.filter(state=BUILD_STATE_FINISHED)


class RemoteRepositoryFilter(filters.FilterSet):
name = filters.CharFilter(lookup_expr='icontains')

class Meta:
model = RemoteRepository
fields = [
'name',
'vcs',
'vcs_provider',
'organization',
]


class RemoteOrganizationFilter(filters.FilterSet):
name = filters.CharFilter(lookup_expr='icontains')

class Meta:
model = RemoteOrganization
fields = [
'name',
'vcs_provider',
]
54 changes: 54 additions & 0 deletions readthedocs/api/v3/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from readthedocs.core.utils.extend import SettingsOverrideObject
from readthedocs.builds.models import Build, Version
from readthedocs.core.utils import slugify
from readthedocs.oauth.models import RemoteRepository, RemoteOrganization
from readthedocs.organizations.models import Organization, Team
from readthedocs.projects.constants import (
LANGUAGES,
Expand Down Expand Up @@ -891,3 +892,56 @@ class Meta:
'projects': (ProjectSerializer, {'many': True}),
'teams': (TeamSerializer, {'many': True}),
}


class RemoteOrganizationSerializer(serializers.ModelSerializer):

class Meta:
model = RemoteOrganization
fields = [
'pk',
'slug',
'name',
'avatar_url',
'url',
'vcs_provider',
'created',
'modified',
]
read_only_fields = fields


class RemoteRepositorySerializer(serializers.ModelSerializer):
admin = serializers.SerializerMethodField('is_admin')
organization = RemoteOrganizationSerializer()

class Meta:
model = RemoteRepository
fields = [
'pk',
'name',
'full_name',
'description',
'admin',
'avatar_url',
'ssh_url',
'clone_url',
'html_url',
'vcs',
'vcs_provider',
'created',
'modified',
'organization',
]
read_only_fields = fields

def is_admin(self, obj):
request = self.context['request']

# Use annotated value from RemoteRepositoryViewSet queryset
if hasattr(obj, '_admin'):
return obj._admin

return obj.remote_repository_relations.filter(
user=request.user, admin=True
).exists()
17 changes: 17 additions & 0 deletions readthedocs/api/v3/tests/responses/remoteorganizations-list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"avatar_url": "https://avatars.githubusercontent.com/u/366329?v=4",
"created": "2019-04-29T10:00:00Z",
"modified": "2019-04-29T12:00:00Z",
"name": "Read the Docs",
"pk": 1,
"slug": "readthedocs",
"url": "https://github.com/readthedocs",
"vcs_provider": "github"
}
]
}
32 changes: 32 additions & 0 deletions readthedocs/api/v3/tests/responses/remoterepositories-list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"organization": {
"avatar_url": "https://avatars.githubusercontent.com/u/366329?v=4",
"created": "2019-04-29T10:00:00Z",
"modified": "2019-04-29T12:00:00Z",
"name": "Read the Docs",
"pk": 1,
"slug": "readthedocs",
"url": "https://github.com/readthedocs",
"vcs_provider": "github"
},
"avatar_url": "https://avatars3.githubusercontent.com/u/test-rtd?v=4",
"clone_url": "https://github.com/rtd/project.git",
"created": "2019-04-29T10:00:00Z",
"description": "This is a test project.",
"full_name": "rtd/project",
"html_url": "https://github.com/rtd/project",
"modified": "2019-04-29T12:00:00Z",
"name": "project",
"pk": 1,
"ssh_url": "[email protected]:rtd/project.git",
"vcs": "git",
"vcs_provider": "github",
"admin": true
}
]
}
48 changes: 48 additions & 0 deletions readthedocs/api/v3/tests/test_remoteorganizations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.urls import reverse

from allauth.socialaccount.models import SocialAccount
import django_dynamic_fixture as fixture

from readthedocs.oauth.constants import GITHUB
from readthedocs.oauth.models import (
RemoteOrganization,
RemoteOrganizationRelation,
)
from .mixins import APIEndpointMixin



class RemoteOrganizationEndpointTests(APIEndpointMixin):

def setUp(self):
super().setUp()

self.remote_organization = fixture.get(
RemoteOrganization,
created=self.created,
modified=self.modified,
avatar_url="https://avatars.githubusercontent.com/u/366329?v=4",
name="Read the Docs",
slug="readthedocs",
url="https://github.com/readthedocs",
vcs_provider=GITHUB,
)
social_account = fixture.get(SocialAccount, user=self.me, provider=GITHUB)
fixture.get(
RemoteOrganizationRelation,
remote_organization=self.remote_organization,
user=self.me,
account=social_account
)

def test_remote_organization_list(self):
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
response = self.client.get(
reverse('remoteorganizations-list')
)
self.assertEqual(response.status_code, 200)

self.assertDictEqual(
response.json(),
self._get_response_dict('remoteorganizations-list'),
)
Loading