Commit c5d0f4da by Anthony Mangano

Create entitlement-aware CourseRuns in Publisher

parent 8d83ddfd
......@@ -920,6 +920,26 @@ class CoursesAutoCompleteTests(SiteMixin, TestCase):
response = self.client.get(self.course_autocomplete_url.format(title='test'))
self._assert_response(response, 1)
def test_course_autocomplete_entitlement_info(self):
""" Verify that the response from CourseAutoComplete includes info about whether or not the courses
use entitlements. """
self.user.groups.add(Group.objects.get(name=ADMIN_GROUP_NAME))
self.course.version = Course.SEAT_VERSION
self.course.save()
self.course2.version = Course.ENTITLEMENT_VERSION
self.course2.save()
response = self.client.get(self.course_autocomplete_url.format(title='test'))
self.assertEqual(response.status_code, 200)
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(len(data['results']), 2)
results_by_id = {record['id']: record for record in data['results']}
self.assertFalse(results_by_id[self.course.id]['uses_entitlements'])
self.assertTrue(results_by_id[self.course2.id]['uses_entitlements'])
def _assert_response(self, response, expected_length):
""" Assert autocomplete response. """
self.assertEqual(response.status_code, 200)
......
......@@ -100,6 +100,21 @@ class RevertCourseRevisionView(APIView):
class CoursesAutoComplete(LoginRequiredMixin, autocomplete.Select2QuerySetView):
""" Course Autocomplete. """
def get_results(self, context):
"""
Format the result set so that it can be returned as a JSON object.
Overridden from https://github.com/yourlabs/django-autocomplete-light/blob/3.1.8/src/dal_select2/views.py#L14
to include information about whether or not the suggested Course(s) use entitlements.
"""
return [
{
'id': self.get_result_value(course),
'text': self.get_result_label(course),
'uses_entitlements': course.uses_entitlements
} for course in context['object_list']
]
def get_queryset(self):
if self.q:
user = self.request.user
......
......@@ -220,6 +220,13 @@ class CourseSearchForm(forms.Form):
required=True,
)
def __init__(self, *args, **kwargs):
qs = kwargs.pop('queryset', None)
super(CourseSearchForm, self).__init__(*args, **kwargs)
if qs is not None:
self.fields['course'].queryset = qs
class CourseRunForm(BaseForm):
start = forms.DateTimeField(label=_('Course Start Date'), required=True)
......
......@@ -101,6 +101,13 @@ class Course(TimeStampedModel, ChangedByMixin):
return self.title
@property
def uses_entitlements(self):
"""
Returns a bool indicating whether or not this Course has been configured to use entitlement products.
"""
return self.version == self.ENTITLEMENT_VERSION
@property
def post_back_url(self):
return reverse('publisher:publisher_courses_edit', kwargs={'pk': self.id})
......@@ -508,6 +515,11 @@ class CourseEntitlement(TimeStampedModel):
'default': 0.00,
}
MODE_TO_SEAT_TYPE_MAPPING = {
VERIFIED: Seat.VERIFIED,
PROFESSIONAL: Seat.PROFESSIONAL
}
course = models.ForeignKey(Course, related_name='entitlements')
mode = models.CharField(max_length=63, choices=COURSE_MODE_CHOICES, verbose_name='Course mode')
price = models.DecimalField(**PRICE_FIELD_CONFIG)
......
......@@ -56,7 +56,7 @@
</div>
{% endif %}
<div class="layout-full layout">
<div class="layout-full layout js-courserun-form">
<div class="course-form">
<div class="course-information">
<fieldset class="form-group grid-container grid-manual">
......@@ -110,7 +110,7 @@
</div>
</div>
<div class="layout-full layout">
<div class="layout-full layout js-seat-form{% if hide_seat_form %} hidden{% endif %}">
<div class="course-form">
<div class="course-information">
<fieldset class="form-group grid-container grid-manual">
......@@ -136,7 +136,7 @@
</div>
</div>
{% if seat_form.price.errors %}
<div class="field-message has-error">
<div class="field-message has-error js-seat-form-errors">
<span class="field-message-content">
{{ seat_form.price.errors|escape }}
</span>
......@@ -165,6 +165,7 @@
{% block extra_js %}
<script src="{% static 'js/publisher/course-tabs.js' %}"></script>
<script src="{% static 'js/publisher/seat-type-change.js' %}"></script>
<script src="{% static 'js/publisher/toggle-seat-form.js' %}"></script>
{% endblock %}
{% block js_without_compress %}
......
......@@ -27,7 +27,7 @@ class CourseFactory(factory.DjangoModelFactory):
learner_testimonial = FuzzyText()
level_type = factory.SubFactory(factories.LevelTypeFactory)
image = factory.django.ImageField()
version = FuzzyInteger(0, 1)
version = Course.SEAT_VERSION
primary_subject = factory.SubFactory(factories.SubjectFactory)
secondary_subject = factory.SubFactory(factories.SubjectFactory)
......
......@@ -16,7 +16,8 @@ from course_discovery.apps.course_metadata.tests.factories import OrganizationFa
from course_discovery.apps.ietf_language_tags.models import LanguageTag
from course_discovery.apps.publisher.choices import CourseRunStateChoices, CourseStateChoices, PublisherUserRole
from course_discovery.apps.publisher.mixins import check_course_organization_permission
from course_discovery.apps.publisher.models import CourseUserRole, OrganizationExtension, OrganizationUserRole, Seat
from course_discovery.apps.publisher.models import (Course, CourseUserRole, OrganizationExtension,
OrganizationUserRole, Seat)
from course_discovery.apps.publisher.tests import factories
......@@ -179,6 +180,14 @@ class CourseTests(TestCase):
course=self.course, role=PublisherUserRole.Publisher, user=self.user3
)
def test_uses_entitlements(self):
""" Verify that uses_entitlements is True when version is set to ENTITLEMENT_VERSION, and False otherwise. """
self.course.version = Course.SEAT_VERSION
assert not self.course.uses_entitlements
self.course.version = Course.ENTITLEMENT_VERSION
assert self.course.uses_entitlements
def test_str(self):
""" Verify casting an instance to a string returns a string containing the course title. """
self.assertEqual(str(self.course), self.course.title)
......
......@@ -22,7 +22,7 @@ from pytz import timezone
from testfixtures import LogCapture
from course_discovery.apps.api.tests.mixins import SiteMixin
from course_discovery.apps.core.models import User
from course_discovery.apps.core.models import Currency, User
from course_discovery.apps.core.tests.factories import USER_PASSWORD, UserFactory
from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.course_metadata.tests import toggle_switch
......@@ -349,6 +349,7 @@ class CreateCourseViewTests(SiteMixin, TestCase):
self.assertEqual(response.status_code, 400)
@ddt.ddt
class CreateCourseRunViewTests(SiteMixin, TestCase):
""" Tests for the publisher `CreateCourseRunView`. """
......@@ -356,7 +357,11 @@ class CreateCourseRunViewTests(SiteMixin, TestCase):
super(CreateCourseRunViewTests, self).setUp()
self.user = UserFactory()
self.course_run = factories.CourseRunFactory()
self.course = self.course_run.course
self.course.version = Course.SEAT_VERSION
self.course.save()
factories.CourseStateFactory(course=self.course)
factories.CourseUserRoleFactory.create(course=self.course, role=PublisherUserRole.CourseTeam, user=self.user)
factories.CourseUserRoleFactory.create(course=self.course, role=PublisherUserRole.Publisher, user=UserFactory())
......@@ -415,6 +420,22 @@ class CreateCourseRunViewTests(SiteMixin, TestCase):
response = self.client.get(self.create_course_run_url_new)
self.assertEqual(response.status_code, 200)
def test_courserun_form_for_course_with_entitlements(self):
""" Verify that the Seat fields are hidden for Courses that use entitlements. """
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
response = self.client.get(self.create_course_run_url_new)
self.assertContains(response, '<div class="layout-full layout js-seat-form hidden">', status_code=200)
def test_courserun_form_for_course_without_entitlements(self):
""" Verify that the Seat fields are visible for Courses that do not use entitlements. """
self.course.version = Course.SEAT_VERSION
self.course.save()
response = self.client.get(self.create_course_run_url_new)
self.assertContains(response, '<div class="layout-full layout js-seat-form">', status_code=200)
def test_create_course_run_without_permission(self):
"""
Verify that a course run create page shows the proper error when non-publisher user tries to
......@@ -628,6 +649,126 @@ class CreateCourseRunViewTests(SiteMixin, TestCase):
)
)
@ddt.data(
(CourseEntitlement.PROFESSIONAL, 1, [{'type': Seat.PROFESSIONAL, 'price': 1}]),
(CourseEntitlement.VERIFIED, 1, [{'type': Seat.VERIFIED, 'price': 1}, {'type': Seat.AUDIT, 'price': 0}]),
)
@ddt.unpack
def test_create_run_for_entitlement_course(self, entitlement_mode, entitlement_price, expected_seats):
"""
Verify that when creating a run for a Course that uses entitlements, Seats are created from the
entitlement data associated with the parent course.
"""
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
toggle_switch('publisher_create_audit_seats_for_verified_course_runs', True)
self.course.entitlements.create(mode=entitlement_mode, price=entitlement_price)
post_data = {'start': '2018-02-01 00:00:00', 'end': '2018-02-28 00:00:00', 'pacing_type': 'instructor_paced'}
num_courseruns_before = self.course.course_runs.count()
response = self.client.post(self.create_course_run_url_new, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertTrue(num_courseruns_after > num_courseruns_before)
new_courserun = self.course.course_runs.latest('created')
self.assertEqual(new_courserun.start.strftime('%Y-%m-%d %H:%M:%S'), post_data['start'])
self.assertEqual(new_courserun.end.strftime('%Y-%m-%d %H:%M:%S'), post_data['end'])
self.assertEqual(new_courserun.pacing_type, post_data['pacing_type'])
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_course_run_detail', kwargs={'pk': new_courserun.id}),
status_code=302,
target_status_code=200
)
self.assertEqual(new_courserun.seats.count(), len(expected_seats))
for expected_seat in expected_seats:
actual_seat = new_courserun.seats.get(type=expected_seat['type'])
self.assertEqual(expected_seat['type'], actual_seat.type)
self.assertEqual(expected_seat['price'], actual_seat.price)
def test_create_run_for_misconfigured_entitlement_course(self):
"""
Verify that a user cannot create a new course run for a Course that has been configured to use entitlements
but does not have exactly one entitlement.
"""
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
post_data = {'start': '2018-02-01 00:00:00', 'end': '2018-02-28 00:00:00', 'pacing_type': 'instructor_paced'}
self.assertEqual(self.course.entitlements.count(), 0)
num_courseruns_before = self.course.course_runs.count()
response = self.client.post(self.create_course_run_url_new, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertEqual(num_courseruns_before, num_courseruns_after)
self.assertContains(response, 'The certificate configuration for this course is incorrect', status_code=400)
self.course.entitlements.create(mode=CourseEntitlement.VERIFIED, price=1)
self.course.entitlements.create(mode=CourseEntitlement.PROFESSIONAL, price=1)
self.assertEqual(self.course.entitlements.count(), 2)
response = self.client.post(self.create_course_run_url_new, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertEqual(num_courseruns_before, num_courseruns_after)
self.assertContains(response, 'The certificate configuration for this course is incorrect', status_code=400)
def test_create_run_for_non_usd_entitlement_course(self):
"""
Verify that a user cannot create a new course run for a Course that has been configured to use entitlements
with a currency other than USD.
"""
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
post_data = {'start': '2018-02-01 00:00:00', 'end': '2018-02-28 00:00:00', 'pacing_type': 'instructor_paced'}
self.course.entitlements.create(
mode=CourseEntitlement.VERIFIED,
price=100,
currency=Currency.objects.get(code='JPY')
)
num_courseruns_before = self.course.course_runs.count()
response = self.client.post(self.create_course_run_url_new, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertEqual(num_courseruns_before, num_courseruns_after)
self.assertContains(response, 'The certificate configuration for this course is incorrect', status_code=400)
def test_create_run_for_entitlement_course_with_seat_data_in_form(self):
"""
Verify that a user cannot submit Seat data with the form when creating a new course run for a Course that has
been configured to use entitlements.
"""
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
post_data = {
'start': '2018-02-01 00:00:00',
'end': '2018-02-28 00:00:00',
'pacing_type': 'instructor_paced',
'type': Seat.VERIFIED,
'price': 2
}
self.course.entitlements.create(mode=CourseEntitlement.PROFESSIONAL, price=1)
num_courseruns_before = self.course.course_runs.count()
response = self.client.post(self.create_course_run_url_new, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertEqual(num_courseruns_before, num_courseruns_after)
self.assertContains(response, 'The page could not be updated.', status_code=400)
@ddt.ddt
class CourseRunDetailTests(SiteMixin, TestCase):
......@@ -3496,6 +3637,7 @@ class CourseRevisionViewTests(SiteMixin, TestCase):
return self.client.get(path=revision_path)
@ddt.ddt
class CreateRunFromDashboardViewTests(SiteMixin, TestCase):
""" Tests for the publisher `CreateRunFromDashboardView`. """
......@@ -3503,7 +3645,11 @@ class CreateRunFromDashboardViewTests(SiteMixin, TestCase):
super(CreateRunFromDashboardViewTests, self).setUp()
self.user = UserFactory()
self.organization_extension = factories.OrganizationExtensionFactory()
self.course = factories.CourseFactory(organizations=[self.organization_extension.organization])
self.course.version = Course.SEAT_VERSION
self.course.save()
factories.CourseStateFactory(course=self.course)
factories.CourseUserRoleFactory.create(course=self.course, role=PublisherUserRole.CourseTeam, user=self.user)
factories.CourseUserRoleFactory.create(course=self.course, role=PublisherUserRole.Publisher, user=UserFactory())
......@@ -3594,6 +3740,160 @@ class CreateRunFromDashboardViewTests(SiteMixin, TestCase):
expected_subject = 'Studio URL requested: {title}'.format(title=self.course.title)
self.assertEqual(str(mail.outbox[0].subject), expected_subject)
def test_courserun_form_includes_seat_fields_on_error_for_non_entitlement_course(self):
""" Verify that the Seat fields are visible when error occurs for Courses that do not use entitlements. """
self.course.version = Course.SEAT_VERSION
self.course.save()
post_data = {'course': self.course.id}
response = self.client.post(self.create_course_run_url, post_data)
self.assertContains(response, '<div class="layout-full layout js-seat-form">', status_code=400)
def test_courserun_form_does_not_include_seat_fields_on_error_for_entitlement_course(self):
""" Verify that the Seat fields are hidden when error occurs for Courses that use entitlements. """
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
post_data = {'course': self.course.id}
response = self.client.post(self.create_course_run_url, post_data)
self.assertContains(response, '<div class="layout-full layout js-seat-form hidden">', status_code=400)
@ddt.data(
(CourseEntitlement.PROFESSIONAL, 1, [{'type': Seat.PROFESSIONAL, 'price': 1}]),
(CourseEntitlement.VERIFIED, 1, [{'type': Seat.VERIFIED, 'price': 1}, {'type': Seat.AUDIT, 'price': 0}]),
)
@ddt.unpack
def test_create_run_for_entitlement_course(self, entitlement_mode, entitlement_price, expected_seats):
"""
Verify that when creating a run for a Course that uses entitlements, Seats are created from the
entitlement data associated with the parent course.
"""
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
toggle_switch('publisher_create_audit_seats_for_verified_course_runs', True)
self.course.entitlements.create(mode=entitlement_mode, price=entitlement_price)
post_data = {
'start': '2018-02-01 00:00:00',
'end': '2018-02-28 00:00:00',
'pacing_type': 'instructor_paced',
'course': self.course.id
}
num_courseruns_before = self.course.course_runs.count()
response = self.client.post(self.create_course_run_url, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertTrue(num_courseruns_after > num_courseruns_before)
new_courserun = self.course.course_runs.latest('created')
self.assertEqual(new_courserun.start.strftime('%Y-%m-%d %H:%M:%S'), post_data['start'])
self.assertEqual(new_courserun.end.strftime('%Y-%m-%d %H:%M:%S'), post_data['end'])
self.assertEqual(new_courserun.pacing_type, post_data['pacing_type'])
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_course_run_detail', kwargs={'pk': new_courserun.id}),
status_code=302,
target_status_code=200
)
self.assertEqual(new_courserun.seats.count(), len(expected_seats))
for expected_seat in expected_seats:
actual_seat = new_courserun.seats.get(type=expected_seat['type'])
self.assertEqual(expected_seat['type'], actual_seat.type)
self.assertEqual(expected_seat['price'], actual_seat.price)
def test_create_run_for_misconfigured_entitlement_course(self):
"""
Verify that a user cannot create a new course run for a Course that has been configured to use entitlements
but does not have exactly one entitlement.
"""
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
post_data = {
'start': '2018-02-01 00:00:00',
'end': '2018-02-28 00:00:00',
'pacing_type': 'instructor_paced',
'course': self.course.id
}
self.assertEqual(self.course.entitlements.count(), 0)
num_courseruns_before = self.course.course_runs.count()
response = self.client.post(self.create_course_run_url, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertEqual(num_courseruns_before, num_courseruns_after)
self.assertContains(response, 'The certificate configuration for this course is incorrect', status_code=400)
self.course.entitlements.create(mode=CourseEntitlement.VERIFIED, price=1)
self.course.entitlements.create(mode=CourseEntitlement.PROFESSIONAL, price=1)
self.assertEqual(self.course.entitlements.count(), 2)
response = self.client.post(self.create_course_run_url, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertEqual(num_courseruns_before, num_courseruns_after)
self.assertContains(response, 'The certificate configuration for this course is incorrect', status_code=400)
def test_create_run_for_non_usd_entitlement_course(self):
"""
Verify that a user cannot create a new course run for a Course that has been configured to use entitlements
with a currency other than USD.
"""
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
post_data = {
'start': '2018-02-01 00:00:00',
'end': '2018-02-28 00:00:00',
'pacing_type': 'instructor_paced',
'course': self.course.id
}
self.course.entitlements.create(
mode=CourseEntitlement.VERIFIED,
price=100,
currency=Currency.objects.get(code='JPY')
)
num_courseruns_before = self.course.course_runs.count()
response = self.client.post(self.create_course_run_url, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertEqual(num_courseruns_before, num_courseruns_after)
self.assertContains(response, 'The certificate configuration for this course is incorrect', status_code=400)
def test_create_run_for_entitlement_course_with_seat_data_in_form(self):
"""
Verify that a user cannot submit Seat data with the form when creating a new course run for a Course that has
been configured to use entitlements.
"""
self.course.version = Course.ENTITLEMENT_VERSION
self.course.save()
assign_perm(
OrganizationExtension.VIEW_COURSE_RUN, self.organization_extension.group, self.organization_extension
)
post_data = {
'start': '2018-02-01 00:00:00',
'end': '2018-02-28 00:00:00',
'pacing_type': 'instructor_paced',
'course': self.course.id,
'type': Seat.VERIFIED,
'price': 2
}
self.course.entitlements.create(mode=CourseEntitlement.PROFESSIONAL, price=1)
num_courseruns_before = self.course.course_runs.count()
response = self.client.post(self.create_course_run_url, post_data)
num_courseruns_after = self.course.course_runs.count()
self.assertEqual(num_courseruns_before, num_courseruns_after)
self.assertContains(response, 'The page could not be updated.', status_code=400)
class CreateAdminImportCourseTest(SiteMixin, TestCase):
""" Tests for the publisher `CreateAdminImportCourse`. """
......
......@@ -32,8 +32,9 @@ from course_discovery.apps.publisher.dataloader.create_courses import process_co
from course_discovery.apps.publisher.emails import send_email_for_published_course_run_editing
from course_discovery.apps.publisher.forms import (AdminImportCourseForm, CourseEntitlementForm, CourseForm,
CourseRunForm, CourseSearchForm, SeatForm)
from course_discovery.apps.publisher.models import (PAID_SEATS, Course, CourseRun, CourseRunState, CourseState,
CourseUserRole, OrganizationExtension, Seat, UserAttributes)
from course_discovery.apps.publisher.models import (PAID_SEATS, Course, CourseEntitlement, CourseRun, CourseRunState,
CourseState, CourseUserRole, OrganizationExtension, Seat,
UserAttributes)
from course_discovery.apps.publisher.utils import (get_internal_users, has_role_for_course, is_internal_user,
is_project_coordinator_user, is_publisher_admin, make_bread_crumbs)
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
......@@ -609,18 +610,65 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire
run_initial_data = {'pacing_type': last_run.pacing_type}
return self.run_form(initial=run_initial_data)
def _entitlement_is_valid_for_seat_creation(self, entitlement):
if entitlement is None:
return False
# The SeatForm does not support custom currency values, and assumes everything is USD.
if entitlement.currency is None or entitlement.currency.code != 'USD':
return False
if entitlement.mode not in CourseEntitlement.MODE_TO_SEAT_TYPE_MAPPING:
return False
return True
def _render_post_error(self, request, ctx_overrides=None, status=400):
context = self.get_context_data()
if ctx_overrides:
context.update(ctx_overrides)
return render(request, self.template_name, context, status=status)
def _process_post_request(self, request, parent_course, run_form, seat_form, ctx_overrides=None):
user = request.user
def _process_post_request(self, request, parent_course, context=None):
context = context or {}
run_form = self.run_form(request.POST)
context['run_form'] = run_form
if parent_course.uses_entitlements:
context['hide_seat_form'] = True
# Fail if Seat fields are present in the POST data.
seat_data_in_form = any([key for key in self.seat_form.declared_fields.keys() if key in request.POST])
if seat_data_in_form:
messages.error(
request, _('The page could not be updated. Make sure that all values are correct, then try again.')
)
return self._render_post_error(request, ctx_overrides=context)
try:
entitlement = parent_course.entitlements.get()
except (CourseEntitlement.DoesNotExist, CourseEntitlement.MultipleObjectsReturned):
entitlement = None
if not self._entitlement_is_valid_for_seat_creation(entitlement):
messages.error(
request,
_('The certificate configuration for this course is incorrect. Please fix it, then try again.')
)
return self._render_post_error(request, ctx_overrides=context)
seat_form = self.seat_form({
'type': CourseEntitlement.MODE_TO_SEAT_TYPE_MAPPING[entitlement.mode],
'price': entitlement.price
})
else:
seat_form = self.seat_form(request.POST)
context['seat_form'] = seat_form
context['hide_seat_form'] = False
course_user_roles = parent_course.course_user_roles.filter(role__in=COURSE_ROLES)
has_default_course_user_roles = course_user_roles.count() == len(COURSE_ROLES)
if not (has_default_course_user_roles or waffle.switch_is_active('disable_publisher_permissions')):
logger.error(
'Course [%s] is missing default course roles. Current roles [%s], required roles [%s]',
......@@ -635,16 +683,17 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire
'Please contact your partner manager to create default roles.'
)
)
return self._render_post_error(request, ctx_overrides=ctx_overrides)
return self._render_post_error(request, ctx_overrides=context)
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_post_error(request, ctx_overrides=ctx_overrides)
return self._render_post_error(request, ctx_overrides=context)
try:
with transaction.atomic():
user = request.user
course_run = run_form.save(commit=False, course=parent_course, changed_by=user)
self._set_last_run_data(course_run)
seat_form.save(course_run=course_run, changed_by=user)
......@@ -665,7 +714,7 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire
error_msg = self._format_post_exception_message(ex)
messages.error(request, error_msg)
logger.exception('Unable to create course run and seat for course [%s].', parent_course.id)
return self._render_post_error(request, ctx_overrides=ctx_overrides)
return self._render_post_error(request, ctx_overrides=context)
def get_context_data(self, **kwargs):
parent_course = self._get_parent_course()
......@@ -676,18 +725,13 @@ class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.PublisherUserRequire
context = {
'cancel_url': reverse('publisher:publisher_course_detail', kwargs={'pk': parent_course.pk}),
'run_form': run_form,
'seat_form': seat_form
'seat_form': seat_form,
'hide_seat_form': parent_course.uses_entitlements
}
return context
def post(self, request, *args, **kwargs):
parent_course = self._get_parent_course()
run_form = self.run_form(request.POST)
seat_form = self.seat_form(request.POST)
return self._process_post_request(request, parent_course, run_form, seat_form, ctx_overrides={
'run_form': run_form,
'seat_form': seat_form
})
return self._process_post_request(request, self._get_parent_course())
class CreateRunFromDashboardView(CreateCourseRunView):
......@@ -700,33 +744,23 @@ class CreateRunFromDashboardView(CreateCourseRunView):
def get_context_data(self, **kwargs):
context = {
'cancel_url': reverse('publisher:publisher_dashboard'),
'course_form': self.course_form(),
'course_form': self.course_form(queryset=Course.objects.none()),
'run_form': self.run_form(),
'seat_form': self.seat_form()
'seat_form': self.seat_form(),
'hide_seat_form': False
}
return context
def post(self, request, *args, **kwargs):
course_form = self.course_form(request.POST)
run_form = self.run_form(request.POST)
seat_form = self.seat_form(request.POST)
ctx_overrides = {
'course_form': course_form,
'run_form': run_form,
'seat_form': seat_form,
}
if not course_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_post_error(request, ctx_overrides=ctx_overrides)
return self._render_post_error(request, ctx_overrides={'run_form': self.run_form(request.POST)})
self.parent_course = course_form.cleaned_data.get('course')
return self._process_post_request(
request, self.parent_course, run_form, seat_form, ctx_overrides=ctx_overrides
)
return self._process_post_request(request, self.parent_course, context={'course_form': course_form})
class CourseRunEditView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMixin, UpdateView):
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-06 14:34+0000\n"
"POT-Creation-Date: 2018-02-12 17:50+0000\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"
......@@ -3326,6 +3326,12 @@ msgstr ""
#: apps/publisher/views.py
msgid ""
"The certificate configuration for this Course is incorrect. Please fix it, "
"then try again."
msgstr ""
#: apps/publisher/views.py
msgid ""
"Your organization does not have default roles to review/approve this course-"
"run. Please contact your partner manager to create default roles."
msgstr ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-06 14:34+0000\n"
"POT-Creation-Date: 2018-02-12 17:50+0000\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: 2018-02-06 14:34+0000\n"
"POT-Creation-Date: 2018-02-12 17:50+0000\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"
......@@ -4037,6 +4037,14 @@ msgstr ""
#: apps/publisher/views.py
msgid ""
"The certificate configuration for this Course is incorrect. Please fix it, "
"then try again."
msgstr ""
"Thé çértïfïçäté çönfïgürätïön för thïs Çöürsé ïs ïnçörréçt. Pléäsé fïx ït, "
"thén trý ägäïn. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
#: apps/publisher/views.py
msgid ""
"Your organization does not have default roles to review/approve this course-"
"run. Please contact your partner manager to create default roles."
msgstr ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-06 14:34+0000\n"
"POT-Creation-Date: 2018-02-12 17:50+0000\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"
......
$(document).ready(function() {
var $courseRunForm = $('.js-courserun-form'),
$seatForm = $('.js-seat-form'),
$courseSelect = $('#id_course');
// If the rendered SeatForm is hidden, remove it from the DOM.
if ($seatForm.hasClass('hidden')) {
$seatForm.detach();
$seatForm.removeClass('hidden');
}
if ($courseSelect.length) {
// See https://select2.org/programmatic-control/events for information about the select2:select event.
$courseSelect.on('select2:select', function(e) {
var usesEntitlements = e.params.data.uses_entitlements;
$seatForm.detach();
if (!usesEntitlements) {
// Remove any errors that may have been initially loaded with the form.
$seatForm.find('.js-seat-form-errors').remove();
// Reset inputs before re-attaching the form.
$seatForm.find('#id_type').val('');
$seatForm.find('#seatPriceBlock').hide();
$seatForm.find('#id_price').val('0.0');
$seatForm.find('#creditPrice').hide();
$seatForm.find('#id_credit_price').val('0.0');
// Re-attach the form
$seatForm.insertAfter($courseRunForm);
}
});
}
});
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