Skip to content

Commit 82347f3

Browse files
authored
Merge pull request #5913 from readthedocs/humitos/apiv3-environmentvariables
APIv3 endpoint to manage Environment Variables
2 parents 96e2ad7 + dfab337 commit 82347f3

15 files changed

+293
-12
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

@@ -325,6 +325,7 @@ class ProjectLinksSerializer(BaseLinksSerializer):
325325

326326
versions = serializers.SerializerMethodField()
327327
builds = serializers.SerializerMethodField()
328+
environmentvariables = serializers.SerializerMethodField()
328329
redirects = serializers.SerializerMethodField()
329330
subprojects = serializers.SerializerMethodField()
330331
superproject = serializers.SerializerMethodField()
@@ -343,6 +344,15 @@ def get_versions(self, obj):
343344
)
344345
return self._absolute_url(path)
345346

347+
def get_environmentvariables(self, obj):
348+
path = reverse(
349+
'projects-environmentvariables-list',
350+
kwargs={
351+
'parent_lookup_project__slug': obj.slug,
352+
},
353+
)
354+
return self._absolute_url(path)
355+
346356
def get_redirects(self, obj):
347357
path = reverse(
348358
'projects-redirects-list',
@@ -550,3 +560,46 @@ def get_from_url(self, obj):
550560
def get_to_url(self, obj):
551561
# Overridden only to return ``None`` when the description is ``''``
552562
return obj.to_url or None
563+
564+
565+
class EnvironmentVariableLinksSerializer(BaseLinksSerializer):
566+
_self = serializers.SerializerMethodField()
567+
project = serializers.SerializerMethodField()
568+
569+
def get__self(self, obj):
570+
path = reverse(
571+
'projects-environmentvariables-detail',
572+
kwargs={
573+
'parent_lookup_project__slug': obj.project.slug,
574+
'environmentvariable_pk': obj.pk,
575+
},
576+
)
577+
return self._absolute_url(path)
578+
579+
def get_project(self, obj):
580+
path = reverse(
581+
'projects-detail',
582+
kwargs={
583+
'project_slug': obj.project.slug,
584+
},
585+
)
586+
return self._absolute_url(path)
587+
588+
589+
class EnvironmentVariableSerializer(serializers.ModelSerializer):
590+
591+
value = serializers.CharField(write_only=True)
592+
project = serializers.SlugRelatedField(slug_field='slug', read_only=True)
593+
_links = EnvironmentVariableLinksSerializer(source='*', read_only=True)
594+
595+
class Meta:
596+
model = EnvironmentVariable
597+
fields = [
598+
'pk',
599+
'created',
600+
'modified',
601+
'name',
602+
'value',
603+
'project',
604+
'_links',
605+
]

readthedocs/api/v3/tests/mixins.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ class APIEndpointMixin(TestCase):
2020
fixtures = []
2121

2222
def setUp(self):
23-
created = make_aware(datetime.datetime(2019, 4, 29, 10, 0, 0))
24-
modified = make_aware(datetime.datetime(2019, 4, 29, 12, 0, 0))
23+
self.created = make_aware(datetime.datetime(2019, 4, 29, 10, 0, 0))
24+
self.modified = make_aware(datetime.datetime(2019, 4, 29, 12, 0, 0))
2525

2626
self.me = fixture.get(
2727
User,
28-
date_joined=created,
28+
date_joined=self.created,
2929
username='testuser',
3030
projects=[],
3131
)
@@ -34,8 +34,8 @@ def setUp(self):
3434
# objects (like a Project for translations/subprojects)
3535
self.project = fixture.get(
3636
Project,
37-
pub_date=created,
38-
modified_date=modified,
37+
pub_date=self.created,
38+
modified_date=self.modified,
3939
description='Project description',
4040
repo='https://github.com/rtfd/project',
4141
project_url='http://project.com',
@@ -51,8 +51,8 @@ def setUp(self):
5151

5252
self.redirect = fixture.get(
5353
Redirect,
54-
create_dt=created,
55-
update_dt=modified,
54+
create_dt=self.created,
55+
update_dt=self.modified,
5656
from_url='/docs/',
5757
to_url='/documentation/',
5858
redirect_type='page',
@@ -61,8 +61,8 @@ def setUp(self):
6161

6262
self.subproject = fixture.get(
6363
Project,
64-
pub_date=created,
65-
modified_date=modified,
64+
pub_date=self.created,
65+
modified_date=self.modified,
6666
description='SubProject description',
6767
repo='https://github.com/rtfd/subproject',
6868
project_url='http://subproject.com',
@@ -91,7 +91,7 @@ def setUp(self):
9191

9292
self.build = fixture.get(
9393
Build,
94-
date=created,
94+
date=self.created,
9595
type='html',
9696
state='finished',
9797
error='',

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"_links": {
6161
"_self": "https://readthedocs.org/api/v3/projects/project/",
6262
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
63+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
6364
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
6465
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
6566
"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
@@ -49,6 +49,7 @@
4949
"_self": "https://readthedocs.org/api/v3/projects/project/",
5050
"versions": "https://readthedocs.org/api/v3/projects/project/versions/",
5151
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
52+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
5253
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
5354
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
5455
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",

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

Lines changed: 1 addition & 0 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/",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"_self": "https://readthedocs.org/api/v3/projects/project/",
6969
"versions": "https://readthedocs.org/api/v3/projects/project/versions/",
7070
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
71+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
7172
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
7273
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
7374
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
@@ -91,6 +92,7 @@
9192
"_self": "https://readthedocs.org/api/v3/projects/subproject/",
9293
"versions": "https://readthedocs.org/api/v3/projects/subproject/versions/",
9394
"builds": "https://readthedocs.org/api/v3/projects/subproject/builds/",
95+
"environmentvariables": "https://readthedocs.org/api/v3/projects/subproject/environmentvariables/",
9496
"redirects": "https://readthedocs.org/api/v3/projects/subproject/redirects/",
9597
"subprojects": "https://readthedocs.org/api/v3/projects/subproject/subprojects/",
9698
"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
@@ -11,6 +11,7 @@
1111
"_links": {
1212
"_self": "https://readthedocs.org/api/v3/projects/project/",
1313
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
14+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
1415
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
1516
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
1617
"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
@@ -32,6 +32,7 @@
3232
"_links": {
3333
"_self": "https://readthedocs.org/api/v3/projects/project/",
3434
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
35+
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
3536
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
3637
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
3738
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
from .mixins import APIEndpointMixin
2+
from django.urls import reverse
3+
4+
import django_dynamic_fixture as fixture
5+
from readthedocs.projects.models import EnvironmentVariable
6+
7+
8+
class EnvironmentVariablessEndpointTests(APIEndpointMixin):
9+
10+
def setUp(self):
11+
super().setUp()
12+
13+
self.environmentvariable = fixture.get(
14+
EnvironmentVariable,
15+
created=self.created,
16+
modified=self.modified,
17+
project=self.project,
18+
name='ENVVAR',
19+
value='a1b2c3',
20+
)
21+
22+
def test_unauthed_projects_environmentvariables_list(self):
23+
response = self.client.get(
24+
reverse(
25+
'projects-environmentvariables-list',
26+
kwargs={
27+
'parent_lookup_project__slug': self.project.slug,
28+
}),
29+
)
30+
self.assertEqual(response.status_code, 401)
31+
32+
def test_projects_environmentvariables_list(self):
33+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
34+
response = self.client.get(
35+
reverse(
36+
'projects-environmentvariables-list',
37+
kwargs={
38+
'parent_lookup_project__slug': self.project.slug,
39+
}),
40+
)
41+
self.assertEqual(response.status_code, 200)
42+
43+
response_json = response.json()
44+
self.assertDictEqual(
45+
response_json,
46+
self._get_response_dict('projects-environmentvariables-list'),
47+
)
48+
49+
def test_unauthed_projects_environmentvariables_detail(self):
50+
response = self.client.get(
51+
reverse(
52+
'projects-environmentvariables-detail',
53+
kwargs={
54+
'parent_lookup_project__slug': self.project.slug,
55+
'environmentvariable_pk': self.environmentvariable.pk,
56+
}),
57+
)
58+
self.assertEqual(response.status_code, 401)
59+
60+
def test_projects_environmentvariables_detail(self):
61+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
62+
response = self.client.get(
63+
reverse(
64+
'projects-environmentvariables-detail',
65+
kwargs={
66+
'parent_lookup_project__slug': self.project.slug,
67+
'environmentvariable_pk': self.environmentvariable.pk,
68+
}),
69+
)
70+
self.assertEqual(response.status_code, 200)
71+
72+
response_json = response.json()
73+
self.assertDictEqual(
74+
response_json,
75+
self._get_response_dict('projects-environmentvariables-detail'),
76+
)
77+
78+
def test_unauthed_projects_environmentvariables_list_post(self):
79+
data = {}
80+
81+
response = self.client.post(
82+
reverse(
83+
'projects-environmentvariables-list',
84+
kwargs={
85+
'parent_lookup_project__slug': self.others_project.slug,
86+
}),
87+
data,
88+
)
89+
self.assertEqual(response.status_code, 401)
90+
91+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
92+
response = self.client.post(
93+
reverse(
94+
'projects-environmentvariables-list',
95+
kwargs={
96+
'parent_lookup_project__slug': self.others_project.slug,
97+
}),
98+
data,
99+
)
100+
self.assertEqual(response.status_code, 403)
101+
102+
def test_projects_environmentvariables_list_post(self):
103+
self.assertEqual(self.project.environmentvariable_set.count(), 1)
104+
data = {
105+
'name': 'NEWENVVAR',
106+
'value': 'c3b2a1',
107+
}
108+
109+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
110+
response = self.client.post(
111+
reverse(
112+
'projects-environmentvariables-list',
113+
kwargs={
114+
'parent_lookup_project__slug': self.project.slug,
115+
}),
116+
data,
117+
)
118+
self.assertEqual(self.project.environmentvariable_set.count(), 2)
119+
self.assertEqual(response.status_code, 201)
120+
121+
environmentvariable = self.project.environmentvariable_set.get(name='NEWENVVAR')
122+
self.assertEqual(environmentvariable.value, 'c3b2a1')
123+
124+
response_json = response.json()
125+
response_json['created'] = '2019-04-29T10:00:00Z'
126+
response_json['modified'] = '2019-04-29T12:00:00Z'
127+
self.assertDictEqual(
128+
response_json,
129+
self._get_response_dict('projects-environmentvariables-list_POST'),
130+
)
131+
132+
def test_projects_environmentvariables_detail_delete(self):
133+
self.assertEqual(self.project.environmentvariable_set.count(), 1)
134+
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
135+
response = self.client.delete(
136+
reverse(
137+
'projects-environmentvariables-detail',
138+
kwargs={
139+
'parent_lookup_project__slug': self.project.slug,
140+
'environmentvariable_pk': self.environmentvariable.pk,
141+
}),
142+
)
143+
self.assertEqual(response.status_code, 204)
144+
self.assertEqual(self.project.environmentvariable_set.count(), 0)

0 commit comments

Comments
 (0)