Skip to content

Ship API v3 #6169

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 32 commits into from
Oct 30, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b65098c
Ship API v3
humitos Sep 11, 2019
79f7740
Allow users to generate/revoke API Tokens
humitos Sep 11, 2019
6d8b6cd
Test cases for list/create/delete API Tokens
humitos Sep 11, 2019
2163fed
Lint
humitos Sep 12, 2019
4d65213
Update readthedocs/profiles/views.py
humitos Sep 12, 2019
630cf84
Use one step deletion to revoke the token
humitos Sep 12, 2019
f7e168f
Remove redundant documentation and make it cleaner
humitos Oct 1, 2019
1bb3742
Mention where to find the token in the root API URL
humitos Oct 1, 2019
5ab0c5e
Add a tip mentioning that it's possible to browse the API
humitos Oct 1, 2019
9678048
Remove Project docstring (shown in browseable API resource)
humitos Oct 1, 2019
192fe2f
Remove unuseful (Project, Version) filters
humitos Oct 1, 2019
5fe990c
Remove half-baked docs from the Browsable API and make it consistent
humitos Oct 1, 2019
67ac5e5
Build trigger response docs
humitos Oct 1, 2019
8e59cf5
Better docs for version update
humitos Oct 1, 2019
e6f711d
Fix same page link
humitos Oct 1, 2019
536d0a2
Merge branch 'master' of github.com:readthedocs/readthedocs.org into …
humitos Oct 8, 2019
995287f
Remove duplicated references docs
humitos Oct 8, 2019
a888bc8
Remove privacy_level field from APIv3
humitos Oct 8, 2019
8c3caf8
Update docs for different Redirect types
humitos Oct 9, 2019
03f2d29
Add more redirect creation via APIv3 tests
humitos Oct 9, 2019
ffab3b2
Merge branch 'master' of github.com:readthedocs/readthedocs.org into …
humitos Oct 9, 2019
06d31f6
Merge branch 'master' of github.com:readthedocs/readthedocs.org into …
humitos Oct 9, 2019
558e143
Merge branch 'humitos/ship-api-v3' of github.com:readthedocs/readthed…
humitos Oct 9, 2019
9eb8df8
Remove privacy_level field from APIv3 (#6257)
humitos Oct 9, 2019
e3e5d73
Valid status codes for privacy URL tests
humitos Oct 14, 2019
7aa32ca
Update APIv3 design document
humitos Oct 14, 2019
9bb7cb2
Apply suggestions from code review
humitos Oct 22, 2019
6b88986
Re-add "Subproject details" section in docs
humitos Oct 22, 2019
5a398d4
Merge branch 'master' of github.com:readthedocs/readthedocs.org into …
humitos Oct 22, 2019
afcfbf7
Merge branch 'humitos/ship-api-v3' of github.com:readthedocs/readthed…
humitos Oct 22, 2019
b7adaf6
Fix tests introduced in merge conflict
humitos Oct 22, 2019
4c40669
Ignore linting in a method override
humitos Oct 28, 2019
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
1 change: 1 addition & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ from Read the Docs.
:maxdepth: 3

v2
v3
16 changes: 8 additions & 8 deletions docs/api/v2.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
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
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 <https://github.com/readthedocs/readthedocs.org/issues>`_.
See its full documentation at :doc:`/api/v3`.


Authentication and authorization
Expand Down
18 changes: 5 additions & 13 deletions docs/api/v3.rst
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
:orphan:

API v3
======

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:[email protected]?subject=APIv3%20beta%20test

.. contents:: Table of contents
:local:
:backlinks: none
Expand All @@ -37,6 +24,11 @@ Token
The ``Authorization`` HTTP header can be specified with ``Token <your-access-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 <https://readthedocs.org/accounts/tokens/>`_.


Session
~~~~~~~
Expand Down
12 changes: 11 additions & 1 deletion readthedocs/profiles/urls/private.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
43 changes: 32 additions & 11 deletions readthedocs/profiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -213,22 +213,43 @@ 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, queryset=None):
return self.request.user.auth_token
29 changes: 29 additions & 0 deletions readthedocs/rtd_tests/tests/test_profile_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
31 changes: 23 additions & 8 deletions readthedocs/templates/profiles/private/token_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@
{% block edit_content_header %} {% trans "API Tokens" %} {% endblock %}

{% block edit_content %}
<p class="empty">
{% blocktrans trimmed with contact_email="[email protected]" %}
API Tokens are currently an invite-only Beta feature.
In case you want to test APIv3 and give us feedback on it,
please <a href="mailto:{{ contact_email }}">email us</a>.
{% endblocktrans %}
</p>

<p>Personal Access Token are tokens that allow you to use the Read the Docs APIv3 being authenticated as yourself. See <a href="https://docs.readthedocs.org/page/api/v3.html">APIv3 documentation</a> for more information.</p>

{% if not object_list %}
<div class="button-bar">
<ul>
<li>
<form method="post" action="{% url "profiles_tokens_create" %}">
{% csrf_token %}
<input type="submit" value="{% trans "Generate API Token" %}">
</form>
</li>
</ul>
</div>
{% endif %}

<div class="module">
<div class="module-list">
<div class="module-list-wrapper">
Expand All @@ -27,6 +32,16 @@
<li class="module-item">
<div>Created: {{ token.created }}</div>
<div>Token: {{ token.key }}</div>

<ul class="module-item-menu">
<li>
<form method="post" action="{% url "profiles_tokens_delete" %}">
{% csrf_token %}
<input type="submit" value="{% trans "Revoke" %}">
</form>
</li>
</ul>

</li>
{% empty %}
<li class="module-item">
Expand Down