diff --git a/common/djangoapps/course_modes/migrations/0007_auto__add_coursemodesarchive__chg_field_coursemode_course_id.py b/common/djangoapps/course_modes/migrations/0007_auto__add_coursemodesarchive__chg_field_coursemode_course_id.py new file mode 100644 index 0000000000000000000000000000000000000000..c76b8745cba34116d0ff6e87e1ef1b6d0555de9c --- /dev/null +++ b/common/djangoapps/course_modes/migrations/0007_auto__add_coursemodesarchive__chg_field_coursemode_course_id.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'CourseModesArchive' + db.create_table('course_modes_coursemodesarchive', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)), + ('mode_slug', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('mode_display_name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('min_price', self.gf('django.db.models.fields.IntegerField')(default=0)), + ('suggested_prices', self.gf('django.db.models.fields.CommaSeparatedIntegerField')(default='', max_length=255, blank=True)), + ('currency', self.gf('django.db.models.fields.CharField')(default='usd', max_length=8)), + ('expiration_date', self.gf('django.db.models.fields.DateField')(default=None, null=True, blank=True)), + ('expiration_datetime', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True, blank=True)), + )) + db.send_create_signal('course_modes', ['CourseModesArchive']) + + + # Changing field 'CourseMode.course_id' + db.alter_column('course_modes_coursemode', 'course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255)) + + def backwards(self, orm): + # Deleting model 'CourseModesArchive' + db.delete_table('course_modes_coursemodesarchive') + + + # Changing field 'CourseMode.course_id' + db.alter_column('course_modes_coursemode', 'course_id', self.gf('django.db.models.fields.CharField')(max_length=255)) + + models = { + 'course_modes.coursemode': { + 'Meta': {'unique_together': "(('course_id', 'mode_slug', 'currency'),)", 'object_name': 'CourseMode'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'expiration_date': ('django.db.models.fields.DateField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'expiration_datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'min_price': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'mode_display_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'mode_slug': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'suggested_prices': ('django.db.models.fields.CommaSeparatedIntegerField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}) + }, + 'course_modes.coursemodesarchive': { + 'Meta': {'object_name': 'CourseModesArchive'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'expiration_date': ('django.db.models.fields.DateField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'expiration_datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'min_price': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'mode_display_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'mode_slug': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'suggested_prices': ('django.db.models.fields.CommaSeparatedIntegerField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}) + } + } + + complete_apps = ['course_modes'] \ No newline at end of file diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index abba0e15565dbf714bae3358dc7be4a96fddb481..e180b8ed7bbbe6236ff09ff3c20a45a473d3d2e9 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -128,3 +128,33 @@ class CourseMode(models.Model): return u"{} : {}, min={}, prices={}".format( self.course_id.to_deprecated_string(), self.mode_slug, self.min_price, self.suggested_prices ) + + +class CourseModesArchive(models.Model): + """ + Store the CourseModesArchives in this model + + """ + # the course that this mode is attached to + course_id = CourseKeyField(max_length=255, db_index=True) + + # the reference to this mode that can be used by Enrollments to generate + # similar behavior for the same slug across courses + mode_slug = models.CharField(max_length=100) + + # The 'pretty' name that can be translated and displayed + mode_display_name = models.CharField(max_length=255) + + # minimum price in USD that we would like to charge for this mode of the course + min_price = models.IntegerField(default=0) + + # the suggested prices for this mode + suggested_prices = models.CommaSeparatedIntegerField(max_length=255, blank=True, default='') + + # the currency these prices are in, using lower case ISO currency codes + currency = models.CharField(default="usd", max_length=8) + + # turn this mode off after the given expiration date + expiration_date = models.DateField(default=None, null=True, blank=True) + + expiration_datetime = models.DateTimeField(default=None, null=True, blank=True) diff --git a/lms/djangoapps/instructor/tests/test_ecommerce.py b/lms/djangoapps/instructor/tests/test_ecommerce.py index 19020b7c3dfd2683319be4d4e307d9b749c827f5..52602dc7952c270e37979c95e256785995cdf599 100644 --- a/lms/djangoapps/instructor/tests/test_ecommerce.py +++ b/lms/djangoapps/instructor/tests/test_ecommerce.py @@ -16,12 +16,10 @@ from mock import patch from student.roles import CourseFinanceAdminRole -# pylint: disable=E1101 @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) class TestECommerceDashboardViews(ModuleStoreTestCase): """ - Check for email view on the new instructor dashboard - for Mongo-backed courses + Check for E-commerce view on the new instructor dashboard """ def setUp(self): self.course = CourseFactory.create() @@ -71,6 +69,75 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): self.assertFalse('Download All e-Commerce Purchase' in response.content) self.assertFalse('<span>Total Amount: <span>$' + str(total_amount) + '</span></span>' in response.content) + def test_user_view_course_price(self): + """ + test to check if the user views the set price button and price in + the instructor dashboard + """ + response = self.client.get(self.url) + self.assertTrue(self.e_commerce_link in response.content) + + # Total amount html should render in e-commerce page, total amount will be 0 + course_honor_mode = CourseMode.mode_for_course(self.course.id, 'honor') + + price = course_honor_mode.min_price + self.assertTrue('Course Price: <span>$' + str(price) + '</span>' in response.content) + self.assertFalse('+ Set Price</a></span>' in response.content) + + # removing the course finance_admin role of login user + CourseFinanceAdminRole(self.course.id).remove_users(self.instructor) + + # total amount should not be visible in e-commerce page if the user is not finance admin + url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()}) + response = self.client.get(url) + self.assertFalse('+ Set Price</a></span>' in response.content) + + def test_update_course_price_check(self): + price = 200 + # course B + course2 = CourseFactory.create(org='EDX', display_name='test_course', number='100') + mode = CourseMode( + course_id=course2.id.to_deprecated_string(), mode_slug='honor', + mode_display_name='honor', min_price=30, currency='usd' + ) + mode.save() + # course A update + CourseMode.objects.filter(course_id=self.course.id).update(min_price=price) + + set_course_price_url = reverse('set_course_mode_price', kwargs={'course_id': self.course.id.to_deprecated_string()}) + data = {'course_price': price, 'currency': 'usd'} + response = self.client.post(set_course_price_url, data) + self.assertTrue('CourseMode price updated successfully' in response.content) + + # Course A updated total amount should be visible in e-commerce page if the user is finance admin + url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()}) + response = self.client.get(url) + + self.assertTrue('Course Price: <span>$' + str(price) + '</span>' in response.content) + + def test_user_admin_set_course_price(self): + """ + test to set the course price related functionality. + test al the scenarios for setting a new course price + """ + set_course_price_url = reverse('set_course_mode_price', kwargs={'course_id': self.course.id.to_deprecated_string()}) + data = {'course_price': '12%', 'currency': 'usd'} + + # Value Error course price should be a numeric value + response = self.client.post(set_course_price_url, data) + self.assertTrue("Please Enter the numeric value for the course price" in response.content) + + # validation check passes and course price is successfully added + data['course_price'] = 100 + response = self.client.post(set_course_price_url, data) + self.assertTrue("CourseMode price updated successfully" in response.content) + + course_honor_mode = CourseMode.objects.get(mode_slug='honor') + course_honor_mode.delete() + # Course Mode not exist with mode slug honor + response = self.client.post(set_course_price_url, data) + self.assertTrue("CourseMode with the mode slug({mode_slug}) DoesNotExist".format(mode_slug='honor') in response.content) + def test_add_coupon(self): """ Test Add Coupon Scenarios. Handle all the HttpResponses return by add_coupon view @@ -221,9 +288,9 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): percentage_discount=20, created_by=self.instructor ) coupon1.save() - data = {'coupon_id': coupon.id, 'code': '11111', 'discount': '12'} + data = {'coupon_id': coupon.id, 'code': '11111', 'discount': '12'} # pylint: disable=E1101 response = self.client.post(update_coupon_url, data=data) - self.assertTrue('coupon with the coupon id ({coupon_id}) already exist'.format(coupon_id=coupon.id) in response.content) + self.assertTrue('coupon with the coupon id ({coupon_id}) already exist'.format(coupon_id=coupon.id) in response.content) # pylint: disable=E1101 course_registration = CourseRegistrationCode( code='Vs23Ws4j', course_id=self.course.id.to_deprecated_string(), @@ -231,8 +298,8 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): ) course_registration.save() - data = {'coupon_id': coupon.id, 'code': 'Vs23Ws4j', - 'discount': '6', 'course_id': coupon.course_id.to_deprecated_string()} + data = {'coupon_id': coupon.id, 'code': 'Vs23Ws4j', # pylint: disable=E1101 + 'discount': '6', 'course_id': coupon.course_id.to_deprecated_string()} # pylint: disable=E1101 response = self.client.post(update_coupon_url, data=data) self.assertTrue("The code ({code}) that you have tried to define is already in use as a registration code". format(code=data['code']) in response.content) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 99a6e6d4dcfb58e8a58b3553ae522f109ab1ae0a..49a5a4248c5f808f43d83b26010bd5fc7829100b 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -576,8 +576,8 @@ def get_purchase_transaction(request, course_id, csv=False): # pylint: disable= } return JsonResponse(response_payload) else: - header, datarows = analytics.csvs.format_dictlist(student_data, query_features) - return analytics.csvs.create_csv_response("e-commerce_purchase_transactions.csv", header, datarows) + header, datarows = instructor_analytics.csvs.format_dictlist(student_data, query_features) + return instructor_analytics.csvs.create_csv_response("e-commerce_purchase_transactions.csv", header, datarows) @ensure_csrf_cookie @@ -671,7 +671,7 @@ def registration_codes_csv(file_name, codes_list, csv_type=None): registration_codes = instructor_analytics.basic.course_registration_features(query_features, codes_list, csv_type) header, data_rows = instructor_analytics.csvs.format_dictlist(registration_codes, query_features) - return analytics.csvs.create_csv_response(file_name, header, data_rows) + return instructor_analytics.csvs.create_csv_response(file_name, header, data_rows) def random_code_generator(): diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index baac51455ed90495554b13a32e4b81548fa520ad..78942c00305363eac7db8a6884079539c397a53d 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -1,9 +1,12 @@ """ Instructor Dashboard Views """ +from django.views.decorators.http import require_POST +from django.contrib.auth.decorators import login_required import logging - +import datetime +import pytz from django.utils.translation import ugettext as _ from django_future.csrf import ensure_csrf_cookie from django.views.decorators.cache import cache_control @@ -11,7 +14,7 @@ from edxmako.shortcuts import render_to_response from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.utils.html import escape -from django.http import Http404 +from django.http import Http404, HttpResponse, HttpResponseNotFound from django.conf import settings from lms.lib.xblock.runtime import quote_slashes @@ -27,7 +30,7 @@ from django_comment_client.utils import has_forum_access from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR from student.models import CourseEnrollment from shoppingcart.models import Coupon, PaidCourseRegistration -from course_modes.models import CourseMode +from course_modes.models import CourseMode, CourseModesArchive from student.roles import CourseFinanceAdminRole from bulk_email.models import CourseAuthorization @@ -130,6 +133,10 @@ def _section_e_commerce(course_key, access): """ Provide data for the corresponding dashboard section """ coupons = Coupon.objects.filter(course_id=course_key).order_by('-is_active') total_amount = None + course_price = None + course_honor_mode = CourseMode.mode_for_course(course_key, 'honor') + if course_honor_mode and course_honor_mode.min_price > 0: + course_price = course_honor_mode.min_price if access['finance_admin']: total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(course_key) @@ -148,12 +155,46 @@ def _section_e_commerce(course_key, access): 'generate_registration_code_csv_url': reverse('generate_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}), 'active_registration_code_csv_url': reverse('active_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}), 'spent_registration_code_csv_url': reverse('spent_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}), + 'set_course_mode_url': reverse('set_course_mode_price', kwargs={'course_id': course_key.to_deprecated_string()}), 'coupons': coupons, 'total_amount': total_amount, + 'course_price': course_price } return section_data +@ensure_csrf_cookie +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +@require_POST +@login_required +def set_course_mode_price(request, course_id): + """ + set the new course price and add new entry in the CourseModesArchive Table + """ + try: + course_price = int(request.POST['course_price']) + except ValueError: + return HttpResponseNotFound(_("Please Enter the numeric value for the course price")) + currency = request.POST['currency'] + course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) + + course_honor_mode = CourseMode.objects.filter(mode_slug='honor', course_id=course_key) + if not course_honor_mode: + return HttpResponseNotFound( + _("CourseMode with the mode slug({mode_slug}) DoesNotExist").format(mode_slug='honor') + ) + CourseModesArchive.objects.create( + course_id=course_id, mode_slug='honor', mode_display_name='Honor Code Certificate', + min_price=getattr(course_honor_mode[0], 'min_price'), currency=getattr(course_honor_mode[0], 'currency'), + expiration_datetime=datetime.datetime.now(pytz.utc), expiration_date=datetime.date.today() + ) + course_honor_mode.update( + min_price=course_price, + currency=currency + ) + return HttpResponse(_("CourseMode price updated successfully")) + + def _section_course_info(course_key, access): """ Provide data for the corresponding dashboard section """ course = get_course_by_id(course_key, depth=None) diff --git a/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee b/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee index 8d71b440853b2e055e1e881d1b339ca2c8808536..ddef9d8a27f92f58260d0c439374a4d2ec09f627 100644 --- a/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee +++ b/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee @@ -11,10 +11,10 @@ class ECommerce # gather elements @$list_purchase_csv_btn = @$section.find("input[name='list-purchase-transaction-csv']'") @$transaction_group_name = @$section.find("input[name='transaction_group_name']'") - @$transaction_group_name = @$section.find("input[name='transaction_group_name']'") - @$download_transaction_group_name = @$section.find("input[name='transaction_group_name']'") - @$active_transaction_group_name = @$section.find("input[name='transaction_group_name']'") - @$spent_transaction_group_name = @$section.find('input[name="course_registration_code_number"]') + @$course_registration_number = @$section.find("input[name='course_registration_code_number']'") + @$download_transaction_group_name = @$section.find("input[name='download_transaction_group_name']'") + @$active_transaction_group_name = @$section.find("input[name='active_transaction_group_name']'") + @$spent_transaction_group_name = @$section.find('input[name="spent_transaction_group_name"]') @$generate_registration_code_form = @$section.find("form#course_codes_number") @$download_registration_codes_form = @$section.find("form#download_registration_codes") diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index d76ab6fdbb88faac5535b03920ab900effce8e82..e974e623e2474d95e4cb2aca92a860841a18968d 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -945,7 +945,7 @@ input[name="subject"] { } // coupon edit and add modals - #add-coupon-modal, #edit-coupon-modal{ + #add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal{ .inner-wrapper { background: #fff; } @@ -961,6 +961,10 @@ input[name="subject"] { @include button(simple, $blue); @extend .button-reset; } + input[type="submit"]#set_course_button{ + @include button(simple, $blue); + @extend .button-reset; + } .modal-form-error { box-shadow: inset 0 -1px 2px 0 #f3d9db; -webkit-box-sizing: border-box; @@ -997,9 +1001,21 @@ input[name="subject"] { li:last-child{ margin-bottom: 0px !important; } - } - #coupon-content { + li#set-course-mode-modal-field-price{ + width: 100%; + label.required:after { + content: "*"; + margin-left: 5px; + } + } + li#set-course-mode-modal-field-currency{ + margin-left: 0px !important; + select { + width: 100%; + } + } + #coupon-content, #course-content { padding: 20px; header { margin: 0; diff --git a/lms/templates/instructor/instructor_dashboard_2/e-commerce.html b/lms/templates/instructor/instructor_dashboard_2/e-commerce.html index a97a199c03697848ec524f57e7a2dc8e9b1258a6..3777e27ecd9a63406b8c19e2fb4126362a3f3fea 100644 --- a/lms/templates/instructor/instructor_dashboard_2/e-commerce.html +++ b/lms/templates/instructor/instructor_dashboard_2/e-commerce.html @@ -2,6 +2,7 @@ <%page args="section_data"/> <%include file="add_coupon_modal.html" args="section_data=section_data" /> <%include file="edit_coupon_modal.html" args="section_data=section_data" /> +<%include file="set_course_mode_price_modal.html" args="section_data=section_data" /> <div class="ecommerce-wrapper"> <h3 class="coupon-errors" id="code-error"></h3> @@ -43,17 +44,23 @@ </form> </p> <hr> + <h2>${_("Course Price")}</h2> + <span class="tip">${_("Course Price: ")}<span>$${section_data['course_price']}</span> + %if section_data['access']['finance_admin'] is True: + <a id="course_price_link" href="#set-course-mode-price-modal" rel="leanModal" class="add blue-button">+ Set Price</a> + %endif + </span> + <hr> %if section_data['access']['finance_admin'] is True: + <h2>${_("Transactions")}</h2> + %if section_data['total_amount'] is not None: + <span>${_("Total Amount: ")}<span>$${section_data['total_amount']}</span></span> + %endif - <h2>${_("Transactions")}</h2> - %if section_data['total_amount'] is not None: - <span>${_("Total Amount: ")}<span>$${section_data['total_amount']}</span></span> - %endif - - <p>${_("Click to generate a CSV file for all purchase transactions in this course")}</p> + <p>${_("Click to generate a CSV file for all purchase transactions in this course")}</p> - <p><input type="button" name="list-purchase-transaction-csv" value="${_("Download All e-Commerce Purchases")}" data-endpoint="${ section_data['get_purchase_transaction_url'] }" data-csv="true"></p> -%endif + <p><input type="button" name="list-purchase-transaction-csv" value="${_("Download All e-Commerce Purchases")}" data-endpoint="${ section_data['get_purchase_transaction_url'] }" data-csv="true"></p> + %endif <h2>${_("Coupons List")}</h2> @@ -202,9 +209,38 @@ return false; } }); + $('#course_price_link').click(function () { + reset_input_fields(); + }); $('#add_coupon_link').click(function () { reset_input_fields(); }); + $('#set_price_form').submit(function () { + $("#set_course_button").attr('disabled', true); + // Get the Code and Discount value and trim it + var course_price = $.trim($('#mode_price').val()); + var currency = $.trim($('#course_mode_currency').val()); + + // Check if empty of not + if (course_price === '') { + $('#set_price_form #course_form_error').attr('style', 'display: block !important'); + $('#set_price_form #course_form_error').text("${_('Please Enter the Course Price')}"); + $("#set_course_button").removeAttr('disabled'); + return false; + } + if (!$.isNumeric(course_price)) { + $("#set_course_button").removeAttr('disabled'); + $('#set_price_form #course_form_error').attr('style', 'display: block !important'); + $('#set_price_form #course_form_error').text("${_('Please Enter the Numeric value for Discount')}"); + return false; + } + if (currency == '') { + $('#set_price_form #course_form_error').attr('style', 'display: block !important'); + $('#set_price_form #course_form_error').text("${_('Please Select the Currency')}"); + $("#set_course_button").removeAttr('disabled'); + return false; + } + }); $('#add_coupon_form').submit(function () { $("#add_coupon_button").attr('disabled', true); // Get the Code and Discount value and trim it @@ -238,6 +274,16 @@ } }); + $('#set_price_form').on('ajax:complete', function (event, xhr) { + if (xhr.status == 200) { + location.reload(true); + } else { + $("#set_course_button").removeAttr('disabled'); + $('#set_price_form #course_form_error').attr('style', 'display: block !important'); + $('#set_price_form #course_form_error').text(xhr.responseText); + } + }); + $('#add_coupon_form').on('ajax:complete', function (event, xhr) { if (xhr.status == 200) { location.reload(true); @@ -261,6 +307,7 @@ $('.close-modal').click(function (e) { $("#update_coupon_button").removeAttr('disabled'); $("#add_coupon_button").removeAttr('disabled'); + $("#set_course_button").removeAttr('disabled'); reset_input_fields(); e.preventDefault(); }); @@ -270,7 +317,9 @@ $(".remove_coupon").focus(); $("#edit-coupon-modal").attr("aria-hidden", "true"); $(".edit-right").focus(); + $("#set-course-mode-price-modal").attr("aria-hidden", "true"); $("#add_coupon_button").removeAttr('disabled'); + $("#set_course_button").removeAttr('disabled'); $("#update_coupon_button").removeAttr('disabled'); reset_input_fields(); }; @@ -288,16 +337,17 @@ $("#add-coupon-modal .close-modal").click(onModalClose); $("#edit-coupon-modal .close-modal").click(onModalClose); - $("#add-coupon-modal .close-modal").click(reset_input_fields); + $("#set-course-mode-price-modal .close-modal").click(reset_input_fields); // Hitting the ESC key will exit the modal - $("#add-coupon-modal, #edit-coupon-modal").on("keydown", function (e) { + $("#add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal").on("keydown", function (e) { var keyCode = e.keyCode || e.which; // 27 is the ESC key if (keyCode === 27) { e.preventDefault(); $("#add-coupon-modal .close-modal").click(); + $("#set-course-mode-price-modal .close-modal").click(); $("#edit-coupon-modal .close-modal").click(); } }); @@ -306,7 +356,9 @@ $('#coupon-error').val(''); $('#coupon-error').attr('style', 'display: none'); $('#add_coupon_form #coupon_form_error').attr('style', 'display: none'); + $('#set_price_form #course_form_error').attr('style', 'display: none'); $('#add_coupon_form #coupon_form_error').text(); + $('input#mode_price').val(''); $('input#coupon_code').val(''); $('input#coupon_discount').val(''); $('textarea#coupon_description').val(''); diff --git a/lms/templates/instructor/instructor_dashboard_2/set_course_mode_price_modal.html b/lms/templates/instructor/instructor_dashboard_2/set_course_mode_price_modal.html new file mode 100644 index 0000000000000000000000000000000000000000..f0c37f97cc0ebc60513e5699b19e5e2852eabbc0 --- /dev/null +++ b/lms/templates/instructor/instructor_dashboard_2/set_course_mode_price_modal.html @@ -0,0 +1,48 @@ +<%! from django.utils.translation import ugettext as _ %> +<%! from django.core.urlresolvers import reverse %> +<%page args="section_data"/> +<section id="set-course-mode-price-modal" class="modal" role="dialog" tabindex="-1" aria-label="${_('Set Course Mode Price')}"> + <div class="inner-wrapper"> + <button class="close-modal"> + <i class="icon-remove"></i> + <span class="sr"> + ## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen) + ${_('Close')} + </span> + </button> + + <div id="course-content"> + <header> + <h2>${_("Set Course Mode Price")}</h2> + </header> + + <div class="instructions"> + <p> + ${_("Please enter Course Mode detail below")}</p> + </div> + + <form id="set_price_form" action="${section_data['set_course_mode_url']}" method="post" data-remote="true"> + <div id="course_form_error" class="modal-form-error"></div> + <fieldset class="group group-form group-form-requiredinformation"> + <legend class="is-hidden">${_("Required Information")}</legend> + + <ol class="list-input"> + <li class="field required text" id="set-course-mode-modal-field-price"> + <label for="mode_price" class="required">${_("Course Price")}</label> + <input class="field" id="mode_price" type="text" name="course_price" placeholder="${section_data['course_price']}" aria-required="true"> + </li> + <li class="field required text" id="set-course-mode-modal-field-currency"> + <label for="course_mode_currency" class="required text">${_("Currency")}</label> + <select class="field required" id="course_mode_currency" name="currency"> + <option value="usd">USD</option> + </select> + </li> + </ol> + </fieldset> + <div class="submit"> + <input name="submit" type="submit" id="set_course_button" value="${_('Set Price')}"/> + </div> + </form> + </div> + </div> +</section> diff --git a/lms/urls.py b/lms/urls.py index 815e14cf2bc9a084cfd971451ed0f7bf39a1d94c..05df6193b4879258c3e818533343841718671d81 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -280,6 +280,8 @@ if settings.COURSEWARE_ENABLED: # For the instructor url(r'^courses/{}/instructor$'.format(settings.COURSE_ID_PATTERN), 'instructor.views.instructor_dashboard.instructor_dashboard_2', name="instructor_dashboard"), + url(r'^courses/{}/set_course_mode_price$'.format(settings.COURSE_ID_PATTERN), + 'instructor.views.instructor_dashboard.set_course_mode_price', name="set_course_mode_price"), url(r'^courses/{}/instructor/api/'.format(settings.COURSE_ID_PATTERN), include('instructor.views.api_urls')), url(r'^courses/{}/remove_coupon$'.format(settings.COURSE_ID_PATTERN),