Commit 7afd9918 by Dave Chamberlain

Added simple endpoint for PATCH for course_runs api to allow the…

    Added simple endpoint for PATCH for course_runs api to allow the modification of existing fields.
    Changed the way permissions are handled
    inccoporated feedback
    Fixed a bunch of unit tests to use the pytest style assertions

    ECOM-7024
parent 8329583e
...@@ -39,7 +39,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -39,7 +39,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
""" Verify the endpoint returns the details for a single course. """ """ Verify the endpoint returns the details for a single course. """
url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key}) url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key})
with self.assertNumQueries(9): with self.assertNumQueries(10):
response = self.client.get(url) response = self.client.get(url)
assert response.status_code == 200 assert response.status_code == 200
...@@ -51,7 +51,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -51,7 +51,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key}) url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key})
with self.assertNumQueries(12): with self.assertNumQueries(13):
response = self.client.get(url) response = self.client.get(url)
assert response.status_code == 200 assert response.status_code == 200
assert response.data.get('programs') == [] assert response.data.get('programs') == []
...@@ -66,7 +66,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -66,7 +66,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key}) url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key})
url += '?include_deleted_programs=1' url += '?include_deleted_programs=1'
with self.assertNumQueries(12): with self.assertNumQueries(13):
response = self.client.get(url) response = self.client.get(url)
assert response.status_code == 200 assert response.status_code == 200
assert response.data == \ assert response.data == \
...@@ -78,10 +78,10 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -78,10 +78,10 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key}) url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key})
with self.assertNumQueries(12): with self.assertNumQueries(13):
response = self.client.get(url) response = self.client.get(url)
assert response.status_code == 200 assert response.status_code == 200
self.assertEqual(response.data.get('programs'), []) assert response.data.get('programs') == []
def test_get_include_unpublished_programs(self): def test_get_include_unpublished_programs(self):
""" """
...@@ -93,17 +93,47 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -93,17 +93,47 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key}) url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key})
url += '?include_unpublished_programs=1' url += '?include_unpublished_programs=1'
with self.assertNumQueries(12): with self.assertNumQueries(13):
response = self.client.get(url) response = self.client.get(url)
assert response.status_code == 200 assert response.status_code == 200
assert response.data == \ assert response.data == \
self.serialize_course_run(self.course_run, extra_context={'include_unpublished_programs': True}) self.serialize_course_run(self.course_run, extra_context={'include_unpublished_programs': True})
def test_partial_update(self):
""" Verify the endpoint supports partially updating a course_run's fields, provided user has permission. """
url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key})
expected_min_effort = 867
expected_max_effort = 5309
data = {
'max_effort': expected_max_effort,
'min_effort': expected_min_effort,
}
# Update this course_run with the new info
response = self.client.patch(url, data, format='json')
assert response.status_code == 200
# refresh and make sure we have the new effort levels
self.course_run.refresh_from_db()
assert self.course_run.max_effort == expected_max_effort
assert self.course_run.min_effort == expected_min_effort
def test_partial_update_bad_permission(self):
""" Verify partially updating will fail if user doesn't have permission. """
user = UserFactory(is_staff=False, is_superuser=False)
self.client.force_authenticate(user)
url = reverse('api:v1:course_run-detail', kwargs={'key': self.course_run.key})
response = self.client.patch(url, {}, format='json')
assert response.status_code == 403
def test_list(self): def test_list(self):
""" Verify the endpoint returns a list of all catalogs. """ """ Verify the endpoint returns a list of all catalogs. """
url = reverse('api:v1:course_run-list') url = reverse('api:v1:course_run-list')
with self.assertNumQueries(11): with self.assertNumQueries(12):
response = self.client.get(url) response = self.client.get(url)
assert response.status_code == 200 assert response.status_code == 200
...@@ -116,7 +146,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -116,7 +146,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
""" Verify the endpoint returns a list of all catalogs sorted by course start date. """ """ Verify the endpoint returns a list of all catalogs sorted by course start date. """
url = '{root}?ordering=start'.format(root=reverse('api:v1:course_run-list')) url = '{root}?ordering=start'.format(root=reverse('api:v1:course_run-list'))
with self.assertNumQueries(11): with self.assertNumQueries(12):
response = self.client.get(url) response = self.client.get(url)
assert response.status_code == 200 assert response.status_code == 200
...@@ -132,7 +162,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -132,7 +162,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
query = 'title:Some random title' query = 'title:Some random title'
url = '{root}?q={query}'.format(root=reverse('api:v1:course_run-list'), query=query) url = '{root}?q={query}'.format(root=reverse('api:v1:course_run-list'), query=query)
with self.assertNumQueries(37): with self.assertNumQueries(38):
response = self.client.get(url) response = self.client.get(url)
actual_sorted = sorted(response.data['results'], key=lambda course_run: course_run['key']) actual_sorted = sorted(response.data['results'], key=lambda course_run: course_run['key'])
...@@ -248,7 +278,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -248,7 +278,7 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
url = '{}?{}'.format(reverse('api:v1:course_run-contains'), qs) url = '{}?{}'.format(reverse('api:v1:course_run-contains'), qs)
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 400) assert response.status_code == 400
def test_contains_multiple_course_runs(self): def test_contains_multiple_course_runs(self):
qs = urllib.parse.urlencode({ qs = urllib.parse.urlencode({
...@@ -281,4 +311,4 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC ...@@ -281,4 +311,4 @@ class CourseRunViewSetTests(SerializationMixin, ElasticsearchTestMixin, APITestC
url = '{}?{}'.format(reverse('api:v1:course_run-contains'), qs) url = '{}?{}'.format(reverse('api:v1:course_run-contains'), qs)
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 400) assert response.status_code == 400
...@@ -2,7 +2,7 @@ from django.db.models.functions import Lower ...@@ -2,7 +2,7 @@ from django.db.models.functions import Lower
from rest_framework import viewsets, status from rest_framework import viewsets, status
from rest_framework.decorators import list_route from rest_framework.decorators import list_route
from rest_framework.filters import DjangoFilterBackend, OrderingFilter from rest_framework.filters import DjangoFilterBackend, OrderingFilter
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated, DjangoModelPermissions
from rest_framework.response import Response from rest_framework.response import Response
from course_discovery.apps.api import filters, serializers from course_discovery.apps.api import filters, serializers
...@@ -14,14 +14,14 @@ from course_discovery.apps.course_metadata.models import CourseRun ...@@ -14,14 +14,14 @@ from course_discovery.apps.course_metadata.models import CourseRun
# pylint: disable=no-member # pylint: disable=no-member
class CourseRunViewSet(PartnerMixin, viewsets.ReadOnlyModelViewSet): class CourseRunViewSet(PartnerMixin, viewsets.ModelViewSet):
""" CourseRun resource. """ """ CourseRun resource. """
filter_backends = (DjangoFilterBackend, OrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
filter_class = filters.CourseRunFilter filter_class = filters.CourseRunFilter
lookup_field = 'key' lookup_field = 'key'
lookup_value_regex = COURSE_RUN_ID_REGEX lookup_value_regex = COURSE_RUN_ID_REGEX
ordering_fields = ('start',) ordering_fields = ('start',)
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated, DjangoModelPermissions)
queryset = CourseRun.objects.all().order_by(Lower('key')) queryset = CourseRun.objects.all().order_by(Lower('key'))
serializer_class = serializers.CourseRunWithProgramsSerializer serializer_class = serializers.CourseRunWithProgramsSerializer
...@@ -127,6 +127,10 @@ class CourseRunViewSet(PartnerMixin, viewsets.ReadOnlyModelViewSet): ...@@ -127,6 +127,10 @@ class CourseRunViewSet(PartnerMixin, viewsets.ReadOnlyModelViewSet):
""" """
return super(CourseRunViewSet, self).list(request, *args, **kwargs) return super(CourseRunViewSet, self).list(request, *args, **kwargs)
def partial_update(self, request, *args, **kwargs):
""" Update one, or more, fields for a course run. """
return super(CourseRunViewSet, self).partial_update(request, *args, **kwargs)
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
""" Retrieve details for a course run. """ """ Retrieve details for a course run. """
return super(CourseRunViewSet, self).retrieve(request, *args, **kwargs) return super(CourseRunViewSet, self).retrieve(request, *args, **kwargs)
......
...@@ -9,6 +9,8 @@ INSTALLED_APPS += [ ...@@ -9,6 +9,8 @@ INSTALLED_APPS += [
'course_discovery.apps.edx_catalog_extensions', 'course_discovery.apps.edx_catalog_extensions',
] ]
DEFAULT_PARTNER_ID = 1
TEST_NON_SERIALIZED_APPS = [ TEST_NON_SERIALIZED_APPS = [
# Prevents the issue described at https://code.djangoproject.com/ticket/23727. # Prevents the issue described at https://code.djangoproject.com/ticket/23727.
'django.contrib.contenttypes', 'django.contrib.contenttypes',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment