From fdf531aec08921a1fecfb472bd1b0e2318c21087 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 13 Jan 2014 15:15:46 -0500 Subject: [PATCH] Migrations for LinkedIn. Clean up common. Add the ability to dry-run the command without sending e-mail. Don't save courses sent during a dry run Switch to EmailMessage for LinkedIn so we can send HTML emails Update subject copy. Use correct name for CertificationName Fix up certificate url information. --- .../management/commands/linkedin_mailusers.py | 32 ++++++--- .../commands/tests/test_mailusers.py | 16 +++++ .../linkedin/migrations/0001_initial.py | 70 +++++++++++++++++++ .../linkedin/migrations/__init__.py | 0 lms/envs/common.py | 4 -- 5 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 lms/djangoapps/linkedin/migrations/0001_initial.py create mode 100644 lms/djangoapps/linkedin/migrations/__init__.py diff --git a/lms/djangoapps/linkedin/management/commands/linkedin_mailusers.py b/lms/djangoapps/linkedin/management/commands/linkedin_mailusers.py index 32d04cd898..75f097cca2 100644 --- a/lms/djangoapps/linkedin/management/commands/linkedin_mailusers.py +++ b/lms/djangoapps/linkedin/management/commands/linkedin_mailusers.py @@ -7,7 +7,7 @@ import json import urllib from django.conf import settings -from django.core.mail import send_mail +from django.core.mail import EmailMessage from django.core.management.base import BaseCommand from django.template import Context from django.template.loader import get_template @@ -43,6 +43,13 @@ class Command(BaseCommand): "all users that have earned certificates to date to add their " "certificates. Afterwards the default, one email per " "certificate mail form will be used."),) + option_list = option_list + ( + make_option( + '--mock', + action='store_true', + dest='mock_run', + default=False, + help="Run without sending the final e-mails."),) def __init__(self): super(Command, self).__init__() @@ -50,6 +57,7 @@ class Command(BaseCommand): def handle(self, *args, **options): whitelist = settings.LINKEDIN_API['EMAIL_WHITELIST'] grandfather = options.get('grandfather', False) + mock_run = options.get('mock_run', False) accounts = LinkedIn.objects.filter(has_linkedin_account=True) for account in accounts: user = account.user @@ -65,8 +73,9 @@ class Command(BaseCommand): if not certificates: continue if grandfather: - self.send_grandfather_email(user, certificates) - emailed.extend([cert.course_id for cert in certificates]) + self.send_grandfather_email(user, certificates, mock_run) + if not mock_run: + emailed.extend([cert.course_id for cert in certificates]) else: for certificate in certificates: self.send_triggered_email(user, certificate) @@ -83,11 +92,11 @@ class Command(BaseCommand): tracking_code = '-'.join([ 'eml', 'prof', # the 'product'--no idea what that's supposed to mean - course.org, # Partner's name + 'edX', # Partner's name course.number, # Certificate's name 'gf' if grandfather else 'T']) query = [ - ('pfCertificationName', certificate.name), + ('pfCertificationName', course.display_name_with_default), ('pfAuthorityName', settings.PLATFORM_NAME), ('pfAuthorityId', settings.LINKEDIN_API['COMPANY_ID']), ('pfCertificationUrl', certificate.download_url), @@ -99,7 +108,7 @@ class Command(BaseCommand): ('force', 'true')] return 'http://www.linkedin.com/profile/guided?' + urllib.urlencode(query) - def send_grandfather_email(self, user, certificates): + def send_grandfather_email(self, user, certificates, mock_run=False): """ Send the 'grandfathered' email informing historical students that they may now post their certificates on their LinkedIn profiles. @@ -124,13 +133,14 @@ class Command(BaseCommand): 'course_title': course_title, 'course_image_url': course_img_url, 'course_end_date': course_end_date, - 'linkedin_add_url': self.certificate_url(cert), + 'linkedin_add_url': self.certificate_url(cert, True), }) context = {'courses_list': courses_list, 'num_courses': len(courses_list)} body = render_to_string('linkedin/linkedin_email.html', context) - subject = 'Congratulations! Put your certificates on LinkedIn' - self.send_email(user, subject, body) + subject = '{}, Add your Achievements to your LinkedIn Profile'.format(user.profile.name) + if not mock_run: + self.send_email(user, subject, body) def send_triggered_email(self, user, certificate): """ @@ -153,4 +163,6 @@ class Command(BaseCommand): """ fromaddr = settings.DEFAULT_FROM_EMAIL toaddr = '%s <%s>' % (user.profile.name, user.email) - send_mail(subject, body, fromaddr, (toaddr,)) + msg = EmailMessage(subject, body, fromaddr, (toaddr,)) + msg.content_subtype = "html" + msg.send() diff --git a/lms/djangoapps/linkedin/management/commands/tests/test_mailusers.py b/lms/djangoapps/linkedin/management/commands/tests/test_mailusers.py index e0fb55adab..c6dc66c56d 100644 --- a/lms/djangoapps/linkedin/management/commands/tests/test_mailusers.py +++ b/lms/djangoapps/linkedin/management/commands/tests/test_mailusers.py @@ -115,8 +115,24 @@ class MailusersTests(TestCase): self.assertEqual(len(mail.outbox), 2) self.assertEqual( mail.outbox[0].to, ['Fred Flintstone ']) + self.assertEqual( + mail.outbox[0].subject, 'Fred Flintstone, Add your Achievements to your LinkedIn Profile') self.assertEqual( mail.outbox[1].to, ['Barney Rubble ']) + self.assertEqual( + mail.outbox[1].subject, 'Barney Rubble, Add your Achievements to your LinkedIn Profile') + + def test_mail_users_grandfather_mock(self): + """ + test that we aren't sending anything when in mock_run mode + """ + fut = mailusers.Command().handle + fut(grandfather=True, mock_run=True) + self.assertEqual( + json.loads(self.fred.linkedin.emailed_courses), []) + self.assertEqual( + json.loads(self.barney.linkedin.emailed_courses), []) + self.assertEqual(len(mail.outbox), 0) def test_mail_users_only_new_courses(self): """ diff --git a/lms/djangoapps/linkedin/migrations/0001_initial.py b/lms/djangoapps/linkedin/migrations/0001_initial.py new file mode 100644 index 0000000000..b6ceeef7d7 --- /dev/null +++ b/lms/djangoapps/linkedin/migrations/0001_initial.py @@ -0,0 +1,70 @@ +# -*- 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 model 'LinkedIn' + db.create_table('linkedin_linkedin', ( + ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True, primary_key=True)), + ('has_linkedin_account', self.gf('django.db.models.fields.NullBooleanField')(default=None, null=True, blank=True)), + ('emailed_courses', self.gf('django.db.models.fields.TextField')(default='[]')), + )) + db.send_create_signal('linkedin', ['LinkedIn']) + + + def backwards(self, orm): + # Deleting model 'LinkedIn' + db.delete_table('linkedin_linkedin') + + + 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'}) + }, + 'linkedin.linkedin': { + 'Meta': {'object_name': 'LinkedIn'}, + 'emailed_courses': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + 'has_linkedin_account': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['linkedin'] \ No newline at end of file diff --git a/lms/djangoapps/linkedin/migrations/__init__.py b/lms/djangoapps/linkedin/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/envs/common.py b/lms/envs/common.py index 29d5c92bb8..a3cff7580c 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1147,10 +1147,6 @@ GRADES_DOWNLOAD = { ##################### LinkedIn ##################### INSTALLED_APPS += ('django_openid_auth',) -LINKEDIN_API = { - 'COMPANY_NAME': 'edX', - -} ############################ LinkedIn Integration ############################# INSTALLED_APPS += ('linkedin',)