WL-135 added the migration for the expiration_date field in the coupons model
added the functionality of adding the expiration date, and also handled in the shopping cart when the user try to add the expired coupon code in the shopping cart added expiration date in the coupons list in the ecommerce page added the unit tests and jasmine tests fix the quality issues and rebased with master regenerated the migration file with a different number i18n and verified te course mode changes suggested by stephan
This commit is contained in:
committed by
Muhammad Shoaib
parent
30b572b1b1
commit
10f8d8c097
@@ -4,6 +4,8 @@ Unit tests for instructor.api methods.
|
||||
"""
|
||||
import datetime
|
||||
import ddt
|
||||
import random
|
||||
import pytz
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
@@ -61,7 +63,7 @@ from .test_tools import msk_from_problem_urlname
|
||||
from ..views.tools import get_extended_due
|
||||
|
||||
EXPECTED_CSV_HEADER = '"code","course_id","company_name","created_by","redeemed_by","invoice_id","purchaser","customer_reference_number","internal_reference"'
|
||||
EXPECTED_COUPON_CSV_HEADER = '"course_id","percentage_discount","code_redeemed_count","description"'
|
||||
EXPECTED_COUPON_CSV_HEADER = '"code","course_id","percentage_discount","code_redeemed_count","description"'
|
||||
|
||||
# ddt data for test cases involving reports
|
||||
REPORTS_DATA = (
|
||||
@@ -3331,8 +3333,27 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
#now create coupons with the expiration dates
|
||||
for i in range(5):
|
||||
coupon = Coupon(
|
||||
code='coupon{0}'.format(i), description='test_description', course_id=self.course.id,
|
||||
percentage_discount='{0}'.format(i), created_by=self.instructor, is_active=True,
|
||||
expiration_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
response = self.client.get(get_coupon_code_url)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
# filter all the coupons
|
||||
for coupon in Coupon.objects.all():
|
||||
self.assertIn('"{code}","{course_id}","{discount}","0","{description}","{expiration_date}"'.format(
|
||||
code=coupon.code,
|
||||
course_id=coupon.course_id,
|
||||
discount=coupon.percentage_discount,
|
||||
description=coupon.description,
|
||||
expiration_date=coupon.display_expiry_date
|
||||
), response.content)
|
||||
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_COUPON_CSV_HEADER))
|
||||
|
||||
@@ -3,6 +3,8 @@ Unit tests for Ecommerce feature flag in new instructor dashboard.
|
||||
"""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
import datetime
|
||||
import pytz
|
||||
from django.test.utils import override_settings
|
||||
from mock import patch
|
||||
|
||||
@@ -144,13 +146,26 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
|
||||
"""
|
||||
# URL for add_coupon
|
||||
add_coupon_url = reverse('add_coupon', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
expiration_date = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
|
||||
data = {
|
||||
'code': 'A2314', 'course_id': self.course.id.to_deprecated_string(),
|
||||
'description': 'ADSADASDSAD', 'created_by': self.instructor, 'discount': 5
|
||||
'description': 'ADSADASDSAD', 'created_by': self.instructor, 'discount': 5,
|
||||
'expiration_date': '{month}/{day}/{year}'.format(month=expiration_date.month, day=expiration_date.day, year=expiration_date.year)
|
||||
}
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
self.assertTrue("coupon with the coupon code ({code}) added successfully".format(code=data['code']) in response.content)
|
||||
|
||||
#now add the coupon with the wrong value in the expiration_date
|
||||
# server will through the ValueError Exception in the expiration_date field
|
||||
data = {
|
||||
'code': '213454', 'course_id': self.course.id.to_deprecated_string(),
|
||||
'description': 'ADSADASDSAD', 'created_by': self.instructor, 'discount': 5,
|
||||
'expiration_date': expiration_date.strftime('"%d/%m/%Y')
|
||||
}
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
self.assertTrue("Please enter the date in this format i-e month/day/year" in response.content)
|
||||
|
||||
data = {
|
||||
'code': 'A2314', 'course_id': self.course.id.to_deprecated_string(),
|
||||
'description': 'asdsasda', 'created_by': self.instructor, 'discount': 99
|
||||
@@ -221,13 +236,15 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
|
||||
"""
|
||||
coupon = Coupon(
|
||||
code='AS452', description='asdsadsa', course_id=self.course.id.to_deprecated_string(),
|
||||
percentage_discount=10, created_by=self.instructor
|
||||
percentage_discount=10, created_by=self.instructor,
|
||||
expiration_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
# URL for edit_coupon_info
|
||||
edit_url = reverse('get_coupon_info', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
response = self.client.post(edit_url, {'id': coupon.id})
|
||||
self.assertTrue('coupon with the coupon id ({coupon_id}) updated successfully'.format(coupon_id=coupon.id) in response.content)
|
||||
self.assertIn(coupon.display_expiry_date, response.content)
|
||||
|
||||
response = self.client.post(edit_url, {'id': 444444})
|
||||
self.assertTrue('coupon with the coupon id ({coupon_id}) DoesNotExist'.format(coupon_id=444444) in response.content)
|
||||
|
||||
@@ -18,6 +18,7 @@ from django.views.decorators.cache import cache_control
|
||||
from django.core.exceptions import ValidationError, PermissionDenied
|
||||
from django.core.mail.message import EmailMessage
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import validate_email
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -28,6 +29,8 @@ import random
|
||||
import unicodecsv
|
||||
import urllib
|
||||
from util.file import store_uploaded_file, course_and_time_based_filename_generator, FileValidationException, UniversalNewlineIterator
|
||||
import datetime
|
||||
import pytz
|
||||
from util.json_request import JsonResponse
|
||||
from instructor.views.instructor_task_helpers import extract_email_features, extract_task_features
|
||||
|
||||
@@ -1007,9 +1010,14 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument
|
||||
Respond with csv which contains a summary of all Active Coupons.
|
||||
"""
|
||||
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
active_coupons = Coupon.objects.filter(course_id=course_id, is_active=True)
|
||||
active_coupons = Coupon.objects.filter(
|
||||
Q(course_id=course_id),
|
||||
Q(is_active=True),
|
||||
Q(expiration_date__gt=datetime.datetime.now(pytz.UTC)) |
|
||||
Q(expiration_date__isnull=True)
|
||||
)
|
||||
query_features = [
|
||||
'course_id', 'percentage_discount', 'code_redeemed_count', 'description'
|
||||
'code', 'course_id', 'percentage_discount', 'code_redeemed_count', 'description', 'expiration_date'
|
||||
]
|
||||
coupons_list = instructor_analytics.basic.coupon_codes_features(query_features, active_coupons)
|
||||
header, data_rows = instructor_analytics.csvs.format_dictlist(coupons_list, query_features)
|
||||
|
||||
@@ -10,7 +10,8 @@ from util.json_request import JsonResponse
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from shoppingcart.models import Coupon, CourseRegistrationCode
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
import datetime
|
||||
import pytz
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -79,9 +80,22 @@ def add_coupon(request, course_id): # pylint: disable=unused-argument
|
||||
return JsonResponse({
|
||||
'message': _("Please Enter the Coupon Discount Value Less than or Equal to 100")
|
||||
}, status=400) # status code 400: Bad Request
|
||||
expiration_date = None
|
||||
if request.POST.get('expiration_date'):
|
||||
expiration_date = request.POST.get('expiration_date')
|
||||
try:
|
||||
expiration_date = datetime.datetime.strptime(expiration_date, "%m/%d/%Y").replace(tzinfo=pytz.UTC) + datetime.timedelta(days=1)
|
||||
except ValueError:
|
||||
return JsonResponse({
|
||||
'message': _("Please enter the date in this format i-e month/day/year")
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
coupon = Coupon(
|
||||
code=code, description=description, course_id=course_id,
|
||||
percentage_discount=discount, created_by_id=request.user.id
|
||||
code=code, description=description,
|
||||
course_id=course_id,
|
||||
percentage_discount=discount,
|
||||
created_by_id=request.user.id,
|
||||
expiration_date=expiration_date
|
||||
)
|
||||
coupon.save()
|
||||
return JsonResponse(
|
||||
@@ -143,10 +157,12 @@ def get_coupon_info(request, course_id): # pylint: disable=unused-argument
|
||||
'message': _("coupon with the coupon id ({coupon_id}) is already inactive").format(coupon_id=coupon_id)
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
expiry_date = coupon.display_expiry_date
|
||||
return JsonResponse({
|
||||
'coupon_code': coupon.code,
|
||||
'coupon_description': coupon.description,
|
||||
'coupon_course_id': coupon.course_id.to_deprecated_string(),
|
||||
'coupon_discount': coupon.percentage_discount,
|
||||
'expiry_date': expiry_date,
|
||||
'message': _('coupon with the coupon id ({coupon_id}) updated successfully').format(coupon_id=coupon_id)
|
||||
}) # status code 200: OK by default
|
||||
|
||||
@@ -30,7 +30,7 @@ SALE_ORDER_FEATURES = ('id', 'company_name', 'company_contact_name', 'company_co
|
||||
|
||||
AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES
|
||||
COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'created_by', 'created_at')
|
||||
COUPON_FEATURES = ('course_id', 'percentage_discount', 'description')
|
||||
COUPON_FEATURES = ('code', 'course_id', 'percentage_discount', 'description', 'expiration_date')
|
||||
|
||||
|
||||
def sale_order_record_features(course_id, features):
|
||||
@@ -228,6 +228,7 @@ def coupon_codes_features(features, coupons_list):
|
||||
# codes csv. In the case of active and generated registration codes the redeemed_by value will be None.
|
||||
# They have not been redeemed yet
|
||||
|
||||
coupon_dict['expiration_date'] = coupon.display_expiry_date
|
||||
coupon_dict['course_id'] = coupon_dict['course_id'].to_deprecated_string()
|
||||
return coupon_dict
|
||||
return [extract_coupon(coupon, features) for coupon in coupons_list]
|
||||
|
||||
@@ -22,6 +22,10 @@ from courseware.tests.factories import InstructorFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
import datetime
|
||||
from django.db.models import Q
|
||||
import pytz
|
||||
|
||||
|
||||
class TestAnalyticsBasic(ModuleStoreTestCase):
|
||||
""" Test basic analytics functions. """
|
||||
@@ -303,7 +307,7 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
|
||||
|
||||
def test_coupon_codes_features(self):
|
||||
query_features = [
|
||||
'course_id', 'percentage_discount', 'code_redeemed_count', 'description'
|
||||
'course_id', 'percentage_discount', 'code_redeemed_count', 'description', 'expiration_date'
|
||||
]
|
||||
for i in range(10):
|
||||
coupon = Coupon(
|
||||
@@ -314,13 +318,29 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
|
||||
is_active=True
|
||||
)
|
||||
coupon.save()
|
||||
active_coupons = Coupon.objects.filter(course_id=self.course.id, is_active=True)
|
||||
#now create coupons with the expiration dates
|
||||
for i in range(5):
|
||||
coupon = Coupon(
|
||||
code='coupon{0}'.format(i), description='test_description', course_id=self.course.id,
|
||||
percentage_discount='{0}'.format(i), created_by=self.instructor, is_active=True,
|
||||
expiration_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
active_coupons = Coupon.objects.filter(
|
||||
Q(course_id=self.course.id),
|
||||
Q(is_active=True),
|
||||
Q(expiration_date__gt=datetime.datetime.now(pytz.UTC)) |
|
||||
Q(expiration_date__isnull=True)
|
||||
)
|
||||
active_coupons_list = coupon_codes_features(query_features, active_coupons)
|
||||
self.assertEqual(len(active_coupons_list), len(active_coupons))
|
||||
for active_coupon in active_coupons_list:
|
||||
self.assertEqual(set(active_coupon.keys()), set(query_features))
|
||||
self.assertIn(active_coupon['percentage_discount'], [coupon.percentage_discount for coupon in active_coupons])
|
||||
self.assertIn(active_coupon['description'], [coupon.description for coupon in active_coupons])
|
||||
if active_coupon['expiration_date']:
|
||||
self.assertIn(active_coupon['expiration_date'], [coupon.display_expiry_date for coupon in active_coupons])
|
||||
self.assertIn(
|
||||
active_coupon['course_id'],
|
||||
[coupon.course_id.to_deprecated_string() for coupon in active_coupons]
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
# -*- 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 field 'Coupon.expiration_date'
|
||||
db.add_column('shoppingcart_coupon', 'expiration_date',
|
||||
self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'Coupon.expiration_date'
|
||||
db.delete_column('shoppingcart_coupon', 'expiration_date')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'shoppingcart.certificateitem': {
|
||||
'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.coupon': {
|
||||
'Meta': {'object_name': 'Coupon'},
|
||||
'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 6, 0, 0)'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'})
|
||||
},
|
||||
'shoppingcart.couponredemption': {
|
||||
'Meta': {'object_name': 'CouponRedemption'},
|
||||
'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.courseregcodeitem': {
|
||||
'Meta': {'object_name': 'CourseRegCodeItem', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.courseregcodeitemannotation': {
|
||||
'Meta': {'object_name': 'CourseRegCodeItemAnnotation'},
|
||||
'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.courseregistrationcode': {
|
||||
'Meta': {'object_name': 'CourseRegistrationCode'},
|
||||
'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 6, 0, 0)'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'purchase_order'", 'null': 'True', 'to': "orm['shoppingcart.Order']"})
|
||||
},
|
||||
'shoppingcart.donation': {
|
||||
'Meta': {'object_name': 'Donation', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'donation_type': ('django.db.models.fields.CharField', [], {'default': "'general'", 'max_length': '32'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.donationconfiguration': {
|
||||
'Meta': {'object_name': 'DonationConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.invoice': {
|
||||
'Meta': {'object_name': 'Invoice'},
|
||||
'address_line_1': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'address_line_2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'address_line_3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'total_amount': ('django.db.models.fields.FloatField', [], {}),
|
||||
'zip': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'})
|
||||
},
|
||||
'shoppingcart.order': {
|
||||
'Meta': {'object_name': 'Order'},
|
||||
'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
|
||||
'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
|
||||
'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
|
||||
'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
|
||||
'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order_type': ('django.db.models.fields.CharField', [], {'default': "'personal'", 'max_length': '32'}),
|
||||
'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.orderitem': {
|
||||
'Meta': {'object_name': 'OrderItem'},
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}),
|
||||
'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}),
|
||||
'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||
'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}),
|
||||
'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.paidcourseregistration': {
|
||||
'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.paidcourseregistrationannotation': {
|
||||
'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'},
|
||||
'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.registrationcoderedemption': {
|
||||
'Meta': {'object_name': 'RegistrationCodeRedemption'},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']", 'null': 'True'}),
|
||||
'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 6, 0, 0)', 'null': 'True'}),
|
||||
'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"})
|
||||
},
|
||||
'student.courseenrollment': {
|
||||
'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['shoppingcart']
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
import analytics
|
||||
import pytz
|
||||
@@ -803,12 +804,20 @@ class Coupon(models.Model):
|
||||
created_by = models.ForeignKey(User)
|
||||
created_at = models.DateTimeField(default=datetime.now(pytz.utc))
|
||||
is_active = models.BooleanField(default=True)
|
||||
expiration_date = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return "[Coupon] code: {} course: {}".format(self.code, self.course_id)
|
||||
|
||||
objects = SoftDeleteCouponManager()
|
||||
|
||||
@property
|
||||
def display_expiry_date(self):
|
||||
"""
|
||||
return the coupon expiration date in the readable format
|
||||
"""
|
||||
return (self.expiration_date - timedelta(days=1)).strftime("%B %d, %Y") if self.expiration_date else None
|
||||
|
||||
|
||||
class CouponRedemption(models.Model):
|
||||
"""
|
||||
|
||||
@@ -369,7 +369,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
self.assertIn("Coupon '{0}' is not valid for any course in the shopping cart.".format(self.coupon_code), resp.content)
|
||||
self.assertIn("Discount does not exist against code '{0}'.".format(self.coupon_code), resp.content)
|
||||
|
||||
def test_course_does_not_exist_in_cart_against_valid_reg_code(self):
|
||||
course_key = self.course_key.to_deprecated_string() + 'testing'
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import datetime
|
||||
import decimal
|
||||
import pytz
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.http import (
|
||||
@@ -258,7 +259,12 @@ def use_code(request):
|
||||
Registration Code Redemption page.
|
||||
"""
|
||||
code = request.POST["code"]
|
||||
coupons = Coupon.objects.filter(code=code, is_active=True)
|
||||
coupons = Coupon.objects.filter(
|
||||
Q(code=code),
|
||||
Q(is_active=True),
|
||||
Q(expiration_date__gt=datetime.datetime.now(pytz.UTC)) |
|
||||
Q(expiration_date__isnull=True)
|
||||
)
|
||||
if not coupons:
|
||||
# If no coupons then we check that code against course registration code
|
||||
try:
|
||||
@@ -423,9 +429,8 @@ def use_coupon_code(coupons, user):
|
||||
return HttpResponseBadRequest(_("Only one coupon redemption is allowed against an order"))
|
||||
|
||||
if not is_redemption_applied:
|
||||
log.warning("Course item does not exist for coupon '{code}'".format(code=coupons[0].code))
|
||||
return HttpResponseNotFound(
|
||||
_("Coupon '{code}' is not valid for any course in the shopping cart.".format(code=coupons[0].code)))
|
||||
log.warning("Discount does not exist against code '{code}'.".format(code=coupons[0].code))
|
||||
return HttpResponseNotFound(_("Discount does not exist against code '{code}'.".format(code=coupons[0].code)))
|
||||
|
||||
return HttpResponse(
|
||||
json.dumps({'response': 'success', 'coupon_code_applied': True}),
|
||||
|
||||
35
lms/static/js/instructor_dashboard/ecommerce.js
Normal file
35
lms/static/js/instructor_dashboard/ecommerce.js
Normal file
@@ -0,0 +1,35 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function(Backbone, $, _) {
|
||||
'use strict';
|
||||
|
||||
edx.instructor_dashboard = edx.instructor_dashboard || {};
|
||||
edx.instructor_dashboard.ecommerce = {};
|
||||
|
||||
edx.instructor_dashboard.ecommerce.ExpiryCouponView = Backbone.View.extend({
|
||||
el: 'li#add-coupon-modal-field-expiry',
|
||||
events: {
|
||||
'click input[type="checkbox"]': 'clicked'
|
||||
},
|
||||
initialize: function() {
|
||||
$('li#add-coupon-modal-field-expiry input[name="expiration_date"]').hide();
|
||||
_.bindAll(this, 'clicked');
|
||||
},
|
||||
clicked: function (event) {
|
||||
if (event.currentTarget.checked) {
|
||||
this.$el.find('#coupon_expiration_date').show();
|
||||
this.$el.find('#coupon_expiration_date').focus();
|
||||
}
|
||||
else {
|
||||
this.$el.find('#coupon_expiration_date').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$( "#coupon_expiration_date" ).datepicker({
|
||||
minDate: 0
|
||||
});
|
||||
var view = new edx.instructor_dashboard.ecommerce.ExpiryCouponView();
|
||||
});
|
||||
}).call(this, Backbone, $, _);
|
||||
36
lms/static/js/spec/instructor_dashboard/ecommerce_spec.js
Normal file
36
lms/static/js/spec/instructor_dashboard/ecommerce_spec.js
Normal file
@@ -0,0 +1,36 @@
|
||||
define(['backbone', 'jquery', 'js/instructor_dashboard/ecommerce', 'js/common_helpers/template_helpers'],
|
||||
function (Backbone, $, ExpiryCouponView, TemplateHelpers) {
|
||||
'use strict';
|
||||
var expiryCouponView, createExpiryCoupon;
|
||||
describe("edx.instructor_dashboard.ecommerce.ExpiryCouponView", function() {
|
||||
beforeEach(function() {
|
||||
setFixtures('<li class="field full-width" id="add-coupon-modal-field-expiry"><input id="expiry-check" type="checkbox"/><label for="expiry-check"></label><input type="text" id="coupon_expiration_date" class="field" name="expiration_date" aria-required="true"/></li>')
|
||||
expiryCouponView = new ExpiryCouponView();
|
||||
});
|
||||
|
||||
it("is defined", function () {
|
||||
expect(expiryCouponView).toBeDefined();
|
||||
});
|
||||
|
||||
it("triggers the callback when the checkbox is clicked", function () {
|
||||
var target = expiryCouponView.$el.find('input[type="checkbox"]');
|
||||
spyOn(expiryCouponView, 'clicked');
|
||||
expiryCouponView.delegateEvents();
|
||||
target.click();
|
||||
expect(expiryCouponView.clicked).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows the input field when the checkbox is checked", function () {
|
||||
var target = expiryCouponView.$el.find('input[type="checkbox"]');
|
||||
target.attr("checked","checked");
|
||||
target.click();
|
||||
expect(expiryCouponView.$el.find('#coupon_expiration_date')).toHaveAttr('style','display: inline;');
|
||||
});
|
||||
|
||||
it("hides the input field when the checkbox is unchecked", function () {
|
||||
var target = expiryCouponView.$el.find('input[type="checkbox"]');
|
||||
expect(expiryCouponView.$el.find('#coupon_expiration_date')).toHaveAttr('style','display: none;');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -270,6 +270,10 @@
|
||||
},
|
||||
|
||||
// Backbone classes loaded explicitly until they are converted to use RequireJS
|
||||
'js/instructor_dashboard/ecommerce': {
|
||||
exports: 'edx.instructor_dashboard.ecommerce.ExpiryCouponView',
|
||||
deps: ['backbone', 'jquery', 'underscore']
|
||||
},
|
||||
'js/models/cohort': {
|
||||
exports: 'CohortModel',
|
||||
deps: ['backbone']
|
||||
@@ -497,6 +501,7 @@
|
||||
'lms/include/js/spec/views/file_uploader_spec.js',
|
||||
'lms/include/js/spec/dashboard/donation.js',
|
||||
'lms/include/js/spec/shoppingcart/shoppingcart_spec.js',
|
||||
'lms/include/js/spec/instructor_dashboard/ecommerce_spec.js',
|
||||
'lms/include/js/spec/student_account/account_spec.js',
|
||||
'lms/include/js/spec/student_account/access_spec.js',
|
||||
'lms/include/js/spec/student_account/login_spec.js',
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
#ui-datepicker-div{z-index: 12000 !important; width: 16.5em !important}
|
||||
|
||||
|
||||
.instructor-dashboard-wrapper-2 {
|
||||
position: relative;
|
||||
// display: table;
|
||||
@@ -1325,12 +1328,13 @@ input[name="subject"] {
|
||||
th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid $border-color-1;
|
||||
font-size: 16px;
|
||||
|
||||
&.c_code {
|
||||
width: 170px;
|
||||
width: 110px;
|
||||
}
|
||||
&.c_count {
|
||||
width: 85px;
|
||||
width: 60px;
|
||||
}
|
||||
&.c_course_id {
|
||||
width: 320px;
|
||||
@@ -1339,11 +1343,14 @@ input[name="subject"] {
|
||||
&.c_discount {
|
||||
width: 90px;
|
||||
}
|
||||
&.c_expiry {
|
||||
width: 150px;
|
||||
}
|
||||
&.c_action {
|
||||
width: 89px;
|
||||
width: 60px;
|
||||
}
|
||||
&.c_dsc{
|
||||
width: 260px;
|
||||
width: 200px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
@@ -1361,6 +1368,16 @@ input[name="subject"] {
|
||||
}
|
||||
}
|
||||
}
|
||||
// in_active coupon rows style
|
||||
.expired_coupon{
|
||||
background: #FEEFB3 !important;
|
||||
|
||||
color: rgba(51,51,51,0.2);
|
||||
border-bottom: 1px solid #fff;
|
||||
td:nth-child(3) {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
// coupon items style
|
||||
.coupons-items {
|
||||
@@ -1368,6 +1385,7 @@ input[name="subject"] {
|
||||
padding: ($baseline/2) 0;
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
font-size: 14px;
|
||||
span.old-price{
|
||||
left: -75px;
|
||||
position: relative;
|
||||
@@ -1613,7 +1631,15 @@ input[name="subject"] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#add-coupon-modal{
|
||||
ol.list-input{
|
||||
li{
|
||||
input[type="checkbox"]#expiry-check , input[type="checkbox"]#expiry-check + label {display: inline-block; width: auto;margin-top: 10px;}
|
||||
&.full-width{width: 100%;}
|
||||
input#coupon_expiration_date{width: 278px;display: inline-block;float: right;}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-distribution-widget {
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
<input class="field readonly" id="coupon_course_id" type="text" name="course_id" value="${section_data['course_id'] | h}"
|
||||
readonly aria-required="true"/>
|
||||
</li>
|
||||
<li class="field full-width" id="add-coupon-modal-field-expiry">
|
||||
<input id="expiry-check" type="checkbox" value="true" />
|
||||
<label for="expiry-check">${_('Add expiration date')}</label>
|
||||
<input type="text" id="coupon_expiration_date" value="" class="field" name="expiration_date"
|
||||
aria-required="true"/>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from datetime import datetime, timedelta %>
|
||||
<%! import pytz %>
|
||||
<%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" />
|
||||
@@ -95,6 +97,7 @@
|
||||
<tr class="coupons-headings">
|
||||
<th class="c_code">${_("Code")}</th>
|
||||
<th class="c_dsc">${_("Description")}</th>
|
||||
<th class="c_expiry">${_("Expiry Date")}</th>
|
||||
<th class="c_discount">${_("Discount (%)")}</th>
|
||||
<th class="c_count">${_("Redeem Count")}</th>
|
||||
<th class="c_action">${_("Actions")}</th>
|
||||
@@ -102,14 +105,21 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
%for coupon in section_data['coupons']:
|
||||
<% current_date = datetime.now(pytz.UTC) %>
|
||||
<% coupon_expiry_date = coupon.expiration_date %>
|
||||
%if coupon.is_active == False:
|
||||
<tr class="coupons-items inactive_coupon">
|
||||
%else:
|
||||
%elif coupon_expiry_date is not None and current_date >= coupon_expiry_date:
|
||||
<tr class="coupons-items expired_coupon">
|
||||
%else:
|
||||
<tr class="coupons-items">
|
||||
%endif
|
||||
<td>${coupon.code}</td>
|
||||
<td>${coupon.description}</td>
|
||||
<td>${coupon.percentage_discount}</td>
|
||||
<td>${_('{code}').format(code=coupon.code)}</td>
|
||||
<td>${_('{description}').format(description=coupon.description)}</td>
|
||||
<td>
|
||||
${coupon.display_expiry_date}
|
||||
</td>
|
||||
<td>${_('{discount}').format(discount=coupon.percentage_discount)}</td>
|
||||
<td>${ coupon.couponredemption_set.filter(order__status='purchased').count() }</td>
|
||||
<td><a data-item-id="${coupon.id}" class='remove_coupon' href='#'>[x]</a><a href="#edit-modal" data-item-id="${coupon.id}" class="edit-right">${_('Edit')}</a></td>
|
||||
</tr>
|
||||
@@ -184,7 +194,7 @@
|
||||
}
|
||||
if($('#invoice_number').val() == "") {
|
||||
$('#error-msg').attr('class','error-msgs')
|
||||
$('#error-msg').html("${_("Invoice number should not be empty.")}").show();
|
||||
$('#error-msg').html("${_('Invoice number should not be empty.')}").show();
|
||||
return
|
||||
}
|
||||
$.ajax({
|
||||
@@ -224,6 +234,12 @@
|
||||
$('input#edit_coupon_discount').val(data.coupon_discount);
|
||||
$('textarea#edit_coupon_description').val(data.coupon_description);
|
||||
$('input#edit_coupon_course_id').val(data.coupon_course_id);
|
||||
if (data.expiry_date) {
|
||||
$('input#edit_coupon_expiration_date').val(data.expiry_date);
|
||||
}
|
||||
else {
|
||||
$('input#edit_coupon_expiration_date').val("${_('Never Expires')}");
|
||||
}
|
||||
$('#edit-modal-trigger').click();
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
@@ -459,6 +475,7 @@
|
||||
var coupon_discount = $.trim($('#coupon_discount').val());
|
||||
var course_id = $.trim($('#coupon_course_id').val());
|
||||
var description = $.trim($('#coupon_description').val());
|
||||
var expiration_date = $.trim($('#coupon_expiration_date').val());
|
||||
|
||||
// Check if empty of not
|
||||
if (code === '') {
|
||||
@@ -485,7 +502,8 @@
|
||||
"code" : code,
|
||||
"discount": coupon_discount,
|
||||
"course_id": course_id,
|
||||
"description": description
|
||||
"description": description,
|
||||
"expiration_date": expiration_date
|
||||
},
|
||||
url: "${section_data['ajax_add_coupon']}",
|
||||
success: function (data) {
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
<input class="field readonly" id="edit_coupon_course_id" type="text" name="course_id" value=""
|
||||
readonly aria-required="true"/>
|
||||
</li>
|
||||
|
||||
<li class="field" id="edit-coupon-modal-field-expiration-date">
|
||||
<label for="edit_coupon_expiration_date">${_("Expiration Date")}</label>
|
||||
<input class="field readonly" id="edit_coupon_expiration_date" type="text" name="expiration_date" value=""
|
||||
readonly aria-required="true"/>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<%static:js group='application'/>
|
||||
|
||||
## Backbone classes declared explicitly until RequireJS is supported
|
||||
<script type="text/javascript" src="${static.url('js/instructor_dashboard/ecommerce.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/models/notification.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/views/notification.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/views/file_uploader.js')}"></script>
|
||||
|
||||
Reference in New Issue
Block a user