Merge branch 'rc/2014-07-02' into testmergemaster
Conflicts: lms/djangoapps/instructor/views/instructor_dashboard.py
This commit is contained in:
@@ -36,13 +36,15 @@ from importlib import import_module
|
||||
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
import lms.lib.comment_client as cc
|
||||
from util.query import use_read_replica_if_available
|
||||
from xmodule_django.models import CourseKeyField, NoneToEmptyManager
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from functools import total_ordering
|
||||
|
||||
from certificates.models import GeneratedCertificate
|
||||
from course_modes.models import CourseMode
|
||||
|
||||
unenroll_done = Signal(providing_args=["course_enrollment"])
|
||||
log = logging.getLogger(__name__)
|
||||
AUDIT_LOG = logging.getLogger("audit")
|
||||
@@ -953,6 +955,11 @@ class CourseEnrollment(models.Model):
|
||||
# (side-effects are bad)
|
||||
if getattr(self, 'can_refund', None) is not None:
|
||||
return True
|
||||
|
||||
# If the student has already been given a certificate they should not be refunded
|
||||
if GeneratedCertificate.certificate_for_student(self.user, self.course_id) is not None:
|
||||
return False
|
||||
|
||||
course_mode = CourseMode.mode_for_course(self.course_id, 'verified')
|
||||
if course_mode is None:
|
||||
return False
|
||||
|
||||
@@ -30,6 +30,8 @@ from student.views import (process_survey_link, _cert_info,
|
||||
change_enrollment, complete_course_mode_info)
|
||||
from student.tests.factories import UserFactory, CourseModeFactory
|
||||
|
||||
from certificates.models import CertificateStatuses
|
||||
from certificates.tests.factories import GeneratedCertificateFactory
|
||||
import shoppingcart
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -212,6 +214,7 @@ class DashboardTest(TestCase):
|
||||
self.assertFalse(course_mode_info['show_upsell'])
|
||||
self.assertIsNone(course_mode_info['days_for_upsell'])
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_refundable(self):
|
||||
verified_mode = CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
@@ -227,6 +230,26 @@ class DashboardTest(TestCase):
|
||||
verified_mode.save()
|
||||
self.assertFalse(enrollment.refundable())
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
def test_refundable_when_certificate_exists(self):
|
||||
verified_mode = CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
mode_slug='verified',
|
||||
mode_display_name='Verified',
|
||||
expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1)
|
||||
)
|
||||
enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode='verified')
|
||||
|
||||
self.assertTrue(enrollment.refundable())
|
||||
|
||||
generated_certificate = GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
status=CertificateStatuses.downloadable,
|
||||
mode='verified'
|
||||
)
|
||||
|
||||
self.assertFalse(enrollment.refundable())
|
||||
|
||||
class EnrollInCourseTest(TestCase):
|
||||
"""Tests enrolling and unenrolling in courses."""
|
||||
|
||||
@@ -81,6 +81,9 @@ class CertificateWhitelist(models.Model):
|
||||
|
||||
|
||||
class GeneratedCertificate(models.Model):
|
||||
|
||||
MODES = Choices('verified', 'honor', 'audit')
|
||||
|
||||
user = models.ForeignKey(User)
|
||||
course_id = CourseKeyField(max_length=255, blank=True, default=None)
|
||||
verify_uuid = models.CharField(max_length=32, blank=True, default='')
|
||||
@@ -90,7 +93,6 @@ class GeneratedCertificate(models.Model):
|
||||
key = models.CharField(max_length=32, blank=True, default='')
|
||||
distinction = models.BooleanField(default=False)
|
||||
status = models.CharField(max_length=32, default='unavailable')
|
||||
MODES = Choices('verified', 'honor', 'audit')
|
||||
mode = models.CharField(max_length=32, choices=MODES, default=MODES.honor)
|
||||
name = models.CharField(blank=True, max_length=255)
|
||||
created_date = models.DateTimeField(
|
||||
@@ -102,6 +104,18 @@ class GeneratedCertificate(models.Model):
|
||||
class Meta:
|
||||
unique_together = (('user', 'course_id'),)
|
||||
|
||||
@classmethod
|
||||
def certificate_for_student(cls, student, course_id):
|
||||
"""
|
||||
This returns the certificate for a student for a particular course
|
||||
or None if no such certificate exits.
|
||||
"""
|
||||
try:
|
||||
return cls.objects.get(user=student, course_id=course_id)
|
||||
except cls.DoesNotExist:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def certificate_status_for_student(student, course_id):
|
||||
'''
|
||||
|
||||
0
lms/djangoapps/certificates/tests/__init__.py
Normal file
0
lms/djangoapps/certificates/tests/__init__.py
Normal file
16
lms/djangoapps/certificates/tests/factories.py
Normal file
16
lms/djangoapps/certificates/tests/factories.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from factory.django import DjangoModelFactory
|
||||
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
from certificates.models import GeneratedCertificate, CertificateStatuses
|
||||
|
||||
# Factories don't have __init__ methods, and are self documenting
|
||||
# pylint: disable=W0232
|
||||
class GeneratedCertificateFactory(DjangoModelFactory):
|
||||
|
||||
FACTORY_FOR = GeneratedCertificate
|
||||
|
||||
course_id = None
|
||||
status = CertificateStatuses.unavailable
|
||||
mode = GeneratedCertificate.MODES.honor
|
||||
name = ''
|
||||
24
lms/djangoapps/certificates/tests/tests.py
Normal file
24
lms/djangoapps/certificates/tests/tests.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
Tests for the certificates models.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from student.tests.factories import UserFactory
|
||||
from certificates.models import CertificateStatuses, GeneratedCertificate, certificate_status_for_student
|
||||
|
||||
|
||||
class CertificatesModelTest(TestCase):
|
||||
"""
|
||||
Tests for the GeneratedCertificate model
|
||||
"""
|
||||
|
||||
def test_certificate_status_for_student(self):
|
||||
student = UserFactory()
|
||||
course = CourseFactory.create(org='edx', number='verified', display_name='Verified Course')
|
||||
|
||||
certificate_status = certificate_status_for_student(student, course.id)
|
||||
self.assertEqual(certificate_status['status'], CertificateStatuses.unavailable)
|
||||
self.assertEqual(certificate_status['mode'], GeneratedCertificate.MODES.honor)
|
||||
@@ -2,6 +2,8 @@
|
||||
Instructor Dashboard Views
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.cache import cache_control
|
||||
@@ -27,12 +29,14 @@ from student.models import CourseEnrollment
|
||||
from bulk_email.models import CourseAuthorization
|
||||
from class_dashboard.dashboard_data import get_section_display_name, get_array_section_has_problem
|
||||
|
||||
from analyticsclient.client import RestClient
|
||||
from analyticsclient.client import RestClient, ClientError
|
||||
from analyticsclient.course import Course
|
||||
|
||||
from .tools import get_units_with_due_date, title_or_url, bulk_email_is_enabled_for_course
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@@ -250,22 +254,7 @@ def _section_analytics(course_key, access):
|
||||
}
|
||||
|
||||
if settings.FEATURES.get('ENABLE_ANALYTICS_ACTIVE_COUNT'):
|
||||
auth_token = settings.ANALYTICS_DATA_TOKEN
|
||||
base_url = settings.ANALYTICS_DATA_URL
|
||||
|
||||
client = RestClient(base_url=base_url, auth_token=auth_token)
|
||||
course = Course(client, course_key)
|
||||
|
||||
section_data['active_student_count'] = course.recent_active_user_count['count']
|
||||
|
||||
def format_date(value):
|
||||
return value.split('T')[0]
|
||||
|
||||
start = course.recent_active_user_count['interval_start']
|
||||
end = course.recent_active_user_count['interval_end']
|
||||
|
||||
section_data['active_student_count_start'] = format_date(start)
|
||||
section_data['active_student_count_end'] = format_date(end)
|
||||
_update_active_students(course_key, section_data)
|
||||
|
||||
return section_data
|
||||
|
||||
@@ -284,3 +273,30 @@ def _section_metrics(course_key, access):
|
||||
'post_metrics_data_csv_url': reverse('post_metrics_data_csv'),
|
||||
}
|
||||
return section_data
|
||||
|
||||
|
||||
def _update_active_students(course_key, section_data):
|
||||
auth_token = settings.ANALYTICS_DATA_TOKEN
|
||||
base_url = settings.ANALYTICS_DATA_URL
|
||||
|
||||
section_data['active_student_count'] = 'N/A'
|
||||
section_data['active_student_count_start'] = 'N/A'
|
||||
section_data['active_student_count_end'] = 'N/A'
|
||||
|
||||
try:
|
||||
client = RestClient(base_url=base_url, auth_token=auth_token)
|
||||
course = Course(client, course_key.to_deprecated_string())
|
||||
|
||||
section_data['active_student_count'] = course.recent_active_user_count['count']
|
||||
|
||||
def format_date(value):
|
||||
return value.split('T')[0]
|
||||
|
||||
start = course.recent_active_user_count['interval_start']
|
||||
end = course.recent_active_user_count['interval_end']
|
||||
|
||||
section_data['active_student_count_start'] = format_date(start)
|
||||
section_data['active_student_count_end'] = format_date(end)
|
||||
|
||||
except (ClientError, KeyError) as e:
|
||||
log.exception(e)
|
||||
|
||||
@@ -99,7 +99,7 @@ if settings.FEATURES["ENABLE_SYSADMIN_DASHBOARD"]:
|
||||
)
|
||||
|
||||
urlpatterns += (
|
||||
url(r'support/', include('dashboard.support_urls')),
|
||||
url(r'^support/', include('dashboard.support_urls')),
|
||||
)
|
||||
|
||||
#Semi-static views (these need to be rendered and have the login bar, but don't change)
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
-e git+https://github.com/edx/bok-choy.git@82b4e82d79b9d4c6d087ebbfa26ea23235728e62#egg=bok_choy
|
||||
-e git+https://github.com/edx-solutions/django-splash.git@9965a53c269666a30bb4e2b3f6037c138aef2a55#egg=django-splash
|
||||
-e git+https://github.com/edx/acid-block.git@459aff7b63db8f2c5decd1755706c1a64fb4ebb1#egg=acid-xblock
|
||||
-e git+https://github.com/edx/edx-ora2.git@release-2014-06-30T13.39#egg=edx-ora2
|
||||
-e git+https://github.com/edx/edx-ora2.git@release-2014-06-23T13.19#egg=edx-ora2
|
||||
-e git+https://github.com/edx/opaque-keys.git@5929789900b3d0a354ce7274bde74edfd0430f03#egg=opaque-keys
|
||||
-e git+https://github.com/edx/ease.git@f9f47fb6b5c7c8b6c3360efa72eb56561e1a03b0#egg=ease
|
||||
-e git+https://github.com/edx/ease.git@97de68448e5495385ba043d3091f570a699d5b5f#egg=ease
|
||||
-e git+https://github.com/edx/i18n-tools.git@f5303e82dff368c7595884d9325aeea1d802da25#egg=i18n-tools
|
||||
|
||||
Reference in New Issue
Block a user