diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 30756d8d20..4083954f77 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -133,6 +133,9 @@ AUDIT_LOG = logging.getLogger("audit") ReverifyInfo = namedtuple('ReverifyInfo', 'course_id course_name course_number date status display') # pylint: disable=invalid-name SETTING_CHANGE_INITIATED = 'edx.user.settings.change_initiated' +# Disable this warning because it doesn't make sense to completely refactor tests to appease Pylint +# pylint: disable=logging-format-interpolation + def csrf_token(context): """A csrf token that can be included in a form.""" @@ -296,12 +299,13 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa default_status = 'processing' - default_info = {'status': default_status, - 'show_disabled_download_button': False, - 'show_download_url': False, - 'show_survey_button': False, - 'can_unenroll': True - } + default_info = { + 'status': default_status, + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': False, + 'can_unenroll': True, + } if cert_status is None: return default_info @@ -500,9 +504,14 @@ def is_course_blocked(request, redeemed_registration_codes, course_key): u"User %s (%s) opted out of receiving emails from course %s", request.user.username, request.user.email, - course_key + course_key, + ) + track.views.server_track( + request, + "change-email1-settings", + {"receive_emails": "no", "course": course_key.to_deprecated_string()}, + page='dashboard', ) - track.views.server_track(request, "change-email1-settings", {"receive_emails": "no", "course": course_key.to_deprecated_string()}, page='dashboard') break return blocked @@ -725,7 +734,7 @@ def _create_recent_enrollment_message(course_enrollments, course_modes): # pyli recently_enrolled_courses = _get_recently_enrolled_courses(course_enrollments) if recently_enrolled_courses: - messages = [ + enroll_messages = [ { "course_id": enrollment.course_overview.id, "course_name": enrollment.course_overview.display_name, @@ -738,7 +747,7 @@ def _create_recent_enrollment_message(course_enrollments, course_modes): # pyli return render_to_string( 'enrollment/course_enrollment_message.html', - {'course_enrollment_messages': messages, 'platform_name': platform_name} + {'course_enrollment_messages': enroll_messages, 'platform_name': platform_name} ) @@ -777,7 +786,11 @@ def _allow_donation(course_modes, course_id, enrollment): """ donations_enabled = DonationConfiguration.current().enabled - return donations_enabled and enrollment.mode in course_modes[course_id] and course_modes[course_id][enrollment.mode].min_price == 0 + return ( + donations_enabled and + enrollment.mode in course_modes[course_id] and + course_modes[course_id][enrollment.mode].min_price == 0 + ) def _update_email_opt_in(request, org): @@ -1011,7 +1024,7 @@ def change_enrollment(request, check_access=True): enroll_mode = CourseMode.auto_enroll_mode(course_id, available_modes) if enroll_mode: CourseEnrollment.enroll(user, course_id, check_access=check_access, mode=enroll_mode) - except Exception: + except Exception: # pylint: disable=broad-except return HttpResponseBadRequest(_("Could not enroll")) # If we have more than one course mode or professional ed is enabled, @@ -1073,32 +1086,43 @@ def login_user(request, error=""): # pylint: disable=too-many-statements,unused third_party_auth_successful = True except User.DoesNotExist: AUDIT_LOG.warning( - u'Login failed - user with username {username} has no social auth with backend_name {backend_name}'.format( - username=username, backend_name=backend_name)) - return HttpResponse( - _("You've successfully logged into your {provider_name} account, but this account isn't linked with an {platform_name} account yet.").format( - platform_name=platform_name, provider_name=requested_provider.name - ) - + "

" + - _("Use your {platform_name} username and password to log into {platform_name} below, " - "and then link your {platform_name} account with {provider_name} from your dashboard.").format( - platform_name=platform_name, provider_name=requested_provider.name - ) - + "

" + - _("If you don't have an {platform_name} account yet, " - "click Register at the top of the page.").format( - platform_name=platform_name), - content_type="text/plain", - status=403 + u"Login failed - user with username {username} has no social auth " + "with backend_name {backend_name}".format( + username=username, backend_name=backend_name) ) + message = _( + "You've successfully logged into your {provider_name} account, " + "but this account isn't linked with an {platform_name} account yet." + ).format( + platform_name=platform_name, + provider_name=requested_provider.name, + ) + message += "

" + message += _( + "Use your {platform_name} username and password to log into {platform_name} below, " + "and then link your {platform_name} account with {provider_name} from your dashboard." + ).format( + platform_name=platform_name, + provider_name=requested_provider.name, + ) + message += "

" + message += _( + "If you don't have an {platform_name} account yet, " + "click Register at the top of the page." + ).format( + platform_name=platform_name + ) + + return HttpResponse(message, content_type="text/plain", status=403) else: if 'email' not in request.POST or 'password' not in request.POST: return JsonResponse({ "success": False, - "value": _('There was an error receiving your login information. Please email us.'), # TODO: User error message - }) # TODO: this should be status code 400 # pylint: disable=fixme + # TODO: User error message + "value": _('There was an error receiving your login information. Please email us.'), + }) # TODO: this should be status code 400 email = request.POST['email'] password = request.POST['password'] @@ -1129,9 +1153,11 @@ def login_user(request, error=""): # pylint: disable=too-many-statements,unused user_found_by_email_lookup = user if user_found_by_email_lookup and LoginFailures.is_feature_enabled(): if LoginFailures.is_user_locked_out(user_found_by_email_lookup): + lockout_message = _('This account has been temporarily locked due ' + 'to excessive login failures. Try again later.') return JsonResponse({ "success": False, - "value": _('This account has been temporarily locked due to excessive login failures. Try again later.'), + "value": lockout_message, }) # TODO: this should be status code 429 # pylint: disable=fixme # see if the user must reset his/her password due to any policy settings @@ -1239,7 +1265,8 @@ def login_user(request, error=""): # pylint: disable=too-many-statements,unused AUDIT_LOG.warning(u"Login failed - Account not active for user {0}, resending activation".format(username)) reactivation_email_for_user(user) - not_activated_msg = _("This account has not been activated. We have sent another activation message. Please check your email for the activation instructions.") + not_activated_msg = _("This account has not been activated. We have sent another activation " + "message. Please check your email for the activation instructions.") return JsonResponse({ "success": False, "value": not_activated_msg, @@ -1608,7 +1635,7 @@ def create_account_with_params(request, params): if settings.FEATURES.get('ENABLE_DISCUSSION_EMAIL_DIGEST'): try: enable_notifications(user) - except Exception: + except Exception: # pylint: disable=broad-except log.exception("Enable discussion notifications failed for user {id}.".format(id=user.id)) dog_stats_api.increment("common.student.account_created") @@ -2010,9 +2037,9 @@ def password_reset(request): def password_reset_confirm_wrapper( - request, - uidb36=None, - token=None, + request, + uidb36=None, + token=None, ): """ A wrapper around django.contrib.auth.views.password_reset_confirm. Needed because we want to set the user as active at this step. @@ -2046,6 +2073,8 @@ def password_reset_confirm_wrapper( num_distinct = settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STAFF_PASSWORDS_BEFORE_REUSE'] else: num_distinct = settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STUDENT_PASSWORDS_BEFORE_REUSE'] + # Because of how ngettext is, splitting the following into shorter lines would be ugly. + # pylint: disable=line-too-long err_msg = ungettext( "You are re-using a password that you have used recently. You must have {num} distinct password before reusing a previous password.", "You are re-using a password that you have used recently. You must have {num} distinct passwords before reusing a previous password.", @@ -2055,6 +2084,8 @@ def password_reset_confirm_wrapper( # also, check to see if passwords are getting reset too frequent if PasswordHistory.is_password_reset_too_soon(user): num_days = settings.ADVANCED_SECURITY_CONFIG['MIN_TIME_IN_DAYS_BETWEEN_ALLOWED_RESETS'] + # Because of how ngettext is, splitting the following into shorter lines would be ugly. + # pylint: disable=line-too-long err_msg = ungettext( "You are resetting passwords too frequently. Due to security policies, {num} day must elapse between password resets.", "You are resetting passwords too frequently. Due to security policies, {num} days must elapse between password resets.", @@ -2287,18 +2318,28 @@ def change_email_settings(request): u"User %s (%s) opted in to receive emails from course %s", user.username, user.email, - course_id + course_id, + ) + track.views.server_track( + request, + "change-email-settings", + {"receive_emails": "yes", "course": course_id}, + page='dashboard', ) - track.views.server_track(request, "change-email-settings", {"receive_emails": "yes", "course": course_id}, page='dashboard') else: Optout.objects.get_or_create(user=user, course_id=course_key) log.info( u"User %s (%s) opted out of receiving emails from course %s", user.username, user.email, - course_id + course_id, + ) + track.views.server_track( + request, + "change-email-settings", + {"receive_emails": "no", "course": course_id}, + page='dashboard', ) - track.views.server_track(request, "change-email-settings", {"receive_emails": "no", "course": course_id}, page='dashboard') return JsonResponse({"success": True}) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 2609bdd047..14f5069a0b 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -7,7 +7,8 @@ of a variety of types. Used by capa_problem.py """ - +# TODO: Refactor this code and fix this issue. +# pylint: disable=attribute-defined-outside-init # standard library imports import abc import cgi @@ -541,7 +542,7 @@ class LoncapaResponse(object): # If we can't do that, create the
and set the message # as the text of the
- except: + except Exception: # pylint: disable=broad-except response_msg_div = etree.Element('div') response_msg_div.text = str(response_msg) @@ -1225,7 +1226,6 @@ class MultipleChoiceResponse(LoncapaResponse): i = 0 for response in self.xml.xpath("choicegroup"): # Is Masking enabled? -- check for shuffle or answer-pool features - ans_str = response.get("answer-pool") # Masking (self._has_mask) is off, to be re-enabled with a future PR. rtype = response.get('type') if rtype not in ["MultipleChoice"]: @@ -1240,12 +1240,15 @@ class MultipleChoiceResponse(LoncapaResponse): i += 1 # If using the masked name, e.g. mask_0, save the regular name # to support unmasking later (for the logs). - if self.has_mask(): - mask_name = "mask_" + str(mask_ids.pop()) - self._mask_dict[mask_name] = name - choice.set("name", mask_name) - else: - choice.set("name", name) + # Masking is currently disabled so this code is commented, as + # the variable `mask_ids` is not defined. (the feature appears to not be fully implemented) + # The original work for masking was done by Nick Parlante as part of the OLI Hinting feature. + # if self.has_mask(): + # mask_name = "mask_" + str(mask_ids.pop()) + # self._mask_dict[mask_name] = name + # choice.set("name", mask_name) + # else: + choice.set("name", name) def late_transforms(self, problem): """ @@ -1338,12 +1341,13 @@ class MultipleChoiceResponse(LoncapaResponse): Given a masked name, e.g. mask_2, returns the regular name, e.g. choice_0. Fails with LoncapaProblemError if called on a response that is not masking. """ - if not self.has_mask(): - _ = self.capa_system.i18n.ugettext - # Translators: 'unmask_name' is a method name and should not be translated. - msg = _("unmask_name called on response that is not masked") - raise LoncapaProblemError(msg) - return self._mask_dict[name] + # if not self.has_mask(): + # _ = self.capa_system.i18n.ugettext + # # Translators: 'unmask_name' is a method name and should not be translated. + # msg = "unmask_name called on response that is not masked" + # raise LoncapaProblemError(msg) + # return self._mask_dict[name] # TODO: this is not defined + raise NotImplementedError() def unmask_order(self): """ @@ -1750,7 +1754,9 @@ class NumericalResponse(LoncapaResponse): student_float = evaluator({}, {}, student_answer) except UndefinedVariable as undef_var: raise StudentInputError( - _(u"You may not use variables ({bad_variables}) in numerical problems.").format(bad_variables=undef_var.message) + _(u"You may not use variables ({bad_variables}) in numerical problems.").format( + bad_variables=undef_var.message, + ) ) except ValueError as val_err: if 'factorial' in val_err.message: @@ -1802,13 +1808,17 @@ class NumericalResponse(LoncapaResponse): for inclusion, answer in zip(self.inclusion, self.answer_range): boundary = self.get_staff_ans(answer) if boundary.imag != 0: - # Translators: This is an error message for a math problem. If the instructor provided a boundary - # (end limit) for a variable that is a complex number (a + bi), this message displays. - raise StudentInputError(_("There was a problem with the staff answer to this problem: complex boundary.")) + raise StudentInputError( + # Translators: This is an error message for a math problem. If the instructor provided a + # boundary (end limit) for a variable that is a complex number (a + bi), this message displays. + _("There was a problem with the staff answer to this problem: complex boundary.") + ) if isnan(boundary): - # Translators: This is an error message for a math problem. If the instructor did not provide - # a boundary (end limit) for a variable, this message displays. - raise StudentInputError(_("There was a problem with the staff answer to this problem: empty boundary.")) + raise StudentInputError( + # Translators: This is an error message for a math problem. If the instructor did not + # provide a boundary (end limit) for a variable, this message displays. + _("There was a problem with the staff answer to this problem: empty boundary.") + ) boundaries.append(boundary.real) if compare_with_tolerance( student_float, @@ -2164,7 +2174,8 @@ class StringResponse(LoncapaResponse): def get_answers(self): _ = self.capa_system.i18n.ugettext - # Translators: Separator used in StringResponse to display multiple answers. Example: "Answer: Answer_1 or Answer_2 or Answer_3". + # Translators: Separator used in StringResponse to display multiple answers. + # Example: "Answer: Answer_1 or Answer_2 or Answer_3". separator = u' {} '.format(_('or')) return {self.answer_id: separator.join(self.correct_answer)} @@ -2280,7 +2291,9 @@ class CustomResponse(LoncapaResponse): submission = [student_answers[k] for k in idset] except Exception as err: msg = u"[courseware.capa.responsetypes.customresponse] {message}\n idset = {idset}, error = {err}".format( - message=_("error getting student answer from {student_answers}").format(student_answers=student_answers), + message=_("error getting student answer from {student_answers}").format( + student_answers=student_answers, + ), idset=idset, err=err ) @@ -2392,20 +2405,20 @@ class CustomResponse(LoncapaResponse): random_seed=self.context['seed'], unsafely=self.capa_system.can_execute_unsafe_code(), ) - except Exception as err: + except Exception as err: # pylint: disable=broad-except self._handle_exec_exception(err) else: # self.code is not a string; it's a function we created earlier. # this is an interface to the Tutor2 check functions - fn = self.code + tutor_cfn = self.code answer_given = submission[0] if (len(idset) == 1) else submission kwnames = self.xml.get("cfn_extra_args", "").split() kwargs = {n: self.context.get(n) for n in kwnames} log.debug(" submission = %s", submission) try: - ret = fn(self.expect, answer_given, **kwargs) + ret = tutor_cfn(self.expect, answer_given, **kwargs) except Exception as err: # pylint: disable=broad-except self._handle_exec_exception(err) log.debug( @@ -2928,15 +2941,17 @@ class CodeResponse(LoncapaResponse): # Next, we need to check that the contents of the external grader message is safe for the LMS. # 1) Make sure that the message is valid XML (proper opening/closing tags) - # 2) If it is not valid XML, make sure it is valid HTML. Note: html5lib parser will try to repair any broken HTML - # For example: will become . + # 2) If it is not valid XML, make sure it is valid HTML. + # Note: html5lib parser will try to repair any broken HTML + # For example: will become . msg = score_result['msg'] try: etree.fromstring(msg) except etree.XMLSyntaxError as _err: # If `html` contains attrs with no values, like `controls` in