diff --git a/lms/djangoapps/bulk_email/admin.py b/lms/djangoapps/bulk_email/admin.py index 3f6f05e5f1..9ac288b4c8 100644 --- a/lms/djangoapps/bulk_email/admin.py +++ b/lms/djangoapps/bulk_email/admin.py @@ -1,6 +1,8 @@ """ Django admin page for bulk email models """ +from __future__ import absolute_import + from config_models.admin import ConfigurationModelAdmin from django.contrib import admin @@ -79,6 +81,7 @@ To enable email for the course, check the "Email enabled" box, then click "Save" }), ) + admin.site.register(CourseEmail, CourseEmailAdmin) admin.site.register(Optout, OptoutAdmin) admin.site.register(CourseEmailTemplate, CourseEmailTemplateAdmin) diff --git a/lms/djangoapps/bulk_email/models.py b/lms/djangoapps/bulk_email/models.py index 630e12f99d..46df9b2538 100644 --- a/lms/djangoapps/bulk_email/models.py +++ b/lms/djangoapps/bulk_email/models.py @@ -1,14 +1,18 @@ """ Models for bulk email """ +from __future__ import absolute_import + import logging import markupsafe +import six from config_models.models import ConfigurationModel from django.contrib.auth.models import User from django.db import models from opaque_keys.edx.django.models import CourseKeyField from six import text_type +from six.moves import zip from course_modes.models import CourseMode from openedx.core.djangoapps.course_groups.cohorts import get_cohort_by_name @@ -49,10 +53,10 @@ SEND_TO_STAFF = 'staff' SEND_TO_LEARNERS = 'learners' SEND_TO_COHORT = 'cohort' SEND_TO_TRACK = 'track' -EMAIL_TARGET_CHOICES = zip( +EMAIL_TARGET_CHOICES = list(zip( [SEND_TO_MYSELF, SEND_TO_STAFF, SEND_TO_LEARNERS, SEND_TO_COHORT, SEND_TO_TRACK], ['Myself', 'Staff and instructors', 'All students', 'Specific cohort', 'Specific course mode'] -) +)) EMAIL_TARGETS = {target[0] for target in EMAIL_TARGET_CHOICES} @@ -221,7 +225,7 @@ class CourseModeTarget(Target): if mode_slug is None: raise ValueError("Cannot create a CourseModeTarget without specifying a mode_slug.") try: - validate_course_mode(unicode(course_id), mode_slug, include_expired=True) + validate_course_mode(six.text_type(course_id), mode_slug, include_expired=True) except CourseModeNotFoundError: raise ValueError( u"Track {track} does not exist in course {course_id}".format( @@ -419,8 +423,8 @@ class CourseEmailTemplate(models.Model): stored HTML template and the provided `context` dict. """ # HTML-escape string values in the context (used for keyword substitution). - for key, value in context.iteritems(): - if isinstance(value, basestring): + for key, value in six.iteritems(context): + if isinstance(value, six.string_types): context[key] = markupsafe.escape(value) return CourseEmailTemplate._render(self.html_template, htmltext, context) @@ -458,17 +462,11 @@ class CourseAuthorization(models.Model): return u"Course '{}': Instructor Email {}Enabled".format(text_type(self.course_id), not_en) -# .. toggle_name: require_course_email_auth -# .. toggle_type: ConfigurationModel -# .. toggle_default: True (enabled) -# .. toggle_description: If the flag is enabled, course-specific authorization is required, and the course_id is either not provided or not authorixed, the feature is not available. -# .. toggle_category: bulk email -# .. toggle_use_cases: open_edx -# .. toggle_creation_date: 2016-05-05 -# .. toggle_expiration_date: None -# .. toggle_warnings: None -# .. toggle_tickets: None -# .. toggle_status: supported +# .. toggle_name: require_course_email_auth .. toggle_type: ConfigurationModel .. toggle_default: True (enabled) .. +# toggle_description: If the flag is enabled, course-specific authorization is required, and the course_id is either +# not provided or not authorixed, the feature is not available. .. toggle_category: bulk email .. toggle_use_cases: +# open_edx .. toggle_creation_date: 2016-05-05 .. toggle_expiration_date: None .. toggle_warnings: None .. +# toggle_tickets: None .. toggle_status: supported class BulkEmailFlag(ConfigurationModel): """ Enables site-wide configuration for the bulk_email feature. diff --git a/lms/djangoapps/bulk_email/policies.py b/lms/djangoapps/bulk_email/policies.py index 74259ba7a0..f8589ae918 100644 --- a/lms/djangoapps/bulk_email/policies.py +++ b/lms/djangoapps/bulk_email/policies.py @@ -1,6 +1,8 @@ +"""Course Email optOut Policy""" +from __future__ import absolute_import -from edx_ace.policy import Policy, PolicyResult from edx_ace.channel import ChannelType +from edx_ace.policy import Policy, PolicyResult from opaque_keys.edx.keys import CourseKey from bulk_email.models import Optout @@ -13,6 +15,7 @@ class CourseEmailOptout(Policy): if not course_ids: return PolicyResult(deny=frozenset()) + # pylint: disable=line-too-long course_keys = [CourseKey.from_string(course_id) for course_id in course_ids] if Optout.objects.filter(user__username=message.recipient.username, course_id__in=course_keys).count() == len(course_keys): return PolicyResult(deny={ChannelType.EMAIL}) diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index 5b353f0ff9..106d600797 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -3,6 +3,8 @@ This module contains celery task functions for handling the sending of bulk email to a course. """ +from __future__ import absolute_import + import json import logging import random @@ -25,8 +27,6 @@ from boto.ses.exceptions import ( SESLocalAddressCharacterError, SESMaxSendingRateExceededError ) -from util.string_utils import _has_non_ascii_characters - from celery import current_task, task from celery.exceptions import RetryTaskError from celery.states import FAILURE, RETRY, SUCCESS @@ -52,6 +52,7 @@ from lms.djangoapps.instructor_task.subtasks import ( from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.lib.courses import course_image_url from util.date_utils import get_default_time_display +from util.string_utils import _has_non_ascii_characters log = logging.getLogger('edx.celery.task') diff --git a/lms/djangoapps/verify_student/image.py b/lms/djangoapps/verify_student/image.py index a733da6ca6..d672f8a69c 100644 --- a/lms/djangoapps/verify_student/image.py +++ b/lms/djangoapps/verify_student/image.py @@ -1,6 +1,8 @@ """ Image encoding helpers for the verification app. """ +from __future__ import absolute_import + import logging log = logging.getLogger(__name__) diff --git a/lms/djangoapps/verify_student/signals.py b/lms/djangoapps/verify_student/signals.py index 5d4d2f4c80..a5c8062681 100644 --- a/lms/djangoapps/verify_student/signals.py +++ b/lms/djangoapps/verify_student/signals.py @@ -1,6 +1,8 @@ """ Signal handler for setting default course verification dates """ +from __future__ import absolute_import + from django.core.exceptions import ObjectDoesNotExist from django.dispatch.dispatcher import receiver diff --git a/lms/djangoapps/verify_student/ssencrypt.py b/lms/djangoapps/verify_student/ssencrypt.py index e7aeeba37f..65ad66bb74 100644 --- a/lms/djangoapps/verify_student/ssencrypt.py +++ b/lms/djangoapps/verify_student/ssencrypt.py @@ -15,7 +15,7 @@ An RSA private key can be in any of the following formats: * PKCS#1 RSAPrivateKey DER SEQUENCE (binary or PEM encoding) * PKCS#8 PrivateKeyInfo DER SEQUENCE (binary or PEM encoding) """ -from __future__ import division +from __future__ import absolute_import, division import base64 import binascii @@ -23,7 +23,6 @@ import hmac import logging import os from hashlib import md5, sha256 -from six import text_type from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -33,6 +32,7 @@ from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import CBC from cryptography.hazmat.primitives.hashes import SHA1 from cryptography.hazmat.primitives.padding import PKCS7 +from six import text_type log = logging.getLogger(__name__) diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index d194fb3f24..9dec48b2f8 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -2,12 +2,18 @@ Views for the verification flow """ +from __future__ import absolute_import + import datetime import decimal import json import logging -import urllib +import six +import six.moves.urllib.error # pylint: disable=import-error +import six.moves.urllib.parse # pylint: disable=import-error +import six.moves.urllib.request # pylint: disable=import-error +from course_modes.models import CourseMode from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.staticfiles.storage import staticfiles_storage @@ -24,13 +30,18 @@ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from django.views.generic.base import View from edx_rest_api_client.exceptions import SlumberBaseException -from eventtracking import tracker +from edxmako.shortcuts import render_to_response, render_to_string from ipware.ip import get_ip from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey +from shoppingcart.models import CertificateItem, Order +from shoppingcart.processors import get_purchase_endpoint, get_signed_purchase_params +from student.models import CourseEnrollment +from track import segment +from util.db import outer_atomic +from util.json_request import JsonResponse +from xmodule.modulestore.django import modulestore -from course_modes.models import CourseMode -from edxmako.shortcuts import render_to_response, render_to_string from lms.djangoapps.commerce.utils import EcommerceService, is_account_activation_requirement_disabled from lms.djangoapps.verify_student.image import InvalidImageData, decode_image_data from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline @@ -45,13 +56,6 @@ from openedx.core.djangoapps.user_api.accounts import NAME_MIN_LENGTH from openedx.core.djangoapps.user_api.accounts.api import update_account_settings from openedx.core.djangoapps.user_api.errors import AccountValidationError, UserNotFound from openedx.core.lib.log_utils import audit_log -from shoppingcart.models import CertificateItem, Order -from shoppingcart.processors import get_purchase_endpoint, get_signed_purchase_params -from student.models import CourseEnrollment -from track import segment -from util.db import outer_atomic -from util.json_request import JsonResponse -from xmodule.modulestore.django import modulestore log = logging.getLogger(__name__) @@ -379,7 +383,7 @@ class PayAndVerifyView(View): if not course.start or course.start < now(): courseware_url = reverse( 'course_root', - kwargs={'course_id': unicode(course_key)} + kwargs={'course_id': six.text_type(course_key)} ) full_name = ( @@ -392,7 +396,7 @@ class PayAndVerifyView(View): # use that amount to pre-fill the price selection form. contribution_amount = request.session.get( 'donation_for_course', {} - ).get(unicode(course_key), '') + ).get(six.text_type(course_key), '') # Remember whether the user is upgrading # so we can fire an analytics event upon payment. @@ -413,7 +417,7 @@ class PayAndVerifyView(View): context = { 'contribution_amount': contribution_amount, 'course': course, - 'course_key': unicode(course_key), + 'course_key': six.text_type(course_key), 'checkpoint_location': request.GET.get('checkpoint'), 'course_mode': relevant_course_mode, 'courseware_url': courseware_url, @@ -441,10 +445,10 @@ class PayAndVerifyView(View): # utm_params is [(u'utm_content', u'course-v1:IDBx IDB20.1x 1T2017'),... utm_params = [item for item in self.request.GET.items() if 'utm_' in item[0]] # utm_params is utm_content=course-v1%3AIDBx+IDB20.1x+1T2017&... - utm_params = urllib.urlencode(utm_params, True) + utm_params = six.moves.urllib.parse.urlencode(utm_params, True) # pylint: disable=too-many-function-args # utm_params is utm_content=course-v1:IDBx+IDB20.1x+1T2017&... # (course-keys do not have url encoding) - utm_params = urllib.unquote(utm_params) + utm_params = six.moves.urllib.parse.unquote(utm_params) # pylint: disable=too-many-function-args if utm_params: if '?' in url: url = url + '&' + utm_params @@ -453,8 +457,8 @@ class PayAndVerifyView(View): return url def _redirect_if_necessary( - self, message, already_verified, already_paid, is_enrolled, course_key, - user_is_trying_to_pay, user, sku + self, message, already_verified, already_paid, is_enrolled, course_key, + user_is_trying_to_pay, user, sku ): """Redirect the user to a more appropriate page if necessary. @@ -488,7 +492,7 @@ class PayAndVerifyView(View): """ url = None - course_kwargs = {'course_id': unicode(course_key)} + course_kwargs = {'course_id': six.text_type(course_key)} if already_verified and already_paid: # If they've already paid and verified, there's nothing else to do, @@ -597,7 +601,7 @@ class PayAndVerifyView(View): return [ { 'name': step, - 'title': unicode(self.STEP_TITLES[step]), + 'title': six.text_type(self.STEP_TITLES[step]), } for step in display_steps if step not in remove_steps @@ -631,7 +635,7 @@ class PayAndVerifyView(View): display_steps = set(step['name'] for step in display_steps) - for step, step_requirements in self.STEP_REQUIREMENTS.iteritems(): + for step, step_requirements in six.iteritems(self.STEP_REQUIREMENTS): if step in display_steps: for requirement in step_requirements: all_requirements[requirement] = True @@ -729,7 +733,7 @@ class PayAndVerifyView(View): def checkout_with_ecommerce_service(user, course_key, course_mode, processor): """ Create a new basket and trigger immediate checkout, using the E-Commerce API. """ - course_id = unicode(course_key) + course_id = six.text_type(course_key) try: api = ecommerce_api_client(user) # Make an API call to create the order and retrieve the results @@ -781,7 +785,7 @@ def checkout_with_shoppingcart(request, user, course_key, course_mode, amount): 'payment_form_data': get_signed_purchase_params( cart, callback_url=callback_url, - extra_data=[unicode(course_key), course_mode.slug] + extra_data=[six.text_type(course_key), course_mode.slug] ), } return payment_data @@ -798,7 +802,7 @@ def create_order(request): course_id = request.POST['course_id'] course_id = CourseKey.from_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) - contribution = request.POST.get("contribution", donation_for_course.get(unicode(course_id), 0)) + contribution = request.POST.get("contribution", donation_for_course.get(six.text_type(course_id), 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: @@ -1135,7 +1139,7 @@ def results_callback(request): access_key = access_key_and_sig.split(":")[0] # This is what we should be doing... - #if not sig_valid: + # if not sig_valid: # return HttpResponseBadRequest("Signature is invalid") # This is what we're doing until we can figure out why we disagree on sigs @@ -1237,6 +1241,7 @@ class ReverifyView(View): the user submitted during initial verification. """ + @method_decorator(login_required) def get(self, request): """