From b65098c6e042133e4e416de6e1aa8434c9be8c3a Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 11 Sep 2019 16:16:13 +0200 Subject: [PATCH 01/25] Ship API v3 * Remove message about contacting support to get early API v3 access * Remove all mentions to API v3 beta in the docs * Mark API v2 as deprecated * Add a note about where to get the access Token --- docs/api/index.rst | 1 + docs/api/v2.rst | 16 ++++++++-------- docs/api/v3.rst | 18 +++++------------- .../templates/profiles/private/token_list.html | 8 -------- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/docs/api/index.rst b/docs/api/index.rst index 8d0464d6e5d..173eefda508 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -11,3 +11,4 @@ from Read the Docs. :maxdepth: 3 v2 + v3 diff --git a/docs/api/v2.rst b/docs/api/v2.rst index c4a5200c844..fbdecf7ebfc 100644 --- a/docs/api/v2.rst +++ b/docs/api/v2.rst @@ -1,5 +1,5 @@ -API v2 -====== +API v2 (deprecated) +=================== The Read the Docs API uses :abbr:`REST (Representational State Transfer)`. JSON is returned by all API responses including errors @@ -7,16 +7,16 @@ and HTTP response status codes are to designate success and failure. .. note:: - A newer API v3 is in early development stages. + A newer and better API v3 is ready to use. Some improvements coming in v3 are: - * Search API - * Write access - * Simpler URLs which use slugs instead of numeric IDs + * Simpler URLs which use slugs + * Token based authentication + * Import a new project + * Activate a version * Improved error reporting - If there are features you would like in v3, please get in touch - in the `issue tracker `_. + See its full documentation at :doc:`/api/v3`. Authentication and authorization diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 8fc12c8a85e..e2a06c1081d 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -1,5 +1,3 @@ -:orphan: - API v3 ====== @@ -7,17 +5,6 @@ The Read the Docs API uses :abbr:`REST (Representational State Transfer)`. JSON is returned by all API responses including errors and HTTP response status codes are to designate success and failure. -.. warning:: - - APIv3 is currently under development and it's not ready to use yet. - - -.. note:: - - If you want to beta test it, please `get in touch`_ with us so we can give you early access. - -.. _get in touch: mailto:support@readthedocs.org?subject=APIv3%20beta%20test - .. contents:: Table of contents :local: :backlinks: none @@ -37,6 +24,11 @@ Token The ``Authorization`` HTTP header can be specified with ``Token `` to authenticate as a user and have the same permissions that the user itself. +.. note:: + + You will find your access Token under + `your profile settings `_. + Session ~~~~~~~ diff --git a/readthedocs/templates/profiles/private/token_list.html b/readthedocs/templates/profiles/private/token_list.html index 810c7285f51..eaf64f96fee 100644 --- a/readthedocs/templates/profiles/private/token_list.html +++ b/readthedocs/templates/profiles/private/token_list.html @@ -9,14 +9,6 @@ {% block edit_content_header %} {% trans "API Tokens" %} {% endblock %} {% block edit_content %} -

- {% blocktrans trimmed with contact_email="support@readthedocs.org" %} - API Tokens are currently an invite-only Beta feature. - In case you want to test APIv3 and give us feedback on it, - please email us. - {% endblocktrans %} -

-

Personal Access Token are tokens that allow you to use the Read the Docs APIv3 being authenticated as yourself. See APIv3 documentation for more information.

From 79f77400c57072f0abfe6710c0e0678dd3121545 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 11 Sep 2019 23:25:22 +0200 Subject: [PATCH 02/25] Allow users to generate/revoke API Tokens --- readthedocs/profiles/urls/private.py | 12 +++++- readthedocs/profiles/views.py | 42 ++++++++++++++----- .../profiles/private/token_list.html | 23 ++++++++++ 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/readthedocs/profiles/urls/private.py b/readthedocs/profiles/urls/private.py index 02a894d9000..ed62b29d407 100644 --- a/readthedocs/profiles/urls/private.py +++ b/readthedocs/profiles/urls/private.py @@ -35,9 +35,19 @@ tokens_urls = [ url( r'^tokens/$', - views.TokenList.as_view(), + views.TokenListView.as_view(), name='profiles_tokens', ), + url( + r'^tokens/create/$', + views.TokenCreateView.as_view(), + name='profiles_tokens_create', + ), + url( + r'^tokens/delete/$', + views.TokenDeleteView.as_view(), + name='profiles_tokens_delete', + ), ] urlpatterns += tokens_urls diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py index 2e9ccedc93d..915ce84a4b6 100644 --- a/readthedocs/profiles/views.py +++ b/readthedocs/profiles/views.py @@ -10,7 +10,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.translation import ugettext_lazy as _ -from django.views.generic import ListView +from django.views.generic import ListView, DeleteView, View from rest_framework.authtoken.models import Token from readthedocs.core.forms import UserAdvertisingForm, UserDeleteForm @@ -213,22 +213,42 @@ def account_advertising(request): class TokenMixin: - """Environment Variables to be added when building the Project.""" + """Mixin class to handle API Tokens.""" model = Token - lookup_url_kwarg = 'token_pk' + + def get_success_url(self): + return reverse('profiles_tokens') + + +class TokenListView(TokenMixin, ListView): + + """View to list all the Tokens that belong to a User.""" + template_name = 'profiles/private/token_list.html' def get_queryset(self): - # Token has a OneToOneField relation with User + # NOTE: we are currently showing just one token since the DRF model has + # a OneToOneField relation with User. Although, we plan to have multiple + # scope-based tokens. return Token.objects.filter(user__in=[self.request.user]) - def get_success_url(self): - return reverse( - 'projects_token', - args=[self.get_project().slug], - ) +class TokenCreateView(TokenMixin, View): + + """Simple view to generate a Token object for the logged in User.""" + + http_method_names= ['post'] + + def post(self, request, *args, **kwargs): + _, created = Token.objects.get_or_create(user=self.request.user) + if created: + messages.info(request, 'API Token created successfully.') + return HttpResponseRedirect(self.get_success_url()) + +class TokenDeleteView(TokenMixin, DeleteView): + + """View to delete/revoke the current Token of the logged in User.""" -class TokenList(TokenMixin, ListView): - pass + def get_object(self): + return self.request.user.auth_token diff --git a/readthedocs/templates/profiles/private/token_list.html b/readthedocs/templates/profiles/private/token_list.html index eaf64f96fee..beab20b2059 100644 --- a/readthedocs/templates/profiles/private/token_list.html +++ b/readthedocs/templates/profiles/private/token_list.html @@ -11,6 +11,19 @@ {% block edit_content %}

Personal Access Token are tokens that allow you to use the Read the Docs APIv3 being authenticated as yourself. See APIv3 documentation for more information.

+ {% if not object_list %} +
+
    +
  • +
    + {% csrf_token %} + +
    +
  • +
+
+ {% endif %} +
@@ -19,6 +32,16 @@
  • Created: {{ token.created }}
    Token: {{ token.key }}
    + +
      +
    • +
      + {% csrf_token %} + +
      +
    • +
    +
  • {% empty %}
  • From 6d8b6cd20dd2254ffebbebb93a0bf188b50b3f5c Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 11 Sep 2019 23:38:01 +0200 Subject: [PATCH 03/25] Test cases for list/create/delete API Tokens --- .../rtd_tests/tests/test_profile_views.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/readthedocs/rtd_tests/tests/test_profile_views.py b/readthedocs/rtd_tests/tests/test_profile_views.py index de7c244bf1b..77f2eeaa6f4 100644 --- a/readthedocs/rtd_tests/tests/test_profile_views.py +++ b/readthedocs/rtd_tests/tests/test_profile_views.py @@ -3,6 +3,7 @@ from django.test import TestCase from django.urls import reverse from django_dynamic_fixture import get +from rest_framework.authtoken.models import Token class ProfileViewsTest(TestCase): @@ -106,3 +107,31 @@ def test_account_advertising(self): self.assertEqual(resp['Location'], reverse('account_advertising')) self.user.profile.refresh_from_db() self.assertFalse(self.user.profile.allow_ads) + + def test_list_api_tokens(self): + resp = self.client.get(reverse('profiles_tokens')) + self.assertEqual(resp.status_code, 200) + self.assertContains(resp, 'No API Tokens currently configured.') + + Token.objects.create(user=self.user) + resp = self.client.get(reverse('profiles_tokens')) + self.assertEqual(resp.status_code, 200) + self.assertContains(resp, f'Token: {self.user.auth_token.key}') + + def test_create_api_token(self): + self.assertEqual(Token.objects.filter(user=self.user).count(), 0) + + resp = self.client.get(reverse('profiles_tokens_create')) + self.assertEqual(resp.status_code, 405) # GET not allowed + + resp = self.client.post(reverse('profiles_tokens_create')) + self.assertEqual(resp.status_code, 302) + self.assertEqual(Token.objects.filter(user=self.user).count(), 1) + + def test_delete_api_token(self): + Token.objects.create(user=self.user) + self.assertEqual(Token.objects.filter(user=self.user).count(), 1) + + resp = self.client.post(reverse('profiles_tokens_delete')) + self.assertEqual(resp.status_code, 302) + self.assertEqual(Token.objects.filter(user=self.user).count(), 0) From 2163fed198a2f51df8ed04f0be5cb9cdee1faaf3 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 12 Sep 2019 13:15:41 +0200 Subject: [PATCH 04/25] Lint --- readthedocs/profiles/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py index 915ce84a4b6..30933560955 100644 --- a/readthedocs/profiles/views.py +++ b/readthedocs/profiles/views.py @@ -238,7 +238,7 @@ class TokenCreateView(TokenMixin, View): """Simple view to generate a Token object for the logged in User.""" - http_method_names= ['post'] + http_method_names = ['post'] def post(self, request, *args, **kwargs): _, created = Token.objects.get_or_create(user=self.request.user) @@ -246,9 +246,10 @@ def post(self, request, *args, **kwargs): messages.info(request, 'API Token created successfully.') return HttpResponseRedirect(self.get_success_url()) + class TokenDeleteView(TokenMixin, DeleteView): """View to delete/revoke the current Token of the logged in User.""" - def get_object(self): + def get_object(self, queryset=None): return self.request.user.auth_token From 4d65213f42fedba6d76c441a24d95d6cbe01e025 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 12 Sep 2019 18:36:28 +0200 Subject: [PATCH 05/25] Update readthedocs/profiles/views.py Co-Authored-By: Santos Gallegos --- readthedocs/profiles/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py index 30933560955..d774fafca7d 100644 --- a/readthedocs/profiles/views.py +++ b/readthedocs/profiles/views.py @@ -243,7 +243,7 @@ class TokenCreateView(TokenMixin, View): def post(self, request, *args, **kwargs): _, created = Token.objects.get_or_create(user=self.request.user) if created: - messages.info(request, 'API Token created successfully.') + messages.info(request, 'API Token created successfully') return HttpResponseRedirect(self.get_success_url()) From 630cf846f8b46f94c88d1f2155f0003fdae426d5 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 12 Sep 2019 23:10:02 +0200 Subject: [PATCH 06/25] Use one step deletion to revoke the token --- readthedocs/profiles/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py index d774fafca7d..fc24c7f77d6 100644 --- a/readthedocs/profiles/views.py +++ b/readthedocs/profiles/views.py @@ -251,5 +251,7 @@ class TokenDeleteView(TokenMixin, DeleteView): """View to delete/revoke the current Token of the logged in User.""" + http_method_names = ['post'] + def get_object(self, queryset=None): return self.request.user.auth_token From f7e168f929bff3a33b48e066786cfada653536ee Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 13:25:27 +0200 Subject: [PATCH 07/25] Remove redundant documentation and make it cleaner Just mention relevant fields (not obvious ones) and status codes. --- docs/api/v3.rst | 149 +++++++++++++----------------------------------- 1 file changed, 41 insertions(+), 108 deletions(-) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index e2a06c1081d..e019fc4fa29 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -47,6 +47,22 @@ When a user is trying to authenticate via session, Resources --------- +This section shows all the resources that are currently available in APIv3. +There are some URL attributes that applies to all of these resources: + +:?fields=: + + Specify which fields are going to be returned in the response. + +:?omit=: + + Specify which fields are going to be omitted from the response. + +:?expand=: + + Some resources allow to expand/add extra fields on their responses (see _`Project details` for example). + + Projects ~~~~~~~~ @@ -74,21 +90,8 @@ Projects list "results": ["PROJECT"] } - :>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. - - :query string name: name of the project. - :query string name__contains: part of the name of the project. - :query string slug: slug of the project. - :query string slug__contains: part of the slug of the project. :query string language: language code as ``en``, ``es``, ``ru``, etc. - :query string privacy_level: one of ``public``, ``private``, ``protected``. :query string programming_language: programming language code as ``py``, ``js``, etc. - :query string repository_type: one of ``git``, ``hg``, ``bzr``, ``svn``. - - :requestheader Authorization: token to authenticate. Project details @@ -166,15 +169,9 @@ Project details } } - :>json string name: The name of the project. - :>json string slug: The project slug (used in the URL). - - .. TODO: complete the returned data docs once agreed on this. - - :requestheader Authorization: token to authenticate. - - :statuscode 200: Success - :statuscode 404: There is no ``Project`` with this slug + :query string expand: allows to add/expand some extra fields in the response. + Allowed values are ``active_versions``, ``active_versions.last_build`` and + ``active_versions.last_build.config``. Multiple fields can be passed separated by commas. Project create @@ -182,7 +179,7 @@ Project create .. http:post:: /api/v3/projects/ - Import a project into Read the Docs. + Import a project under authenticated user. **Example request**: @@ -213,11 +210,6 @@ Project create `See Project details <#project-details>`_ - :requestheader Authorization: token to authenticate. - - :statuscode 201: Created - :statuscode 400: Some field is invalid - Versions ~~~~~~~~ @@ -255,17 +247,8 @@ Versions listing "results": ["VERSION"] } - :>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 ``Version`` objects. - - :query integer limit: limit number of object returned - :query integer offset: offset from the whole list returned - :query boolean active: whether return active versions only - :query boolean built: whether return only built version - - :requestheader Authorization: token to authenticate. + :query boolean active: return active versions only + :query boolean built: return only built version Version detail @@ -312,21 +295,16 @@ Version detail } } - :>json integer id: ID for this version on the database - :>json string slug: The slug for this version - :>json string verbose_name: The name of the version - :>json string identifier: A version control identifier for this version (eg. the commit hash of the tag) - :>json string ref: tag or branch pointed by this version (available only when version is ``stable`` or ``latest``) - :>json string built: Whether this version has been built - :>json string active: Whether this version is active - :>json string type: The type of this version (typically "tag" or "branch") - :>json string last_build: Build object representing the last build of this version - :>json array downloads: URLs to downloads of this version's documentation - :requestheader Authorization: token to authenticate. + :>json string ref: the version slug where the ``stable`` version points to. + ``null`` when it's not the stable version. + :>json string build: the version has at least one successful build. + :>json string uploaded: the version was uploaded manually. + - :statuscode 200: Success - :statuscode 404: There is no ``Version`` with this slug for this project + :query string expand: allows to add/expand some extra fields in the response. + Allowed values are ``last_build`` and ``last_build.config``. + Multiple fields can be passed separated by commas. Version update @@ -347,10 +325,7 @@ Version update :requestheader Authorization: token to authenticate. - :statuscode 204: Edited successfully - :statuscode 400: Some field is invalid - :statuscode 401: Not valid permissions - :statuscode 404: There is no ``Version`` with this slug for this project + :statuscode 204: Updated successfully Builds @@ -440,22 +415,14 @@ Build details } } - :>json integer id: The ID of the build - :>json string date: The ISO-8601 datetime of the build. + :>json string created: The ISO-8601 datetime when the build was created. + :>json string finished: The ISO-8601 datetime when the build has finished. :>json integer duration: The length of the build in seconds. :>json string state: The state of the build (one of ``triggered``, ``building``, ``installing``, ``cloning``, or ``finished``) - :>json boolean success: Whether the build was successful :>json string error: An error message if the build was unsuccessful - :>json string commit: A version control identifier for this build (eg. the commit hash) - :>json string builder: The hostname server that built the docs - :>json string cold_storage: Whether the build was removed from database and stored externally - :query boolean include_config: whether or not include the configs used for this build. Default is ``false`` - - :requestheader Authorization: token to authenticate. - - :statuscode 200: Success - :statuscode 404: There is no ``Build`` with this ID + :query string expand: allows to add/expand some extra fields in the response. + Allowed value is ``config``. Builds listing @@ -483,9 +450,7 @@ Builds listing } :query string commit: commit hash to filter the builds returned by commit - :query boolean running: whether or not to filter the builds returned by currently building - - :requestheader Authorization: token to authenticate. + :query boolean running: filter the builds that are currently building/running Build triggering @@ -508,11 +473,7 @@ Build triggering `See Build details <#build-details>`_ - :requestheader Authorization: token to authenticate. - - :statuscode 202: Accepted - :statuscode 400: Some field is invalid - :statuscode 401: Not valid permissions + :statuscode 202: the build was triggered Subprojects @@ -549,13 +510,6 @@ Subprojects listing "results": ["PROJECT"] } - :>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. - - :requestheader Authorization: token to authenticate. - Translations ~~~~~~~~~~~~ @@ -588,13 +542,6 @@ Translations listing "results": ["PROJECT"] } - :>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. - - :requestheader Authorization: token to authenticate. - Redirects ~~~~~~~~~ @@ -690,10 +637,7 @@ Redirect create `See Redirect details <#redirect-details>`_ - :requestheader Authorization: token to authenticate. - - :statuscode 201: Created - :statuscode 400: Some field is invalid + :statuscode 201: redirect created successfully Redirect update @@ -727,12 +671,6 @@ Redirect update `See Redirect details <#redirect-details>`_ - :requestheader Authorization: token to authenticate. - - :statuscode 200: Success - :statuscode 400: Some field is invalid - - Redirect delete ++++++++++++++++ @@ -748,9 +686,7 @@ Redirect delete -X DELETE \ -H "Authorization: Token " https://readthedocs.org/api/v3/projects/pip/redirects/1/ - :requestheader Authorization: token to authenticate. - - :statuscode 204: Deleted successfully + :statuscode 204: Redirect deleted successfully Environment Variables @@ -845,10 +781,7 @@ Environment Variable create `See Environment Variable details <#environmentvariable-details>`_ - :requestheader Authorization: token to authenticate. - - :statuscode 201: Created - :statuscode 400: Some field is invalid + :statuscode 201: Environment variable created successfully Environment Variable delete @@ -868,4 +801,4 @@ Environment Variable delete :requestheader Authorization: token to authenticate. - :statuscode 204: Deleted successfully + :statuscode 204: Environment variable deleted successfully From 1bb37427abf037ba7c71684b97e1209c434c053b Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 13:37:09 +0200 Subject: [PATCH 08/25] Mention where to find the token in the root API URL --- readthedocs/api/v3/routers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readthedocs/api/v3/routers.py b/readthedocs/api/v3/routers.py index 90588c0997d..7b768b4b2fb 100644 --- a/readthedocs/api/v3/routers.py +++ b/readthedocs/api/v3/routers.py @@ -9,9 +9,9 @@ class DocsAPIRootView(APIRootView): """ Read the Docs APIv3 root endpoint. - API is browseable by sending the header ``Authorization: Token `` on each request. + The API is browseable by sending the header ``Authorization: Token `` on each request. You can find your Token at [https://readthedocs.org/accounts/tokens/](https://readthedocs.org/accounts/tokens/). - Full documentation at [https://docs.readthedocs.io/page/api/v3.html](https://docs.readthedocs.io/page/api/v3.html). + Read its full documentation at [https://docs.readthedocs.io/page/api/v3.html](https://docs.readthedocs.io/page/api/v3.html). """ # noqa def get_view_name(self): From 5ab0c5e23c7125edbbcd742583cd9e9edee17fb7 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 13:38:19 +0200 Subject: [PATCH 09/25] Add a tip mentioning that it's possible to browse the API --- docs/api/v3.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index e019fc4fa29..1d4f6b523b1 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -63,6 +63,11 @@ There are some URL attributes that applies to all of these resources: Some resources allow to expand/add extra fields on their responses (see _`Project details` for example). +.. tip:: + + You can browse the full API by accessing its root URL: https://readthedocs.org/api/v3/ + + Projects ~~~~~~~~ From 967804846ee680105f4b7b4c6b64d3b34fb5d969 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 13:38:41 +0200 Subject: [PATCH 10/25] Remove Project docstring (shown in browseable API resource) --- readthedocs/api/v3/views.py | 67 ------------------------------------- 1 file changed, 67 deletions(-) diff --git a/readthedocs/api/v3/views.py b/readthedocs/api/v3/views.py index 11ef4fe2380..4910aaa5ee8 100644 --- a/readthedocs/api/v3/views.py +++ b/readthedocs/api/v3/views.py @@ -75,56 +75,6 @@ class ProjectsViewSet(APIv3Settings, NestedViewSetMixin, ProjectQuerySetMixin, FlexFieldsMixin, ProjectImportMixin, CreateModelMixin, ReadOnlyModelViewSet): - # Markdown docstring is automatically rendered by BrowsableAPIRenderer. - - """ - Endpoints related to ``Project`` objects. - - * Listing objects. - * Detailed object. - - Retrieving only needed data using ``?fields=`` URL attribute is allowed. - On the other hand, you can use ``?omit=`` and list the fields you want to skip in the response. - - ### Filters - - Allowed via URL attributes: - - * slug - * slug__contains - * name - * name__contains - - ### Expandable fields - - There are some fields that are not returned by default because they are - expensive to calculate. Although, they are available for those cases where - they are needed. - - Allowed via ``?expand=`` URL attribute: - - * users - * active_versions - * active_versions.last_build - * active_versions.last_build.confg - - - ### Examples: - - * List my projects: ``/api/v3/projects/`` - * List my projects with offset and limit: ``/api/v3/projects/?offset=10&limit=25`` - * Filter list: ``/api/v3/projects/?name__contains=test`` - * Retrieve only needed data: ``/api/v3/projects/?fields=slug,created`` - * Retrieve specific project: ``/api/v3/projects/{project_slug}/`` - * Expand required fields: ``/api/v3/projects/{project_slug}/?expand=active_versions`` - * Translations of a project: ``/api/v3/projects/{project_slug}/translations/`` - * Subprojects of a project: ``/api/v3/projects/{project_slug}/subprojects/`` - * Superproject of a project: ``/api/v3/projects/{project_slug}/superproject/`` - - Go to [https://docs.readthedocs.io/page/api/v3.html](https://docs.readthedocs.io/page/api/v3.html) - for a complete documentation of the APIv3. - """ # noqa - model = Project lookup_field = 'slug' lookup_url_kwarg = 'project_slug' @@ -169,23 +119,6 @@ def get_queryset(self): 'users', ) - def get_view_description(self, *args, **kwargs): # pylint: disable=arguments-differ - """ - Make valid links for the user's documentation browseable API. - - If the user has already one project, we pick the first and make all the - links for that project. Otherwise, we default to the placeholder. - """ - description = super().get_view_description(*args, **kwargs) - - project = None - if self.request and self.request.user.is_authenticated(): - project = self.request.user.projects.first() - if project: - # TODO: make the links clickable when ``kwargs.html=True`` - return mark_safe(description.format(project_slug=project.slug)) - return description - def create(self, request, *args, **kwargs): """ Import Project. From 192fe2f213c123ca1b17101a322e153554e3fb87 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 13:39:09 +0200 Subject: [PATCH 11/25] Remove unuseful (Project, Version) filters --- readthedocs/api/v3/filters.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/readthedocs/api/v3/filters.py b/readthedocs/api/v3/filters.py index b24ffb31b63..747efecd2aa 100644 --- a/readthedocs/api/v3/filters.py +++ b/readthedocs/api/v3/filters.py @@ -6,50 +6,21 @@ class ProjectFilter(filters.FilterSet): - name__contains = filters.CharFilter( - field_name='name', - lookup_expr='contains', - ) - slug__contains = filters.CharFilter( - field_name='slug', - lookup_expr='contains', - ) - repository_type = filters.CharFilter( - field_name='repo_type', - lookup_expr='exact', - ) class Meta: model = Project fields = [ - 'name', - 'name__contains', - 'slug', - 'slug__contains', 'language', - 'privacy_level', 'programming_language', - 'repository_type', ] class VersionFilter(filters.FilterSet): - verbose_name__contains = filters.CharFilter( - field_name='verbose_name', - lookup_expr='contains', - ) - slug__contains = filters.CharFilter( - field_name='slug', - lookup_expr='contains', - ) class Meta: model = Version fields = [ 'verbose_name', - 'verbose_name__contains', - 'slug', - 'slug__contains', 'privacy_level', 'active', 'built', From 5fe990c3393f5a7789c7ecf968da3d59ec2d9151 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 13:57:57 +0200 Subject: [PATCH 12/25] Remove half-baked docs from the Browsable API and make it consistent --- readthedocs/api/v3/views.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/readthedocs/api/v3/views.py b/readthedocs/api/v3/views.py index 4910aaa5ee8..2aa61aee3f3 100644 --- a/readthedocs/api/v3/views.py +++ b/readthedocs/api/v3/views.py @@ -147,7 +147,6 @@ def perform_create(self, serializer): @action(detail=True, methods=['get']) def superproject(self, request, project_slug): - """Return the superproject of a ``Project``.""" project = self.get_object() try: superproject = project.superprojects.first().parent @@ -161,16 +160,8 @@ class SubprojectRelationshipViewSet(APIv3Settings, NestedViewSetMixin, ProjectQuerySetMixin, FlexFieldsMixin, ListModelMixin, GenericViewSet): - # Markdown docstring exposed at BrowsableAPIRenderer. - - """List subprojects of a ``Project``.""" - - # Private/Internal docstring - - """ - The main query is done via the ``NestedViewSetMixin`` using the - ``parents_query_lookups`` defined when registering the urls. - """ # noqa + # The main query is done via the ``NestedViewSetMixin`` using the + # ``parents_query_lookups`` defined when registering the urls. model = Project lookup_field = 'slug' @@ -183,15 +174,8 @@ class TranslationRelationshipViewSet(APIv3Settings, NestedViewSetMixin, ProjectQuerySetMixin, FlexFieldsMixin, ListModelMixin, GenericViewSet): - # Markdown docstring exposed at BrowsableAPIRenderer. - - """List translations of a ``Project``.""" - - # Private/Internal docstring - """ - The main query is done via the ``NestedViewSetMixin`` using the - ``parents_query_lookups`` defined when registering the urls. - """ # noqa + # The main query is done via the ``NestedViewSetMixin`` using the + # ``parents_query_lookups`` defined when registering the urls. model = Project lookup_field = 'slug' @@ -248,6 +232,7 @@ def update(self, request, *args, **kwargs): class BuildsViewSet(APIv3Settings, NestedViewSetMixin, ProjectQuerySetMixin, FlexFieldsMixin, ReadOnlyModelViewSet): + model = Build lookup_field = 'pk' lookup_url_kwarg = 'build_pk' From 67ac5e515f592b50e59cbd0b87e0a9284291e9aa Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 14:03:05 +0200 Subject: [PATCH 13/25] Build trigger response docs --- docs/api/v3.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 1d4f6b523b1..33d1cb34b1b 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -476,7 +476,13 @@ Build triggering **Example response**: - `See Build details <#build-details>`_ + .. sourcecode:: json + + { + "build": "{BUILD}", + "project": "{PROJECT}", + "version": "{VERSION}" + } :statuscode 202: the build was triggered From 8e59cf501ea938caefc46fba9d6ef1ca4a3b4ce4 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 14:14:28 +0200 Subject: [PATCH 14/25] Better docs for version update --- docs/api/v3.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 33d1cb34b1b..5e71fc3417a 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -321,6 +321,16 @@ Version update **Example request**: + .. sourcecode:: bash + + $ curl \ + -X PATCH \ + -H "Authorization: Token " https://readthedocs.org/api/v3/projects/pip/version/0.23/ \ + -H "Content-Type: application/json" \ + -d @body.json + + The content of ``body.json`` is like, + .. sourcecode:: json { @@ -328,8 +338,6 @@ Version update "privacy_level": "public" } - :requestheader Authorization: token to authenticate. - :statuscode 204: Updated successfully From e6f711d06de43a46e5fe2b80c6245ea4470461f8 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Oct 2019 14:16:23 +0200 Subject: [PATCH 15/25] Fix same page link --- docs/api/v3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 5e71fc3417a..4c917643c27 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -60,7 +60,7 @@ There are some URL attributes that applies to all of these resources: :?expand=: - Some resources allow to expand/add extra fields on their responses (see _`Project details` for example). + Some resources allow to expand/add extra fields on their responses (see `Project details <#project-details>`_ for example). .. tip:: From 995287f54c42e2e5e6b9a7a7e3d699c023dc354e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Oct 2019 10:49:02 +0200 Subject: [PATCH 16/25] Remove duplicated references docs --- docs/api/v3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 4bface87bdd..05a1a56903d 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -60,7 +60,7 @@ There are some URL attributes that applies to all of these resources: :?expand=: - Some resources allow to expand/add extra fields on their responses (see `Project details <#project-details>`_ for example). + Some resources allow to expand/add extra fields on their responses (see `Project details <#project-details>`__ for example). .. tip:: @@ -213,7 +213,7 @@ Project create **Example response**: - `See Project details <#project-details>`_ + `See Project details <#project-details>`__ Versions From a888bc8bf6db8740ec58292ba0764496a0b9eecf Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Oct 2019 18:54:52 +0200 Subject: [PATCH 17/25] Remove privacy_level field from APIv3 Read the Docs Community edition does not support privacy levels at all. We are removing it from here now. We can re-add them once we have RTD_ALLOW_PRIVACY_LEVELS=True in the Corporate site. (see https://github.com/readthedocs/readthedocs.org/pull/6194) --- docs/api/v3.rst | 5 ----- readthedocs/api/v3/serializers.py | 21 +------------------ .../v3/tests/responses/projects-detail.json | 8 ------- .../api/v3/tests/responses/projects-list.json | 4 ---- .../tests/responses/projects-list_POST.json | 4 ---- .../responses/projects-subprojects-list.json | 8 ------- .../responses/projects-superproject.json | 4 ---- .../projects-versions-builds-list_POST.json | 8 ------- .../responses/projects-versions-detail.json | 4 ---- readthedocs/api/v3/tests/test_projects.py | 2 +- 10 files changed, 2 insertions(+), 66 deletions(-) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 05a1a56903d..40b72a60688 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -136,10 +136,6 @@ Project details }, "default_version": "stable", "default_branch": "master", - "privacy_level": { - "code": "public", - "name": "Public", - }, "subproject_of": null, "translation_of": null, "urls": { @@ -335,7 +331,6 @@ Version update { "active": true, - "privacy_level": "public" } :statuscode 204: Updated successfully diff --git a/readthedocs/api/v3/serializers.py b/readthedocs/api/v3/serializers.py index 44a90721e33..b089cef03d2 100644 --- a/readthedocs/api/v3/serializers.py +++ b/readthedocs/api/v3/serializers.py @@ -159,14 +159,6 @@ def get_success(self, obj): return None -class PrivacyLevelSerializer(serializers.Serializer): - code = serializers.CharField(source='privacy_level') - name = serializers.SerializerMethodField() - - def get_name(self, obj): - return obj.privacy_level.title() - - class VersionLinksSerializer(BaseLinksSerializer): _self = serializers.SerializerMethodField() builds = serializers.SerializerMethodField() @@ -212,7 +204,6 @@ def get_documentation(self, obj): class VersionSerializer(FlexFieldsModelSerializer): - privacy_level = PrivacyLevelSerializer(source='*') ref = serializers.CharField() downloads = serializers.SerializerMethodField() urls = VersionURLsSerializer(source='*') @@ -228,7 +219,6 @@ class Meta: 'ref', 'built', 'active', - 'privacy_level', 'type', 'downloads', 'urls', @@ -257,14 +247,13 @@ class VersionUpdateSerializer(serializers.ModelSerializer): """ Used when modifying (update action) a ``Version``. - It only allows to make the Version active/non-active and private/public. + It only allows to make the Version active/non-active. """ class Meta: model = Version fields = [ 'active', - 'privacy_level', ] @@ -434,11 +423,6 @@ class ProjectUpdateSerializer(FlexFieldsModelSerializer): repository = RepositorySerializer(source='*') homepage = serializers.URLField(source='project_url') - # Exclude ``Protected`` as a possible value for Privacy Level - privacy_level_choices = list(PRIVACY_CHOICES) - privacy_level_choices.remove((PROTECTED, _('Protected'))) - privacy_level = serializers.ChoiceField(choices=privacy_level_choices) - class Meta: model = Project fields = ( @@ -452,7 +436,6 @@ class Meta: # Advanced Settings -> General Settings 'default_version', 'default_branch', - 'privacy_level', 'analytics_code', 'show_version_warning', 'single_version', @@ -468,7 +451,6 @@ class ProjectSerializer(FlexFieldsModelSerializer): language = LanguageSerializer() programming_language = ProgrammingLanguageSerializer() repository = RepositorySerializer(source='*') - privacy_level = PrivacyLevelSerializer(source='*') urls = ProjectURLsSerializer(source='*') subproject_of = serializers.SerializerMethodField() translation_of = serializers.SerializerMethodField() @@ -497,7 +479,6 @@ class Meta: 'repository', 'default_version', 'default_branch', - 'privacy_level', 'subproject_of', 'translation_of', 'users', diff --git a/readthedocs/api/v3/tests/responses/projects-detail.json b/readthedocs/api/v3/tests/responses/projects-detail.json index dafbee8d1b5..1b113265560 100644 --- a/readthedocs/api/v3/tests/responses/projects-detail.json +++ b/readthedocs/api/v3/tests/responses/projects-detail.json @@ -34,10 +34,6 @@ "builds": "https://readthedocs.org/api/v3/projects/project/versions/v1.0/builds/", "project": "https://readthedocs.org/api/v3/projects/project/" }, - "privacy_level": { - "code": "public", - "name": "Public" - }, "ref": null, "slug": "v1.0", "type": "tag", @@ -68,10 +64,6 @@ }, "modified": "2019-04-29T12:00:00Z", "name": "project", - "privacy_level": { - "code": "public", - "name": "Public" - }, "programming_language": { "code": "words", "name": "Only Words" diff --git a/readthedocs/api/v3/tests/responses/projects-list.json b/readthedocs/api/v3/tests/responses/projects-list.json index 2313c2247cb..624cc03ba9b 100644 --- a/readthedocs/api/v3/tests/responses/projects-list.json +++ b/readthedocs/api/v3/tests/responses/projects-list.json @@ -24,10 +24,6 @@ }, "default_version": "latest", "default_branch": "master", - "privacy_level": { - "code": "public", - "name": "Public" - }, "subproject_of": null, "translation_of": null, "urls": { diff --git a/readthedocs/api/v3/tests/responses/projects-list_POST.json b/readthedocs/api/v3/tests/responses/projects-list_POST.json index 20ea4ce9edc..5d2a5613068 100644 --- a/readthedocs/api/v3/tests/responses/projects-list_POST.json +++ b/readthedocs/api/v3/tests/responses/projects-list_POST.json @@ -20,10 +20,6 @@ }, "modified": "2019-04-29T12:00:00Z", "name": "Test Project", - "privacy_level": { - "code": "public", - "name": "Public" - }, "programming_language": { "code": "py", "name": "Python" diff --git a/readthedocs/api/v3/tests/responses/projects-subprojects-list.json b/readthedocs/api/v3/tests/responses/projects-subprojects-list.json index e5df1382523..72fd6345ff1 100644 --- a/readthedocs/api/v3/tests/responses/projects-subprojects-list.json +++ b/readthedocs/api/v3/tests/responses/projects-subprojects-list.json @@ -24,10 +24,6 @@ }, "default_version": "latest", "default_branch": "master", - "privacy_level": { - "code": "public", - "name": "Public" - }, "subproject_of": { "id": 1, "name": "project", @@ -49,10 +45,6 @@ }, "default_version": "latest", "default_branch": "master", - "privacy_level": { - "code": "public", - "name": "Public" - }, "subproject_of": null, "translation_of": null, "urls": { diff --git a/readthedocs/api/v3/tests/responses/projects-superproject.json b/readthedocs/api/v3/tests/responses/projects-superproject.json index 865cf46f812..8c8ce5284c3 100644 --- a/readthedocs/api/v3/tests/responses/projects-superproject.json +++ b/readthedocs/api/v3/tests/responses/projects-superproject.json @@ -19,10 +19,6 @@ }, "modified": "2019-04-29T12:00:00Z", "name": "project", - "privacy_level": { - "code": "public", - "name": "Public" - }, "programming_language": { "code": "words", "name": "Only Words" diff --git a/readthedocs/api/v3/tests/responses/projects-versions-builds-list_POST.json b/readthedocs/api/v3/tests/responses/projects-versions-builds-list_POST.json index 6f099147022..bb86d6345e6 100644 --- a/readthedocs/api/v3/tests/responses/projects-versions-builds-list_POST.json +++ b/readthedocs/api/v3/tests/responses/projects-versions-builds-list_POST.json @@ -40,10 +40,6 @@ }, "modified": "2019-04-29T12:00:00Z", "name": "project", - "privacy_level": { - "code": "public", - "name": "Public" - }, "programming_language": { "code": "words", "name": "Only Words" @@ -85,10 +81,6 @@ "builds": "https://readthedocs.org/api/v3/projects/project/versions/v1.0/builds/", "project": "https://readthedocs.org/api/v3/projects/project/" }, - "privacy_level": { - "code": "public", - "name": "Public" - }, "ref": null, "slug": "v1.0", "type": "tag", diff --git a/readthedocs/api/v3/tests/responses/projects-versions-detail.json b/readthedocs/api/v3/tests/responses/projects-versions-detail.json index 861d951f36e..be23e2cdefe 100644 --- a/readthedocs/api/v3/tests/responses/projects-versions-detail.json +++ b/readthedocs/api/v3/tests/responses/projects-versions-detail.json @@ -9,10 +9,6 @@ "builds": "https://readthedocs.org/api/v3/projects/project/versions/v1.0/builds/", "project": "https://readthedocs.org/api/v3/projects/project/" }, - "privacy_level": { - "code": "public", - "name": "Public" - }, "ref": null, "slug": "v1.0", "type": "tag", diff --git a/readthedocs/api/v3/tests/test_projects.py b/readthedocs/api/v3/tests/test_projects.py index a74aad0ba83..829790e0dfd 100644 --- a/readthedocs/api/v3/tests/test_projects.py +++ b/readthedocs/api/v3/tests/test_projects.py @@ -222,7 +222,7 @@ def test_update_project(self): self.assertEqual(self.project.project_url, 'https://updated-homepage.org') self.assertEqual(self.project.default_version, 'stable') self.assertEqual(self.project.default_branch, 'updated-default-branch') - self.assertEqual(self.project.privacy_level, 'private') + self.assertEqual(self.project.privacy_level, 'public') self.assertEqual(self.project.analytics_code, 'UA-XXXXXX') self.assertEqual(self.project.show_version_warning, False) self.assertEqual(self.project.single_version, True) From 8c3caf8fc2ff569dd173feb99bfb38cafcced1c9 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 9 Oct 2019 11:51:24 +0200 Subject: [PATCH 18/25] Update docs for different Redirect types --- docs/api/v3.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 05a1a56903d..7df3449ff7c 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -652,6 +652,16 @@ Redirect create "type": "page" } + .. note:: + + ``type`` can be one of ``prefix``, ``page``, ``exact``, ``sphinx_html`` and ``sphinx_htmldir``. + + Depending on the ``type`` of the redirect, some fields may not be needed: + + * ``prefix`` type does not require ``to_url``. + * ``page`` and ``exact`` types require ``from_url`` and ``to_url`` + * ``sphinx_html`` and ``sphinx_htmldir`` types do not require ``from_url`` and ``to_url`` + **Example response**: `See Redirect details <#redirect-details>`_ From 03f2d2919d186f4d04ac524b10dd5f653755110b Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 9 Oct 2019 11:51:43 +0200 Subject: [PATCH 19/25] Add more redirect creation via APIv3 tests --- readthedocs/api/v3/tests/test_redirects.py | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/readthedocs/api/v3/tests/test_redirects.py b/readthedocs/api/v3/tests/test_redirects.py index e14f485e64e..02aa768fc83 100644 --- a/readthedocs/api/v3/tests/test_redirects.py +++ b/readthedocs/api/v3/tests/test_redirects.py @@ -1,6 +1,8 @@ from .mixins import APIEndpointMixin from django.urls import reverse +from readthedocs.redirects.models import Redirect + class RedirectsEndpointTests(APIEndpointMixin): @@ -117,6 +119,56 @@ def test_projects_redirects_list_post(self): self._get_response_dict('projects-redirects-list_POST'), ) + def test_projects_redirects_type_prefix_list_post(self): + self.assertEqual(Redirect.objects.count(), 1) + data = { + 'from_url': '/redirect-this/', + 'type': 'prefix', + } + + self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') + response = self.client.post( + reverse( + 'projects-redirects-list', + kwargs={ + 'parent_lookup_project__slug': self.project.slug, + }, + ), + data, + ) + self.assertEqual(response.status_code, 201) + self.assertEqual(Redirect.objects.all().count(), 2) + + redirect = Redirect.objects.first() + self.assertEqual(redirect.redirect_type, 'prefix') + self.assertEqual(redirect.from_url, '/redirect-this/') + self.assertEqual(redirect.to_url, '') + + def test_projects_redirects_type_sphinx_html_list_post(self): + self.assertEqual(Redirect.objects.count(), 1) + data = { + 'type': 'sphinx_html', + } + + self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}') + response = self.client.post( + reverse( + 'projects-redirects-list', + kwargs={ + 'parent_lookup_project__slug': self.project.slug, + }, + ), + data, + ) + self.assertEqual(response.status_code, 201) + self.assertEqual(Redirect.objects.all().count(), 2) + + redirect = Redirect.objects.first() + self.assertEqual(redirect.redirect_type, 'sphinx_html') + self.assertEqual(redirect.from_url, '') + self.assertEqual(redirect.to_url, '') + + def test_projects_redirects_detail_put(self): data = { 'from_url': '/changed/', From e3e5d73332a3bc863efbb2a94d982d48c040f810 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Oct 2019 10:07:30 +0200 Subject: [PATCH 20/25] Valid status codes for privacy URL tests - 405 when authenticated performing a GET. - 302 when un-athenticated performing a GET. --- readthedocs/rtd_tests/tests/test_privacy_urls.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py index d13464cda02..60ab4fdaff4 100644 --- a/readthedocs/rtd_tests/tests/test_privacy_urls.py +++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py @@ -435,13 +435,19 @@ class PrivateUserProfileMixin(URLAccessMixin): def setUp(self): super().setUp() + + self.response_data.update({ + '/accounts/tokens/create/': {'status_code': 405}, + '/accounts/tokens/delete/': {'status_code': 405}, + }) + self.default_kwargs.update( { 'username': self.tester.username, } ) - def test_public_urls(self): + def test_private_urls(self): from readthedocs.profiles.urls.private import urlpatterns self._test_url(urlpatterns) @@ -469,6 +475,14 @@ class PrivateUserProfileUnauthAccessTest(PrivateUserProfileMixin, TestCase): # Auth protected default_status_code = 302 + def setUp(self): + super().setUp() + + self.response_data.update({ + '/accounts/tokens/create/': {'status_code': 302}, + '/accounts/tokens/delete/': {'status_code': 302}, + }) + def login(self): pass From 7aa32ca663eb55acb7b669a8046e69509cfcc510 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Oct 2019 10:17:30 +0200 Subject: [PATCH 21/25] Update APIv3 design document --- docs/development/design/apiv3.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/development/design/apiv3.rst b/docs/development/design/apiv3.rst index 618023a2670..8c31e817498 100644 --- a/docs/development/design/apiv3.rst +++ b/docs/development/design/apiv3.rst @@ -70,10 +70,6 @@ Version 1 The first implementation of APIv3 will cover the following aspects: -.. note:: - - This is currently implemented and live. Although, it's only for internal testing. - * Authentication * all endpoints require authentication via ``Authorization:`` request header @@ -109,6 +105,10 @@ The first implementation of APIv3 will cover the following aspects: Version 2 +++++++++ +.. note:: + + This is currently implemented and live. + Second iteration will polish issues found from the first step, and add new endpoints to allow *import a project and configure it* without the needed of using the WebUI as a main goal. @@ -124,6 +124,7 @@ This iteration will include: * Edit Project attributes ("Settings" and "Advanced settings-Global settings" in the WebUI) * Trigger Build for default version * Allow CRUD for Redirect, Environment Variables and Notifications (``WebHook`` and ``EmailHook``) +* Create/Delete a Project as subproject of another Project * Documentation From 9bb7cb2b2073a7072808afc98429b641f9a31324 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 22 Oct 2019 19:39:17 +0200 Subject: [PATCH 22/25] Apply suggestions from code review Co-Authored-By: Santos Gallegos --- docs/api/v3.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 6944ad0965e..6463d8aba93 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -276,8 +276,8 @@ Versions listing "results": ["VERSION"] } - :query boolean active: return active versions only - :query boolean built: return only built version + :query boolean active: return only active versions + :query boolean built: return only built versions Version detail @@ -734,8 +734,8 @@ Redirect create Depending on the ``type`` of the redirect, some fields may not be needed: * ``prefix`` type does not require ``to_url``. - * ``page`` and ``exact`` types require ``from_url`` and ``to_url`` - * ``sphinx_html`` and ``sphinx_htmldir`` types do not require ``from_url`` and ``to_url`` + * ``page`` and ``exact`` types require ``from_url`` and ``to_url``. + * ``sphinx_html`` and ``sphinx_htmldir`` types do not require ``from_url`` and ``to_url``. **Example response**: From 6b889866e17d68558303b6916b40762809c32a50 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 22 Oct 2019 19:42:57 +0200 Subject: [PATCH 23/25] Re-add "Subproject details" section in docs It was removed by mistake. Probably while solving a merge conflict --- docs/api/v3.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/api/v3.rst b/docs/api/v3.rst index 6944ad0965e..c685ddaa642 100644 --- a/docs/api/v3.rst +++ b/docs/api/v3.rst @@ -523,6 +523,34 @@ 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 " 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/" + } + } + + Subprojects listing +++++++++++++++++++ From b7adaf6cb06e83165a2d53827609a68606aa2871 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 22 Oct 2019 23:23:07 +0200 Subject: [PATCH 24/25] Fix tests introduced in merge conflict --- readthedocs/profiles/views.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py index c4a7c204e25..eedc5b3542e 100644 --- a/readthedocs/profiles/views.py +++ b/readthedocs/profiles/views.py @@ -4,11 +4,18 @@ from django.contrib.auth import logout from django.contrib.auth.models import User from django.contrib.messages.views import SuccessMessageMixin +from django.http import HttpResponseRedirect from django.urls import reverse from django.utils.translation import ugettext_lazy as _ -from django.views.generic import ListView, DeleteView, View from rest_framework.authtoken.models import Token -from vanilla import DetailView, FormView, ListView, UpdateView +from vanilla import ( + CreateView, + DeleteView, + DetailView, + FormView, + ListView, + UpdateView +) from readthedocs.core.forms import ( UserAdvertisingForm, @@ -109,7 +116,7 @@ class TokenListView(TokenMixin, ListView): pass -class TokenCreateView(TokenMixin, View): +class TokenCreateView(TokenMixin, CreateView): """Simple view to generate a Token object for the logged in User.""" From 4c40669cc9e48e2b4c4b87c3dfd2b784c8e640ee Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 28 Oct 2019 12:01:23 +0100 Subject: [PATCH 25/25] Ignore linting in a method override --- readthedocs/profiles/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py index eedc5b3542e..b37ac7eafe3 100644 --- a/readthedocs/profiles/views.py +++ b/readthedocs/profiles/views.py @@ -135,5 +135,5 @@ class TokenDeleteView(TokenMixin, DeleteView): http_method_names = ['post'] - def get_object(self, queryset=None): + def get_object(self, queryset=None): # noqa return self.request.user.auth_token