Skip to content

APIv3 endpoint to manage Environment Variables #5913

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion readthedocs/api/v3/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

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


Expand Down Expand Up @@ -325,6 +325,7 @@ class ProjectLinksSerializer(BaseLinksSerializer):

versions = serializers.SerializerMethodField()
builds = serializers.SerializerMethodField()
environmentvariables = serializers.SerializerMethodField()
redirects = serializers.SerializerMethodField()
subprojects = serializers.SerializerMethodField()
superproject = serializers.SerializerMethodField()
Expand All @@ -343,6 +344,15 @@ def get_versions(self, obj):
)
return self._absolute_url(path)

def get_environmentvariables(self, obj):
path = reverse(
'projects-environmentvariables-list',
kwargs={
'parent_lookup_project__slug': obj.slug,
},
)
return self._absolute_url(path)

def get_redirects(self, obj):
path = reverse(
'projects-redirects-list',
Expand Down Expand Up @@ -550,3 +560,46 @@ def get_from_url(self, obj):
def get_to_url(self, obj):
# Overridden only to return ``None`` when the description is ``''``
return obj.to_url or None


class EnvironmentVariableLinksSerializer(BaseLinksSerializer):
_self = serializers.SerializerMethodField()
project = serializers.SerializerMethodField()

def get__self(self, obj):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 underscsores is weird here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. This is because we are using _self as our attribute since it's kind of a "special name". Then, we need get_ and the attribute...

path = reverse(
'projects-environmentvariables-detail',
kwargs={
'parent_lookup_project__slug': obj.project.slug,
'environmentvariable_pk': obj.pk,
},
)
return self._absolute_url(path)

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


class EnvironmentVariableSerializer(serializers.ModelSerializer):

value = serializers.CharField(write_only=True)
project = serializers.SlugRelatedField(slug_field='slug', read_only=True)
_links = EnvironmentVariableLinksSerializer(source='*', read_only=True)

class Meta:
model = EnvironmentVariable
fields = [
'pk',
'created',
'modified',
'name',
'value',
'project',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need the project here? (haven't seen the other endpoints)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just make sure it's the same everywhere, unless we have a good reason (and a code comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project is the main relation with the object(s). I think it's good to have it here. All the other responses also relates with the project.

Actually, VersionSerializer is the only one that does not returns project. I think I should add it there as well to keep consistency.

'_links',
]
20 changes: 10 additions & 10 deletions readthedocs/api/v3/tests/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ class APIEndpointMixin(TestCase):
fixtures = []

def setUp(self):
created = make_aware(datetime.datetime(2019, 4, 29, 10, 0, 0))
modified = make_aware(datetime.datetime(2019, 4, 29, 12, 0, 0))
self.created = make_aware(datetime.datetime(2019, 4, 29, 10, 0, 0))
self.modified = make_aware(datetime.datetime(2019, 4, 29, 12, 0, 0))

self.me = fixture.get(
User,
date_joined=created,
date_joined=self.created,
username='testuser',
projects=[],
)
Expand All @@ -34,8 +34,8 @@ def setUp(self):
# objects (like a Project for translations/subprojects)
self.project = fixture.get(
Project,
pub_date=created,
modified_date=modified,
pub_date=self.created,
modified_date=self.modified,
description='Project description',
repo='https://github.com/rtfd/project',
project_url='http://project.com',
Expand All @@ -51,8 +51,8 @@ def setUp(self):

self.redirect = fixture.get(
Redirect,
create_dt=created,
update_dt=modified,
create_dt=self.created,
update_dt=self.modified,
from_url='/docs/',
to_url='/documentation/',
redirect_type='page',
Expand All @@ -61,8 +61,8 @@ def setUp(self):

self.subproject = fixture.get(
Project,
pub_date=created,
modified_date=modified,
pub_date=self.created,
modified_date=self.modified,
description='SubProject description',
repo='https://github.com/rtfd/subproject',
project_url='http://subproject.com',
Expand Down Expand Up @@ -91,7 +91,7 @@ def setUp(self):

self.build = fixture.get(
Build,
date=created,
date=self.created,
type='html',
state='finished',
error='',
Expand Down
1 change: 1 addition & 0 deletions readthedocs/api/v3/tests/responses/projects-detail.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/project/",
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/project/environmentvariables/1/",
"project": "https://readthedocs.org/api/v3/projects/project/"
},
"created": "2019-04-29T10:00:00Z",
"modified": "2019-04-29T12:00:00Z",
"pk": 1,
"project": "project",
"name": "ENVVAR"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/project/environmentvariables/1/",
"project": "https://readthedocs.org/api/v3/projects/project/"
},
"created": "2019-04-29T10:00:00Z",
"modified": "2019-04-29T12:00:00Z",
"pk": 1,
"project": "project",
"name": "ENVVAR"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/project/environmentvariables/2/",
"project": "https://readthedocs.org/api/v3/projects/project/"
},
"created": "2019-04-29T10:00:00Z",
"modified": "2019-04-29T12:00:00Z",
"pk": 2,
"project": "project",
"name": "NEWENVVAR"
}
1 change: 1 addition & 0 deletions readthedocs/api/v3/tests/responses/projects-list.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"_self": "https://readthedocs.org/api/v3/projects/project/",
"versions": "https://readthedocs.org/api/v3/projects/project/versions/",
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
Expand Down
1 change: 1 addition & 0 deletions readthedocs/api/v3/tests/responses/projects-list_POST.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/test-project/",
"builds": "https://readthedocs.org/api/v3/projects/test-project/builds/",
"environmentvariables": "https://readthedocs.org/api/v3/projects/test-project/environmentvariables/",
"redirects": "https://readthedocs.org/api/v3/projects/test-project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/test-project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/test-project/superproject/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"_self": "https://readthedocs.org/api/v3/projects/project/",
"versions": "https://readthedocs.org/api/v3/projects/project/versions/",
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
Expand All @@ -91,6 +92,7 @@
"_self": "https://readthedocs.org/api/v3/projects/subproject/",
"versions": "https://readthedocs.org/api/v3/projects/subproject/versions/",
"builds": "https://readthedocs.org/api/v3/projects/subproject/builds/",
"environmentvariables": "https://readthedocs.org/api/v3/projects/subproject/environmentvariables/",
"redirects": "https://readthedocs.org/api/v3/projects/subproject/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/subproject/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/subproject/superproject/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/project/",
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"_links": {
"_self": "https://readthedocs.org/api/v3/projects/project/",
"builds": "https://readthedocs.org/api/v3/projects/project/builds/",
"environmentvariables": "https://readthedocs.org/api/v3/projects/project/environmentvariables/",
"redirects": "https://readthedocs.org/api/v3/projects/project/redirects/",
"subprojects": "https://readthedocs.org/api/v3/projects/project/subprojects/",
"superproject": "https://readthedocs.org/api/v3/projects/project/superproject/",
Expand Down
144 changes: 144 additions & 0 deletions readthedocs/api/v3/tests/test_environmentvariables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
from .mixins import APIEndpointMixin
from django.urls import reverse

import django_dynamic_fixture as fixture
from readthedocs.projects.models import EnvironmentVariable


class EnvironmentVariablessEndpointTests(APIEndpointMixin):

def setUp(self):
super().setUp()

self.environmentvariable = fixture.get(
EnvironmentVariable,
created=self.created,
modified=self.modified,
project=self.project,
name='ENVVAR',
value='a1b2c3',
)

def test_unauthed_projects_environmentvariables_list(self):
response = self.client.get(
reverse(
'projects-environmentvariables-list',
kwargs={
'parent_lookup_project__slug': self.project.slug,
}),
)
self.assertEqual(response.status_code, 401)

def test_projects_environmentvariables_list(self):
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
response = self.client.get(
reverse(
'projects-environmentvariables-list',
kwargs={
'parent_lookup_project__slug': self.project.slug,
}),
)
self.assertEqual(response.status_code, 200)

response_json = response.json()
self.assertDictEqual(
response_json,
self._get_response_dict('projects-environmentvariables-list'),
)

def test_unauthed_projects_environmentvariables_detail(self):
response = self.client.get(
reverse(
'projects-environmentvariables-detail',
kwargs={
'parent_lookup_project__slug': self.project.slug,
'environmentvariable_pk': self.environmentvariable.pk,
}),
)
self.assertEqual(response.status_code, 401)

def test_projects_environmentvariables_detail(self):
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
response = self.client.get(
reverse(
'projects-environmentvariables-detail',
kwargs={
'parent_lookup_project__slug': self.project.slug,
'environmentvariable_pk': self.environmentvariable.pk,
}),
)
self.assertEqual(response.status_code, 200)

response_json = response.json()
self.assertDictEqual(
response_json,
self._get_response_dict('projects-environmentvariables-detail'),
)

def test_unauthed_projects_environmentvariables_list_post(self):
data = {}

response = self.client.post(
reverse(
'projects-environmentvariables-list',
kwargs={
'parent_lookup_project__slug': self.others_project.slug,
}),
data,
)
self.assertEqual(response.status_code, 401)

self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
response = self.client.post(
reverse(
'projects-environmentvariables-list',
kwargs={
'parent_lookup_project__slug': self.others_project.slug,
}),
data,
)
self.assertEqual(response.status_code, 403)

def test_projects_environmentvariables_list_post(self):
self.assertEqual(self.project.environmentvariable_set.count(), 1)
data = {
'name': 'NEWENVVAR',
'value': 'c3b2a1',
}

self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
response = self.client.post(
reverse(
'projects-environmentvariables-list',
kwargs={
'parent_lookup_project__slug': self.project.slug,
}),
data,
)
self.assertEqual(self.project.environmentvariable_set.count(), 2)
self.assertEqual(response.status_code, 201)

environmentvariable = self.project.environmentvariable_set.get(name='NEWENVVAR')
self.assertEqual(environmentvariable.value, 'c3b2a1')

response_json = response.json()
response_json['created'] = '2019-04-29T10:00:00Z'
response_json['modified'] = '2019-04-29T12:00:00Z'
self.assertDictEqual(
response_json,
self._get_response_dict('projects-environmentvariables-list_POST'),
)

def test_projects_environmentvariables_detail_delete(self):
self.assertEqual(self.project.environmentvariable_set.count(), 1)
self.client.credentials(HTTP_AUTHORIZATION=f'Token {self.token.key}')
response = self.client.delete(
reverse(
'projects-environmentvariables-detail',
kwargs={
'parent_lookup_project__slug': self.project.slug,
'environmentvariable_pk': self.environmentvariable.pk,
}),
)
self.assertEqual(response.status_code, 204)
self.assertEqual(self.project.environmentvariable_set.count(), 0)
Loading