diff --git a/common/djangoapps/student/migrations/0044_rename_linkedin_config_field.py b/common/djangoapps/student/migrations/0044_rename_linkedin_config_field.py new file mode 100644 index 0000000000..d656c9c683 --- /dev/null +++ b/common/djangoapps/student/migrations/0044_rename_linkedin_config_field.py @@ -0,0 +1,190 @@ +# -*- 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): + # Deleting field 'LinkedInAddToProfileConfiguration.dashboard_tracking_code' + db.delete_column('student_linkedinaddtoprofileconfiguration', 'dashboard_tracking_code') + + # Adding field 'LinkedInAddToProfileConfiguration.company_identifier' + db.add_column('student_linkedinaddtoprofileconfiguration', 'company_identifier', + self.gf('django.db.models.fields.TextField')(default=''), + keep_default=False) + + + def backwards(self, orm): + # Adding field 'LinkedInAddToProfileConfiguration.dashboard_tracking_code' + db.add_column('student_linkedinaddtoprofileconfiguration', 'dashboard_tracking_code', + self.gf('django.db.models.fields.TextField')(default='', blank=True), + keep_default=False) + + # Deleting field 'LinkedInAddToProfileConfiguration.company_identifier' + db.delete_column('student_linkedinaddtoprofileconfiguration', 'company_identifier') + + + 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'}) + }, + 'student.anonymoususerid': { + 'Meta': {'object_name': 'AnonymousUserId'}, + 'anonymous_user_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.courseaccessrole': { + 'Meta': {'unique_together': "(('user', 'org', 'course_id', 'role'),)", 'object_name': 'CourseAccessRole'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'org': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}), + 'role': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + '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']"}) + }, + 'student.courseenrollmentallowed': { + 'Meta': {'unique_together': "(('email', 'course_id'),)", 'object_name': 'CourseEnrollmentAllowed'}, + 'auto_enroll': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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'}), + 'email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'student.dashboardconfiguration': { + 'Meta': {'object_name': 'DashboardConfiguration'}, + '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'}), + 'recent_enrollment_time_delta': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'student.linkedinaddtoprofileconfiguration': { + 'Meta': {'object_name': 'LinkedInAddToProfileConfiguration'}, + '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'}), + 'company_identifier': ('django.db.models.fields.TextField', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'student.loginfailures': { + 'Meta': {'object_name': 'LoginFailures'}, + 'failure_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lockout_until': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.passwordhistory': { + 'Meta': {'object_name': 'PasswordHistory'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'time_set': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.pendingemailchange': { + 'Meta': {'object_name': 'PendingEmailChange'}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.pendingnamechange': { + 'Meta': {'object_name': 'PendingNameChange'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.registration': { + 'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"}, + 'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'student.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"}, + 'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'city': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}), + 'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}), + 'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'student.usersignupsource': { + 'Meta': {'object_name': 'UserSignupSource'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'site': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'student.userstanding': { + 'Meta': {'object_name': 'UserStanding'}, + 'account_status': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}), + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'standing_last_changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'standing'", 'unique': 'True', 'to': "orm['auth.User']"}) + }, + 'student.usertestgroup': { + 'Meta': {'object_name': 'UserTestGroup'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}) + } + } + + complete_apps = ['student'] \ No newline at end of file diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 6631a2d5a4..c92736608c 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -16,13 +16,13 @@ import json import logging from pytz import UTC import uuid -from collections import defaultdict +from collections import defaultdict, OrderedDict import dogstats_wrapper as dog_stats_api from django.db.models import Q import pytz from urllib import urlencode -from django.utils.translation import ugettext_lazy +from django.utils.translation import ugettext as _, ugettext_lazy from django.conf import settings from django.utils import timezone from django.contrib.auth.models import User @@ -1442,28 +1442,56 @@ class DashboardConfiguration(ConfigurationModel): class LinkedInAddToProfileConfiguration(ConfigurationModel): """ LinkedIn Add to Profile Configuration + + This configuration enables the "Add to Profile" LinkedIn + button on the student dashboard. The button appears when + users have a certificate available; when clicked, + users are sent to the LinkedIn site with a pre-filled + form allowing them to add the certificate to their + LinkedIn profile. """ - # tracking code field - dashboard_tracking_code = models.TextField( - blank=True, + + MODE_TO_CERT_NAME = { + "honor": ugettext_lazy(u"{platform_name} Honor Code Certificate for {course_name}"), + "verified": ugettext_lazy(u"{platform_name} Verified Certificate for {course_name}"), + "professional": ugettext_lazy(u"{platform_name} Professional Certificate for {course_name}"), + } + + company_identifier = models.TextField( help_text=ugettext_lazy( - u"A dashboard tracking code field for LinkedIn Add-to-profile Certificates. " + u"The company identifier for the LinkedIn Add-to-Profile button " u"e.g 0_0dPSPyS070e0HsE9HNz_13_d11_" ) ) - @classmethod - def linked_in_dashboard_tracking_code_url(cls, params): - """ - Get the linked-in Configuration. - """ - config = cls.current() - if config.enabled: - return u'http://www.linkedin.com/profile/add?_ed={tracking_code}&{params}'.format( - tracking_code=config.dashboard_tracking_code, - params=urlencode(params) - ) - return None + def add_to_profile_url(self, course_name, enrollment_mode, cert_url, source="o"): + """Construct the URL for the "add to profile" button. - def __unicode__(self): - return self.dashboard_tracking_code + Arguments: + course_name (unicode): The display name of the course. + enrollment_mode (str): The enrollment mode of the user (e.g. "verified", "honor", "professional") + cert_url (str): The download URL for the certificate. + + Keyword Arguments: + source (str): Either "o" (for onsite/UI), "e" (for emails), or "m" (for mobile) + + """ + params = OrderedDict([ + ('_ed', self.company_identifier), + ('pfCertificationName', self._cert_name(course_name, enrollment_mode).encode('utf-8')), + ('pfCertificationUrl', cert_url), + ('source', source) + ]) + return u'http://www.linkedin.com/profile/add?{params}'.format( + params=urlencode(params) + ) + + def _cert_name(self, course_name, enrollment_mode): + """Name of the certification, for display on LinkedIn. """ + return self.MODE_TO_CERT_NAME.get( + enrollment_mode, + _(u"{platform_name} Certificate for {course_name}") + ).format( + platform_name=settings.PLATFORM_NAME, + course_name=course_name + ) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 445e22ea67..eb1a0ffbf9 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -9,6 +9,7 @@ from datetime import datetime, timedelta import logging import pytz import unittest +import ddt from django.conf import settings from django.contrib.auth.models import User, AnonymousUser @@ -44,9 +45,17 @@ from config_models.models import cache log = logging.getLogger(__name__) +@ddt.ddt class CourseEndingTest(TestCase): """Test things related to course endings: certificates, surveys, etc""" + def setUp(self): + super(CourseEndingTest, self).setUp() + + # Clear the model-based config cache to avoid + # interference between tests. + cache.clear() + def test_process_survey_link(self): username = "fred" user = Mock(username=username) @@ -194,8 +203,11 @@ class CourseEndingTest(TestCase): user = Mock(username="fred") survey_url = "http://a_survey.com" - course = Mock(end_of_course_survey_url=survey_url, certificates_display_behavior='end') - course.display_name = u'edx/abc/courseregisters®' + course = Mock( + end_of_course_survey_url=survey_url, + certificates_display_behavior='end', + display_name=u'edx/abc/courseregisters®' + ) download_url = 'http://s3.edx/cert' cert_status = { @@ -204,25 +216,28 @@ class CourseEndingTest(TestCase): 'mode': 'honor' } LinkedInAddToProfileConfiguration( - dashboard_tracking_code='0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9', - enabled=True).save() + company_identifier='0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9', + enabled=True + ).save() status_dict = _cert_info(user, course, cert_status, 'honor') - self.assertIn( - 'http://www.linkedin.com/profile/add?_ed=0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9', - status_dict['linked_in_url'] + expected_url = ( + 'http://www.linkedin.com/profile/add' + '?_ed=0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9&' + 'pfCertificationName=edX+Honor+Code+Certificate+for+edx%2Fabc%2Fcourseregisters%C2%AE&' + 'pfCertificationUrl=http%3A%2F%2Fs3.edx%2Fcert&' + 'source=o' ) - self.assertIn('pfCertificationName', status_dict['linked_in_url']) - self.assertIn('pfCertificationUrl', status_dict['linked_in_url']) - self.assertIn('courseregisters', status_dict['linked_in_url']) - self.assertIn('Honor+Code+Certificate', status_dict['linked_in_url']) + self.assertEqual(expected_url, status_dict['linked_in_url']) def test_linked_in_url_not_exists_without_config(self): - # Test case with Linked-In URL empty with if linked-in-config is none. - cache.clear() user = Mock(username="fred") survey_url = "http://a_survey.com" - course = Mock(end_of_course_survey_url=survey_url, certificates_display_behavior='end') + course = Mock( + display_name="Demo Course", + end_of_course_survey_url=survey_url, + certificates_display_behavior='end' + ) download_url = 'http://s3.edx/cert' cert_status = { @@ -246,16 +261,54 @@ class CourseEndingTest(TestCase): } ) - # adding config. linked-in-url will be return + # Enabling the configuration will cause the LinkedIn + # "add to profile" button to appear. + # We need to clear the cache again to make sure we + # pick up the modified configuration. + cache.clear() LinkedInAddToProfileConfiguration( - dashboard_tracking_code='0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9', - enabled=True).save() + company_identifier='0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9', + enabled=True + ).save() status_dict = _cert_info(user, course, cert_status, 'honor') - self.assertIn( - 'http://www.linkedin.com/profile/add?_ed=0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9', - status_dict['linked_in_url'] + expected_url = ( + 'http://www.linkedin.com/profile/add' + '?_ed=0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9&' + 'pfCertificationName=edX+Verified+Certificate+for+Demo+Course&' + 'pfCertificationUrl=http%3A%2F%2Fs3.edx%2Fcert&' + 'source=o' ) + self.assertEqual(expected_url, status_dict['linked_in_url']) + + @ddt.data( + ('honor', 'edX Honor Code Certificate for DemoX'), + ('verified', 'edX Verified Certificate for DemoX'), + ('professional', 'edX Professional Certificate for DemoX'), + ('default_mode', 'edX Certificate for DemoX') + ) + @ddt.unpack + def test_linked_in_url_certificate_types(self, cert_mode, cert_name): + user = Mock(username="fred") + course = Mock( + display_name='DemoX', + end_of_course_survey_url='http://example.com', + certificates_display_behavior='end' + ) + cert_status = { + 'status': 'downloadable', + 'grade': '67', + 'download_url': 'http://edx.org', + 'mode': cert_mode + } + + LinkedInAddToProfileConfiguration( + company_identifier="abcd123", + enabled=True + ).save() + + status_dict = _cert_info(user, course, cert_status, cert_mode) + self.assertIn(cert_name.replace(' ', '+'), status_dict['linked_in_url']) class DashboardTest(ModuleStoreTestCase): @@ -511,8 +564,10 @@ class DashboardTest(ModuleStoreTestCase): """ self.client.login(username="jack", password="test") - tracking_code = '0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9' - LinkedInAddToProfileConfiguration(dashboard_tracking_code=tracking_code, enabled=True).save() + LinkedInAddToProfileConfiguration( + company_identifier='0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9', + enabled=True + ).save() CourseModeFactory.create( course_id=self.course.id, @@ -542,14 +597,14 @@ class DashboardTest(ModuleStoreTestCase): self.assertEquals(response.status_code, 200) self.assertIn('Add Certificate to LinkedIn', response.content) - response_url = ( - 'http://www.linkedin.com/profile/add?_ed=' - '{tracking_code}&pfCertificationUrl={download}&pfCertificationName=' - 'Honor+Code+Certificate+for+{name}' - ).format( - tracking_code=tracking_code, download=download_url, name='Omega' + expected_url = ( + 'http://www.linkedin.com/profile/add' + '?_ed=0_mC_o2MizqdtZEmkVXjH4eYwMj4DnkCWrZP_D9&' + 'pfCertificationName=edX+Honor+Code+Certificate+for+Omega&' + 'pfCertificationUrl=www.edx.org&' + 'source=o' ) - self.assertContains(response, response_url) + self.assertContains(response, expected_url) class EnrollInCourseTest(TestCase): diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 5230398561..02a1abf86c 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -354,30 +354,16 @@ def _cert_info(user, course, cert_status, course_mode): else: status_dict['download_url'] = cert_status['download_url'] - # getting linkedin URL and then pass the params which appears - # on user profile. if linkedin config is empty don't show the button. - - modes_dict = { - "honor": "Honor Code Certificate", - "verified": "Verified Certificate", - "professional": "Professional Certificate", - } - - certification_name = u'{type} for {course_name}'.format( - type=modes_dict.get(course_mode, "Certificate"), course_name=course.display_name - ).encode('utf-8') - - params_dict = { - 'pfCertificationName': certification_name, - 'pfCertificationUrl': cert_status['download_url'], - } - - # following method will construct and return url if current enabled config exists otherwise return None - # In case of None linked-in-button will not appear on dashboard. - - status_dict['linked_in_url'] = LinkedInAddToProfileConfiguration.linked_in_dashboard_tracking_code_url( - params_dict - ) + # If enabled, show the LinkedIn "add to profile" button + # Clicking this button sends the user to LinkedIn where they + # can add the certificate information to their profile. + linkedin_config = LinkedInAddToProfileConfiguration.current() + if linkedin_config.enabled: + status_dict['linked_in_url'] = linkedin_config.add_to_profile_url( + course.display_name, + cert_status.get('mode'), + cert_status['download_url'] + ) if status in ('generating', 'ready', 'notpassing', 'restricted'): if 'grade' not in cert_status: diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index a3533ea559..0a26ab7dc3 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -901,11 +901,51 @@ } &.course-status-certavailable { + background-color: $gray-l5; + border: 0; - .action-certificate { + .message-copy { + width: flex-grid(6, 12); + position: relative; + float: left; + } - .btn { + .actions { + width: flex-grid(6, 12); + position: relative; + @include float(right); + + .action { + @include margin(0, 0, ($baseline/2), ($baseline*.75)); + float: none; + text-align: center; + + &:last-child { + margin-bottom: 0; + } + + .btn { + float: none; + } + } + + .action-certificate .btn { @extend %btn-inherited-primary; + @include box-sizing(border-box); + float: none; + border-radius: 3px; + display: block; + @include padding(7px, ($baseline*.75), 7px, ($baseline*.75)); + text-align: center; + + a:link, a:visited { + color: #fff; + } + } + + .action-share .btn { + display: inline; + letter-spacing: 0; } } } diff --git a/lms/templates/dashboard/_dashboard_certificate_information.html b/lms/templates/dashboard/_dashboard_certificate_information.html index d9ff7eaa11..109136e9eb 100644 --- a/lms/templates/dashboard/_dashboard_certificate_information.html +++ b/lms/templates/dashboard/_dashboard_certificate_information.html @@ -6,7 +6,7 @@ cert_name_short = course.cert_name_short if cert_name_short == "": cert_name_short = settings.CERT_NAME_SHORT - + cert_name_long = course.cert_name_long if cert_name_long == "": cert_name_long = settings.CERT_NAME_LONG @@ -53,13 +53,13 @@ else: