Commit d63a5abf by Adam Palay

Merge branch 'rc/2014-07-02' into testmergemaster

Conflicts:
	lms/djangoapps/instructor/views/instructor_dashboard.py
parents 34ac6abe d11bb29c
......@@ -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):
'''
......
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 = ''
"""
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
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