Commit 2361dd47 by Waheed Ahmed

Added CourseRunState change endpoint.

ECOM-7004
parent 7f2cad81
......@@ -10,7 +10,7 @@ from rest_framework import serializers
from course_discovery.apps.core.models import User
from course_discovery.apps.publisher.emails import send_email_for_studio_instance_created
from course_discovery.apps.publisher.models import CourseUserRole, CourseRun, CourseState
from course_discovery.apps.publisher.models import CourseUserRole, CourseRun, CourseState, CourseRunState
class CourseUserRoleSerializer(serializers.ModelSerializer):
......@@ -128,3 +128,32 @@ class CourseStateSerializer(serializers.ModelSerializer):
)
return instance
class CourseRunStateSerializer(serializers.ModelSerializer):
"""Serializer for `CourseRunState` model to change course-run workflow state. """
class Meta:
model = CourseRunState
fields = ('name', 'approved_by_role', 'owner_role', 'course_run',)
extra_kwargs = {
'course_run': {'read_only': True},
'approved_by_role': {'read_only': True},
'owner_role': {'read_only': True}
}
def update(self, instance, validated_data):
state = validated_data.get('name')
try:
instance.change_state(state=state)
except TransitionNotAllowed:
# pylint: disable=no-member
raise serializers.ValidationError(
{
'name': _('Cannot switch from state `{state}` to `{target_state}`').format(
state=instance.name, target_state=state
)
}
)
return instance
......@@ -5,12 +5,13 @@ from rest_framework.exceptions import ValidationError
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.publisher.api.serializers import (
CourseUserRoleSerializer, GroupUserSerializer, UpdateCourseKeySerializer, CourseRevisionSerializer,
CourseStateSerializer
CourseStateSerializer, CourseRunStateSerializer
)
from course_discovery.apps.publisher.choices import CourseStateChoices, CourseRunStateChoices
from course_discovery.apps.publisher.models import CourseState, CourseRunState
from course_discovery.apps.publisher.tests.factories import (
CourseFactory, CourseRunFactory, CourseRunStateFactory, CourseStateFactory, CourseUserRoleFactory
)
from course_discovery.apps.publisher.tests.factories import CourseFactory
from course_discovery.apps.publisher.choices import CourseStateChoices
from course_discovery.apps.publisher.models import CourseState
from course_discovery.apps.publisher.tests.factories import CourseUserRoleFactory, CourseRunFactory, CourseStateFactory
class CourseUserRoleSerializerTests(TestCase):
......@@ -157,3 +158,33 @@ class CourseStateSerializerTests(TestCase):
with self.assertRaises(ValidationError):
serializer.update(self.course_state, data)
class CourseRunStateSerializerTests(TestCase):
serializer_class = CourseRunStateSerializer
def setUp(self):
super(CourseRunStateSerializerTests, self).setUp()
self.run_state = CourseRunStateFactory(name=CourseRunStateChoices.Draft)
def test_update(self):
"""
Verify that we can update course-run workflow state with serializer.
"""
self.assertNotEqual(self.run_state, CourseRunStateChoices.Review)
serializer = self.serializer_class(self.run_state)
data = {'name': CourseRunStateChoices.Review}
serializer.update(self.run_state, data)
self.run_state = CourseRunState.objects.get(course_run=self.run_state.course_run)
self.assertEqual(self.run_state.name, CourseRunStateChoices.Review)
def test_update_with_error(self):
"""
Verify that serializer raises `ValidationError` with wrong transition.
"""
serializer = self.serializer_class(self.run_state)
data = {'name': CourseRunStateChoices.Published}
with self.assertRaises(ValidationError):
serializer.update(self.run_state, data)
......@@ -14,9 +14,9 @@ from guardian.shortcuts import assign_perm
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
from course_discovery.apps.course_metadata.tests import toggle_switch
from course_discovery.apps.publisher.choices import PublisherUserRole, CourseStateChoices
from course_discovery.apps.publisher.choices import PublisherUserRole, CourseStateChoices, CourseRunStateChoices
from course_discovery.apps.publisher.constants import INTERNAL_USER_GROUP_NAME
from course_discovery.apps.publisher.models import CourseRun, OrganizationExtension, CourseState
from course_discovery.apps.publisher.models import CourseRun, CourseState, CourseRunState, OrganizationExtension
from course_discovery.apps.publisher.tests import factories, JSON_CONTENT_TYPE
......@@ -424,3 +424,54 @@ class ChangeCourseStateViewTests(TestCase):
}
self.assertEqual(response.data, expected)
class ChangeCourseRunStateViewTests(TestCase):
def setUp(self):
super(ChangeCourseRunStateViewTests, self).setUp()
self.run_state = factories.CourseRunStateFactory(name=CourseRunStateChoices.Draft)
self.user = UserFactory()
self.user.groups.add(Group.objects.get(name=INTERNAL_USER_GROUP_NAME))
self.change_state_url = reverse('publisher:api:change_course_run_state', kwargs={'pk': self.run_state.id})
self.client.login(username=self.user.username, password=USER_PASSWORD)
def test_change_course_run_state(self):
"""
Verify that publisher user can change course-run workflow state.
"""
self.assertNotEqual(self.run_state.name, CourseRunStateChoices.Review)
response = self.client.patch(
self.change_state_url,
data=json.dumps({'name': CourseRunStateChoices.Review}),
content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 200)
self.run_state = CourseRunState.objects.get(course_run=self.run_state.course_run)
self.assertEqual(self.run_state.name, CourseRunStateChoices.Review)
def test_change_course_run_state_with_error(self):
"""
Verify that user cannot change course-run workflow state directly from `Draft` to `Published`.
"""
response = self.client.patch(
self.change_state_url,
data=json.dumps({'name': CourseRunStateChoices.Published}),
content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 400)
expected = {
'name': 'Cannot switch from state `{state}` to `{target_state}`'.format(
state=self.run_state.name, target_state=CourseRunStateChoices.Published
)
}
self.assertEqual(response.data, expected)
......@@ -3,7 +3,7 @@ from django.conf.urls import url
from course_discovery.apps.publisher.api.views import (
CourseRoleAssignmentView, OrganizationGroupUserView, UpdateCourseKeyView, CourseRevisionDetailView,
ChangeCourseStateView
ChangeCourseStateView, ChangeCourseRunStateView
)
urlpatterns = [
......@@ -13,4 +13,5 @@ urlpatterns = [
url(r'^course_state/(?P<pk>\d+)/$', ChangeCourseStateView.as_view(), name='change_course_state'),
url(r'^course_runs/(?P<pk>\d+)/$', UpdateCourseKeyView.as_view(), name='update_course_key'),
url(r'^course_revisions/(?P<history_id>\d+)/$', CourseRevisionDetailView.as_view(), name='course_revisions'),
url(r'^course_run_state/(?P<pk>\d+)/$', ChangeCourseRunStateView.as_view(), name='change_course_run_state'),
]
......@@ -7,9 +7,11 @@ from course_discovery.apps.publisher.api.permissions import (
)
from course_discovery.apps.publisher.api.serializers import (
CourseUserRoleSerializer, GroupUserSerializer, UpdateCourseKeySerializer, CourseRevisionSerializer,
CourseStateSerializer
CourseStateSerializer, CourseRunStateSerializer
)
from course_discovery.apps.publisher.models import (
Course, CourseState, CourseRun, CourseRunState, CourseUserRole, OrganizationExtension
)
from course_discovery.apps.publisher.models import CourseUserRole, OrganizationExtension, CourseRun, CourseState, Course
class CourseRoleAssignmentView(UpdateAPIView):
......@@ -45,3 +47,9 @@ class ChangeCourseStateView(UpdateAPIView):
permission_classes = (IsAuthenticated, PublisherUserPermission,)
queryset = CourseState.objects.all()
serializer_class = CourseStateSerializer
class ChangeCourseRunStateView(UpdateAPIView):
permission_classes = (IsAuthenticated, PublisherUserPermission,)
queryset = CourseRunState.objects.all()
serializer_class = CourseRunStateSerializer
......@@ -552,3 +552,15 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
def published(self):
# TODO: send email etc.
pass
def change_state(self, state):
if state == CourseRunStateChoices.Draft:
self.draft()
elif state == CourseRunStateChoices.Review:
self.review()
elif state == CourseRunStateChoices.Approved:
self.approved()
elif state == CourseRunStateChoices.Published:
self.published()
self.save()
......@@ -439,12 +439,14 @@ class CourseStateTests(TestCase):
self.assertEqual(self.course_state.name, state)
@ddt.ddt
class CourseRunStateTests(TestCase):
""" Tests for the publisher `CourseRunState` model. """
def setUp(self):
super(CourseRunStateTests, self).setUp()
self.run_state = factories.CourseRunStateFactory(name=CourseRunStateChoices.Draft)
@classmethod
def setUpClass(cls):
super(CourseRunStateTests, cls).setUpClass()
cls.run_state = factories.CourseRunStateFactory(name=CourseRunStateChoices.Draft)
def test_str(self):
"""
......@@ -452,37 +454,18 @@ class CourseRunStateTests(TestCase):
"""
self.assertEqual(str(self.run_state), self.run_state.get_name_display())
def test_draft(self):
self.run_state.review()
self.assertNotEqual(self.run_state.name, CourseRunStateChoices.Draft)
self.run_state.draft()
self.assertEqual(self.run_state.name, CourseRunStateChoices.Draft)
def test_review(self):
self.assertNotEqual(self.run_state.name, CourseRunStateChoices.Review)
self.run_state.review()
self.assertEqual(self.run_state.name, CourseRunStateChoices.Review)
def test_approved(self):
self.run_state.review()
self.assertNotEqual(self.run_state.name, CourseRunStateChoices.Approved)
self.run_state.approved()
self.assertEqual(self.run_state.name, CourseRunStateChoices.Approved)
def test_published(self):
self.run_state.name = CourseRunStateChoices.Approved
self.run_state.save()
self.assertNotEqual(self.run_state.name, CourseRunStateChoices.Published)
@ddt.data(
CourseRunStateChoices.Review,
CourseRunStateChoices.Approved,
CourseRunStateChoices.Published,
CourseRunStateChoices.Draft
)
def test_change_state(self, state):
"""
Verify that we can change course-run state according to workflow.
"""
self.assertNotEqual(self.run_state.name, state)
self.run_state.published()
self.run_state.change_state(state=state)
self.assertEqual(self.run_state.name, CourseRunStateChoices.Published)
self.assertEqual(self.run_state.name, state)
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-03 13:07+0500\n"
"POT-Creation-Date: 2017-02-03 14:54+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-03 13:07+0500\n"
"POT-Creation-Date: 2017-02-03 14:54+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-03 13:07+0500\n"
"POT-Creation-Date: 2017-02-03 14:54+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-03 13:07+0500\n"
"POT-Creation-Date: 2017-02-03 14:54+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
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