From cb7d8dcb86983f1c1cc255fce92df9269c3f2473 Mon Sep 17 00:00:00 2001 From: Saleem Latif Date: Fri, 22 Sep 2017 14:56:05 +0500 Subject: [PATCH] Create API View for ApiAccessRequest model --- .../core/djangoapps/api_admin/api/__init__.py | 0 .../core/djangoapps/api_admin/api/filters.py | 12 +++ openedx/core/djangoapps/api_admin/api/urls.py | 8 ++ .../djangoapps/api_admin/api/v1/__init__.py | 0 .../api_admin/api/v1/serializers.py | 18 ++++ .../api_admin/api/v1/tests/__init__.py | 0 .../api_admin/api/v1/tests/test_views.py | 97 +++++++++++++++++++ .../core/djangoapps/api_admin/api/v1/urls.py | 11 +++ .../core/djangoapps/api_admin/api/v1/views.py | 59 +++++++++++ openedx/core/djangoapps/api_admin/urls.py | 5 +- 10 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 openedx/core/djangoapps/api_admin/api/__init__.py create mode 100644 openedx/core/djangoapps/api_admin/api/filters.py create mode 100644 openedx/core/djangoapps/api_admin/api/urls.py create mode 100644 openedx/core/djangoapps/api_admin/api/v1/__init__.py create mode 100644 openedx/core/djangoapps/api_admin/api/v1/serializers.py create mode 100644 openedx/core/djangoapps/api_admin/api/v1/tests/__init__.py create mode 100644 openedx/core/djangoapps/api_admin/api/v1/tests/test_views.py create mode 100644 openedx/core/djangoapps/api_admin/api/v1/urls.py create mode 100644 openedx/core/djangoapps/api_admin/api/v1/views.py diff --git a/openedx/core/djangoapps/api_admin/api/__init__.py b/openedx/core/djangoapps/api_admin/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/core/djangoapps/api_admin/api/filters.py b/openedx/core/djangoapps/api_admin/api/filters.py new file mode 100644 index 0000000000..a2599fd22d --- /dev/null +++ b/openedx/core/djangoapps/api_admin/api/filters.py @@ -0,0 +1,12 @@ +from rest_framework import filters + + +class IsOwnerOrStaffFilterBackend(filters.BaseFilterBackend): + """ + Filter that only allows users to see their own objects or all objects if it is staff user. + """ + def filter_queryset(self, request, queryset, view): + if request.user.is_staff: + return queryset + else: + return queryset.filter(user=request.user) diff --git a/openedx/core/djangoapps/api_admin/api/urls.py b/openedx/core/djangoapps/api_admin/api/urls.py new file mode 100644 index 0000000000..b7e2a65a50 --- /dev/null +++ b/openedx/core/djangoapps/api_admin/api/urls.py @@ -0,0 +1,8 @@ +""" +URL definitions for api access request API. +""" +from django.conf.urls import include, url + +urlpatterns = [ + url(r'^v1/', include('openedx.core.djangoapps.api_admin.api.v1.urls', namespace='v1')), +] diff --git a/openedx/core/djangoapps/api_admin/api/v1/__init__.py b/openedx/core/djangoapps/api_admin/api/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/core/djangoapps/api_admin/api/v1/serializers.py b/openedx/core/djangoapps/api_admin/api/v1/serializers.py new file mode 100644 index 0000000000..35d4bea47d --- /dev/null +++ b/openedx/core/djangoapps/api_admin/api/v1/serializers.py @@ -0,0 +1,18 @@ +""" +API v1 serializers. +""" +from rest_framework import serializers + +from openedx.core.djangoapps.api_admin.models import ApiAccessRequest + + +class ApiAccessRequestSerializer(serializers.ModelSerializer): + """ + ApiAccessRequest serializer. + """ + class Meta(object): + model = ApiAccessRequest + fields = ( + 'id', 'created', 'modified', 'user', 'status', 'website', + 'reason', 'company_name', 'company_address', 'site', 'contacted' + ) diff --git a/openedx/core/djangoapps/api_admin/api/v1/tests/__init__.py b/openedx/core/djangoapps/api_admin/api/v1/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openedx/core/djangoapps/api_admin/api/v1/tests/test_views.py b/openedx/core/djangoapps/api_admin/api/v1/tests/test_views.py new file mode 100644 index 0000000000..75c8bc556e --- /dev/null +++ b/openedx/core/djangoapps/api_admin/api/v1/tests/test_views.py @@ -0,0 +1,97 @@ +""" +Tests for the `api_admin` api module. +""" +import json + +from rest_framework.reverse import reverse + +from django.contrib.auth.models import User +from django.test import TestCase + +from openedx.core.djangoapps.api_admin.tests import factories +from openedx.core.djangolib.testing.utils import skip_unless_lms +from student.tests.factories import UserFactory + + +@skip_unless_lms +class ApiAccessRequestViewTests(TestCase): + """ + Tests for API access request api views. + """ + password = 'test' + + def setUp(self): + """ + Perform operations common to all test cases. + """ + self.user = UserFactory.create(password=self.password) + self.client.login(username=self.user.username, password=self.password) + + # Create APIAccessRequest records for testing. + factories.ApiAccessRequestFactory.create_batch(5) + factories.ApiAccessRequestFactory.create(user=self.user) + + self.url = reverse('api_admin:api:v1:list_api_access_request') + + def update_user_and_re_login(self, **kwargs): + """ + Update attributes of currently logged in user. + """ + self.client.logout() + User.objects.filter(id=self.user.id).update(**kwargs) + self.client.login(username=self.user.username, password=self.password) + + def _assert_api_access_request_response(self, api_response, expected_results_count): + """ + Assert API response on `API Access Request` endpoint. + """ + json_content = json.loads(api_response.content) + self.assertEqual(api_response.status_code, 200) + self.assertEqual(json_content['count'], expected_results_count) + + def test_list_view_for_not_authenticated_user(self): + """ + Make sure API end point 'api_access_request' returns access denied if user is not authenticated. + """ + self.update_user_and_re_login(is_staff=False) + + response = self.client.get(self.url) + self._assert_api_access_request_response(api_response=response, expected_results_count=1) + + def test_list_view_for_non_staff_user(self): + """ + Make sure API end point 'api_access_request' returns api access requests made only by the requesting user. + """ + self.client.logout() + + response = self.client.get(self.url) + self.assertEqual(response.status_code, 401) + + def test_list_view_for_staff_user(self): + """ + Make sure API end point 'api_access_request' returns all api access requests to staff user. + """ + self.update_user_and_re_login(is_staff=True) + + response = self.client.get(self.url) + self._assert_api_access_request_response(api_response=response, expected_results_count=6) + + def test_filtering_for_staff_user(self): + """ + Make sure that staff user can filter API Access Requests with username. + """ + self.update_user_and_re_login(is_staff=True) + + response = self.client.get(self.url + '?user__username={}'.format(self.user.username)) + self._assert_api_access_request_response(api_response=response, expected_results_count=1) + + def test_filtering_for_non_existing_user(self): + """ + Make sure that 404 is returned if user does not exist against the username + used for filtering. + """ + self.update_user_and_re_login(is_staff=True) + + response = self.client.get(self.url + '?user__username={}'.format('non-existing-user-name')) + self.assertEqual(response.status_code, 200) + self._assert_api_access_request_response(api_response=response, expected_results_count=0) diff --git a/openedx/core/djangoapps/api_admin/api/v1/urls.py b/openedx/core/djangoapps/api_admin/api/v1/urls.py new file mode 100644 index 0000000000..27c109bc1b --- /dev/null +++ b/openedx/core/djangoapps/api_admin/api/v1/urls.py @@ -0,0 +1,11 @@ +""" +URL definitions for api access request API v1. +""" +from django.conf.urls import patterns, url + +from openedx.core.djangoapps.api_admin.api.v1 import views + +urlpatterns = patterns( + '', + url(r'^api_access_request/$', views.ApiAccessRequestView.as_view(), name='list_api_access_request'), +) diff --git a/openedx/core/djangoapps/api_admin/api/v1/views.py b/openedx/core/djangoapps/api_admin/api/v1/views.py new file mode 100644 index 0000000000..d7fc2f87fe --- /dev/null +++ b/openedx/core/djangoapps/api_admin/api/v1/views.py @@ -0,0 +1,59 @@ +""" +API Views. +""" +from django_filters.rest_framework import DjangoFilterBackend + +from edx_rest_framework_extensions.authentication import JwtAuthentication +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.generics import ListAPIView +from rest_framework_oauth.authentication import OAuth2Authentication + +from openedx.core.djangoapps.api_admin.api.v1 import serializers as api_access_serializers +from openedx.core.djangoapps.api_admin.models import ApiAccessRequest +from openedx.core.djangoapps.api_admin.api.filters import IsOwnerOrStaffFilterBackend + + +class ApiAccessRequestView(ListAPIView): + """ + Return `API Access Requests` in the form of a paginated list. + + Raises: + NotFound: Raised if user with `username` provided in `GET` parameters does not exist. + PermissionDenied: Raised if `username` is provided in `GET` parameters but the requesting + user does not have access rights to filter results. + + Example: + `GET: /api-admin/api/v1/api_access_request/` + { + "count": 1, + "num_pages": 1, + "current_page": 1, + "results": [ + { + "id": 1, + "created": "2017-09-25T08:41:48.934364Z", + "modified": "2017-09-25T08:42:04.185209Z", + "user": 6, + "status": "denied", + "website": "https://www.example.com/", + "reason": "Example", + "company_name": "Example Name", + "company_address": "Silicon Valley", + "site": 1, + "contacted": true + } + ], + "next": null, + "start": 0, + "previous": null + } + """ + authentication_classes = (JwtAuthentication, OAuth2Authentication, SessionAuthentication,) + permission_classes = (IsAuthenticated, ) + serializer_class = api_access_serializers.ApiAccessRequestSerializer + filter_backends = (IsOwnerOrStaffFilterBackend, DjangoFilterBackend) + + queryset = ApiAccessRequest.objects.all() + + filter_fields = ('user__username', 'status', 'company_name', 'site__domain', 'contacted') diff --git a/openedx/core/djangoapps/api_admin/urls.py b/openedx/core/djangoapps/api_admin/urls.py index 8458057c49..4728ca8da9 100644 --- a/openedx/core/djangoapps/api_admin/urls.py +++ b/openedx/core/djangoapps/api_admin/urls.py @@ -1,6 +1,6 @@ """URLs for API access management.""" -from django.conf.urls import url +from django.conf.urls import include, url from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import login_required @@ -67,4 +67,7 @@ urlpatterns = ( api_access_enabled_or_404(login_required(ApiRequestView.as_view())), name="api-request" ), + url( + r'^api/', include('openedx.core.djangoapps.api_admin.api.urls', namespace='api'), + ), )