Commit 4fa2ec83 by Awais Jibran

Handle & show appropriate error msg when course run create fails.

parent a58cdc19
...@@ -14,8 +14,11 @@ logger = logging.getLogger(__name__) ...@@ -14,8 +14,11 @@ logger = logging.getLogger(__name__)
def get_related_discovery_course_run(publisher_course_run): def get_related_discovery_course_run(publisher_course_run):
try:
discovery_course = publisher_course_run.course.discovery_counterpart discovery_course = publisher_course_run.course.discovery_counterpart
return discovery_course.course_runs.latest('start') return discovery_course.course_runs.latest('start')
except ObjectDoesNotExist:
return
@receiver(post_save, sender=CourseRun) @receiver(post_save, sender=CourseRun)
...@@ -41,27 +44,38 @@ def create_course_run_in_studio_receiver(sender, instance, created, **kwargs): ...@@ -41,27 +44,38 @@ def create_course_run_in_studio_receiver(sender, instance, created, **kwargs):
partner = course.partner partner = course.partner
if not partner: if not partner:
logger.error('Failed to publish course run [%d] to Studio. Related course [%d] has no associated Partner.', logger.error(
instance.id, course.id) 'Failed to publish course run [%d] to Studio. Related course [%d] has no associated Partner.',
instance.id, course.id
)
return return
logger.info('Publishing course run [%d] to Studio...', instance.id) logger.info('Publishing course run [%d] to Studio...', instance.id)
api = StudioAPI(instance.course.partner.studio_api_client) api = StudioAPI(instance.course.partner.studio_api_client)
try:
try:
discovery_course_run = get_related_discovery_course_run(instance) discovery_course_run = get_related_discovery_course_run(instance)
if discovery_course_run:
try:
logger.info('Creating a re-run of [%s]...', discovery_course_run.key) logger.info('Creating a re-run of [%s]...', discovery_course_run.key)
response = api.create_course_rerun_in_studio(instance, discovery_course_run) response = api.create_course_rerun_in_studio(instance, discovery_course_run)
except ObjectDoesNotExist: except SlumberBaseException as ex:
logger.exception(
'Failed to create course re-run [%s] on Studio: %s',
discovery_course_run.key,
ex.content
)
raise
else:
try:
logger.info('Creating a new run of [%s]...', instance.course.key) logger.info('Creating a new run of [%s]...', instance.course.key)
response = api.create_course_run_in_studio(instance) response = api.create_course_run_in_studio(instance)
except SlumberBaseException as ex:
logger.exception('Failed to create course run [%s] on Studio: %s', instance.id, ex.content)
raise
instance.lms_course_id = response['id'] instance.lms_course_id = response['id']
instance.save() instance.save()
except SlumberBaseException as ex:
logger.exception('Failed to create course run [%d] on Studio: %s', instance.id, ex.content)
raise
try: try:
api.update_course_run_image_in_studio(instance) api.update_course_run_image_in_studio(instance)
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
<div class="heading"> <div class="heading">
{% trans "Course Team Admin" %} {% trans "Course Team Admin" %}
</div> </div>
<div>{{ object.course_team_admin.full_name }}</div> <div>{% firstof object.course_team_admin.full_name object.course_team_admin.username %}</div>
</div> </div>
<div class="info-item history-field-container"> <div class="info-item history-field-container">
......
...@@ -213,6 +213,39 @@ class TestCreateCourseRunInStudio: ...@@ -213,6 +213,39 @@ class TestCreateCourseRunInStudio:
json.dumps(body).encode('utf8') json.dumps(body).encode('utf8')
) )
@responses.activate
@mock.patch.object(Partner, 'access_token', return_value='JWT fake')
@override_switch('enable_publisher_create_course_run_in_studio', active=True)
def test_create_course_run_error_with_discovery_run(self, mock_access_token): # pylint: disable=unused-argument
"""
Tests that course run creations raises exception and logs expected exception message
"""
number = 'TestX'
organization = OrganizationFactory()
partner = organization.partner
course_key = '{org}+{number}'.format(org=organization.key, number=number)
discovery_course_run = DiscoveryCourseRunFactory(course__partner=partner, course__key=course_key)
body = {'error': 'Server error'}
studio_url_root = partner.studio_url.strip('/')
url = '{root}/api/v1/course_runs/{course_run_key}/rerun/'.format(
root=studio_url_root,
course_run_key=discovery_course_run.key
)
responses.add(responses.POST, url, json=body, status=500)
with mock.patch('course_discovery.apps.publisher.signals.logger.exception') as mock_logger:
with pytest.raises(HttpServerError):
CourseRunFactory(lms_course_id=None, course__organizations=[organization], course__number=number)
assert len(responses.calls) == 1
mock_logger.assert_called_with(
'Failed to create course re-run [%s] on Studio: %s',
discovery_course_run.key,
json.dumps(body).encode('utf8')
)
@pytest.mark.django_db @pytest.mark.django_db
class TestCreateOrganizations: class TestCreateOrganizations:
......
...@@ -576,6 +576,16 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire ...@@ -576,6 +576,16 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire
return initial_seat_data return initial_seat_data
def _handle_exception(self, exception):
error_msg = _('There was an error saving this course run:')
default_message = u'{msg} {ex}'.format(msg=error_msg, ex=exception)
try:
json_response = exception.response.json()
error_fields = ','.join(json_response.keys())
return u'{default}. Error fields: {error_fields}'.format(default=default_message, error_fields=error_fields)
except: # pylint: disable=bare-except
return default_message
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
parent_course = self.get_parent_course() parent_course = self.get_parent_course()
last_run = self.get_last_run() last_run = self.get_last_run()
...@@ -590,6 +600,17 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire ...@@ -590,6 +600,17 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire
} }
return context return context
def render_bad_response(self, run_form, seat_form):
context = self.get_context_data()
context.update(
{
'run_form': run_form,
'seat_form': seat_form
}
)
return render(self.request, self.template_name, context, status=400)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
user = request.user user = request.user
parent_course = self.get_parent_course() parent_course = self.get_parent_course()
...@@ -597,9 +618,30 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire ...@@ -597,9 +618,30 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire
run_form = self.run_form(request.POST) run_form = self.run_form(request.POST)
seat_form = self.seat_form(request.POST) seat_form = self.seat_form(request.POST)
course_user_roles = parent_course.course_user_roles.filter(role__in=COURSE_ROLES) course_user_roles = parent_course.course_user_roles.filter(role__in=COURSE_ROLES)
publisher_permission_flag_enabled = waffle.switch_is_active('disable_publisher_permissions')
if not (course_user_roles.count() == len(COURSE_ROLES) or publisher_permission_flag_enabled):
logger.error(
'Course [%s] is missing default course roles. Current roles [%s], required roles [%s]',
parent_course.id,
course_user_roles.count(),
len(COURSE_ROLES),
)
messages.error(
request,
_(
'Your organization does not have default roles to review/approve this course-run. '
'Please contact your partner manager to create default roles.'
)
)
return self.render_bad_response(run_form, seat_form)
if not (run_form.is_valid() and seat_form.is_valid()):
messages.error(
request, _('The page could not be updated. Make sure that all values are correct, then try again.')
)
return self.render_bad_response(run_form, seat_form)
if course_user_roles.count() == len(COURSE_ROLES) or waffle.switch_is_active('disable_publisher_permissions'):
if run_form.is_valid() and seat_form.is_valid():
try: try:
with transaction.atomic(): with transaction.atomic():
course_run = run_form.save(commit=False, course=parent_course, changed_by=user) course_run = run_form.save(commit=False, course=parent_course, changed_by=user)
...@@ -617,39 +659,13 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire ...@@ -617,39 +659,13 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire
emails.send_email_for_course_creation(parent_course, course_run, request.site) emails.send_email_for_course_creation(parent_course, course_run, request.site)
return HttpResponseRedirect(reverse(self.success_url, kwargs={'pk': course_run.id})) return HttpResponseRedirect(reverse(self.success_url, kwargs={'pk': course_run.id}))
except Exception as error: # pylint: disable=broad-except except Exception as ex: # pylint: disable=broad-except
# pylint: disable=no-member # pylint: disable=no-member
error_msg = _('There was an error saving this course run: {error}').format(error=error) error_msg = self._handle_exception(ex)
messages.error(request, error_msg) messages.error(request, error_msg)
logger.exception('Unable to create course run and seat for course [%s].', parent_course.id) logger.exception('Unable to create course run and seat for course [%s].', parent_course.id)
else:
messages.error(
request, _('The page could not be updated. Make sure that all values are correct, then try again.')
)
else:
logger.error(
'Course [%s] is missing default course roles. Current roles [%s], required roles [%s]',
parent_course.id,
course_user_roles.count(),
len(COURSE_ROLES),
)
messages.error(
request,
_(
"Your organization does not have default roles to review/approve this course-run. "
"Please contact your partner manager to create default roles."
)
)
context = self.get_context_data() return self.render_bad_response(run_form, seat_form)
context.update(
{
'run_form': run_form,
'seat_form': seat_form
}
)
return render(request, self.template_name, context, status=400)
class CreateRunFromDashboardView(CreateCourseRunView): class CreateRunFromDashboardView(CreateCourseRunView):
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-01-30 16:13+0000\n" "POT-Creation-Date: 2018-01-31 16:41+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -3313,13 +3313,7 @@ msgid "marketing" ...@@ -3313,13 +3313,7 @@ msgid "marketing"
msgstr "" msgstr ""
#: apps/publisher/views.py #: apps/publisher/views.py
#, python-brace-format msgid "There was an error saving this course run:"
msgid "You have successfully created a course run for {course_title}."
msgstr ""
#: apps/publisher/views.py
#, python-brace-format
msgid "There was an error saving this course run: {error}"
msgstr "" msgstr ""
#: apps/publisher/views.py #: apps/publisher/views.py
...@@ -3329,6 +3323,11 @@ msgid "" ...@@ -3329,6 +3323,11 @@ msgid ""
msgstr "" msgstr ""
#: apps/publisher/views.py #: apps/publisher/views.py
#, python-brace-format
msgid "You have successfully created a course run for {course_title}."
msgstr ""
#: apps/publisher/views.py
msgid "Course run updated successfully." msgid "Course run updated successfully."
msgstr "" msgstr ""
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-01-30 16:13+0000\n" "POT-Creation-Date: 2018-01-31 16:41+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-01-30 16:13+0000\n" "POT-Creation-Date: 2018-01-31 16:41+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -4022,18 +4022,10 @@ msgid "marketing" ...@@ -4022,18 +4022,10 @@ msgid "marketing"
msgstr "märkétïng Ⱡ'σяєм ιρѕυм ∂σł#" msgstr "märkétïng Ⱡ'σяєм ιρѕυм ∂σł#"
#: apps/publisher/views.py #: apps/publisher/views.py
#, python-brace-format msgid "There was an error saving this course run:"
msgid "You have successfully created a course run for {course_title}."
msgstr "" msgstr ""
"Ýöü hävé süççéssfüllý çréätéd ä çöürsé rün för {course_title}. Ⱡ'σяєм ιρѕυм " "Théré wäs än érrör sävïng thïs çöürsé rün: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#" "¢σηѕє¢тєтυя #"
#: apps/publisher/views.py
#, python-brace-format
msgid "There was an error saving this course run: {error}"
msgstr ""
"Théré wäs än érrör sävïng thïs çöürsé rün: {error} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт "
"αмєт, ¢σηѕє¢тєтυя α#"
#: apps/publisher/views.py #: apps/publisher/views.py
msgid "" msgid ""
...@@ -4050,6 +4042,13 @@ msgstr "" ...@@ -4050,6 +4042,13 @@ msgstr ""
"ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт #" "ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт #"
#: apps/publisher/views.py #: apps/publisher/views.py
#, python-brace-format
msgid "You have successfully created a course run for {course_title}."
msgstr ""
"Ýöü hävé süççéssfüllý çréätéd ä çöürsé rün för {course_title}. Ⱡ'σяєм ιρѕυм "
"∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: apps/publisher/views.py
msgid "Course run updated successfully." msgid "Course run updated successfully."
msgstr "" msgstr ""
"Çöürsé rün üpdätéd süççéssfüllý. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#" "Çöürsé rün üpdätéd süççéssfüllý. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-01-30 16:13+0000\n" "POT-Creation-Date: 2018-01-31 16:41+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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