From d9b553b378fcd38b88e08c6248dc2b3ec13bcd47 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Mon, 5 Nov 2012 15:29:49 -0800 Subject: [PATCH 01/34] Adding a better command for assigning roles. --- .../commands/assign_roles_for_course.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py diff --git a/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py b/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py new file mode 100644 index 0000000000..82f2290bc7 --- /dev/null +++ b/lms/djangoapps/django_comment_client/management/commands/assign_roles_for_course.py @@ -0,0 +1,27 @@ +""" +This must be run only after seed_permissions_roles.py! + +Creates default roles for all users in the provided course. Just runs through +Enrollments. +""" +from django.core.management.base import BaseCommand, CommandError + +from student.models import CourseEnrollment, assign_default_role + +class Command(BaseCommand): + args = 'course_id' + help = 'Add roles for all users in a course' + + def handle(self, *args, **options): + if len(args) == 0: + raise CommandError("Please provide a course id") + if len(args) > 1: + raise CommandError("Too many arguments") + course_id = args[0] + + print "Updated roles for ", + for i, enrollment in enumerate(CourseEnrollment.objects.filter(course_id=course_id), start=1): + assign_default_role(None, enrollment) + if i % 1000 == 0: + print "{0}...".format(i), + print From 5bc0b4864a558363945c0cb701024b4898c3b9fb Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 17 Nov 2012 15:24:23 -0500 Subject: [PATCH 02/34] moving cert link and survey logic into views, adding tests. In-progress. --- common/djangoapps/student/tests.py | 41 +++++++++++----- common/djangoapps/student/views.py | 77 ++++++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/common/djangoapps/student/tests.py b/common/djangoapps/student/tests.py index cde95153fd..8a46b2e458 100644 --- a/common/djangoapps/student/tests.py +++ b/common/djangoapps/student/tests.py @@ -6,11 +6,14 @@ Replace this with more appropriate tests for your application. """ import logging from datetime import datetime +from hashlib import sha1 from django.test import TestCase +from mock import patch, Mock from nose.plugins.skip import SkipTest from .models import User, UserProfile, CourseEnrollment, replicate_user, USER_FIELDS_TO_COPY +import .views COURSE_1 = 'edX/toy/2012_Fall' COURSE_2 = 'edx/full/6.002_Spring_2012' @@ -55,7 +58,7 @@ class ReplicationTest(TestCase): # This hasattr lameness is here because we don't want this test to be # triggered when we're being run by CMS tests (Askbot doesn't exist # there, so the test will fail). - # + # # seen_response_count isn't a field we care about, so it shouldn't have # been copied over. if hasattr(portal_user, 'seen_response_count'): @@ -74,7 +77,7 @@ class ReplicationTest(TestCase): # During this entire time, the user data should never have made it over # to COURSE_2 - self.assertRaises(User.DoesNotExist, + self.assertRaises(User.DoesNotExist, User.objects.using(COURSE_2).get, id=portal_user.id) @@ -108,19 +111,19 @@ class ReplicationTest(TestCase): # Grab all the copies we expect course_user = User.objects.using(COURSE_1).get(id=portal_user.id) self.assertEquals(portal_user, course_user) - self.assertRaises(User.DoesNotExist, + self.assertRaises(User.DoesNotExist, User.objects.using(COURSE_2).get, id=portal_user.id) course_enrollment = CourseEnrollment.objects.using(COURSE_1).get(id=portal_enrollment.id) self.assertEquals(portal_enrollment, course_enrollment) - self.assertRaises(CourseEnrollment.DoesNotExist, + self.assertRaises(CourseEnrollment.DoesNotExist, CourseEnrollment.objects.using(COURSE_2).get, id=portal_enrollment.id) course_user_profile = UserProfile.objects.using(COURSE_1).get(id=portal_user_profile.id) self.assertEquals(portal_user_profile, course_user_profile) - self.assertRaises(UserProfile.DoesNotExist, + self.assertRaises(UserProfile.DoesNotExist, UserProfile.objects.using(COURSE_2).get, id=portal_user_profile.id) @@ -174,30 +177,44 @@ class ReplicationTest(TestCase): portal_user.save() portal_user_profile.gender = 'm' portal_user_profile.save() - - # Grab all the copies we expect, and make sure it doesn't end up in + + # Grab all the copies we expect, and make sure it doesn't end up in # places we don't expect. course_user = User.objects.using(COURSE_1).get(id=portal_user.id) self.assertEquals(portal_user, course_user) - self.assertRaises(User.DoesNotExist, + self.assertRaises(User.DoesNotExist, User.objects.using(COURSE_2).get, id=portal_user.id) course_enrollment = CourseEnrollment.objects.using(COURSE_1).get(id=portal_enrollment.id) self.assertEquals(portal_enrollment, course_enrollment) - self.assertRaises(CourseEnrollment.DoesNotExist, + self.assertRaises(CourseEnrollment.DoesNotExist, CourseEnrollment.objects.using(COURSE_2).get, id=portal_enrollment.id) course_user_profile = UserProfile.objects.using(COURSE_1).get(id=portal_user_profile.id) self.assertEquals(portal_user_profile, course_user_profile) - self.assertRaises(UserProfile.DoesNotExist, + self.assertRaises(UserProfile.DoesNotExist, UserProfile.objects.using(COURSE_2).get, id=portal_user_profile.id) +class CourseEndingTest(TestCase): + """Test things related to course endings: certificates, surveys, etc""" + def test_process_survey_link(self): + username = "fred" + id = sha1(username) + link1 = "http://www.mysurvey.com" + self.assertEqual(process_survey_link(link1), link1) + link2 = "http://www.mysurvey.com?unique={UNIQUE_ID}" + link2_expected = "http://www.mysurvey.com?unique={UNIQUE_ID}".format(UNIQUE_ID=id) + self.assertEqual(views.process_survey_link(link2), link2_expected) + def test_cert_info(self): + user = Mock(username="fred") + survey_url = "http://a_survey.com" + course = Mock(end_of_course_survey_url=survey_url) + cert_status = None - - + self.assertEqual(views._cert_info(user, course, None), {'status': 'processing'}) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index e7562f83d0..2ebb98da7a 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -39,6 +39,8 @@ from xmodule.modulestore.exceptions import ItemNotFoundError from datetime import date from collections import namedtuple +from hashlib import sha1 + from courseware.courses import get_courses_by_university from courseware.access import has_access @@ -107,9 +109,9 @@ def get_date_for_press(publish_date): # strip off extra months, and just use the first: date = re.sub(multimonth_pattern, ", ", publish_date) if re.search(day_pattern, date): - date = datetime.datetime.strptime(date, "%B %d, %Y") - else: - date = datetime.datetime.strptime(date, "%B, %Y") + date = datetime.datetime.strptime(date, "%B %d, %Y") + else: + date = datetime.datetime.strptime(date, "%B, %Y") return date def press(request): @@ -127,6 +129,73 @@ def press(request): return render_to_response('static_templates/press.html', {'articles': articles}) +def process_survey_link(survey_link, user): + """ + If {UNIQUE_ID} appears in the link, replace it with a unique id for the user. + Currently, this is sha1(user.username). Otherwise, return survey_link. + """ + to_replace = '{UNIQUE_ID}' + if to_replace in survey_link: + unique_id = sha1(user.username) + return survey_link.replace(to_replace, unique_id) + + return survey_link + + +def cert_info(user, course): + """ + Get the certificate info needed to render the dashboard section for the given + student and course. Returns a dictionary with keys: + + 'status': one of 'generating', 'ready', 'notpassing', 'processing' + 'show_download_url': bool + 'download_url': url, only present if show_download_url is True + 'show_survey_button': bool + 'survey_url': url, only if show_survey_button is True + 'grade': if status is not 'processing' + """ + if not course.has_ended(): + return {} + + return _cert_info(user, course, certificate_status_for_student(user, course.id)) + +def _cert_info(user, course, cert_status): + """ + Implements the logic for cert_info -- split out for testing. + """ + default_status = 'processing' + if cert_status is None: + return {'status': default_status} + + # simplify the status for the template using this lookup table + template_state = { + CertificateStatuses.generating: 'generating', + CertificateStatuses.regenerating: 'generating', + CertificateStatuses.downloadable: 'ready', + CertificateStatuses.notpassing: 'notpassing', + } + + status = template_state.get(cert_status['status'], default_status) + + d = {'status': status, + 'show_download_url': status in ('generating', 'ready'),} + + if (status in ('generating', 'ready', 'not-available') and + course.end_of_course_survey_url is not None): + d.update({ + 'show_survey_button': True, + 'survey_url': process_survey_link(course.end_of_course_survey_url, user)}) + else: + d['show_survey_button'] = False + + if template_state == 'ready': + d['download_url'] = cert_status['download_url'] + + if template_state in 'generating', 'ready', 'notpassing': + d['grade'] = cert_status['grade'] + + return d + @login_required @ensure_csrf_cookie def dashboard(request): @@ -163,7 +232,7 @@ def dashboard(request): # TODO: workaround to not have to zip courses and certificates in the template # since before there is a migration to certificates if settings.MITX_FEATURES.get('CERTIFICATES_ENABLED'): - cert_statuses = { course.id: certificate_status_for_student(request.user, course.id) for course in courses} + cert_statuses = { course.id: cert_info(request.user, course) for course in courses} else: cert_statuses = {} From 3a44f043d29060fb7fe997dbec6f6e1dce727394 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 17 Nov 2012 15:58:49 -0500 Subject: [PATCH 03/34] Add tests, make them pass. --- common/djangoapps/student/tests.py | 80 +++++++++++++++++++++++++-- common/djangoapps/student/views.py | 21 ++++--- lms/djangoapps/certificates/models.py | 6 +- 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/common/djangoapps/student/tests.py b/common/djangoapps/student/tests.py index 8a46b2e458..16eec86379 100644 --- a/common/djangoapps/student/tests.py +++ b/common/djangoapps/student/tests.py @@ -13,7 +13,7 @@ from mock import patch, Mock from nose.plugins.skip import SkipTest from .models import User, UserProfile, CourseEnrollment, replicate_user, USER_FIELDS_TO_COPY -import .views +from .views import process_survey_link, _cert_info, unique_id_for_user COURSE_1 = 'edX/toy/2012_Fall' COURSE_2 = 'edx/full/6.002_Spring_2012' @@ -204,17 +204,85 @@ class CourseEndingTest(TestCase): def test_process_survey_link(self): username = "fred" - id = sha1(username) + user = Mock(username=username) + id = unique_id_for_user(user) link1 = "http://www.mysurvey.com" - self.assertEqual(process_survey_link(link1), link1) + self.assertEqual(process_survey_link(link1, user), link1) + link2 = "http://www.mysurvey.com?unique={UNIQUE_ID}" link2_expected = "http://www.mysurvey.com?unique={UNIQUE_ID}".format(UNIQUE_ID=id) - self.assertEqual(views.process_survey_link(link2), link2_expected) + self.assertEqual(process_survey_link(link2, user), link2_expected) def test_cert_info(self): user = Mock(username="fred") survey_url = "http://a_survey.com" course = Mock(end_of_course_survey_url=survey_url) - cert_status = None - self.assertEqual(views._cert_info(user, course, None), {'status': 'processing'}) + self.assertEqual(_cert_info(user, course, None), + {'status': 'processing', + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': False,}) + + cert_status = {'status': 'unavailable'} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'processing', + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': False}) + + cert_status = {'status': 'generating', 'grade': '67'} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'generating', + 'show_disabled_download_button': True, + 'show_download_url': False, + 'show_survey_button': True, + 'survey_url': survey_url, + 'grade': '67' + }) + + cert_status = {'status': 'regenerating', 'grade': '67'} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'generating', + 'show_disabled_download_button': True, + 'show_download_url': False, + 'show_survey_button': True, + 'survey_url': survey_url, + 'grade': '67' + }) + + download_url = 'http://s3.edx/cert' + cert_status = {'status': 'downloadable', 'grade': '67', + 'download_url': download_url} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'ready', + 'show_disabled_download_button': False, + 'show_download_url': True, + 'download_url': download_url, + 'show_survey_button': True, + 'survey_url': survey_url, + 'grade': '67' + }) + + cert_status = {'status': 'notpassing', 'grade': '67', + 'download_url': download_url} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'notpassing', + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': True, + 'survey_url': survey_url, + 'grade': '67' + }) + + # Test a course that doesn't have a survey specified + course2 = Mock(end_of_course_survey_url=None) + cert_status = {'status': 'notpassing', 'grade': '67', + 'download_url': download_url} + self.assertEqual(_cert_info(user, course2, cert_status), + {'status': 'notpassing', + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': False, + 'grade': '67' + }) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 2ebb98da7a..250eddff57 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -129,6 +129,9 @@ def press(request): return render_to_response('static_templates/press.html', {'articles': articles}) +def unique_id_for_user(user): + return sha1(user.username).hexdigest() + def process_survey_link(survey_link, user): """ If {UNIQUE_ID} appears in the link, replace it with a unique id for the user. @@ -136,8 +139,7 @@ def process_survey_link(survey_link, user): """ to_replace = '{UNIQUE_ID}' if to_replace in survey_link: - unique_id = sha1(user.username) - return survey_link.replace(to_replace, unique_id) + return survey_link.replace(to_replace, unique_id_for_user(user)) return survey_link @@ -150,6 +152,7 @@ def cert_info(user, course): 'status': one of 'generating', 'ready', 'notpassing', 'processing' 'show_download_url': bool 'download_url': url, only present if show_download_url is True + 'show_disabled_download_button': bool -- true if state is 'generating' 'show_survey_button': bool 'survey_url': url, only if show_survey_button is True 'grade': if status is not 'processing' @@ -165,7 +168,10 @@ def _cert_info(user, course, cert_status): """ default_status = 'processing' if cert_status is None: - return {'status': default_status} + return {'status': default_status, + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': False} # simplify the status for the template using this lookup table template_state = { @@ -178,9 +184,10 @@ def _cert_info(user, course, cert_status): status = template_state.get(cert_status['status'], default_status) d = {'status': status, - 'show_download_url': status in ('generating', 'ready'),} + 'show_download_url': status == 'ready', + 'show_disabled_download_button': status == 'generating',} - if (status in ('generating', 'ready', 'not-available') and + if (status in ('generating', 'ready', 'notpassing') and course.end_of_course_survey_url is not None): d.update({ 'show_survey_button': True, @@ -188,10 +195,10 @@ def _cert_info(user, course, cert_status): else: d['show_survey_button'] = False - if template_state == 'ready': + if status == 'ready': d['download_url'] = cert_status['download_url'] - if template_state in 'generating', 'ready', 'notpassing': + if status in ('generating', 'ready', 'notpassing'): d['grade'] = cert_status['grade'] return d diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 2d6f384443..b9bd55b9af 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -75,7 +75,9 @@ def certificate_status_for_student(student, course_id): This returns a dictionary with a key for status, and other information. The status is one of the following: - unavailable - A student is not eligible for a certificate. + unavailable - No entry for this student--if they are actually in + the course, they probably have not been graded for + certificate generation yet. generating - A request has been made to generate a certificate, but it has not been generated yet. regenerating - A request has been made to regenerate a certificate, @@ -90,7 +92,7 @@ def certificate_status_for_student(student, course_id): "download_url". If the student has been graded, the dictionary also contains their - grade for the course. + grade for the course with the key "grade". ''' try: From 03dd7b375a6a824ca331822d517d9ad1969adc8e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 17 Nov 2012 15:59:12 -0500 Subject: [PATCH 04/34] get rid of submodule init--not needed post-askbot --- rakefile | 1 - 1 file changed, 1 deletion(-) diff --git a/rakefile b/rakefile index f06b9c3633..c95d822531 100644 --- a/rakefile +++ b/rakefile @@ -54,7 +54,6 @@ default_options = { task :predjango do sh("find . -type f -name *.pyc -delete") sh('pip install -q --upgrade -r local-requirements.txt') - sh('git submodule update --init') end task :clean_test_files do From 003dc7ef517901cc701307f6d761b7014bf4f702 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 17 Nov 2012 16:19:52 -0500 Subject: [PATCH 05/34] Get rid of check for non-existent CERTIFICATES_ENABLED flag... --- common/djangoapps/student/views.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 250eddff57..b354b8c20a 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -236,12 +236,7 @@ def dashboard(request): show_courseware_links_for = frozenset(course.id for course in courses if has_access(request.user, course, 'load')) - # TODO: workaround to not have to zip courses and certificates in the template - # since before there is a migration to certificates - if settings.MITX_FEATURES.get('CERTIFICATES_ENABLED'): - cert_statuses = { course.id: cert_info(request.user, course) for course in courses} - else: - cert_statuses = {} + cert_statuses = { course.id: cert_info(request.user, course) for course in courses} context = {'courses': courses, 'message': message, From 61ddec46dd58b2991e19718a23d8aa175ccaa97b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 17 Nov 2012 16:20:17 -0500 Subject: [PATCH 06/34] Use params from view in template. --- lms/templates/dashboard.html | 55 +++++++++++++++--------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 2069685a6c..423f53aa35 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -159,54 +159,43 @@ %> % if course.has_ended() and cert_status: <% - passing_grade = False - cert_button = False - survey_button = False - if cert_status['status'] in [CertificateStatuses.generating, CertificateStatuses.regenerating]: + if cert_status['status'] == 'generating': status_css_class = 'course-status-certrendering' - cert_button = True - survey_button = True - passing_grade = True - elif cert_status['status'] == CertificateStatuses.downloadable: + elif cert_status['status'] == 'ready': status_css_class = 'course-status-certavailable' - cert_button = True - survey_button = True - passing_grade = True - elif cert_status['status'] == CertificateStatuses.notpassing: + elif cert_status['status'] == 'notpassing': status_css_class = 'course-status-certnotavailable' - survey_button = True else: - # This is primarily the 'unavailable' state, but also 'error', 'deleted', etc. status_css_class = 'course-status-processing' - - if survey_button and not course.end_of_course_survey_url: - survey_button = False %>
- % if cert_status['status'] == CertificateStatuses.unavailable: -

Final course details are being wrapped up at this time. - Your final standing will be available shortly.

- % elif passing_grade: -

You have received a grade of - ${cert_status['grade']} - in this course.

- % elif cert_status['status'] == CertificateStatuses.notpassing: -

You did not complete the necessary requirements for completion of this course. -

+ % if cert_status['status'] == 'processing': +

Final course details are being wrapped up at + this time. Your final standing will be available shortly.

+ % elif cert_status['status'] in ('generating', 'ready'): +

You have received a grade of + ${cert_status['grade']} + in this course.

+ % elif cert_status['status'] == 'notpassing': +

You did not complete the necessary requirements for + completion of this course.

% endif - % if cert_button or survey_button: + + % if cert_status['show_disabled_download_button'] or cert_status['show_download_url'] or cert_status['show_survey_button']: From 71b585bb614d80e79c8866b46145c6bdf6e95f20 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Sat, 17 Nov 2012 16:33:00 -0500 Subject: [PATCH 07/34] move unique_id_for_user into student/models.py --- common/djangoapps/student/models.py | 47 +++++++++++++++++++---------- common/djangoapps/student/tests.py | 6 ++-- common/djangoapps/student/views.py | 6 +--- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 0eded21df1..4c91682ca6 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -36,10 +36,12 @@ file and check it in at the same time as your model changes. To do that, 3. Add the migration file created in mitx/common/djangoapps/student/migrations/ """ from datetime import datetime +from hashlib import sha1 import json import logging import uuid + from django.conf import settings from django.contrib.auth.models import User from django.db import models @@ -125,9 +127,9 @@ class UserProfile(models.Model): self.meta = json.dumps(js) class TestCenterUser(models.Model): - """This is our representation of the User for in-person testing, and + """This is our representation of the User for in-person testing, and specifically for Pearson at this point. A few things to note: - + * Pearson only supports Latin-1, so we have to make sure that the data we capture here will work with that encoding. * While we have a lot of this demographic data in UserProfile, it's much @@ -135,9 +137,9 @@ class TestCenterUser(models.Model): UserProfile, but we'll need to have a step where people who are signing up re-enter their demographic data into the fields we specify. * Users are only created here if they register to take an exam in person. - + The field names and lengths are modeled on the conventions and constraints - of Pearson's data import system, including oddities such as suffix having + of Pearson's data import system, including oddities such as suffix having a limit of 255 while last_name only gets 50. """ # Our own record keeping... @@ -148,21 +150,21 @@ class TestCenterUser(models.Model): # and is something Pearson needs to know to manage updates. Unlike # updated_at, this will not get incremented when we do a batch data import. user_updated_at = models.DateTimeField(db_index=True) - + # Unique ID given to us for this User by the Testing Center. It's null when # we first create the User entry, and is assigned by Pearson later. candidate_id = models.IntegerField(null=True, db_index=True) - + # Unique ID we assign our user for a the Test Center. client_candidate_id = models.CharField(max_length=50, db_index=True) - + # Name first_name = models.CharField(max_length=30, db_index=True) last_name = models.CharField(max_length=50, db_index=True) middle_name = models.CharField(max_length=30, blank=True) suffix = models.CharField(max_length=255, blank=True) salutation = models.CharField(max_length=50, blank=True) - + # Address address_1 = models.CharField(max_length=40) address_2 = models.CharField(max_length=40, blank=True) @@ -175,7 +177,7 @@ class TestCenterUser(models.Model): postal_code = models.CharField(max_length=16, blank=True, db_index=True) # country is a ISO 3166-1 alpha-3 country code (e.g. "USA", "CAN", "MNG") country = models.CharField(max_length=3, db_index=True) - + # Phone phone = models.CharField(max_length=35) extension = models.CharField(max_length=8, blank=True, db_index=True) @@ -183,14 +185,27 @@ class TestCenterUser(models.Model): fax = models.CharField(max_length=35, blank=True) # fax_country_code required *if* fax is present. fax_country_code = models.CharField(max_length=3, blank=True) - + # Company company_name = models.CharField(max_length=50, blank=True) - + @property def email(self): return self.user.email +def unique_id_for_user(user): + """ + Return a unique id for a user, suitable for inserting into + e.g. personalized survey links. + + Currently happens to be implemented as a sha1 hash of the username + (and thus assumes that usernames don't change). + """ + return sha1(user.username).hexdigest() + + + + ## TODO: Should be renamed to generic UserGroup, and possibly # Given an optional field for type of group class UserTestGroup(models.Model): @@ -363,10 +378,10 @@ def replicate_user_save(sender, **kwargs): # @receiver(post_save, sender=CourseEnrollment) def replicate_enrollment_save(sender, **kwargs): - """This is called when a Student enrolls in a course. It has to do the + """This is called when a Student enrolls in a course. It has to do the following: - 1. Make sure the User is copied into the Course DB. It may already exist + 1. Make sure the User is copied into the Course DB. It may already exist (someone deleting and re-adding a course). This has to happen first or the foreign key constraint breaks. 2. Replicate the CourseEnrollment. @@ -410,9 +425,9 @@ USER_FIELDS_TO_COPY = ["id", "username", "first_name", "last_name", "email", def replicate_user(portal_user, course_db_name): """Replicate a User to the correct Course DB. This is more complicated than - it should be because Askbot extends the auth_user table and adds its own + it should be because Askbot extends the auth_user table and adds its own fields. So we need to only push changes to the standard fields and leave - the rest alone so that Askbot changes at the Course DB level don't get + the rest alone so that Askbot changes at the Course DB level don't get overridden. """ try: @@ -457,7 +472,7 @@ def is_valid_course_id(course_id): """Right now, the only database that's not a course database is 'default'. I had nicer checking in here originally -- it would scan the courses that were in the system and only let you choose that. But it was annoying to run - tests with, since we don't have course data for some for our course test + tests with, since we don't have course data for some for our course test databases. Hence the lazy version. """ return course_id != 'default' diff --git a/common/djangoapps/student/tests.py b/common/djangoapps/student/tests.py index 16eec86379..4c7c9e2592 100644 --- a/common/djangoapps/student/tests.py +++ b/common/djangoapps/student/tests.py @@ -12,8 +12,10 @@ from django.test import TestCase from mock import patch, Mock from nose.plugins.skip import SkipTest -from .models import User, UserProfile, CourseEnrollment, replicate_user, USER_FIELDS_TO_COPY -from .views import process_survey_link, _cert_info, unique_id_for_user +from .models import (User, UserProfile, CourseEnrollment, + replicate_user, USER_FIELDS_TO_COPY, + unique_id_for_user) +from .views import process_survey_link, _cert_info COURSE_1 = 'edX/toy/2012_Fall' COURSE_2 = 'edx/full/6.002_Spring_2012' diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index b354b8c20a..ac3a97bd89 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -28,7 +28,7 @@ from django.core.cache import cache from django_future.csrf import ensure_csrf_cookie, csrf_exempt from student.models import (Registration, UserProfile, PendingNameChange, PendingEmailChange, - CourseEnrollment) + CourseEnrollment, unique_id_for_user) from certificates.models import CertificateStatuses, certificate_status_for_student @@ -39,7 +39,6 @@ from xmodule.modulestore.exceptions import ItemNotFoundError from datetime import date from collections import namedtuple -from hashlib import sha1 from courseware.courses import get_courses_by_university from courseware.access import has_access @@ -129,9 +128,6 @@ def press(request): return render_to_response('static_templates/press.html', {'articles': articles}) -def unique_id_for_user(user): - return sha1(user.username).hexdigest() - def process_survey_link(survey_link, user): """ If {UNIQUE_ID} appears in the link, replace it with a unique id for the user. From 6326e4dc7e5d2a0a03245447ff8587914f28df80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Thu, 15 Nov 2012 13:28:03 -0500 Subject: [PATCH 08/34] Add stub for Gates foundation announcement [#39636489 #39636491 #39636495 #39636903] --- lms/static/images/press/mass_seal_240x180.png | Bin 0 -> 21604 bytes lms/templates/feed.rss | 9 ++ .../Gates_Foundation_announcement.html | 77 ++++++++++++++++++ lms/urls.py | 2 + 4 files changed, 88 insertions(+) create mode 100644 lms/static/images/press/mass_seal_240x180.png create mode 100644 lms/templates/static_templates/press_releases/Gates_Foundation_announcement.html diff --git a/lms/static/images/press/mass_seal_240x180.png b/lms/static/images/press/mass_seal_240x180.png new file mode 100644 index 0000000000000000000000000000000000000000..6641d4e42df1f47890f009b68b25e728ee1a4b1b GIT binary patch literal 21604 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 EdX Blog 2012-10-14T14:08:12-07:00 + + tag:www.edx.org,2012:Post/7 + 2012-11-12T14:00:00-07:00 + 2012-11-12T14:00:00-07:00 + + TITLE + <img src="${static.url('images/press/mass_seal_240x180.png')}" /> + <p>CONTENT</p> + tag:www.edx.org,2012:Post/6 2012-10-15T14:00:00-07:00 diff --git a/lms/templates/static_templates/press_releases/Gates_Foundation_announcement.html b/lms/templates/static_templates/press_releases/Gates_Foundation_announcement.html new file mode 100644 index 0000000000..e87fcd8aec --- /dev/null +++ b/lms/templates/static_templates/press_releases/Gates_Foundation_announcement.html @@ -0,0 +1,77 @@ +<%! from django.core.urlresolvers import reverse %> +<%inherit file="../../main.html" /> + +<%namespace name='static' file='../../static_content.html'/> + +<%block name="title">TITLE +
+ + +
+
+

TITLE

+
+
+

SUBTITLE

+ +

LOCATION – November 12, 2012 — THE_CONTENT

+ +

About edX

+ +

edX is a not-for-profit enterprise of its founding partners Harvard University and the Massachusetts Institute of Technology that features learning designed specifically for interactive study via the web. Based on a long history of collaboration and their shared educational missions the founders are creating a new online-learning experience. Anant Agarwal, former Director of MIT's Computer Science and Artificial Intelligence Laboratory, serves as the first president of edX. Along with offering online courses, the institutions will use edX to research how students learn and how technology can transform learning—both on-campus and worldwide. edX is based in Cambridge, Massachusetts and is governed by MIT and Harvard.

+ +

About Harvard University

+ +

Harvard University is devoted to excellence in teaching, learning and research, and to developing leaders in many disciplines who make a difference globally. Harvard Faculty are engaged with teaching and research to push the boundaries of human knowledge. The University has twelve degree-granting Schools in addition to the Radcliffe Institute for Advanced Study.

+ +

Established in 1636, Harvard is the oldest institution of higher education in the United States. The University, which is based in Cambridge and Boston, Massachusetts, has an enrollment of over 20,000 degree candidates, including undergraduate, graduate and professional students. Harvard has more than 360,000 alumni around the world.

+ +

About MIT

+

The Massachusetts Institute of Technology — a coeducational, privately endowed research university founded in 1861 — is dedicated to advancing knowledge and educating students in science, technology and other areas of scholarship that will best serve the nation and the world in the 21st century. The Institute has close to 1,000 faculty and 10,000 undergraduate and graduate students. It is organized into five Schools: Architecture and Urban Planning; Engineering; Humanities, Arts, and Social Sciences; Sloan School of Management; and Science.

+ +

MIT's commitment to innovation has led to a host of scientific breakthroughs and technological advances. Achievements of the Institute's faculty and graduates have included the first chemical synthesis of penicillin and vitamin A, the development of inertial guidance systems, modern technologies for artificial limbs and the magnetic core memory that made possible the development of digital computers. Seventy-eight alumni, faculty, researchers and staff have won Nobel Prizes.

+ +

Current areas of research and education include neuroscience and the study of the brain and mind, bioengineering, cancer, energy, the environment and sustainable development, information sciences and technology, new media, financial technology and entrepreneurship.

+ +

About the University of California, Berkeley

+ +

The University of California, Berkeley is the world's premier public university with a mission to excel in teaching, research and public service. This longstanding mission has led to the university's distinguished record of Nobel-level scholarship, constant innovation, a concern for the betterment of our world, and consistently high rankings of its schools and departments. The campus offers superior, high value education for extraordinarily talented students from all walks of life; operational excellence and a commitment to the competitiveness and prosperity of California and the nation.

+ +

The University of California was chartered in 1868 and its flagship campus in Berkeley, on San Francisco Bay, was envisioned as a “City of Learning.” Today, there are more than 1,500 fulltime and 500 part-time faculty members dispersed among more than 130 academic departments and more than 80 interdisciplinary research units. Twenty-two Nobel Prizes have been garnered by faculty and 28 by UC Berkeley alumni. There are 9 Nobel Laureates, 32 MacArthur Fellows, and 4 Pulitzer Prize winners among the current faculty.

+ +

About The University of Texas System

+ +

Educating students, providing care for patients, conducting groundbreaking research and serving the needs of Texans and the nation for more than 130 years, The University of Texas System is one of the largest public university systems in the United States, with nine academic universities and six health science centers. Student enrollment exceeded 215,000 in the 2011 academic year. The UT System confers more than one-third of the state's undergraduate degrees and educates nearly three-fourths of the state's health care professionals annually. The UT System has an annual operating budget of $13.1 billion (FY 2012) including $2.3 billion in sponsored programs funded by federal, state, local and private sources. With roughly 87,000 employees, the UT System is one of the largest employers in the state. www.utsystem.edu

+ +
+

edX Contact: Dan O’Connell

+

oconnell@edx.org

+

617-480-6585

+
+ + +
+
+
diff --git a/lms/urls.py b/lms/urls.py index 527f3f858e..529396c20e 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -101,6 +101,8 @@ urlpatterns = ('', {'template': 'press_releases/UT_joins_edX.html'}, name="press/ut-joins-edx"), url(r'^press/cengage-to-provide-book-content$', 'static_template_view.views.render', {'template': 'press_releases/Cengage_to_provide_book_content.html'}, name="press/cengage-to-provide-book-content"), + url(r'^press/gates-foundation-announcement$', 'static_template_view.views.render', + {'template': 'press_releases/Gates_Foundation_announcement.html'}, name="press/gates-foundation-announcement"), # Should this always update to point to the latest press release? (r'^pressrelease$', 'django.views.generic.simple.redirect_to', {'url': '/press/uc-berkeley-joins-edx'}), From cabbc8906a76273a712b4e31986cf3437a79fd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Fri, 16 Nov 2012 17:31:39 -0500 Subject: [PATCH 09/34] Add press release text and update news feed [#39636489 #39636491 #39636495 #39636903] --- lms/templates/feed.rss | 4 +- .../Gates_Foundation_announcement.html | 58 ++++++++----------- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/lms/templates/feed.rss b/lms/templates/feed.rss index 9df3c29633..4bd66db799 100644 --- a/lms/templates/feed.rss +++ b/lms/templates/feed.rss @@ -12,9 +12,9 @@ 2012-11-12T14:00:00-07:00 2012-11-12T14:00:00-07:00 - TITLE + edX and Massachusetts Community Colleges join in Gates-Funded educational initiative <img src="${static.url('images/press/mass_seal_240x180.png')}" /> - <p>CONTENT</p> + <p></p>
tag:www.edx.org,2012:Post/6 diff --git a/lms/templates/static_templates/press_releases/Gates_Foundation_announcement.html b/lms/templates/static_templates/press_releases/Gates_Foundation_announcement.html index e87fcd8aec..0cdd51e90b 100644 --- a/lms/templates/static_templates/press_releases/Gates_Foundation_announcement.html +++ b/lms/templates/static_templates/press_releases/Gates_Foundation_announcement.html @@ -3,7 +3,7 @@ <%namespace name='static' file='../../static_content.html'/> -<%block name="title">TITLE +<%block name="title">edX and Massachusetts Community Colleges Join in Gates-Funded Educational Initiative