Skip to content
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

Create subproject relationship via APIv3 endpoint #6176

Merged
Show file tree
Hide file tree
Changes from 8 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
105 changes: 100 additions & 5 deletions docs/api/v3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,41 @@ This allows for documentation projects to share a search index and a namespace o
but still be maintained independently.
See :doc:`/subprojects` for more information.


Subproject details
++++++++++++++++++


.. http:get:: /api/v3/projects/(str:project_slug)/subprojects/(str:alias_slug)/

Retrieve details of a subproject relationship.

**Example request**:

.. sourcecode:: bash

$ curl -H "Authorization: Token <token>" https://readthedocs.org/api/v3/projects/pip/subprojects/subproject-alias/

**Example response**:

.. sourcecode:: json

{
"alias": "subproject-alias",
"child": ["PROJECT"],
"_links": {
"parent": "/api/v3/projects/pip/"
}
}

:>json integer count: total number of projects.
:>json string next: URI for next set of projects.
:>json string previous: URI for previous set of projects.
:>json array results: array of ``project relationship`` objects.

:requestheader Authorization: token to authenticate.


Subprojects listing
+++++++++++++++++++

Expand All @@ -554,16 +589,76 @@ Subprojects listing
"count": 25,
"next": "/api/v3/projects/pip/subprojects/?limit=10&offset=10",
"previous": null,
"results": ["PROJECT"]
"results": ["SUBPROJECT RELATIONSHIP"]
}

:>json integer count: total number of projects.
:>json string next: URI for next set of projects.
:>json string previous: URI for previous set of projects.
:>json array results: array of ``project`` objects.
:>json integer count: total number of sub-projects.
:>json string next: URI for next set of sub-projects.
:>json string previous: URI for previous set of sub-projects.
:>json array results: array of ``subproject relationship`` objects.

:requestheader Authorization: token to authenticate.


Subproject create
+++++++++++++++++


.. http:post:: /api/v3/projects/(str:project_slug)/subprojects/

Create a subproject relationship between two projects.

**Example request**:

.. sourcecode:: bash

$ curl \
-X POST \
-H "Authorization: Token <token>" https://readthedocs.org/api/v3/projects/pip/subprojects/ \
-H "Content-Type: application/json" \
-d @body.json

The content of ``body.json`` is like,

.. sourcecode:: json

{
"child": "subproject-child-slug",
"alias": "subproject-alias"
}

**Example response**:

`See Subproject details <#subproject-details>`_

:>json string child: slug of the child project in the relationship.
:>json string alias: optional slug alias to be used in the URL.
stsewd marked this conversation as resolved.
Show resolved Hide resolved

:requestheader Authorization: token to authenticate.

:statuscode 201: Created
:statuscode 400: Some field is invalid


Subproject delete
+++++++++++++++++

.. http:delete:: /api/v3/projects/(str:project_slug)/subprojects/(str:alias_slug)/

Delete a subproject relationship.

**Example request**:

.. sourcecode:: bash

$ curl \
-X DELETE \
-H "Authorization: Token <token>" https://readthedocs.org/api/v3/projects/pip/subprojects/subproject-alias/

:requestheader Authorization: token to authenticate.

:statuscode 204: Deleted successfully


Translations
~~~~~~~~~~~~
Expand Down
1 change: 1 addition & 0 deletions readthedocs/api/v3/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class NestedParentObjectMixin:
PROJECT_LOOKUP_NAMES = [
'project__slug',
'projects__slug',
'parent__slug',
'superprojects__parent__slug',
'main_language_project__slug',
]
Expand Down
96 changes: 94 additions & 2 deletions readthedocs/api/v3/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from readthedocs.builds.models import Build, Version
from readthedocs.projects.constants import LANGUAGES, PROGRAMMING_LANGUAGES, REPO_CHOICES, PRIVACY_CHOICES, PROTECTED
from readthedocs.projects.models import Project, EnvironmentVariable
from readthedocs.projects.models import Project, EnvironmentVariable, ProjectRelationship
from readthedocs.redirects.models import Redirect, TYPE_CHOICES as REDIRECT_TYPE_CHOICES


Expand Down Expand Up @@ -387,7 +387,7 @@ def get_subprojects(self, obj):
path = reverse(
'projects-subprojects-list',
kwargs={
'parent_lookup_superprojects__parent__slug': obj.slug,
'parent_lookup_parent__slug': obj.slug,
},
)
return self._absolute_url(path)
Expand Down Expand Up @@ -541,6 +541,98 @@ def get_subproject_of(self, obj):
return None


class SubprojectCreateSerializer(FlexFieldsModelSerializer):

"""Serializer used to define a Project as subproject of another Project."""

child = serializers.SlugRelatedField(
slug_field='slug',
queryset=Project.objects.all(),
)

def validate_child(self, value):
# Check the user is maintainer of the child project
user = self.context['request'].user
if user not in value.users.all():
raise serializers.ValidationError(
'You do not have permissions on the child project',
)
return value

class Meta:
stsewd marked this conversation as resolved.
Show resolved Hide resolved
model = ProjectRelationship
fields = [
'child',
'alias',
]

class SubprojectLinksSerializer(BaseLinksSerializer):
_self = serializers.SerializerMethodField()
parent = serializers.SerializerMethodField()

def get__self(self, obj):
path = reverse(
'projects-subprojects-detail',
kwargs={
'parent_lookup_parent__slug': obj.parent.slug,
'alias_slug': obj.alias,
},
)
return self._absolute_url(path)

def get_parent(self, obj):
path = reverse(
'projects-detail',
kwargs={
'project_slug': obj.parent.slug,
},
)
return self._absolute_url(path)


class ChildProjectSerializer(ProjectSerializer):

"""
Serializer to render a Project when listed under ProjectRelationship.

It's exactly the same as ``ProjectSerializer`` but without some fields.
"""

class Meta(ProjectSerializer.Meta):
fields = [
field for field in ProjectSerializer.Meta.fields
if field not in [
'subproject_of',
]
]

class SubprojectSerializer(FlexFieldsModelSerializer):

"""Serializer to render a subproject (``ProjectRelationship``)."""

child = ChildProjectSerializer()
_links = SubprojectLinksSerializer(source='*')

class Meta:
model = ProjectRelationship
fields = [
'child',
'alias',
'_links',
]


class SubprojectDestroySerializer(FlexFieldsModelSerializer):

"""Serializer used to remove a subproject relationship to a Project."""

class Meta:
model = ProjectRelationship
fields = (
'alias',
)


class RedirectLinksSerializer(BaseLinksSerializer):
_self = serializers.SerializerMethodField()
project = serializers.SerializerMethodField()
Expand Down
2 changes: 1 addition & 1 deletion readthedocs/api/v3/tests/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def setUp(self):
users=[],
versions=[],
)
self.project.add_subproject(self.subproject)
self.project_relationship = self.project.add_subproject(self.subproject)

self.version = fixture.get(
Version,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/project/subprojects/subproject/",
"parent": "https://readthedocs.org/api/v3/projects/project/"
},
"alias": "subproject",
"child": {
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/subproject/",
"builds": "https://readthedocs.org/api/v3/projects/subproject/builds/",
"environmentvariables": "https://readthedocs.org/api/v3/projects/subproject/environmentvariables/",
"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/",
"translations": "https://readthedocs.org/api/v3/projects/subproject/translations/",
"versions": "https://readthedocs.org/api/v3/projects/subproject/versions/"
},
"created": "2019-04-29T10:00:00Z",
"default_branch": "master",
"default_version": "latest",
"homepage": "http://subproject.com",
"id": 2,
"language": {
"code": "en",
"name": "English"
},
"modified": "2019-04-29T12:00:00Z",
"name": "subproject",
"privacy_level": {
"code": "public",
"name": "Public"
},
"programming_language": {
"code": "words",
"name": "Only Words"
},
"repository": {
"type": "git",
"url": "https://github.com/rtfd/subproject"
},
"slug": "subproject",
"tags": [],
"translation_of": null,
"urls": {
"builds": "https://readthedocs.org/projects/subproject/builds/",
"documentation": "http://readthedocs.org/docs/project/projects/subproject/en/latest/",
"home": "https://readthedocs.org/projects/subproject/",
"versions": "https://readthedocs.org/projects/subproject/versions/"
},
"users": []
}
}
Loading