-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
|
||
|
@@ -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() | ||
|
@@ -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', | ||
|
@@ -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): | ||
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', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
'_links', | ||
] |
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" | ||
} |
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 needget_
and the attribute...