Skip to content

Commit b0c081b

Browse files
committed
Merge branch 'master' of github.com:rtfd/readthedocs.org into humitos/apiv3-docs
2 parents ecb0b6f + 39c0b56 commit b0c081b

19 files changed

+745
-430
lines changed

readthedocs/api/v3/serializers.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from readthedocs.builds.models import Build, Version
1212
from readthedocs.projects.constants import LANGUAGES, PROGRAMMING_LANGUAGES, REPO_CHOICES
13-
from readthedocs.projects.models import Project
13+
from readthedocs.projects.models import Project, EnvironmentVariable
1414
from readthedocs.redirects.models import Redirect, TYPE_CHOICES as REDIRECT_TYPE_CHOICES
1515

1616

@@ -335,6 +335,7 @@ class ProjectLinksSerializer(BaseLinksSerializer):
335335

336336
versions = serializers.SerializerMethodField()
337337
builds = serializers.SerializerMethodField()
338+
environmentvariables = serializers.SerializerMethodField()
338339
redirects = serializers.SerializerMethodField()
339340
subprojects = serializers.SerializerMethodField()
340341
superproject = serializers.SerializerMethodField()
@@ -353,6 +354,15 @@ def get_versions(self, obj):
353354
)
354355
return self._absolute_url(path)
355356

357+
def get_environmentvariables(self, obj):
358+
path = reverse(
359+
'projects-environmentvariables-list',
360+
kwargs={
361+
'parent_lookup_project__slug': obj.slug,
362+
},
363+
)
364+
return self._absolute_url(path)
365+
356366
def get_redirects(self, obj):
357367
path = reverse(
358368
'projects-redirects-list',
@@ -559,3 +569,46 @@ def get_from_url(self, obj):
559569
def get_to_url(self, obj):
560570
# Overridden only to return ``None`` when the description is ``''``
561571
return obj.to_url or None
572+
573+
574+
class EnvironmentVariableLinksSerializer(BaseLinksSerializer):
575+
_self = serializers.SerializerMethodField()
576+
project = serializers.SerializerMethodField()
577+
578+
def get__self(self, obj):
579+
path = reverse(
580+
'projects-environmentvariables-detail',
581+
kwargs={
582+
'parent_lookup_project__slug': obj.project.slug,
583+
'environmentvariable_pk': obj.pk,
584+
},
585+
)
586+
return self._absolute_url(path)
587+
588+
def get_project(self, obj):
589+
path = reverse(
590+
'projects-detail',
591+
kwargs={
592+
'project_slug': obj.project.slug,
593+
},
594+
)
595+
return self._absolute_url(path)
596+
597+
598+
class EnvironmentVariableSerializer(serializers.ModelSerializer):
599+
600+
value = serializers.CharField(write_only=True)
601+
project = serializers.SlugRelatedField(slug_field='slug', read_only=True)
602+
_links = EnvironmentVariableLinksSerializer(source='*', read_only=True)
603+
604+
class Meta:
605+
model = EnvironmentVariable
606+
fields = [
607+
'pk',
608+
'created',
609+
'modified',
610+
'name',
611+
'value',
612+
'project',
613+
'_links',
614+
]

readthedocs/api/v3/tests/mixins.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import datetime
2+
import json
3+
from pathlib import Path
4+
5+
import django_dynamic_fixture as fixture
6+
from django.contrib.auth.models import User
7+
from django.core.cache import cache
8+
from django.test import TestCase
9+
from django.utils.timezone import make_aware
10+
from rest_framework.authtoken.models import Token
11+
from rest_framework.test import APIClient
12+
13+
from readthedocs.builds.constants import TAG
14+
from readthedocs.builds.models import Build, Version
15+
from readthedocs.projects.models import Project
16+
from readthedocs.redirects.models import Redirect
17+
18+
19+
class APIEndpointMixin(TestCase):
20+
21+
fixtures = []
22+
23+
def setUp(self):
24+
self.created = make_aware(datetime.datetime(2019, 4, 29, 10, 0, 0))
25+
self.modified = make_aware(datetime.datetime(2019, 4, 29, 12, 0, 0))
26+
27+
self.me = fixture.get(
28+
User,
29+
date_joined=self.created,
30+
username='testuser',
31+
projects=[],
32+
)
33+
self.token = fixture.get(Token, key='me', user=self.me)
34+
# Defining all the defaults helps to avoid creating ghost / unwanted
35+
# objects (like a Project for translations/subprojects)
36+
self.project = fixture.get(
37+
Project,
38+
pub_date=self.created,
39+
modified_date=self.modified,
40+
description='Project description',
41+
repo='https://github.com/rtfd/project',
42+
project_url='http://project.com',
43+
name='project',
44+
slug='project',
45+
related_projects=[],
46+
main_language_project=None,
47+
users=[self.me],
48+
versions=[],
49+
)
50+
for tag in ('tag', 'project', 'test'):
51+
self.project.tags.add(tag)
52+
53+
self.redirect = fixture.get(
54+
Redirect,
55+
create_dt=self.created,
56+
update_dt=self.modified,
57+
from_url='/docs/',
58+
to_url='/documentation/',
59+
redirect_type='page',
60+
project=self.project,
61+
)
62+
63+
self.subproject = fixture.get(
64+
Project,
65+
pub_date=self.created,
66+
modified_date=self.modified,
67+
description='SubProject description',
68+
repo='https://github.com/rtfd/subproject',
69+
project_url='http://subproject.com',
70+
name='subproject',
71+
slug='subproject',
72+
related_projects=[],
73+
main_language_project=None,
74+
users=[],
75+
versions=[],
76+
)
77+
self.project.add_subproject(self.subproject)
78+
79+
self.version = fixture.get(
80+
Version,
81+
slug='v1.0',
82+
verbose_name='v1.0',
83+
identifier='a1b2c3',
84+
project=self.project,
85+
active=True,
86+
built=True,
87+
type=TAG,
88+
)
89+
90+
self.build = fixture.get(
91+
Build,
92+
date=self.created,
93+
type='html',
94+
state='finished',
95+
error='',
96+
success=True,
97+
_config = {'property': 'test value'},
98+
version=self.version,
99+
project=self.project,
100+
builder='builder01',
101+
commit='a1b2c3',
102+
length=60,
103+
)
104+
105+
self.other = fixture.get(User, projects=[])
106+
self.others_token = fixture.get(Token, key='other', user=self.other)
107+
self.others_project = fixture.get(
108+
Project,
109+
slug='others_project',
110+
related_projects=[],
111+
main_language_project=None,
112+
users=[self.other],
113+
versions=[],
114+
)
115+
116+
self.client = APIClient()
117+
118+
def tearDown(self):
119+
# Cleanup cache to avoid throttling on tests
120+
cache.clear()
121+
122+
def _get_response_dict(self, view_name):
123+
filename = Path(__file__).absolute().parent / 'responses' / f'{view_name}.json'
124+
return json.load(open(filename))
125+
126+
def assertDictEqual(self, d1, d2):
127+
"""
128+
Show the differences between the dicts in a human readable way.
129+
130+
It's just a helper for debugging API responses.
131+
"""
132+
import datadiff
133+
return super().assertDictEqual(d1, d2, datadiff.diff(d1, d2))

readthedocs/api/v3/tests/responses/projects-detail.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"_links": {
6060
"_self": "https://readthedocs.org/api/v3/projects/project/",
6161
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
62+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
6263
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
6364
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
6465
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"_links": {
3+
"_self": "https://readthedocs.org/api/v3/projects/project/environmentvariables/1/",
4+
"project": "https://readthedocs.org/api/v3/projects/project/"
5+
},
6+
"created": "2019-04-29T10:00:00Z",
7+
"modified": "2019-04-29T12:00:00Z",
8+
"pk": 1,
9+
"project": "project",
10+
"name": "ENVVAR"
11+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"count": 1,
3+
"next": null,
4+
"previous": null,
5+
"results": [
6+
{
7+
"_links": {
8+
"_self": "https://readthedocs.org/api/v3/projects/project/environmentvariables/1/",
9+
"project": "https://readthedocs.org/api/v3/projects/project/"
10+
},
11+
"created": "2019-04-29T10:00:00Z",
12+
"modified": "2019-04-29T12:00:00Z",
13+
"pk": 1,
14+
"project": "project",
15+
"name": "ENVVAR"
16+
}
17+
]
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"_links": {
3+
"_self": "https://readthedocs.org/api/v3/projects/project/environmentvariables/2/",
4+
"project": "https://readthedocs.org/api/v3/projects/project/"
5+
},
6+
"created": "2019-04-29T10:00:00Z",
7+
"modified": "2019-04-29T12:00:00Z",
8+
"pk": 2,
9+
"project": "project",
10+
"name": "NEWENVVAR"
11+
}

readthedocs/api/v3/tests/responses/projects-list.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"_self": "https://readthedocs.org/api/v3/projects/project/",
5151
"versions": "https://readthedocs.org/api/v3/projects/project/versions/",
5252
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
53+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
5354
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
5455
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
5556
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",

readthedocs/api/v3/tests/responses/projects-list_POST.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"_links": {
33
"_self": "https://readthedocs.org/api/v3/projects/test-project/",
44
"builds": "https://readthedocs.org/api/v3/projects/test-project/builds/",
5+
"environmentvariables": "https://readthedocs.org/api/v3/projects/test-project/environmentvariables/",
56
"redirects": "https://readthedocs.org/api/v3/projects/test-project/redirects/",
67
"subprojects": "https://readthedocs.org/api/v3/projects/test-project/subprojects/",
78
"superproject": "https://readthedocs.org/api/v3/projects/test-project/superproject/",
@@ -11,8 +12,8 @@
1112
"created": "2019-04-29T10:00:00Z",
1213
"default_branch": "master",
1314
"default_version": "latest",
14-
"description": null,
1515
"id": 4,
16+
"homepage": "http://template.readthedocs.io/",
1617
"language": {
1718
"code": "en",
1819
"name": "English"
@@ -36,8 +37,10 @@
3637
"tags": [],
3738
"translation_of": null,
3839
"urls": {
40+
"builds": "https://readthedocs.org/projects/test-project/builds/",
3941
"documentation": "http://readthedocs.org/docs/test-project/en/latest/",
40-
"project_homepage": "http://template.readthedocs.io/"
42+
"home": "https://readthedocs.org/projects/test-project/",
43+
"versions": "https://readthedocs.org/projects/test-project/versions/"
4144
},
4245
"users": [
4346
{

readthedocs/api/v3/tests/responses/projects-subprojects-list.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"_self": "https://readthedocs.org/api/v3/projects/project/",
7171
"versions": "https://readthedocs.org/api/v3/projects/project/versions/",
7272
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
73+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
7374
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
7475
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
7576
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
@@ -94,6 +95,7 @@
9495
"_self": "https://readthedocs.org/api/v3/projects/subproject/",
9596
"versions": "https://readthedocs.org/api/v3/projects/subproject/versions/",
9697
"builds": "https://readthedocs.org/api/v3/projects/subproject/builds/",
98+
"environmentvariables": "https://readthedocs.org/api/v3/projects/subproject/environmentvariables/",
9799
"redirects": "https://readthedocs.org/api/v3/projects/subproject/redirects/",
98100
"subprojects": "https://readthedocs.org/api/v3/projects/subproject/subprojects/",
99101
"superproject": "https://readthedocs.org/api/v3/projects/subproject/superproject/",

readthedocs/api/v3/tests/responses/projects-superproject.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"_links": {
1111
"_self": "https://readthedocs.org/api/v3/projects/project/",
1212
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
13+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
1314
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
1415
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
1516
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",

readthedocs/api/v3/tests/responses/projects-versions-builds-list_POST.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"_links": {
3232
"_self": "https://readthedocs.org/api/v3/projects/project/",
3333
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
34+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
3435
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
3536
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
3637
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from .mixins import APIEndpointMixin
2+
from django.urls import reverse
3+
4+
5+
class BuildsEndpointTests(APIEndpointMixin):
6+
7+
def test_projects_builds_list(self):
8+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
9+
response = self.client.get(
10+
reverse(
11+
'projects-builds-list',
12+
kwargs={
13+
'parent_lookup_project__slug': self.project.slug,
14+
}),
15+
)
16+
self.assertEqual(response.status_code, 200)
17+
18+
def test_projects_builds_detail(self):
19+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
20+
response = self.client.get(
21+
reverse(
22+
'projects-builds-detail',
23+
kwargs={
24+
'parent_lookup_project__slug': self.project.slug,
25+
'build_pk': self.build.pk,
26+
}),
27+
)
28+
self.assertEqual(response.status_code, 200)
29+
30+
self.assertDictEqual(
31+
response.json(),
32+
self._get_response_dict('projects-builds-detail'),
33+
)
34+
35+
def test_projects_versions_builds_list_post(self):
36+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
37+
self.assertEqual(self.project.builds.count(), 1)
38+
response = self.client.post(
39+
reverse(
40+
'projects-versions-builds-list',
41+
kwargs={
42+
'parent_lookup_project__slug': self.project.slug,
43+
'parent_lookup_version__slug': self.version.slug,
44+
}),
45+
)
46+
self.assertEqual(response.status_code, 202)
47+
self.assertEqual(self.project.builds.count(), 2)
48+
49+
response_json = response.json()
50+
response_json['build']['created'] = '2019-04-29T14:00:00Z'
51+
self.assertDictEqual(
52+
response_json,
53+
self._get_response_dict('projects-versions-builds-list_POST'),
54+
)

0 commit comments

Comments
 (0)