Skip to content
Snippets Groups Projects
Commit 1dfd3310 authored by David Baumgold's avatar David Baumgold
Browse files

Refactor student views

Fix pylint/pep8 warnings, use JsonResponse instead of HttpResponse where useful,
put in TODOs to change HTTP status codes to be more accurate.
parent 01ac04ca
No related merge requests found
......@@ -23,6 +23,7 @@ from external_auth.models import ExternalAuthMap
TEST_DATA_MIXED_MODULESTORE = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {})
class LoginTest(TestCase):
'''
Test student.views.login_user() view
......@@ -224,7 +225,11 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
"""
response = self.client.post(reverse('login'), {'email': self.user_w_map.email, 'password': ''})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, json.dumps({'success': False, 'redirect': reverse('shib-login')}))
obj = json.loads(response.content)
self.assertEqual(obj, {
'success': False,
'redirect': reverse('shib-login'),
})
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
def test__get_course_enrollment_domain(self):
......
......@@ -68,8 +68,11 @@ class ResetPasswordTests(TestCase):
bad_pwd_resp = password_reset(bad_pwd_req)
# If they've got an unusable password, we return a successful response code
self.assertEquals(bad_pwd_resp.status_code, 200)
self.assertEquals(bad_pwd_resp.content, json.dumps({'success': True,
'value': "('registration/password_reset_done.html', [])"}))
obj = json.loads(bad_pwd_resp.content)
self.assertEquals(obj, {
'success': True,
'value': "('registration/password_reset_done.html', [])",
})
@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
def test_nonexist_email_password_reset(self):
......@@ -80,12 +83,19 @@ class ResetPasswordTests(TestCase):
# Note: even if the email is bad, we return a successful response code
# This prevents someone potentially trying to "brute-force" find out which emails are and aren't registered with edX
self.assertEquals(bad_email_resp.status_code, 200)
self.assertEquals(bad_email_resp.content, json.dumps({'success': True,
'value': "('registration/password_reset_done.html', [])"}))
@unittest.skipIf(settings.FEATURES.get('DISABLE_RESET_EMAIL_TEST', False),
dedent("""Skipping Test because CMS has not provided necessary templates for password reset.
If LMS tests print this message, that needs to be fixed."""))
obj = json.loads(bad_email_resp.content)
self.assertEquals(obj, {
'success': True,
'value': "('registration/password_reset_done.html', [])",
})
@unittest.skipIf(
settings.FEATURES.get('DISABLE_RESET_EMAIL_TEST', False),
dedent("""
Skipping Test because CMS has not provided necessary templates for password reset.
If LMS tests print this message, that needs to be fixed.
""")
)
@patch('django.core.mail.send_mail')
@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
def test_reset_password_email(self, send_email):
......@@ -94,9 +104,11 @@ class ResetPasswordTests(TestCase):
good_req = self.request_factory.post('/password_reset/', {'email': self.user.email})
good_resp = password_reset(good_req)
self.assertEquals(good_resp.status_code, 200)
self.assertEquals(good_resp.content,
json.dumps({'success': True,
'value': "('registration/password_reset_done.html', [])"}))
obj = json.loads(good_resp.content)
self.assertEquals(obj, {
'success': True,
'value': "('registration/password_reset_done.html', [])",
})
((subject, msg, from_addr, to_addrs), sm_kwargs) = send_email.call_args
self.assertIn("Password reset", subject)
......
......@@ -4,9 +4,7 @@ Student Views
import datetime
import json
import logging
import random
import re
import string # pylint: disable=W0402
import urllib
import uuid
import time
......@@ -18,13 +16,11 @@ from django.contrib.auth import logout, authenticate, login
from django.contrib.auth.models import User, AnonymousUser
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import password_reset_confirm
# from django.contrib.sessions.models import Session
from django.core.cache import cache
from django.core.context_processors import csrf
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.core.validators import validate_email, validate_slug, ValidationError
from django.core.exceptions import ObjectDoesNotExist
from django.db import IntegrityError, transaction
from django.http import (HttpResponse, HttpResponseBadRequest, HttpResponseForbidden,
Http404)
......@@ -33,7 +29,6 @@ from django_future.csrf import ensure_csrf_cookie
from django.utils.http import cookie_date, base36_to_int
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_POST, require_GET
from django.contrib.admin.views.decorators import staff_member_required
from ratelimitbackend.exceptions import RateLimitException
......@@ -46,6 +41,7 @@ from student.models import (
CourseEnrollmentAllowed, UserStanding, LoginFailures
)
from student.forms import PasswordResetFormNoActive
from student.firebase_token_generator import create_token
from verify_student.models import SoftwareSecurePhotoVerification, MidcourseReverificationWindow
from certificates.models import CertificateStatuses, certificate_status_for_student
......@@ -69,7 +65,6 @@ import shoppingcart
import track.views
from dogapi import dog_stats_api
from pytz import UTC
from util.json_request import JsonResponse
......@@ -265,7 +260,6 @@ def get_course_enrollment_pairs(user, course_org_filter, org_filter_out_set):
.format(user.username, enrollment.course_id))
def _cert_info(user, course, cert_status):
"""
Implements the logic for cert_info -- split out for testing.
......@@ -674,8 +668,10 @@ def accounts_login(request):
def login_user(request, error=""):
"""AJAX request to log in the user."""
if 'email' not in request.POST or 'password' not in request.POST:
return HttpResponse(json.dumps({'success': False,
'value': _('There was an error receiving your login information. Please email us.')})) # TODO: User error message
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
email = request.POST['email']
password = request.POST['password']
......@@ -692,7 +688,10 @@ def login_user(request, error=""):
try:
eamap = ExternalAuthMap.objects.get(user=user)
if eamap.external_domain.startswith(external_auth.views.SHIBBOLETH_DOMAIN_PREFIX):
return HttpResponse(json.dumps({'success': False, 'redirect': reverse('shib-login')}))
return JsonResponse({
"success": False,
"redirect": reverse('shib-login'),
}) # TODO: this should be status code 301 # pylint: disable=fixme
except ExternalAuthMap.DoesNotExist:
# This is actually the common case, logging in user without external linked login
AUDIT_LOG.info("User %s w/o external auth attempting login", user)
......@@ -701,12 +700,10 @@ def login_user(request, error=""):
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):
return HttpResponse(
json.dumps({
'success': False,
'value': _('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.'),
}) # TODO: this should be status code 429 # pylint: disable=fixme
# if the user doesn't exist, we want to set the username to an invalid
# username so that authentication is guaranteed to fail and we can take
......@@ -716,8 +713,10 @@ def login_user(request, error=""):
user = authenticate(username=username, password=password, request=request)
# this occurs when there are too many attempts from the same IP address
except RateLimitException:
return HttpResponse(json.dumps({'success': False,
'value': _('Too many failed login attempts. Try again later.')}))
return JsonResponse({
"success": False,
"value": _('Too many failed login attempts. Try again later.'),
}) # TODO: this should be status code 429 # pylint: disable=fixme
if user is None:
# tick the failed login counters if the user exists in the database
if user_found_by_email_lookup and LoginFailures.is_feature_enabled():
......@@ -727,8 +726,10 @@ def login_user(request, error=""):
# doesn't exist, and doesn't have a corresponding password
if username != "":
AUDIT_LOG.warning(u"Login failed - password for {0} is invalid".format(email))
return HttpResponse(json.dumps({'success': False,
'value': _('Email or password is incorrect.')}))
return JsonResponse({
"success": False,
"value": _('Email or password is incorrect.'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
# successful login, clear failed login attempts counters, if applicable
if LoginFailures.is_feature_enabled():
......@@ -753,7 +754,10 @@ def login_user(request, error=""):
redirect_url = try_change_enrollment(request)
dog_stats_api.increment("common.student.successful_login")
response = HttpResponse(json.dumps({'success': True, 'redirect_url': redirect_url}))
response = JsonResponse({
"success": True,
"redirect_url": redirect_url,
})
# set the login cookie for the edx marketing site
# we want this cookie to be accessed via javascript
......@@ -767,12 +771,11 @@ def login_user(request, error=""):
expires_time = time.time() + max_age
expires = cookie_date(expires_time)
response.set_cookie(settings.EDXMKTG_COOKIE_NAME,
'true', max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path='/',
secure=None,
httponly=None)
response.set_cookie(
settings.EDXMKTG_COOKIE_NAME, 'true', max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path='/', secure=None, httponly=None,
)
return response
......@@ -780,8 +783,10 @@ def login_user(request, error=""):
reactivation_email_for_user(user)
not_activated_msg = _("This account has not been activated. We have sent another activation message. Please check your e-mail for the activation instructions.")
return HttpResponse(json.dumps({'success': False,
'value': not_activated_msg}))
return JsonResponse({
"success": False,
"value": not_activated_msg,
}) # TODO: this should be status code 400 # pylint: disable=fixme
@ensure_csrf_cookie
......@@ -799,11 +804,13 @@ def logout_user(request):
else:
target = '/'
response = redirect(target)
response.delete_cookie(settings.EDXMKTG_COOKIE_NAME,
path='/',
domain=settings.SESSION_COOKIE_DOMAIN)
response.delete_cookie(
settings.EDXMKTG_COOKIE_NAME,
path='/', domain=settings.SESSION_COOKIE_DOMAIN,
)
return response
@require_GET
@login_required
@ensure_csrf_cookie
......@@ -890,8 +897,10 @@ def change_setting(request):
up.location = request.POST['location']
up.save()
return HttpResponse(json.dumps({'success': True,
'location': up.location, }))
return JsonResponse({
"success": True,
"location": up.location,
})
def _do_create_account(post_vars):
......@@ -1106,8 +1115,8 @@ def create_account(request, post_override=None):
'-' * 80 + '\n\n' + message)
send_mail(subject, message, from_address, [dest_addr], fail_silently=False)
else:
_res = user.email_user(subject, message, from_address)
except:
user.email_user(subject, message, from_address)
except Exception: # pylint: disable=broad-except
log.warning('Unable to send activation email to user', exc_info=True)
js['value'] = _('Could not send activation e-mail.')
# What is the correct status code to use here? I think it's 500, because
......@@ -1298,8 +1307,10 @@ def password_reset(request):
from_email=settings.DEFAULT_FROM_EMAIL,
request=request,
domain_override=request.get_host())
return HttpResponse(json.dumps({'success': True,
'value': render_to_string('registration/password_reset_done.html', {})}))
return JsonResponse({
'success': True,
'value': render_to_string('registration/password_reset_done.html', {}),
})
def password_reset_confirm_wrapper(
......@@ -1330,23 +1341,30 @@ def reactivation_email_for_user(user):
try:
reg = Registration.objects.get(user=user)
except Registration.DoesNotExist:
return HttpResponse(json.dumps({'success': False,
'error': _('No inactive user with this e-mail exists')}))
return JsonResponse({
"success": False,
"error": _('No inactive user with this e-mail exists'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
d = {'name': user.profile.name,
'key': reg.activation_key}
context = {
'name': user.profile.name,
'key': reg.activation_key,
}
subject = render_to_string('emails/activation_email_subject.txt', d)
subject = render_to_string('emails/activation_email_subject.txt', context)
subject = ''.join(subject.splitlines())
message = render_to_string('emails/activation_email.txt', d)
message = render_to_string('emails/activation_email.txt', context)
try:
_res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except:
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except Exception: # pylint: disable=broad-except
log.warning('Unable to send reactivation email', exc_info=True)
return HttpResponse(json.dumps({'success': False, 'error': _('Unable to send reactivation email')}))
return JsonResponse({
"success": False,
"error": _('Unable to send reactivation email')
}) # TODO: this should be status code 500 # pylint: disable=fixme
return HttpResponse(json.dumps({'success': True}))
return JsonResponse({"success": True})
@ensure_csrf_cookie
......@@ -1360,20 +1378,26 @@ def change_email_request(request):
user = request.user
if not user.check_password(request.POST['password']):
return HttpResponse(json.dumps({'success': False,
'error': _('Invalid password')}))
return JsonResponse({
"success": False,
"error": _('Invalid password'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
new_email = request.POST['new_email']
try:
validate_email(new_email)
except ValidationError:
return HttpResponse(json.dumps({'success': False,
'error': _('Valid e-mail address required.')}))
return JsonResponse({
"success": False,
"error": _('Valid e-mail address required.'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
if User.objects.filter(email=new_email).count() != 0:
## CRITICAL TODO: Handle case sensitivity for e-mails
return HttpResponse(json.dumps({'success': False,
'error': _('An account with this e-mail already exists.')}))
return JsonResponse({
"success": False,
"error": _('An account with this e-mail already exists.'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
pec_list = PendingEmailChange.objects.filter(user=request.user)
if len(pec_list) == 0:
......@@ -1388,8 +1412,10 @@ def change_email_request(request):
if pec.new_email == user.email:
pec.delete()
return HttpResponse(json.dumps({'success': False,
'error': _('Old email is the same as the new email.')}))
return JsonResponse({
"success": False,
"error": _('Old email is the same as the new email.'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
context = {
'key': pec.activation_key,
......@@ -1407,9 +1433,9 @@ def change_email_request(request):
settings.DEFAULT_FROM_EMAIL
)
_res = send_mail(subject, message, from_address, [pec.new_email])
send_mail(subject, message, from_address, [pec.new_email])
return HttpResponse(json.dumps({'success': True}))
return JsonResponse({"success": True})
@ensure_csrf_cookie
......@@ -1491,14 +1517,17 @@ def change_name_request(request):
pnc.new_name = request.POST['new_name']
pnc.rationale = request.POST['rationale']
if len(pnc.new_name) < 2:
return HttpResponse(json.dumps({'success': False, 'error': _('Name required')}))
return JsonResponse({
"success": False,
"error": _('Name required'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
pnc.save()
# The following automatically accepts name change requests. Remove this to
# go back to the old system where it gets queued up for admin approval.
accept_name_change_by_id(pnc.id)
return HttpResponse(json.dumps({'success': True}))
return JsonResponse({"success": True})
@ensure_csrf_cookie
......@@ -1507,14 +1536,19 @@ def pending_name_changes(request):
if not request.user.is_staff:
raise Http404
changes = list(PendingNameChange.objects.all())
js = {'students': [{'new_name': c.new_name,
'rationale': c.rationale,
'old_name': UserProfile.objects.get(user=c.user).name,
'email': c.user.email,
'uid': c.user.id,
'cid': c.id} for c in changes]}
return render_to_response('name_changes.html', js)
students = []
for change in PendingNameChange.objects.all():
profile = UserProfile.objects.get(user=change.user)
students.append({
"new_name": change.new_name,
"rationale": change.rationale,
"old_name": profile.name,
"email": change.user.email,
"uid": change.user.id,
"cid": change.id,
})
return render_to_response("name_changes.html", {"students": students})
@ensure_csrf_cookie
......@@ -1526,17 +1560,23 @@ def reject_name_change(request):
try:
pnc = PendingNameChange.objects.get(id=int(request.POST['id']))
except PendingNameChange.DoesNotExist:
return HttpResponse(json.dumps({'success': False, 'error': _('Invalid ID')}))
return JsonResponse({
"success": False,
"error": _('Invalid ID'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
pnc.delete()
return HttpResponse(json.dumps({'success': True}))
return JsonResponse({"success": True})
def accept_name_change_by_id(id):
try:
pnc = PendingNameChange.objects.get(id=id)
except PendingNameChange.DoesNotExist:
return HttpResponse(json.dumps({'success': False, 'error': _('Invalid ID')}))
return JsonResponse({
"success": False,
"error": _('Invalid ID'),
}) # TODO: this should be status code 400 # pylint: disable=fixme
u = pnc.user
up = UserProfile.objects.get(user=u)
......@@ -1552,7 +1592,7 @@ def accept_name_change_by_id(id):
up.save()
pnc.delete()
return HttpResponse(json.dumps({'success': True}))
return JsonResponse({"success": True})
@ensure_csrf_cookie
......@@ -1589,9 +1629,7 @@ def change_email_settings(request):
log.info(u"User {0} ({1}) opted out of receiving emails from course {2}".format(user.username, user.email, course_id))
track.views.server_track(request, "change-email-settings", {"receive_emails": "no", "course": course_id}, page='dashboard')
return HttpResponse(json.dumps({'success': True}))
from student.firebase_token_generator import create_token
return JsonResponse({"success": True})
@login_required
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment