utils.py 10.1 KB
Newer Older
1
"""
cewing committed
2
CCX Enrollment operations for use by Coach APIs.
3 4 5

Does not include any access control, be sure to check access before calling.
"""
cewing committed
6
import logging
7 8
from courseware.courses import get_course_about_section  # pylint: disable=import-error
from courseware.courses import get_course_by_id  # pylint: disable=import-error
9 10 11 12
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
13 14
from edxmako.shortcuts import render_to_string  # pylint: disable=import-error
from microsite_configuration import microsite  # pylint: disable=import-error
15 16
from xmodule.modulestore.django import modulestore
from xmodule.error_module import ErrorDescriptor
17

18
from .models import (
cewing committed
19 20
    CcxMembership,
    CcxFutureMembership,
21
)
cewing committed
22 23 24 25
from .overrides import get_current_ccx


log = logging.getLogger("edx.ccx")
26

cewing committed
27

28 29
class EmailEnrollmentState(object):
    """ Store the complete enrollment state of an email in a class """
cewing committed
30
    def __init__(self, ccx, email):
31 32 33
        exists_user = User.objects.filter(email=email).exists()
        if exists_user:
            user = User.objects.get(email=email)
cewing committed
34 35
            ccx_member = CcxMembership.objects.filter(ccx=ccx, student=user)
            in_ccx = ccx_member.exists()
36 37
            full_name = user.profile.name
        else:
38
            user = None
cewing committed
39
            in_ccx = False
40 41 42 43
            full_name = None
        self.user = exists_user
        self.member = user
        self.full_name = full_name
cewing committed
44
        self.in_ccx = in_ccx
45 46

    def __repr__(self):
47
        return "{}(user={}, member={}, in_ccx={})".format(
48 49 50
            self.__class__.__name__,
            self.user,
            self.member,
cewing committed
51
            self.in_ccx,
52 53 54
        )

    def to_dict(self):
55
        """ return dict with membership and ccx info """
56 57 58
        return {
            'user': self.user,
            'member': self.member,
cewing committed
59
            'in_ccx': self.in_ccx,
60 61 62
        }


cewing committed
63
def enroll_email(ccx, student_email, auto_enroll=False, email_students=False, email_params=None):
64 65 66
    """
    Send email to newly enrolled student
    """
67
    if email_params is None:
cewing committed
68 69
        email_params = get_email_params(ccx, True)
    previous_state = EmailEnrollmentState(ccx, student_email)
70 71

    if previous_state.user:
72
        user = User.objects.get(email=student_email)
cewing committed
73 74 75
        if not previous_state.in_ccx:
            membership = CcxMembership(
                ccx=ccx, student=user, active=True
76 77 78 79
            )
            membership.save()
        elif auto_enroll:
            # activate existing memberships
cewing committed
80
            membership = CcxMembership.objects.get(student=user, ccx=ccx)
81
            membership.active = True
82 83 84 85 86 87 88
            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:
cewing committed
89 90
        membership = CcxFutureMembership(
            ccx=ccx, auto_enroll=auto_enroll, email=student_email
91
        )
92 93 94 95 96 97
        membership.save()
        if email_students:
            email_params['message'] = 'allowed_enroll'
            email_params['email_address'] = student_email
            send_mail_to_student(student_email, email_params)

cewing committed
98
    after_state = EmailEnrollmentState(ccx, student_email)
99 100 101 102

    return previous_state, after_state


cewing committed
103
def unenroll_email(ccx, student_email, email_students=False, email_params=None):
104 105 106
    """
    send email to unenrolled students
    """
107
    if email_params is None:
cewing committed
108 109
        email_params = get_email_params(ccx, True)
    previous_state = EmailEnrollmentState(ccx, student_email)
110

cewing committed
111 112 113
    if previous_state.in_ccx:
        CcxMembership.objects.get(
            ccx=ccx, student=previous_state.member
114 115 116 117 118 119 120
        ).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:
cewing committed
121
        if CcxFutureMembership.objects.filter(
122
                ccx=ccx, email=student_email).exists():
cewing committed
123 124
            CcxFutureMembership.objects.get(
                ccx=ccx, email=student_email
cewing committed
125
            ).delete()
126 127 128 129 130
        if email_students:
            email_params['message'] = 'allowed_unenroll'
            email_params['email_address'] = student_email
            send_mail_to_student(student_email, email_params)

cewing committed
131
    after_state = EmailEnrollmentState(ccx, student_email)
132 133 134 135

    return previous_state, after_state


cewing committed
136
def get_email_params(ccx, auto_enroll, secure=True):
137 138 139
    """
    get parameters for enrollment emails
    """
140
    protocol = 'https' if secure else 'http'
cewing committed
141
    course_id = ccx.course_id
142 143 144 145 146 147 148 149

    stripped_site_name = microsite.get_value(
        'SITE_NAME',
        settings.SITE_NAME
    )
    registration_url = u'{proto}://{site}{path}'.format(
        proto=protocol,
        site=stripped_site_name,
cewing committed
150
        path=reverse('register_user')
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
    )
    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,
cewing committed
175
        'course': ccx,
176 177 178 179 180 181 182 183
        '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):
184 185 186
    """
    Check parameters, set text template and send email to student
    """
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    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': (
cewing committed
202 203
            'ccx/enroll_email_allowedsubject.txt',
            'ccx/enroll_email_allowedmessage.txt'
204 205
        ),
        'enrolled_enroll': (
cewing committed
206 207
            'ccx/enroll_email_enrolledsubject.txt',
            'ccx/enroll_email_enrolledmessage.txt'
208 209
        ),
        'allowed_unenroll': (
cewing committed
210 211
            'ccx/unenroll_email_subject.txt',
            'ccx/unenroll_email_allowedmessage.txt'
212 213
        ),
        'enrolled_unenroll': (
cewing committed
214 215
            'ccx/unenroll_email_subject.txt',
            'ccx/unenroll_email_enrolledmessage.txt'
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
        ),
    }

    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
        )
242 243


cewing committed
244 245
def get_all_ccx_for_user(user):
    """return all CCXS to which the user is registered
246 247

    Returns a list of dicts: {
cewing committed
248 249 250 251
        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>
252
        mooc_url: <url to view this MOOC>
253 254 255 256
    }
    """
    if user.is_anonymous():
        return []
cewing committed
257
    current_active_ccx = get_current_ccx()
258
    memberships = []
cewing committed
259 260 261 262
    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
263
        mooc_title = get_course_about_section(course, 'title')
264
        url = reverse(
cewing committed
265 266
            'switch_active_ccx',
            args=[course.id.to_deprecated_string(), membership.ccx.id]
267
        )
268
        mooc_url = reverse(
cewing committed
269 270
            'switch_active_ccx',
            args=[course.id.to_deprecated_string(), ]
271
        )
272
        memberships.append({
cewing committed
273 274 275
            'ccx_name': ccx_title,
            'ccx_url': url,
            'active': membership.ccx == current_active_ccx,
276 277
            'mooc_name': mooc_title,
            'mooc_url': mooc_url,
278 279
        })
    return memberships
280

281

cewing committed
282
def get_ccx_membership_triplets(user, course_org_filter, org_filter_out_set):
283
    """
cewing committed
284
    Get the relevant set of (CustomCourseForEdX, CcxMembership, Course)
285 286 287
    triplets to be displayed on a student's dashboard.
    """
    # only active memberships for now
cewing committed
288 289
    for membership in CcxMembership.memberships_for_user(user):
        ccx = membership.ccx
290
        store = modulestore()
cewing committed
291 292
        with store.bulk_operations(ccx.course_id):
            course = store.get_course(ccx.course_id)
293 294 295 296 297 298 299 300 301 302
            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

cewing committed
303
                yield (ccx, membership, course)
304
            else:
305
                log.error("User {0} enrolled in {2} course {1}".format(  # pylint: disable=logging-format-interpolation
cewing committed
306
                    user.username, ccx.course_id, "broken" if course else "non-existent"
307
                ))