""" CCX Enrollment operations for use by Coach APIs. Does not include any access control, be sure to check access before calling. """ import logging from courseware.courses import get_course_about_section # pylint: disable=import-error from courseware.courses import get_course_by_id # pylint: disable=import-error from django.contrib.auth.models import User from django.conf import settings from django.core.urlresolvers import reverse from django.core.mail import send_mail from edxmako.shortcuts import render_to_string # pylint: disable=import-error from microsite_configuration import microsite # pylint: disable=import-error from xmodule.modulestore.django import modulestore from xmodule.error_module import ErrorDescriptor from .models import ( CcxMembership, CcxFutureMembership, ) from .overrides import get_current_ccx log = logging.getLogger("edx.ccx") class EmailEnrollmentState(object): """ Store the complete enrollment state of an email in a class """ def __init__(self, ccx, email): exists_user = User.objects.filter(email=email).exists() if exists_user: user = User.objects.get(email=email) ccx_member = CcxMembership.objects.filter(ccx=ccx, student=user) in_ccx = ccx_member.exists() full_name = user.profile.name else: user = None in_ccx = False full_name = None self.user = exists_user self.member = user self.full_name = full_name self.in_ccx = in_ccx def __repr__(self): return "{}(user={}, member={}, in_ccx={})".format( self.__class__.__name__, self.user, self.member, self.in_ccx, ) def to_dict(self): """ return dict with membership and ccx info """ return { 'user': self.user, 'member': self.member, 'in_ccx': self.in_ccx, } def enroll_email(ccx, student_email, auto_enroll=False, email_students=False, email_params=None): """ Send email to newly enrolled student """ if email_params is None: email_params = get_email_params(ccx, True) previous_state = EmailEnrollmentState(ccx, student_email) if previous_state.user: user = User.objects.get(email=student_email) if not previous_state.in_ccx: membership = CcxMembership( ccx=ccx, student=user, active=True ) membership.save() elif auto_enroll: # activate existing memberships membership = CcxMembership.objects.get(student=user, ccx=ccx) membership.active = True membership.save() if email_students: email_params['message'] = 'enrolled_enroll' email_params['email_address'] = student_email email_params['full_name'] = previous_state.full_name send_mail_to_student(student_email, email_params) else: membership = CcxFutureMembership( ccx=ccx, auto_enroll=auto_enroll, email=student_email ) membership.save() if email_students: email_params['message'] = 'allowed_enroll' email_params['email_address'] = student_email send_mail_to_student(student_email, email_params) after_state = EmailEnrollmentState(ccx, student_email) return previous_state, after_state def unenroll_email(ccx, student_email, email_students=False, email_params=None): """ send email to unenrolled students """ if email_params is None: email_params = get_email_params(ccx, True) previous_state = EmailEnrollmentState(ccx, student_email) if previous_state.in_ccx: CcxMembership.objects.get( ccx=ccx, student=previous_state.member ).delete() if email_students: email_params['message'] = 'enrolled_unenroll' email_params['email_address'] = student_email email_params['full_name'] = previous_state.full_name send_mail_to_student(student_email, email_params) else: if CcxFutureMembership.objects.filter( ccx=ccx, email=student_email).exists(): CcxFutureMembership.objects.get( ccx=ccx, email=student_email ).delete() if email_students: email_params['message'] = 'allowed_unenroll' email_params['email_address'] = student_email send_mail_to_student(student_email, email_params) after_state = EmailEnrollmentState(ccx, student_email) return previous_state, after_state def get_email_params(ccx, auto_enroll, secure=True): """ get parameters for enrollment emails """ protocol = 'https' if secure else 'http' course_id = ccx.course_id stripped_site_name = microsite.get_value( 'SITE_NAME', settings.SITE_NAME ) registration_url = u'{proto}://{site}{path}'.format( proto=protocol, site=stripped_site_name, path=reverse('register_user') ) course_url = u'{proto}://{site}{path}'.format( proto=protocol, site=stripped_site_name, path=reverse( 'course_root', kwargs={'course_id': course_id.to_deprecated_string()} ) ) course_about_url = None if not settings.FEATURES.get('ENABLE_MKTG_SITE', False): course_about_url = u'{proto}://{site}{path}'.format( proto=protocol, site=stripped_site_name, path=reverse( 'about_course', kwargs={'course_id': course_id.to_deprecated_string()} ) ) email_params = { 'site_name': stripped_site_name, 'registration_url': registration_url, 'course': ccx, 'auto_enroll': auto_enroll, 'course_url': course_url, 'course_about_url': course_about_url, } return email_params def send_mail_to_student(student, param_dict): """ Check parameters, set text template and send email to student """ if 'course' in param_dict: param_dict['course_name'] = param_dict['course'].display_name param_dict['site_name'] = microsite.get_value( 'SITE_NAME', param_dict['site_name'] ) subject = None message = None message_type = param_dict['message'] email_template_dict = { 'allowed_enroll': ( 'ccx/enroll_email_allowedsubject.txt', 'ccx/enroll_email_allowedmessage.txt' ), 'enrolled_enroll': ( 'ccx/enroll_email_enrolledsubject.txt', 'ccx/enroll_email_enrolledmessage.txt' ), 'allowed_unenroll': ( 'ccx/unenroll_email_subject.txt', 'ccx/unenroll_email_allowedmessage.txt' ), 'enrolled_unenroll': ( 'ccx/unenroll_email_subject.txt', 'ccx/unenroll_email_enrolledmessage.txt' ), } subject_template, message_template = email_template_dict.get( message_type, (None, None) ) if subject_template is not None and message_template is not None: subject = render_to_string(subject_template, param_dict) message = render_to_string(message_template, param_dict) if subject and message: message = message.strip() subject = ''.join(subject.splitlines()) from_address = microsite.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL ) send_mail( subject, message, from_address, [student], fail_silently=False ) def get_all_ccx_for_user(user): """return all CCXS to which the user is registered Returns a list of dicts: { ccx_name: <formatted title of CCX course> ccx_url: <url to view this CCX> ccx_active: True if this ccx is currently the 'active' one mooc_name: <formatted title of the MOOC course for this CCX> mooc_url: <url to view this MOOC> } """ if user.is_anonymous(): return [] current_active_ccx = get_current_ccx() memberships = [] for membership in CcxMembership.memberships_for_user(user): course = get_course_by_id(membership.ccx.course_id) ccx = membership.ccx ccx_title = ccx.display_name mooc_title = get_course_about_section(course, 'title') url = reverse( 'switch_active_ccx', args=[course.id.to_deprecated_string(), membership.ccx.id] ) mooc_url = reverse( 'switch_active_ccx', args=[course.id.to_deprecated_string(), ] ) memberships.append({ 'ccx_name': ccx_title, 'ccx_url': url, 'active': membership.ccx == current_active_ccx, 'mooc_name': mooc_title, 'mooc_url': mooc_url, }) return memberships def get_ccx_membership_triplets(user, course_org_filter, org_filter_out_set): """ Get the relevant set of (CustomCourseForEdX, CcxMembership, Course) triplets to be displayed on a student's dashboard. """ # only active memberships for now for membership in CcxMembership.memberships_for_user(user): ccx = membership.ccx store = modulestore() with store.bulk_operations(ccx.course_id): course = store.get_course(ccx.course_id) if course and not isinstance(course, ErrorDescriptor): # if we are in a Microsite, then filter out anything that is not # attributed (by ORG) to that Microsite if course_org_filter and course_org_filter != course.location.org: continue # Conversely, if we are not in a Microsite, then let's filter out any enrollments # with courses attributed (by ORG) to Microsites elif course.location.org in org_filter_out_set: continue yield (ccx, membership, course) else: log.error("User {0} enrolled in {2} course {1}".format( # pylint: disable=logging-format-interpolation user.username, ccx.course_id, "broken" if course else "non-existent" ))