Commit b0c2b73d by Sarina Canelake

Merge pull request #5112 from edx/sarina/quality-clean

Quality cleanup
parents a12c7959 467f2988
......@@ -51,10 +51,10 @@ from ratelimitbackend import admin
import analytics
unenroll_done = Signal(providing_args=["course_enrollment"])
UNENROLL_DONE = Signal(providing_args=["course_enrollment"])
log = logging.getLogger(__name__)
AUDIT_LOG = logging.getLogger("audit")
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore # pylint: disable=invalid-name
class AnonymousUserId(models.Model):
......@@ -133,7 +133,7 @@ def anonymous_id_for_user(user, course_id, save=True):
return digest
def user_by_anonymous_id(id):
def user_by_anonymous_id(uid):
"""
Return user by anonymous_user_id using AnonymousUserId lookup table.
......@@ -142,11 +142,11 @@ def user_by_anonymous_id(id):
because this function will be used inside xmodule w/o django access.
"""
if id is None:
if uid is None:
return None
try:
return User.objects.get(anonymoususerid__anonymous_user_id=id)
return User.objects.get(anonymoususerid__anonymous_user_id=uid)
except ObjectDoesNotExist:
return None
......@@ -192,7 +192,7 @@ class UserProfile(models.Model):
MITx fall prototype.
"""
class Meta:
class Meta: # pylint: disable=missing-docstring
db_table = "auth_userprofile"
# CRITICAL TODO/SECURITY
......@@ -250,7 +250,7 @@ class UserProfile(models.Model):
goals = models.TextField(blank=True, null=True)
allow_certificate = models.BooleanField(default=1)
def get_meta(self):
def get_meta(self): # pylint: disable=missing-docstring
js_str = self.meta
if not js_str:
js_str = dict()
......@@ -259,8 +259,8 @@ class UserProfile(models.Model):
return js_str
def set_meta(self, js):
self.meta = json.dumps(js)
def set_meta(self, meta_json): # pylint: disable=missing-docstring
self.meta = json.dumps(meta_json)
def set_login_session(self, session_id=None):
"""
......@@ -487,8 +487,8 @@ class PasswordHistory(models.Model):
return True
if user.is_staff and cls.is_staff_password_reuse_restricted():
min_diff_passwords_required = \
settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STAFF_PASSWORDS_BEFORE_REUSE']
min_diff_passwords_required = \
settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STAFF_PASSWORDS_BEFORE_REUSE']
elif cls.is_student_password_reuse_restricted():
min_diff_passwords_required = \
settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STUDENT_PASSWORDS_BEFORE_REUSE']
......@@ -723,7 +723,7 @@ class CourseEnrollment(models.Model):
)
else:
unenroll_done.send(sender=None, course_enrollment=self)
UNENROLL_DONE.send(sender=None, course_enrollment=self)
self.emit_event(EVENT_NAME_ENROLLMENT_DEACTIVATED)
......@@ -961,12 +961,12 @@ class CourseEnrollment(models.Model):
# Unfortunately, Django's "group by"-style queries look super-awkward
query = use_read_replica_if_available(cls.objects.filter(course_id=course_id, is_active=True).values('mode').order_by().annotate(Count('mode')))
total = 0
d = defaultdict(int)
enroll_dict = defaultdict(int)
for item in query:
d[item['mode']] = item['mode__count']
enroll_dict[item['mode']] = item['mode__count']
total += item['mode__count']
d['total'] = total
return d
enroll_dict['total'] = total
return enroll_dict
def is_paid_course(self):
"""
......@@ -999,7 +999,7 @@ class CourseEnrollment(models.Model):
a verified certificate and the deadline for refunds has not yet passed.
"""
# In order to support manual refunds past the deadline, set can_refund on this object.
# On unenrolling, the "unenroll_done" signal calls CertificateItem.refund_cert_callback(),
# On unenrolling, the "UNENROLL_DONE" signal calls CertificateItem.refund_cert_callback(),
# which calls this method to determine whether to refund the order.
# This can't be set directly because refunds currently happen as a side-effect of unenrolling.
# (side-effects are bad)
......@@ -1031,7 +1031,7 @@ class CourseEnrollmentAllowed(models.Model):
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
class Meta:
class Meta: # pylint: disable=missing-docstring
unique_together = (('email', 'course_id'),)
def __unicode__(self):
......@@ -1055,7 +1055,7 @@ class CourseAccessRole(models.Model):
course_id = CourseKeyField(max_length=255, db_index=True, blank=True)
role = models.CharField(max_length=64, db_index=True)
class Meta:
class Meta: # pylint: disable=missing-docstring
unique_together = ('user', 'org', 'course_id', 'role')
@property
......@@ -1071,7 +1071,7 @@ class CourseAccessRole(models.Model):
Overriding eq b/c the django impl relies on the primary key which requires fetch. sometimes we
just want to compare roles w/o doing another fetch.
"""
return type(self) == type(other) and self._key == other._key
return type(self) == type(other) and self._key == other._key # pylint: disable=protected-access
def __hash__(self):
return hash(self._key)
......@@ -1080,7 +1080,7 @@ class CourseAccessRole(models.Model):
"""
Lexigraphic sort
"""
return self._key < other._key
return self._key < other._key # pylint: disable=protected-access
def __unicode__(self):
return "[CourseAccessRole] user: {} role: {} org: {} course: {}".format(self.user.username, self.role, self.org, self.course_id)
......@@ -1107,32 +1107,32 @@ def get_user_by_username_or_email(username_or_email):
def get_user(email):
u = User.objects.get(email=email)
up = UserProfile.objects.get(user=u)
return u, up
user = User.objects.get(email=email)
u_prof = UserProfile.objects.get(user=user)
return user, u_prof
def user_info(email):
u, up = get_user(email)
print "User id", u.id
print "Username", u.username
print "E-mail", u.email
print "Name", up.name
print "Location", up.location
print "Language", up.language
return u, up
user, u_prof = get_user(email)
print "User id", user.id
print "Username", user.username
print "E-mail", user.email
print "Name", u_prof.name
print "Location", u_prof.location
print "Language", u_prof.language
return user, u_prof
def change_email(old_email, new_email):
u = User.objects.get(email=old_email)
u.email = new_email
u.save()
user = User.objects.get(email=old_email)
user.email = new_email
user.save()
def change_name(email, new_name):
u, up = get_user(email)
up.name = new_name
up.save()
_user, u_prof = get_user(email)
u_prof.name = new_name
u_prof.save()
def user_count():
......@@ -1163,10 +1163,12 @@ def remove_user_from_group(user, group):
utg.users.remove(User.objects.get(username=user))
utg.save()
default_groups = {'email_future_courses': 'Receive e-mails about future MITx courses',
'email_helpers': 'Receive e-mails about how to help with MITx',
'mitx_unenroll': 'Fully unenrolled -- no further communications',
'6002x_unenroll': 'Took and dropped 6002x'}
DEFAULT_GROUPS = {
'email_future_courses': 'Receive e-mails about future MITx courses',
'email_helpers': 'Receive e-mails about how to help with MITx',
'mitx_unenroll': 'Fully unenrolled -- no further communications',
'6002x_unenroll': 'Took and dropped 6002x'
}
def add_user_to_default_group(user, group):
......@@ -1175,7 +1177,7 @@ def add_user_to_default_group(user, group):
except UserTestGroup.DoesNotExist:
utg = UserTestGroup()
utg.name = group
utg.description = default_groups[group]
utg.description = DEFAULT_GROUPS[group]
utg.save()
utg.users.add(User.objects.get(username=user))
utg.save()
......@@ -1188,11 +1190,12 @@ def create_comments_service_user(user):
try:
cc_user = cc.User.from_django_user(user)
cc_user.save()
except Exception as e:
log = logging.getLogger("edx.discussion")
except Exception: # pylint: disable=broad-except
log = logging.getLogger("edx.discussion") # pylint: disable=redefined-outer-name
log.error(
"Could not create comments service user with id {}".format(user.id),
exc_info=True)
exc_info=True
)
# Define login and logout handlers here in the models file, instead of the views file,
# so that they are more likely to be loaded when a Studio user brings up the Studio admin
......@@ -1201,7 +1204,7 @@ def create_comments_service_user(user):
@receiver(user_logged_in)
def log_successful_login(sender, request, user, **kwargs):
def log_successful_login(sender, request, user, **kwargs): # pylint: disable=unused-argument
"""Handler to log when logins have occurred successfully."""
if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
AUDIT_LOG.info(u"Login success - user.id: {0}".format(user.id))
......@@ -1210,7 +1213,7 @@ def log_successful_login(sender, request, user, **kwargs):
@receiver(user_logged_out)
def log_successful_logout(sender, request, user, **kwargs):
def log_successful_logout(sender, request, user, **kwargs): # pylint: disable=unused-argument
"""Handler to log when logouts have occurred successfully."""
if settings.FEATURES['SQUELCH_PII_IN_LOGS']:
AUDIT_LOG.info(u"Logout - user.id: {0}".format(request.user.id))
......@@ -1220,7 +1223,7 @@ def log_successful_logout(sender, request, user, **kwargs):
@receiver(user_logged_in)
@receiver(user_logged_out)
def enforce_single_login(sender, request, user, signal, **kwargs):
def enforce_single_login(sender, request, user, signal, **kwargs): # pylint: disable=unused-argument
"""
Sets the current session id in the user profile,
to prevent concurrent logins.
......
......@@ -46,8 +46,7 @@ from student.models import (
Registration, UserProfile, PendingNameChange,
PendingEmailChange, CourseEnrollment, unique_id_for_user,
CourseEnrollmentAllowed, UserStanding, LoginFailures,
create_comments_service_user, PasswordHistory, UserSignupSource,
anonymous_id_for_user
create_comments_service_user, PasswordHistory, UserSignupSource
)
from student.forms import PasswordResetFormNoActive
......@@ -103,27 +102,29 @@ AUDIT_LOG = logging.getLogger("audit")
ReverifyInfo = namedtuple('ReverifyInfo', 'course_id course_name course_number date status display') # pylint: disable=C0103
def csrf_token(context):
"""A csrf token that can be included in a form."""
csrf_token = context.get('csrf_token', '')
if csrf_token == 'NOTPROVIDED':
token = context.get('csrf_token', '')
if token == 'NOTPROVIDED':
return ''
return (u'<div style="display:none"><input type="hidden"'
' name="csrfmiddlewaretoken" value="%s" /></div>' % (csrf_token))
' name="csrfmiddlewaretoken" value="%s" /></div>' % (token))
# NOTE: This view is not linked to directly--it is called from
# branding/views.py:index(), which is cached for anonymous users.
# This means that it should always return the same thing for anon
# users. (in particular, no switching based on query params allowed)
def index(request, extra_context={}, user=AnonymousUser()):
def index(request, extra_context=None, user=AnonymousUser()):
"""
Render the edX main page.
extra_context is used to allow immediate display of certain modal windows, eg signup,
as used by external_auth.
"""
if extra_context is None:
extra_context = {}
# The course selection work is done in courseware.courses.
domain = settings.FEATURES.get('FORCE_UNIVERSITY_DOMAIN') # normally False
# do explicit check, because domain=None is valid
......@@ -259,8 +260,8 @@ def get_course_enrollment_pairs(user, course_org_filter, org_filter_out_set):
yield (course, enrollment)
else:
log.error("User {0} enrolled in {2} course {1}".format(
user.username, enrollment.course_id, "broken" if course else "non-existent"
))
user.username, enrollment.course_id, "broken" if course else "non-existent"
))
def _cert_info(user, course, cert_status):
......@@ -294,18 +295,20 @@ def _cert_info(user, course, cert_status):
status = template_state.get(cert_status['status'], default_status)
d = {'status': status,
'show_download_url': status == 'ready',
'show_disabled_download_button': status == 'generating',
'mode': cert_status.get('mode', None)}
status_dict = {
'status': status,
'show_download_url': status == 'ready',
'show_disabled_download_button': status == 'generating',
'mode': cert_status.get('mode', None)
}
if (status in ('generating', 'ready', 'notpassing', 'restricted') and
course.end_of_course_survey_url is not None):
d.update({
status_dict.update({
'show_survey_button': True,
'survey_url': process_survey_link(course.end_of_course_survey_url, user)})
else:
d['show_survey_button'] = False
status_dict['show_survey_button'] = False
if status == 'ready':
if 'download_url' not in cert_status:
......@@ -313,7 +316,7 @@ def _cert_info(user, course, cert_status):
user.username, course.id)
return default_info
else:
d['download_url'] = cert_status['download_url']
status_dict['download_url'] = cert_status['download_url']
if status in ('generating', 'ready', 'notpassing', 'restricted'):
if 'grade' not in cert_status:
......@@ -322,9 +325,9 @@ def _cert_info(user, course, cert_status):
# We can add a log.warning here once we think it shouldn't happen.
return default_info
else:
d['grade'] = cert_status['grade']
status_dict['grade'] = cert_status['grade']
return d
return status_dict
@ensure_csrf_cookie
......@@ -446,6 +449,7 @@ def is_course_blocked(request, redeemed_registration_codes, course_key):
return blocked
@login_required
@ensure_csrf_cookie
def dashboard(request):
......@@ -511,7 +515,6 @@ def dashboard(request):
block_courses = frozenset(course.id for course, enrollment in course_enrollment_pairs
if is_course_blocked(request, CourseRegistrationCode.objects.filter(course_id=course.id, registrationcoderedemption__redeemed_by=request.user), course.id))
enrolled_courses_either_paid = frozenset(course.id for course, _enrollment in course_enrollment_pairs
if _enrollment.is_paid_course())
# get info w.r.t ExternalAuthMap
......@@ -608,8 +611,8 @@ def try_change_enrollment(request):
# will return redirect_urls.
if enrollment_response.status_code == 200 and enrollment_response.content != '':
return enrollment_response.content
except Exception, e:
log.exception("Exception automatically enrolling after login: {0}".format(str(e)))
except Exception as exc: # pylint: disable=broad-except
log.exception("Exception automatically enrolling after login: %s", exc)
@require_POST
......@@ -771,7 +774,6 @@ def change_enrollment(request, auto_register=False):
return HttpResponse()
elif action == "add_to_cart":
# Pass the request handling to shoppingcart.views
# The view in shoppingcart.views performs error handling and logs different errors. But this elif clause
......@@ -885,12 +887,17 @@ def login_user(request, error=""): # pylint: disable-msg=too-many-statements,un
username=username, backend_name=backend_name))
return HttpResponseBadRequest(
_("You've successfully logged into your {provider_name} account, but this account isn't linked with an {platform_name} account yet.").format(
platform_name=settings.PLATFORM_NAME, provider_name=requested_provider.NAME)
+ "<br/><br/>" + _("Use your {platform_name} username and password to log into {platform_name} below, "
platform_name=settings.PLATFORM_NAME, provider_name=requested_provider.NAME
)
+ "<br/><br/>" +
_("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=settings.PLATFORM_NAME, provider_name=requested_provider.NAME)
+ "<br/><br/>" + _("If you don't have an {platform_name} account yet, click <strong>Register Now</strong> at the top of the page.").format(
platform_name=settings.PLATFORM_NAME),
platform_name=settings.PLATFORM_NAME, provider_name=requested_provider.NAME
)
+ "<br/><br/>" +
_("If you don't have an {platform_name} account yet, click <strong>Register Now</strong> at the top of the page.").format(
platform_name=settings.PLATFORM_NAME
),
content_type="text/plain",
status=401
)
......@@ -1002,7 +1009,7 @@ def login_user(request, error=""): # pylint: disable-msg=too-many-statements,un
},
context={
'Google Analytics': {
'clientId': tracking_context.get('client_id')
'clientId': tracking_context.get('client_id')
}
}
)
......@@ -1018,10 +1025,10 @@ def login_user(request, error=""): # pylint: disable-msg=too-many-statements,un
log.debug("Setting user session to never expire")
else:
request.session.set_expiry(0)
except Exception as e:
except Exception as exc: # pylint: disable=broad-except
AUDIT_LOG.critical("Login failed - Could not create session. Is memcached running?")
log.critical("Login failed - Could not create session. Is memcached running?")
log.exception(e)
log.exception(exc)
raise
redirect_url = try_change_enrollment(request)
......@@ -1170,14 +1177,14 @@ def disable_account_ajax(request):
def change_setting(request):
"""JSON call to change a profile setting: Right now, location"""
# TODO (vshnayder): location is no longer used
up = UserProfile.objects.get(user=request.user) # request.user.profile_cache
u_prof = UserProfile.objects.get(user=request.user) # request.user.profile_cache
if 'location' in request.POST:
up.location = request.POST['location']
up.save()
u_prof.location = request.POST['location']
u_prof.save()
return JsonResponse({
"success": True,
"location": up.location,
"location": u_prof.location,
})
......@@ -1226,12 +1233,12 @@ def _do_create_account(post_vars, extended_profile=None):
raise AccountValidationError(
_("An account with the Public Username '{username}' already exists.").format(username=post_vars['username']),
field="username"
)
)
elif len(User.objects.filter(email=post_vars['email'])) > 0:
raise AccountValidationError(
_("An account with the Email '{email}' already exists.").format(email=post_vars['email']),
field="email"
)
)
else:
raise
......@@ -1263,7 +1270,7 @@ def _do_create_account(post_vars, extended_profile=None):
profile.year_of_birth = None
try:
profile.save()
except Exception:
except Exception: # pylint: disable=broad-except
log.exception("UserProfile creation failed for user {id}.".format(id=user.id))
raise
......@@ -1295,8 +1302,8 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
# if doing signup for an external authorization, then get email, password, name from the eamap
# don't use the ones from the form, since the user could have hacked those
# unless originally we didn't get a valid email or name from the external auth
DoExternalAuth = 'ExternalAuthMap' in request.session
if DoExternalAuth:
do_external_auth = 'ExternalAuthMap' in request.session
if do_external_auth:
eamap = request.session['ExternalAuthMap']
try:
validate_email(eamap.external_email)
......@@ -1313,15 +1320,15 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
log.debug(u'In create_account with external_auth: user = %s, email=%s', name, email)
# Confirm we have a properly formed request
for a in ['username', 'email', 'password', 'name']:
if a not in post_vars:
js['value'] = _("Error (401 {field}). E-mail us.").format(field=a)
js['field'] = a
for req_field in ['username', 'email', 'password', 'name']:
if req_field not in post_vars:
js['value'] = _("Error (401 {field}). E-mail us.").format(field=req_field)
js['field'] = req_field
return JsonResponse(js, status=400)
if extra_fields.get('honor_code', 'required') == 'required' and \
post_vars.get('honor_code', 'false') != u'true':
js['value'] = _("To enroll, you must follow the honor code.").format(field=a)
js['value'] = _("To enroll, you must follow the honor code.")
js['field'] = 'honor_code'
return JsonResponse(js, status=400)
......@@ -1329,7 +1336,7 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
tos_required = (
not settings.FEATURES.get("AUTH_USE_SHIB") or
not settings.FEATURES.get("SHIB_DISABLE_TOS") or
not DoExternalAuth or
not do_external_auth or
not eamap.external_domain.startswith(
external_auth.views.SHIBBOLETH_DOMAIN_PREFIX
)
......@@ -1337,7 +1344,7 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
if tos_required:
if post_vars.get('terms_of_service', 'false') != u'true':
js['value'] = _("You must accept the terms of service.").format(field=a)
js['value'] = _("You must accept the terms of service.")
js['field'] = 'terms_of_service'
return JsonResponse(js, status=400)
......@@ -1390,8 +1397,8 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
if field_name in ('email', 'username') and len(post_vars[field_name]) > max_length:
error_str = {
'username': _('Username cannot be more than {0} characters long').format(max_length),
'email': _('Email cannot be more than {0} characters long').format(max_length)
'username': _('Username cannot be more than {num} characters long').format(num=max_length),
'email': _('Email cannot be more than {num} characters long').format(num=max_length)
}
js['value'] = error_str[field_name]
js['field'] = field_name
......@@ -1400,20 +1407,20 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
try:
validate_email(post_vars['email'])
except ValidationError:
js['value'] = _("Valid e-mail is required.").format(field=a)
js['value'] = _("Valid e-mail is required.")
js['field'] = 'email'
return JsonResponse(js, status=400)
try:
validate_slug(post_vars['username'])
except ValidationError:
js['value'] = _("Username should only consist of A-Z and 0-9, with no spaces.").format(field=a)
js['value'] = _("Username should only consist of A-Z and 0-9, with no spaces.")
js['field'] = 'username'
return JsonResponse(js, status=400)
# enforce password complexity as an optional feature
# but not if we're doing ext auth b/c those pws never get used and are auto-generated so might not pass validation
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False) and not DoExternalAuth:
if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False) and not do_external_auth:
try:
password = post_vars['password']
......@@ -1449,8 +1456,8 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
try:
with transaction.commit_on_success():
ret = _do_create_account(post_vars, extended_profile)
except AccountValidationError as e:
return JsonResponse({'success': False, 'value': e.message, 'field': e.field}, status=400)
except AccountValidationError as exc:
return JsonResponse({'success': False, 'value': exc.message, 'field': exc.field}, status=400)
(user, profile, registration) = ret
......@@ -1469,14 +1476,14 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
registration_course_id = request.session.get('registration_course_id')
analytics.track(
user.id,
"edx.bi.user.account.registered",
"edx.bi.user.account.registered",
{
"category": "conversion",
"label": registration_course_id
},
context={
'Google Analytics': {
'clientId': tracking_context.get('client_id')
'clientId': tracking_context.get('client_id')
}
}
)
......@@ -1520,17 +1527,17 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
# Immediately after a user creates an account, we log them in. They are only
# logged in until they close the browser. They can't log in again until they click
# the activation link from the email.
login_user = authenticate(username=post_vars['username'], password=post_vars['password'])
login(request, login_user)
new_user = authenticate(username=post_vars['username'], password=post_vars['password'])
login(request, new_user)
request.session.set_expiry(0)
# TODO: there is no error checking here to see that the user actually logged in successfully,
# and is not yet an active user.
if login_user is not None:
AUDIT_LOG.info(u"Login success on new account creation - {0}".format(login_user.username))
if new_user is not None:
AUDIT_LOG.info(u"Login success on new account creation - {0}".format(new_user.username))
if DoExternalAuth:
eamap.user = login_user
if do_external_auth:
eamap.user = new_user
eamap.dtsignup = datetime.datetime.now(UTC)
eamap.save()
AUDIT_LOG.info("User registered with external_auth %s", post_vars['username'])
......@@ -1538,9 +1545,9 @@ def create_account(request, post_override=None): # pylint: disable-msg=too-many
if settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'):
log.info('bypassing activation email')
login_user.is_active = True
login_user.save()
AUDIT_LOG.info(u"Login activated on extauth account - {0} ({1})".format(login_user.username, login_user.email))
new_user.is_active = True
new_user.save()
AUDIT_LOG.info(u"Login activated on extauth account - {0} ({1})".format(new_user.username, new_user.email))
dog_stats_api.increment("common.student.account_created")
redirect_url = try_change_enrollment(request)
......@@ -1623,7 +1630,7 @@ def auto_auth(request):
# If successful, this will return a tuple containing
# the new user object.
try:
user, profile, reg = _do_create_account(post_data)
user, _profile, reg = _do_create_account(post_data)
except AccountValidationError:
# Attempt to retrieve the existing user.
user = User.objects.get(username=username)
......@@ -1669,16 +1676,16 @@ def auto_auth(request):
@ensure_csrf_cookie
def activate_account(request, key):
"""When link in activation e-mail is clicked"""
r = Registration.objects.filter(activation_key=key)
if len(r) == 1:
regs = Registration.objects.filter(activation_key=key)
if len(regs) == 1:
user_logged_in = request.user.is_authenticated()
already_active = True
if not r[0].user.is_active:
r[0].activate()
if not regs[0].user.is_active:
regs[0].activate()
already_active = False
# Enroll student in any pending courses he/she may have if auto_enroll flag is set
student = User.objects.filter(id=r[0].user_id)
student = User.objects.filter(id=regs[0].user_id)
if student:
ceas = CourseEnrollmentAllowed.objects.filter(email=student[0].email)
for cea in ceas:
......@@ -1693,7 +1700,7 @@ def activate_account(request, key):
}
)
return resp
if len(r) == 0:
if len(regs) == 0:
return render_to_response(
"registration/activation_invalid.html",
{'csrf': csrf(request)['csrf_token']}
......@@ -1928,8 +1935,9 @@ def change_email_request(request):
@ensure_csrf_cookie
@transaction.commit_manually
def confirm_email_change(request, key):
""" User requested a new e-mail. This is called when the activation
def confirm_email_change(request, key): # pylint: disable=unused-argument
"""
User requested a new e-mail. This is called when the activation
link is clicked. We confirm with the old e-mail, and update
"""
try:
......@@ -1954,17 +1962,17 @@ def confirm_email_change(request, key):
subject = render_to_string('emails/email_change_subject.txt', address_context)
subject = ''.join(subject.splitlines())
message = render_to_string('emails/confirm_email_change.txt', address_context)
up = UserProfile.objects.get(user=user)
meta = up.get_meta()
u_prof = UserProfile.objects.get(user=user)
meta = u_prof.get_meta()
if 'old_emails' not in meta:
meta['old_emails'] = []
meta['old_emails'].append([user.email, datetime.datetime.now(UTC).isoformat()])
up.set_meta(meta)
up.save()
u_prof.set_meta(meta)
u_prof.save()
# Send it to the old email...
try:
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except Exception:
except Exception: # pylint: disable=broad-except
log.warning('Unable to send confirmation email to old address', exc_info=True)
response = render_to_response("email_change_failed.html", {'email': user.email})
transaction.rollback()
......@@ -1976,7 +1984,7 @@ def confirm_email_change(request, key):
# And send it to the new email...
try:
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except Exception:
except Exception: # pylint: disable=broad-except
log.warning('Unable to send confirmation email to new address', exc_info=True)
response = render_to_response("email_change_failed.html", {'email': pec.new_email})
transaction.rollback()
......@@ -1985,7 +1993,7 @@ def confirm_email_change(request, key):
response = render_to_response("email_change_successful.html", address_context)
transaction.commit()
return response
except Exception:
except Exception: # pylint: disable=broad-except
# If we get an unexpected exception, be sure to rollback the transaction
transaction.rollback()
raise
......@@ -2058,27 +2066,31 @@ def reject_name_change(request):
return JsonResponse({"success": True})
def accept_name_change_by_id(id):
def accept_name_change_by_id(uid):
"""
Accepts the pending name change request for the user represented
by user id `uid`.
"""
try:
pnc = PendingNameChange.objects.get(id=id)
pnc = PendingNameChange.objects.get(id=uid)
except PendingNameChange.DoesNotExist:
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)
user = pnc.user
u_prof = UserProfile.objects.get(user=user)
# Save old name
meta = up.get_meta()
meta = u_prof.get_meta()
if 'old_names' not in meta:
meta['old_names'] = []
meta['old_names'].append([up.name, pnc.rationale, datetime.datetime.now(UTC).isoformat()])
up.set_meta(meta)
meta['old_names'].append([u_prof.name, pnc.rationale, datetime.datetime.now(UTC).isoformat()])
u_prof.set_meta(meta)
up.name = pnc.new_name
up.save()
u_prof.name = pnc.new_name
u_prof.save()
pnc.delete()
return JsonResponse({"success": True})
......
......@@ -26,8 +26,9 @@ class CapaHtmlRenderTest(unittest.TestCase):
problem = new_loncapa_problem(xml_str)
# Render the HTML
rendered_html = etree.XML(problem.get_html())
etree.XML(problem.get_html())
# expect that we made it here without blowing up
self.assertTrue(True)
def test_include_html(self):
# Create a test file to include
......@@ -123,17 +124,17 @@ class CapaHtmlRenderTest(unittest.TestCase):
# Render the HTML
rendered_html = etree.XML(problem.get_html())
# expect the javascript is still present in the rendered html
self.assertTrue("<script type=\"text/javascript\">function(){}</script>" in etree.tostring(rendered_html))
def test_render_response_xml(self):
# Generate some XML for a string response
kwargs = {'question_text': "Test question",
'explanation_text': "Test explanation",
'answer': 'Test answer',
'hints': [('test prompt', 'test_hint', 'test hint text')]}
kwargs = {
'question_text': "Test question",
'explanation_text': "Test explanation",
'answer': 'Test answer',
'hints': [('test prompt', 'test_hint', 'test hint text')]
}
xml_str = StringResponseXMLFactory().build_xml(**kwargs)
# Mock out the template renderer
......@@ -186,18 +187,21 @@ class CapaHtmlRenderTest(unittest.TestCase):
expected_solution_context = {'id': '1_solution_1'}
expected_calls = [mock.call('textline.html', expected_textline_context),
mock.call('solutionspan.html', expected_solution_context),
mock.call('textline.html', expected_textline_context),
mock.call('solutionspan.html', expected_solution_context)]
self.assertEqual(the_system.render_template.call_args_list,
expected_calls)
expected_calls = [
mock.call('textline.html', expected_textline_context),
mock.call('solutionspan.html', expected_solution_context),
mock.call('textline.html', expected_textline_context),
mock.call('solutionspan.html', expected_solution_context)
]
self.assertEqual(
the_system.render_template.call_args_list,
expected_calls
)
def test_render_response_with_overall_msg(self):
# CustomResponse script that sets an overall_message
script=textwrap.dedent("""
script = textwrap.dedent("""
def check_func(*args):
msg = '<p>Test message 1<br /></p><p>Test message 2</p>'
return {'overall_message': msg,
......@@ -205,19 +209,18 @@ class CapaHtmlRenderTest(unittest.TestCase):
""")
# Generate some XML for a CustomResponse
kwargs = {'script':script, 'cfn': 'check_func'}
kwargs = {'script': script, 'cfn': 'check_func'}
xml_str = CustomResponseXMLFactory().build_xml(**kwargs)
# Create the problem and render the html
problem = new_loncapa_problem(xml_str)
# Grade the problem
correctmap = problem.grade_answers({'1_2_1': 'test'})
problem.grade_answers({'1_2_1': 'test'})
# Render the html
rendered_html = etree.XML(problem.get_html())
# Expect that there is a <div> within the response <div>
# with css class response_message
msg_div_element = rendered_html.find(".//div[@class='response_message']")
......@@ -232,7 +235,6 @@ class CapaHtmlRenderTest(unittest.TestCase):
self.assertEqual(msg_p_elements[1].tag, "p")
self.assertEqual(msg_p_elements[1].text, "Test message 2")
def test_substitute_python_vars(self):
# Generate some XML with Python variables defined in a script
# and used later as attributes
......
"""
This test tests that i18n extraction (`paver i18n_extract -v`) works properly.
"""
from datetime import datetime, timedelta
import os
import sys
import string
import string # pylint: disable=deprecated-module
import random
import re
......@@ -65,12 +68,14 @@ class TestGenerate(TestCase):
generate.main(verbosity=0, strict=False)
for locale in CONFIGURATION.translated_locales:
for filename in ('django', 'djangojs'):
mofile = filename+'.mo'
mofile = filename + '.mo'
path = os.path.join(CONFIGURATION.get_messages_dir(locale), mofile)
exists = os.path.exists(path)
self.assertTrue(exists, msg='Missing file in locale %s: %s' % (locale, mofile))
self.assertTrue(datetime.fromtimestamp(os.path.getmtime(path), UTC) >= self.start_time,
msg='File not recently modified: %s' % path)
self.assertTrue(
datetime.fromtimestamp(os.path.getmtime(path), UTC) >= self.start_time,
msg='File not recently modified: %s' % path
)
# Segmenting means that the merge headers don't work they way they
# used to, so don't make this check for now. I'm not sure if we'll
# get the merge header back eventually, or delete this code eventually.
......@@ -88,12 +93,14 @@ class TestGenerate(TestCase):
"""
path = os.path.join(CONFIGURATION.get_messages_dir(locale), 'django.po')
po = pofile(path)
pof = pofile(path)
pattern = re.compile('^#-#-#-#-#', re.M)
match = pattern.findall(po.header)
self.assertEqual(len(match), 3,
msg="Found %s (should be 3) merge comments in the header for %s" % \
(len(match), path))
match = pattern.findall(pof.header)
self.assertEqual(
len(match),
3,
msg="Found %s (should be 3) merge comments in the header for %s" % (len(match), path)
)
def random_name(size=6):
......
......@@ -134,8 +134,6 @@ def xml_store_config(data_dir):
return store
class ModuleStoreTestCase(TestCase):
"""
Subclass for any test case that uses a ModuleStore.
......@@ -282,35 +280,39 @@ class ModuleStoreTestCase(TestCase):
# Call superclass implementation
super(ModuleStoreTestCase, self)._post_teardown()
def create_sample_course(self, org, course, run, block_info_tree=default_block_info_tree, course_fields=None):
def create_sample_course(self, org, course, run, block_info_tree=None, course_fields=None):
"""
create a course in the default modulestore from the collection of BlockInfo
records defining the course tree
Returns:
course_loc: the CourseKey for the created course
"""
if block_info_tree is None:
block_info_tree = default_block_info_tree
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, None):
# with self.store.bulk_operations(self.store.make_course_key(org, course, run)):
course = self.store.create_course(org, course, run, self.user.id, fields=course_fields)
self.course_loc = course.location
def create_sub_tree(parent_loc, block_info):
block = self.store.create_child(
self.user.id,
# TODO remove version_agnostic() when we impl the single transaction
parent_loc.version_agnostic(),
block_info.category, block_id=block_info.block_id,
fields=block_info.fields,
)
for tree in block_info.sub_tree:
create_sub_tree(block.location, tree)
setattr(self, block_info.block_id, block.location.version_agnostic())
for tree in block_info_tree:
create_sub_tree(self.course_loc, tree)
# remove version_agnostic when bulk write works
self.store.publish(self.course_loc.version_agnostic(), self.user.id)
course = self.store.create_course(org, course, run, self.user.id, fields=course_fields)
self.course_loc = course.location # pylint: disable=attribute-defined-outside-init
def create_sub_tree(parent_loc, block_info):
"""Recursively creates a sub_tree on this parent_loc with this block."""
block = self.store.create_child(
self.user.id,
# TODO remove version_agnostic() when we impl the single transaction
parent_loc.version_agnostic(),
block_info.category, block_id=block_info.block_id,
fields=block_info.fields,
)
for tree in block_info.sub_tree:
create_sub_tree(block.location, tree)
setattr(self, block_info.block_id, block.location.version_agnostic())
for tree in block_info_tree:
create_sub_tree(self.course_loc, tree)
# remove version_agnostic when bulk write works
self.store.publish(self.course_loc.version_agnostic(), self.user.id)
return self.course_loc.course_key.version_agnostic()
def create_toy_course(self, org='edX', course='toy', run='2012_Fall'):
......@@ -318,43 +320,43 @@ class ModuleStoreTestCase(TestCase):
Create an equivalent to the toy xml course
"""
# with self.store.bulk_operations(self.store.make_course_key(org, course, run)):
self.toy_loc = self.create_sample_course(
self.toy_loc = self.create_sample_course( # pylint: disable=attribute-defined-outside-init
org, course, run, TOY_BLOCK_INFO_TREE,
{
"textbooks" : [["Textbook", "https://s3.amazonaws.com/edx-textbooks/guttag_computation_v3/"]],
"wiki_slug" : "toy",
"display_name" : "Toy Course",
"graded" : True,
"tabs" : [
CoursewareTab(),
CourseInfoTab(),
StaticTab(name="Syllabus", url_slug="syllabus"),
StaticTab(name="Resources", url_slug="resources"),
DiscussionTab(),
WikiTab(),
ProgressTab(),
"textbooks": [["Textbook", "https://s3.amazonaws.com/edx-textbooks/guttag_computation_v3/"]],
"wiki_slug": "toy",
"display_name": "Toy Course",
"graded": True,
"tabs": [
CoursewareTab(),
CourseInfoTab(),
StaticTab(name="Syllabus", url_slug="syllabus"),
StaticTab(name="Resources", url_slug="resources"),
DiscussionTab(),
WikiTab(),
ProgressTab(),
],
"discussion_topics" : {"General" : {"id" : "i4x-edX-toy-course-2012_Fall"}},
"graceperiod" : datetime.timedelta(days=2, seconds=21599),
"start" : datetime.datetime(2015, 07, 17, 12, tzinfo=pytz.utc),
"xml_attributes" : {"filename" : ["course/2012_Fall.xml", "course/2012_Fall.xml"]},
"pdf_textbooks" : [
"discussion_topics": {"General": {"id": "i4x-edX-toy-course-2012_Fall"}},
"graceperiod": datetime.timedelta(days=2, seconds=21599),
"start": datetime.datetime(2015, 07, 17, 12, tzinfo=pytz.utc),
"xml_attributes": {"filename": ["course/2012_Fall.xml", "course/2012_Fall.xml"]},
"pdf_textbooks": [
{
"tab_title" : "Sample Multi Chapter Textbook",
"id" : "MyTextbook",
"chapters" : [
{"url" : "/static/Chapter1.pdf", "title" : "Chapter 1"},
{"url" : "/static/Chapter2.pdf", "title" : "Chapter 2"}
"tab_title": "Sample Multi Chapter Textbook",
"id": "MyTextbook",
"chapters": [
{"url": "/static/Chapter1.pdf", "title": "Chapter 1"},
{"url": "/static/Chapter2.pdf", "title": "Chapter 2"}
]
}
}
],
"course_image" : "just_a_test.jpg",
"course_image": "just_a_test.jpg",
}
)
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, self.toy_loc):
self.store.create_item(
self.user.id, self.toy_loc, "about", block_id="short_description",
fields={"data" : "A course about toys."}
fields={"data": "A course about toys."}
)
self.store.create_item(
self.user.id, self.toy_loc, "about", block_id="effort",
......
......@@ -7,8 +7,8 @@ The data type and use of it for declaratively creating test courses.
# fields is a dictionary of keys and values. sub_tree is a collection of BlockInfo
from collections import namedtuple
import datetime
BlockInfo = namedtuple('BlockInfo', 'block_id, category, fields, sub_tree')
default_block_info_tree = [
BlockInfo = namedtuple('BlockInfo', 'block_id, category, fields, sub_tree') # pylint: disable=invalid-name
default_block_info_tree = [ # pylint: disable=invalid-name
BlockInfo(
'chapter_x', 'chapter', {}, [
BlockInfo(
......@@ -44,7 +44,7 @@ default_block_info_tree = [
# equivalent to toy course in xml
TOY_BLOCK_INFO_TREE = [
BlockInfo(
'Overview', "chapter", {"display_name" : "Overview"}, [
'Overview', "chapter", {"display_name": "Overview"}, [
BlockInfo(
"Toy_Videos", "videosequence", {
"xml_attributes": {"filename": ["", None]}, "display_name": "Toy Videos", "format": "Lecture Sequence"
......@@ -52,24 +52,24 @@ TOY_BLOCK_INFO_TREE = [
BlockInfo(
"secret:toylab", "html", {
"data": "<b>Lab 2A: Superposition Experiment</b>\n\n<<<<<<< Updated upstream\n<p>Isn't the toy course great?</p>\n\n<p>Let's add some markup that uses non-ascii characters.\nFor example, we should be able to write words like encyclop&aelig;dia, or foreign words like fran&ccedil;ais.\nLooking beyond latin-1, we should handle math symbols: &pi;r&sup2 &le; &#8734.\nAnd it shouldn't matter if we use entities or numeric codes &mdash; &Omega; &ne; &pi; &equiv; &#937; &#8800; &#960;.\n</p>\n=======\n<p>Isn't the toy course great? — &le;</p>\n>>>>>>> Stashed changes\n",
"xml_attributes": { "filename" : [ "html/secret/toylab.xml", "html/secret/toylab.xml" ] },
"display_name" : "Toy lab"
"xml_attributes": {"filename": ["html/secret/toylab.xml", "html/secret/toylab.xml"]},
"display_name": "Toy lab"
}, []
),
BlockInfo(
"toyjumpto", "html", {
"data" : "<a href=\"/jump_to_id/vertical_test\">This is a link to another page and some Chinese 四節比分和七年前</a> <p>Some more Chinese 四節比分和七年前</p>\n",
"xml_attributes": { "filename" : [ "html/toyjumpto.xml", "html/toyjumpto.xml" ] }
"data": "<a href=\"/jump_to_id/vertical_test\">This is a link to another page and some Chinese 四節比分和七年前</a> <p>Some more Chinese 四節比分和七年前</p>\n",
"xml_attributes": {"filename": ["html/toyjumpto.xml", "html/toyjumpto.xml"]}
}, []),
BlockInfo(
"toyhtml", "html", {
"data" : "<a href='/static/handouts/sample_handout.txt'>Sample</a>",
"xml_attributes" : { "filename" : [ "html/toyhtml.xml", "html/toyhtml.xml" ] }
"data": "<a href='/static/handouts/sample_handout.txt'>Sample</a>",
"xml_attributes": {"filename": ["html/toyhtml.xml", "html/toyhtml.xml"]}
}, []),
BlockInfo(
"nonportable", "html", {
"data": "<a href=\"/static/foo.jpg\">link</a>\n",
"xml_attributes" : { "filename" : [ "html/nonportable.xml", "html/nonportable.xml" ] }
"xml_attributes": {"filename": ["html/nonportable.xml", "html/nonportable.xml"]}
}, []),
BlockInfo(
"nonportable_link", "html", {
......@@ -79,7 +79,7 @@ TOY_BLOCK_INFO_TREE = [
BlockInfo(
"badlink", "html", {
"data": "<img src=\"/static//file.jpg\" />\n",
"xml_attributes" : { "filename" : [ "html/badlink.xml", "html/badlink.xml" ] }
"xml_attributes": {"filename": ["html/badlink.xml", "html/badlink.xml"]}
}, []),
BlockInfo(
"with_styling", "html", {
......@@ -89,11 +89,11 @@ TOY_BLOCK_INFO_TREE = [
BlockInfo(
"just_img", "html", {
"data": "<img src=\"/static/foo_bar.jpg\" />",
"xml_attributes": {"filename": [ "html/just_img.xml", "html/just_img.xml" ] }
"xml_attributes": {"filename": ["html/just_img.xml", "html/just_img.xml"]}
}, []),
BlockInfo(
"Video_Resources", "video", {
"youtube_id_1_0" : "1bK-WdDi6Qw", "display_name" : "Video Resources"
"youtube_id_1_0": "1bK-WdDi6Qw", "display_name": "Video Resources"
}, []),
]),
BlockInfo(
......@@ -109,7 +109,7 @@ TOY_BLOCK_INFO_TREE = [
),
BlockInfo(
"secret:magic", "chapter", {
"xml_attributes": {"filename": [ "chapter/secret/magic.xml", "chapter/secret/magic.xml"]}
"xml_attributes": {"filename": ["chapter/secret/magic.xml", "chapter/secret/magic.xml"]}
}, [
BlockInfo(
"toyvideo", "video", {"youtube_id_1_0": "OEoXaMPEzfMA", "display_name": "toyvideo"}, []
......@@ -117,18 +117,18 @@ TOY_BLOCK_INFO_TREE = [
]
),
BlockInfo(
"poll_test", "chapter", {}, [
"poll_test", "chapter", {}, [
BlockInfo(
"T1_changemind_poll_foo", "poll_question", {
"question": "<p>Have you changed your mind? ’</p>",
"answers": [{"text": "Yes", "id": "yes"}, {"text": "No", "id": "no"}],
"xml_attributes": {"reset": "false", "filename": ["", None]},
"display_name": "Change your answer"
}, []) ]
}, [])]
),
BlockInfo(
"vertical_container", "chapter", {
"xml_attributes": {"filename": ["chapter/vertical_container.xml", "chapter/vertical_container.xml"]}
"vertical_container", "chapter", {
"xml_attributes": {"filename": ["chapter/vertical_container.xml", "chapter/vertical_container.xml"]}
}, [
BlockInfo("vertical_sequential", "sequential", {}, [
BlockInfo("vertical_test", "vertical", {
......@@ -163,7 +163,7 @@ TOY_BLOCK_INFO_TREE = [
"T1_changemind_poll_foo_2", "poll_question", {
"question": "<p>Have you changed your mind?</p>",
"answers": [{"text": "Yes", "id": "yes"}, {"text": "No", "id": "no"}],
"xml_attributes": {"reset": "false", "filename": [ "", None]},
"xml_attributes": {"reset": "false", "filename": ["", None]},
"display_name": "Change your answer"
}, []),
]),
......@@ -174,8 +174,8 @@ TOY_BLOCK_INFO_TREE = [
]
),
BlockInfo(
"handout_container", "chapter", {
"xml_attributes" : {"filename" : ["chapter/handout_container.xml", "chapter/handout_container.xml"]}
"handout_container", "chapter", {
"xml_attributes": {"filename": ["chapter/handout_container.xml", "chapter/handout_container.xml"]}
}, [
BlockInfo(
"html_7e5578f25f79", "html", {
......
......@@ -72,54 +72,54 @@ class SplitModuleTest(unittest.TestCase):
"user_id": "test@edx.org",
"fields": {
"tabs": [
{
"type": "courseware"
},
{
"type": "course_info",
"name": "Course Info"
},
{
"type": "discussion",
"name": "Discussion"
},
{
"type": "wiki",
"name": "Wiki"
}
],
"start": _date_field.from_json("2013-02-14T05:00"),
"display_name": "The Ancient Greek Hero",
"grading_policy": {
"GRADER": [
{
"min_count": 5,
"weight": 0.15,
"type": "Homework",
"drop_count": 1,
"short_label": "HWa"
"type": "courseware"
},
{
"short_label": "",
"min_count": 2,
"type": "Lab",
"drop_count": 0,
"weight": 0.15
"type": "course_info",
"name": "Course Info"
},
{
"short_label": "Midterm",
"min_count": 1,
"type": "Midterm Exam",
"drop_count": 0,
"weight": 0.3
"type": "discussion",
"name": "Discussion"
},
{
"short_label": "Final",
"min_count": 1,
"type": "Final Exam",
"drop_count": 0,
"weight": 0.4
"type": "wiki",
"name": "Wiki"
}
],
"start": _date_field.from_json("2013-02-14T05:00"),
"display_name": "The Ancient Greek Hero",
"grading_policy": {
"GRADER": [
{
"min_count": 5,
"weight": 0.15,
"type": "Homework",
"drop_count": 1,
"short_label": "HWa"
},
{
"short_label": "",
"min_count": 2,
"type": "Lab",
"drop_count": 0,
"weight": 0.15
},
{
"short_label": "Midterm",
"min_count": 1,
"type": "Midterm Exam",
"drop_count": 0,
"weight": 0.3
},
{
"short_label": "Final",
"min_count": 1,
"type": "Final Exam",
"drop_count": 0,
"weight": 0.4
}
],
"GRADE_CUTOFFS": {
"Pass": 0.75
......@@ -132,31 +132,31 @@ class SplitModuleTest(unittest.TestCase):
"head12345": {
"end": _date_field.from_json("2013-04-13T04:30"),
"tabs": [
{
"type": "courseware"
},
{
"type": "course_info",
"name": "Course Info"
},
{
"type": "discussion",
"name": "Discussion"
},
{
"type": "wiki",
"name": "Wiki"
},
{
"type": "static_tab",
"name": "Syllabus",
"url_slug": "01356a17b5924b17a04b7fc2426a3798"
},
{
"type": "static_tab",
"name": "Advice for Students",
"url_slug": "57e9991c0d794ff58f7defae3e042e39"
}
{
"type": "courseware"
},
{
"type": "course_info",
"name": "Course Info"
},
{
"type": "discussion",
"name": "Discussion"
},
{
"type": "wiki",
"name": "Wiki"
},
{
"type": "static_tab",
"name": "Syllabus",
"url_slug": "01356a17b5924b17a04b7fc2426a3798"
},
{
"type": "static_tab",
"name": "Advice for Students",
"url_slug": "57e9991c0d794ff58f7defae3e042e39"
}
],
"graceperiod": _time_delta_field.from_json("2 hours 0 minutes 0 seconds"),
"grading_policy": {
......@@ -195,7 +195,7 @@ class SplitModuleTest(unittest.TestCase):
}
},
}}
},
},
{"user_id": "testassist@edx.org",
"update":
{"head12345": {
......@@ -239,50 +239,50 @@ class SplitModuleTest(unittest.TestCase):
"enrollment_end": _date_field.from_json("2013-03-02T05:00"),
"advertised_start": "Fall 2013",
}},
"create": [
{
"id": "chapter1",
"parent": "head12345",
"category": "chapter",
"fields": {
"display_name": "Hercules"
},
},
{
"id": "chapter2",
"parent": "head12345",
"category": "chapter",
"fields": {
"display_name": "Hera heckles Hercules"
},
},
{
"id": "chapter3",
"parent": "head12345",
"category": "chapter",
"fields": {
"display_name": "Hera cuckolds Zeus"
},
},
{
"id": "problem1",
"parent": "chapter3",
"category": "problem",
"fields": {
"display_name": "Problem 3.1",
"graceperiod": _time_delta_field.from_json("4 hours 0 minutes 0 seconds"),
},
},
{
"id": "problem3_2",
"parent": "chapter3",
"category": "problem",
"fields": {
"display_name": "Problem 3.2"
},
}
]
},
"create": [
{
"id": "chapter1",
"parent": "head12345",
"category": "chapter",
"fields": {
"display_name": "Hercules"
},
},
{
"id": "chapter2",
"parent": "head12345",
"category": "chapter",
"fields": {
"display_name": "Hera heckles Hercules"
},
},
{
"id": "chapter3",
"parent": "head12345",
"category": "chapter",
"fields": {
"display_name": "Hera cuckolds Zeus"
},
},
{
"id": "problem1",
"parent": "chapter3",
"category": "problem",
"fields": {
"display_name": "Problem 3.1",
"graceperiod": _time_delta_field.from_json("4 hours 0 minutes 0 seconds"),
},
},
{
"id": "problem3_2",
"parent": "chapter3",
"category": "problem",
"fields": {
"display_name": "Problem 3.2"
},
}
]
},
]
},
"testx.wonderful": {
......@@ -451,7 +451,7 @@ class SplitModuleTest(unittest.TestCase):
}
@staticmethod
def bootstrapDB(split_store):
def bootstrapDB(split_store): # pylint: disable=invalid-name
'''
Sets up the initial data into the db
'''
......@@ -517,7 +517,7 @@ class SplitModuleTest(unittest.TestCase):
SplitModuleTest.modulestore = None
super(SplitModuleTest, self).tearDown()
def findByIdInResult(self, collection, _id):
def findByIdInResult(self, collection, _id): # pylint: disable=invalid-name
"""
Result is a collection of descriptors. Find the one whose block id
matches the _id.
......@@ -716,6 +716,7 @@ class SplitModuleCourseTests(SplitModuleTest):
self.assertEqual(len(result.children[0].children), 1)
self.assertEqual(result.children[0].children[0].locator.version_guid, versions[0])
class SplitModuleItemTests(SplitModuleTest):
'''
Item read tests including inheritance
......@@ -946,6 +947,10 @@ class SplitModuleItemTests(SplitModuleTest):
def version_agnostic(children):
"""
children: list of descriptors
Returns the `children` list with each member version-agnostic
"""
return [child.version_agnostic() for child in children]
......@@ -1035,7 +1040,6 @@ class TestItemCrud(SplitModuleTest):
self.assertIn(new_module.location.version_agnostic(), version_agnostic(parent.children))
self.assertEqual(new_module.definition_locator.definition_id, original.definition_locator.definition_id)
def test_unique_naming(self):
"""
Check that 2 modules of same type get unique block_ids. Also check that if creation provides
......@@ -1760,6 +1764,6 @@ def modulestore():
return SplitModuleTest.modulestore
# pylint: disable=W0613
# pylint: disable=unused-argument, missing-docstring
def render_to_template_mock(*args):
pass
......@@ -33,7 +33,7 @@ class PeerGradingFields(object):
use_for_single_location = Boolean(
display_name=_("Show Single Problem"),
help=_('When True, only the single problem specified by "Link to Problem Location" is shown. '
'When False, a panel is displayed with all problems available for peer grading.'),
'When False, a panel is displayed with all problems available for peer grading.'),
default=False,
scope=Scope.settings
)
......@@ -54,9 +54,9 @@ class PeerGradingFields(object):
scope=Scope.settings)
extended_due = Date(
help=_("Date that this problem is due by for a particular student. This "
"can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due "
"date."),
"can be set by an instructor, and will override the global due "
"date if it is set to a date that is later than the global due "
"date."),
default=None,
scope=Scope.user_state,
)
......@@ -86,6 +86,7 @@ class PeerGradingFields(object):
scope=Scope.content
)
class InvalidLinkLocation(Exception):
"""
Exception for the case in which a peer grading module tries to link to an invalid location.
......@@ -150,7 +151,8 @@ class PeerGradingModule(PeerGradingFields, XModule):
try:
self.student_data_for_location = json.loads(self.student_data_for_location)
except Exception:
except Exception: # pylint: disable=broad-except
# OK with this broad exception because we just want to continue on any error
pass
@property
......@@ -218,9 +220,9 @@ class PeerGradingModule(PeerGradingFields, XModule):
# This is a dev_facing_error
return json.dumps({'error': 'Error handling action. Please try again.', 'success': False})
d = handlers[dispatch](data)
data_dict = handlers[dispatch](data)
return json.dumps(d, cls=ComplexEncoder)
return json.dumps(data_dict, cls=ComplexEncoder)
def query_data_for_location(self, location):
student_id = self.system.anonymous_student_id
......@@ -229,13 +231,12 @@ class PeerGradingModule(PeerGradingFields, XModule):
try:
response = self.peer_gs.get_data_for_location(location, student_id)
count_graded = response['count_graded']
count_required = response['count_required']
_count_graded = response['count_graded']
_count_required = response['count_required']
success = True
except GradingServiceError:
# This is a dev_facing_error
log.exception("Error getting location data from controller for location {0}, student {1}"
.format(location, student_id))
log.exception("Error getting location data from controller for location %s, student %s", location, student_id)
return success, response
......@@ -322,8 +323,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
return response
except GradingServiceError:
# This is a dev_facing_error
log.exception("Error getting next submission. server url: {0} location: {1}, grader_id: {2}"
.format(self.peer_gs.url, location, grader_id))
log.exception("Error getting next submission. server url: %s location: %s, grader_id: %s", self.peer_gs.url, location, grader_id)
# This is a student_facing_error
return {'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
......@@ -355,7 +355,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
if not success:
return self._err_response(message)
data_dict = {k:data.get(k) for k in required}
data_dict = {k: data.get(k) for k in required}
if 'rubric_scores[]' in required:
data_dict['rubric_scores'] = data.getall('rubric_scores[]')
data_dict['grader_id'] = self.system.anonymous_student_id
......@@ -365,15 +365,14 @@ class PeerGradingModule(PeerGradingFields, XModule):
success, location_data = self.query_data_for_location(data_dict['location'])
#Don't check for success above because the response = statement will raise the same Exception as the one
#that will cause success to be false.
response.update({'required_done' : False})
if 'count_graded' in location_data and 'count_required' in location_data and int(location_data['count_graded'])>=int(location_data['count_required']):
response.update({'required_done': False})
if 'count_graded' in location_data and 'count_required' in location_data and int(location_data['count_graded']) >= int(location_data['count_required']):
response['required_done'] = True
return response
except GradingServiceError:
# This is a dev_facing_error
log.exception("""Error saving grade to open ended grading service. server url: {0}"""
.format(self.peer_gs.url)
)
log.exception("Error saving grade to open ended grading service. server url: %s", self.peer_gs.url)
# This is a student_facing_error
return {
'success': False,
......@@ -411,8 +410,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
return response
except GradingServiceError:
# This is a dev_facing_error
log.exception("Error from open ended grading service. server url: {0}, grader_id: {0}, location: {1}"
.format(self.peer_gs.url, grader_id, location))
log.exception("Error from open ended grading service. server url: %s, grader_id: %s, location: %s", self.peer_gs.url, grader_id, location)
# This is a student_facing_error
return {
'success': False,
......@@ -456,8 +454,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
return response
except GradingServiceError:
# This is a dev_facing_error
log.exception("Error from open ended grading service. server url: {0}, location: {0}"
.format(self.peer_gs.url, location))
log.exception("Error from open ended grading service. server url: %s, location: %s", self.peer_gs.url, location)
# This is a student_facing_error
return {'success': False,
'error': EXTERNAL_GRADER_NO_CONTACT_ERROR}
......@@ -492,7 +489,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
if not success:
return self._err_response(message)
data_dict = {k:data.get(k) for k in required}
data_dict = {k: data.get(k) for k in required}
data_dict['rubric_scores'] = data.getall('rubric_scores[]')
data_dict['student_id'] = self.system.anonymous_student_id
data_dict['calibration_essay_id'] = data_dict['submission_id']
......@@ -619,7 +616,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
elif data.get('location') is not None:
problem_location = self.course_id.make_usage_key_from_deprecated_string(data.get('location'))
module = self._find_corresponding_module_for_location(problem_location)
module = self._find_corresponding_module_for_location(problem_location) # pylint: disable-unused-variable
ajax_url = self.ajax_url
html = self.system.render_template('peer_grading/peer_grading_problem.html', {
......
......@@ -54,25 +54,29 @@ class LTIModuleTest(LogicTest):
self.user_id = self.xmodule.runtime.anonymous_student_id
self.lti_id = self.xmodule.lti_id
self.unquoted_resource_link_id= u'{}-i4x-2-3-lti-31de800015cf4afb973356dbe81496df'.format(self.xmodule.runtime.hostname)
self.unquoted_resource_link_id = u'{}-i4x-2-3-lti-31de800015cf4afb973356dbe81496df'.format(self.xmodule.runtime.hostname)
sourcedId = u':'.join(urllib.quote(i) for i in (self.lti_id, self.unquoted_resource_link_id, self.user_id))
sourced_id = u':'.join(urllib.quote(i) for i in (self.lti_id, self.unquoted_resource_link_id, self.user_id))
self.DEFAULTS = {
'namespace' : "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0",
'sourcedId': sourcedId,
self.defaults = {
'namespace': "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0",
'sourcedId': sourced_id,
'action': 'replaceResultRequest',
'grade': 0.5,
'messageIdentifier': '528243ba5241b',
}
def get_request_body(self, params={}):
data = copy(self.DEFAULTS)
def get_request_body(self, params=None):
"""Fetches the body of a request specified by params"""
if params is None:
params = {}
data = copy(self.defaults)
data.update(params)
return self.request_body_xml_template.format(**data)
def get_response_values(self, response):
"""Gets the values from the given response"""
parser = etree.XMLParser(ns_clean=True, recover=True, encoding='utf-8')
root = etree.fromstring(response.body.strip(), parser=parser)
lti_spec_namespace = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0"
......@@ -80,23 +84,23 @@ class LTIModuleTest(LogicTest):
code_major = root.xpath("//def:imsx_codeMajor", namespaces=namespaces)[0].text
description = root.xpath("//def:imsx_description", namespaces=namespaces)[0].text
messageIdentifier = root.xpath("//def:imsx_messageIdentifier", namespaces=namespaces)[0].text
imsx_POXBody = root.xpath("//def:imsx_POXBody", namespaces=namespaces)[0]
message_identifier = root.xpath("//def:imsx_messageIdentifier", namespaces=namespaces)[0].text
imsx_pox_body = root.xpath("//def:imsx_POXBody", namespaces=namespaces)[0]
try:
action = imsx_POXBody.getchildren()[0].tag.replace('{'+lti_spec_namespace+'}', '')
except Exception:
action = imsx_pox_body.getchildren()[0].tag.replace('{' + lti_spec_namespace + '}', '')
except Exception: # pylint: disable=broad-except
action = None
return {
'code_major': code_major,
'description': description,
'messageIdentifier': messageIdentifier,
'messageIdentifier': message_identifier,
'action': action
}
@patch('xmodule.lti_module.LTIModule.get_client_key_secret', return_value=('test_client_key', u'test_client_secret'))
def test_authorization_header_not_present(self, get_key_secret):
def test_authorization_header_not_present(self, _get_key_secret):
"""
Request has no Authorization header.
......@@ -110,14 +114,14 @@ class LTIModuleTest(LogicTest):
'action': None,
'code_major': 'failure',
'description': 'OAuth verification error: Malformed authorization header',
'messageIdentifier': self.DEFAULTS['messageIdentifier'],
'messageIdentifier': self.defaults['messageIdentifier'],
}
self.assertEqual(response.status_code, 200)
self.assertDictEqual(expected_response, real_response)
@patch('xmodule.lti_module.LTIModule.get_client_key_secret', return_value=('test_client_key', u'test_client_secret'))
def test_authorization_header_empty(self, get_key_secret):
def test_authorization_header_empty(self, _get_key_secret):
"""
Request Authorization header has no value.
......@@ -132,7 +136,7 @@ class LTIModuleTest(LogicTest):
'action': None,
'code_major': 'failure',
'description': 'OAuth verification error: Malformed authorization header',
'messageIdentifier': self.DEFAULTS['messageIdentifier'],
'messageIdentifier': self.defaults['messageIdentifier'],
}
self.assertEqual(response.status_code, 200)
self.assertDictEqual(expected_response, real_response)
......@@ -152,7 +156,7 @@ class LTIModuleTest(LogicTest):
'action': None,
'code_major': 'failure',
'description': 'User not found.',
'messageIdentifier': self.DEFAULTS['messageIdentifier'],
'messageIdentifier': self.defaults['messageIdentifier'],
}
self.assertEqual(response.status_code, 200)
self.assertDictEqual(expected_response, real_response)
......@@ -207,7 +211,7 @@ class LTIModuleTest(LogicTest):
'action': None,
'code_major': 'unsupported',
'description': 'Target does not support the requested operation.',
'messageIdentifier': self.DEFAULTS['messageIdentifier'],
'messageIdentifier': self.defaults['messageIdentifier'],
}
self.assertEqual(response.status_code, 200)
self.assertDictEqual(expected_response, real_response)
......@@ -222,20 +226,20 @@ class LTIModuleTest(LogicTest):
request.body = self.get_request_body()
response = self.xmodule.grade_handler(request, '')
description_expected = 'Score for {sourcedId} is now {score}'.format(
sourcedId=self.DEFAULTS['sourcedId'],
score=self.DEFAULTS['grade'],
)
sourcedId=self.defaults['sourcedId'],
score=self.defaults['grade'],
)
real_response = self.get_response_values(response)
expected_response = {
'action': 'replaceResultResponse',
'code_major': 'success',
'description': description_expected,
'messageIdentifier': self.DEFAULTS['messageIdentifier'],
'messageIdentifier': self.defaults['messageIdentifier'],
}
self.assertEqual(response.status_code, 200)
self.assertDictEqual(expected_response, real_response)
self.assertEqual(self.xmodule.module_score, float(self.DEFAULTS['grade']))
self.assertEqual(self.xmodule.module_score, float(self.defaults['grade']))
def test_user_id(self):
expected_user_id = unicode(urllib.quote(self.xmodule.runtime.anonymous_student_id))
......@@ -255,27 +259,27 @@ class LTIModuleTest(LogicTest):
self.assertEqual(real_outcome_service_url, mock_url_prefix + test_service_name)
def test_resource_link_id(self):
with patch('xmodule.lti_module.LTIModule.location', new_callable=PropertyMock) as mock_location:
with patch('xmodule.lti_module.LTIModule.location', new_callable=PropertyMock):
self.xmodule.location.html_id = lambda: 'i4x-2-3-lti-31de800015cf4afb973356dbe81496df'
expected_resource_link_id = unicode(urllib.quote(self.unquoted_resource_link_id))
real_resource_link_id = self.xmodule.get_resource_link_id()
self.assertEqual(real_resource_link_id, expected_resource_link_id)
def test_lis_result_sourcedid(self):
expected_sourcedId = u':'.join(urllib.quote(i) for i in (
expected_sourced_id = u':'.join(urllib.quote(i) for i in (
self.system.course_id.to_deprecated_string(),
self.xmodule.get_resource_link_id(),
self.user_id
))
real_lis_result_sourcedid = self.xmodule.get_lis_result_sourcedid()
self.assertEqual(real_lis_result_sourcedid, expected_sourcedId)
self.assertEqual(real_lis_result_sourcedid, expected_sourced_id)
def test_client_key_secret(self):
"""
LTI module gets client key and secret provided.
"""
#this adds lti passports to system
mocked_course = Mock(lti_passports = ['lti_id:test_client:test_secret'])
mocked_course = Mock(lti_passports=['lti_id:test_client:test_secret'])
modulestore = Mock()
modulestore.get_course.return_value = mocked_course
runtime = Mock(modulestore=modulestore)
......@@ -293,7 +297,7 @@ class LTIModuleTest(LogicTest):
"""
#this adds lti passports to system
mocked_course = Mock(lti_passports = ['test_id:test_client:test_secret'])
mocked_course = Mock(lti_passports=['test_id:test_client:test_secret'])
modulestore = Mock()
modulestore.get_course.return_value = mocked_course
runtime = Mock(modulestore=modulestore)
......@@ -301,7 +305,7 @@ class LTIModuleTest(LogicTest):
#set another lti_id
self.xmodule.lti_id = "another_lti_id"
key_secret = self.xmodule.get_client_key_secret()
expected = ('','')
expected = ('', '')
self.assertEqual(expected, key_secret)
def test_bad_client_key_secret(self):
......@@ -311,7 +315,7 @@ class LTIModuleTest(LogicTest):
There are key and secret provided in wrong format.
"""
#this adds lti passports to system
mocked_course = Mock(lti_passports = ['test_id_test_client_test_secret'])
mocked_course = Mock(lti_passports=['test_id_test_client_test_secret'])
modulestore = Mock()
modulestore.get_course.return_value = mocked_course
runtime = Mock(modulestore=modulestore)
......@@ -348,11 +352,11 @@ class LTIModuleTest(LogicTest):
Tests that xml body was parsed successfully.
"""
mocked_request = self.get_signed_grade_mock_request()
messageIdentifier, sourcedId, grade, action = self.xmodule.parse_grade_xml_body(mocked_request.body)
self.assertEqual(self.DEFAULTS['messageIdentifier'], messageIdentifier)
self.assertEqual(self.DEFAULTS['sourcedId'], sourcedId)
self.assertEqual(self.DEFAULTS['grade'], grade)
self.assertEqual(self.DEFAULTS['action'], action)
message_identifier, sourced_id, grade, action = self.xmodule.parse_grade_xml_body(mocked_request.body)
self.assertEqual(self.defaults['messageIdentifier'], message_identifier)
self.assertEqual(self.defaults['sourcedId'], sourced_id)
self.assertEqual(self.defaults['grade'], grade)
self.assertEqual(self.defaults['action'], action)
@patch('xmodule.lti_module.signature.verify_hmac_sha1', Mock(return_value=False))
@patch('xmodule.lti_module.LTIModule.get_client_key_secret', Mock(return_value=('test_client_key', u'test_client_secret')))
......@@ -372,7 +376,7 @@ class LTIModuleTest(LogicTest):
LTI 1.1 will be used. Otherwise fake namespace will be added to XML.
"""
mock_request = Mock()
mock_request.headers = {
mock_request.headers = { # pylint: disable=no-space-before-operator
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': u'OAuth oauth_nonce="135685044251684026041377608307", \
......
"""View functions for the LMS Student dashboard"""
from django.http import Http404
from edxmako.shortcuts import render_to_response
from django.db import connection
......@@ -15,15 +16,18 @@ def dictfetchall(cursor):
# ensure response from db is a list, not a tuple (which is returned
# by MySQL backed django instances)
rows_from_cursor=cursor.fetchall()
rows_from_cursor = cursor.fetchall()
table = table + [list(row) for row in rows_from_cursor]
return table
def SQL_query_to_list(cursor, query_string):
def SQL_query_to_list(cursor, query_string): # pylint: disable=invalid-name
"""Returns the raw result of the query"""
cursor.execute(query_string)
raw_result=dictfetchall(cursor)
raw_result = dictfetchall(cursor)
return raw_result
def dashboard(request):
"""
Slightly less hackish hack to show staff enrollment numbers and other
......@@ -40,11 +44,11 @@ def dashboard(request):
# two types of results: scalars and tables. Scalars should be represented
# as "Visible Title": Value and tables should be lists of lists where each
# inner list represents a single row of the table
results = {"scalars":{},"tables":{}}
results = {"scalars": {}, "tables": {}}
# count how many users we have
results["scalars"]["Unique Usernames"]=User.objects.filter().count()
results["scalars"]["Activated Usernames"]=User.objects.filter(is_active=1).count()
results["scalars"]["Unique Usernames"] = User.objects.filter().count()
results["scalars"]["Activated Usernames"] = User.objects.filter(is_active=1).count()
# count how many enrollments we have
results["scalars"]["Total Enrollments Across All Courses"] = CourseEnrollment.objects.filter(is_active=1).count()
......@@ -78,6 +82,6 @@ def dashboard(request):
cursor.execute(table_queries[query])
results["tables"][query] = SQL_query_to_list(cursor, table_queries[query])
context={"results":results}
context = {"results": results}
return render_to_response("admin_dashboard.html",context)
return render_to_response("admin_dashboard.html", context)
......@@ -193,7 +193,7 @@ class SingleThreadTestCase(ModuleStoreTestCase):
)
mock_request.assert_called_with(
"get",
StringEndsWithMatcher(thread_id), # url
StringEndsWithMatcher(thread_id), # url
data=None,
params=PartialDictMatcher({"mark_as_read": True, "user_id": 1, "recursive": True}),
headers=ANY,
......@@ -227,7 +227,7 @@ class SingleThreadTestCase(ModuleStoreTestCase):
)
mock_request.assert_called_with(
"get",
StringEndsWithMatcher(thread_id), # url
StringEndsWithMatcher(thread_id), # url
data=None,
params=PartialDictMatcher({
"mark_as_read": True,
......@@ -365,7 +365,7 @@ class UserProfileTestCase(ModuleStoreTestCase):
"course_id": self.course.id.to_deprecated_string(),
"page": params.get("page", 1),
"per_page": views.THREADS_PER_PAGE
}),
}),
headers=ANY,
timeout=ANY
)
......@@ -393,7 +393,7 @@ class UserProfileTestCase(ModuleStoreTestCase):
self.assertEqual(
sorted(response_data.keys()),
["annotated_content_info", "discussion_data", "num_pages", "page"]
)
)
self.assertEqual(len(response_data['discussion_data']), 1)
self.assertEqual(response_data["page"], 1)
self.assertEqual(response_data["num_pages"], 1)
......@@ -444,6 +444,7 @@ class UserProfileTestCase(ModuleStoreTestCase):
)
self.assertEqual(response.status_code, 405)
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
@patch('requests.request')
class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
......@@ -463,8 +464,8 @@ class CommentsServiceRequestHeadersTestCase(UrlResetMixin, ModuleStoreTestCase):
def assert_all_calls_have_header(self, mock_request, key, value):
expected = call(
ANY, # method
ANY, # url
ANY, # method
ANY, # url
data=ANY,
params=ANY,
headers=PartialDictMatcher({key: value}),
......@@ -537,7 +538,7 @@ class ForumFormDiscussionUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
mock_request.side_effect = make_mock_request_impl(text)
request = RequestFactory().get("dummy_url")
request.user = self.student
request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" # so request.is_ajax() == True
request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" # so request.is_ajax() == True
response = views.forum_form_discussion(request, self.course.id.to_deprecated_string())
self.assertEqual(response.status_code, 200)
......@@ -559,7 +560,7 @@ class SingleThreadUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
mock_request.side_effect = make_mock_request_impl(text, thread_id)
request = RequestFactory().get("dummy_url")
request.user = self.student
request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" # so request.is_ajax() == True
request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" # so request.is_ajax() == True
response = views.single_thread(request, self.course.id.to_deprecated_string(), "dummy_discussion_id", thread_id)
self.assertEqual(response.status_code, 200)
......@@ -580,7 +581,7 @@ class UserProfileUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
mock_request.side_effect = make_mock_request_impl(text)
request = RequestFactory().get("dummy_url")
request.user = self.student
request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" # so request.is_ajax() == True
request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" # so request.is_ajax() == True
response = views.user_profile(request, self.course.id.to_deprecated_string(), str(self.student.id))
self.assertEqual(response.status_code, 200)
......@@ -601,7 +602,7 @@ class FollowedThreadsUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin):
mock_request.side_effect = make_mock_request_impl(text)
request = RequestFactory().get("dummy_url")
request.user = self.student
request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" # so request.is_ajax() == True
request.META["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" # so request.is_ajax() == True
response = views.followed_threads(request, self.course.id.to_deprecated_string(), str(self.student.id))
self.assertEqual(response.status_code, 200)
......
"""Tests for the FoldIt module"""
import json
import logging
from functools import partial
......@@ -19,7 +20,7 @@ log = logging.getLogger(__name__)
class FolditTestCase(TestCase):
"""Tests for various responses of the FoldIt module"""
def setUp(self):
self.factory = RequestFactory()
self.url = reverse('foldit_ops')
......@@ -37,10 +38,8 @@ class FolditTestCase(TestCase):
self.tomorrow = now + timedelta(days=1)
self.yesterday = now - timedelta(days=1)
self.user.profile
self.user2.profile
def make_request(self, post_data, user=None):
"""Makes a request to foldit_ops with the given post data and user (if specified)"""
request = self.factory.post(self.url, post_data)
request.user = self.user if not user else user
return request
......@@ -57,6 +56,7 @@ class FolditTestCase(TestCase):
user = self.user if not user else user
def score_dict(puzzle_id, best_score):
"""Returns a valid json-parsable score dict"""
return {"PuzzleID": puzzle_id,
"ScoreType": "score",
"BestScore": best_score,
......@@ -77,7 +77,7 @@ class FolditTestCase(TestCase):
self.assertEqual(response.status_code, 200)
return response
def test_SetPlayerPuzzleScores(self):
def test_SetPlayerPuzzleScores(self): # pylint: disable=invalid-name
puzzle_id = 994391
best_score = 0.078034
......@@ -94,7 +94,7 @@ class FolditTestCase(TestCase):
self.assertEqual(len(top_10), 1)
self.assertEqual(top_10[0]['score'], Score.display_score(best_score))
def test_SetPlayerPuzzleScores_many(self):
def test_SetPlayerPuzzleScores_many(self): # pylint: disable=invalid-name
response = self.make_puzzle_score_request([1, 2], [0.078034, 0.080000])
......@@ -113,15 +113,14 @@ class FolditTestCase(TestCase):
}]
))
def test_SetPlayerPuzzleScores_multiple(self):
def test_SetPlayerPuzzleScores_multiple(self): # pylint: disable=invalid-name
"""
Check that multiple posts with the same id are handled properly
(keep latest for each user, have multiple users work properly)
"""
orig_score = 0.07
puzzle_id = '1'
response = self.make_puzzle_score_request([puzzle_id], [orig_score])
self.make_puzzle_score_request([puzzle_id], [orig_score])
# There should now be a score in the db.
top_10 = Score.get_tops_n(10, puzzle_id)
......@@ -130,7 +129,7 @@ class FolditTestCase(TestCase):
# Reporting a better score should overwrite
better_score = 0.06
response = self.make_puzzle_score_request([1], [better_score])
self.make_puzzle_score_request([1], [better_score])
top_10 = Score.get_tops_n(10, puzzle_id)
self.assertEqual(len(top_10), 1)
......@@ -144,7 +143,7 @@ class FolditTestCase(TestCase):
# reporting a worse score shouldn't
worse_score = 0.065
response = self.make_puzzle_score_request([1], [worse_score])
self.make_puzzle_score_request([1], [worse_score])
top_10 = Score.get_tops_n(10, puzzle_id)
self.assertEqual(len(top_10), 1)
......@@ -155,7 +154,7 @@ class FolditTestCase(TestCase):
delta=0.5
)
def test_SetPlayerPuzzleScores_multiplecourses(self):
def test_SetPlayerPuzzleScores_multiple_courses(self): # pylint: disable=invalid-name
puzzle_id = "1"
player1_score = 0.05
......@@ -164,9 +163,8 @@ class FolditTestCase(TestCase):
course_list_1 = [self.course_id]
course_list_2 = [self.course_id2]
response1 = self.make_puzzle_score_request(
puzzle_id, player1_score, self.user
)
self.make_puzzle_score_request(puzzle_id, player1_score, self.user)
course_1_top_10 = Score.get_tops_n(10, puzzle_id, course_list_1)
course_2_top_10 = Score.get_tops_n(10, puzzle_id, course_list_2)
total_top_10 = Score.get_tops_n(10, puzzle_id)
......@@ -176,9 +174,8 @@ class FolditTestCase(TestCase):
self.assertEqual(len(course_2_top_10), 0)
self.assertEqual(len(total_top_10), 1)
response2 = self.make_puzzle_score_request(
puzzle_id, player2_score, self.user2
)
self.make_puzzle_score_request(puzzle_id, player2_score, self.user2)
course_2_top_10 = Score.get_tops_n(10, puzzle_id, course_list_2)
total_top_10 = Score.get_tops_n(10, puzzle_id)
......@@ -187,7 +184,7 @@ class FolditTestCase(TestCase):
self.assertEqual(len(course_2_top_10), 1)
self.assertEqual(len(total_top_10), 2)
def test_SetPlayerPuzzleScores_manyplayers(self):
def test_SetPlayerPuzzleScores_many_players(self): # pylint: disable=invalid-name
"""
Check that when we send scores from multiple users, the correct order
of scores is displayed. Note that, before being processed by
......@@ -196,18 +193,14 @@ class FolditTestCase(TestCase):
puzzle_id = ['1']
player1_score = 0.08
player2_score = 0.02
response1 = self.make_puzzle_score_request(
puzzle_id, player1_score, self.user
)
self.make_puzzle_score_request(puzzle_id, player1_score, self.user)
# There should now be a score in the db.
top_10 = Score.get_tops_n(10, puzzle_id)
self.assertEqual(len(top_10), 1)
self.assertEqual(top_10[0]['score'], Score.display_score(player1_score))
response2 = self.make_puzzle_score_request(
puzzle_id, player2_score, self.user2
)
self.make_puzzle_score_request(puzzle_id, player2_score, self.user2)
# There should now be two scores in the db
top_10 = Score.get_tops_n(10, puzzle_id)
......@@ -228,32 +221,36 @@ class FolditTestCase(TestCase):
# Top score user should be self.user2.username
self.assertEqual(top_10[0]['username'], self.user2.username)
def test_SetPlayerPuzzleScores_error(self):
def test_SetPlayerPuzzleScores_error(self): # pylint: disable=invalid-name
scores = [{"PuzzleID": 994391,
"ScoreType": "score",
"BestScore": 0.078034,
"CurrentScore": 0.080035,
"ScoreVersion": 23}]
scores = [{
"PuzzleID": 994391,
"ScoreType": "score",
"BestScore": 0.078034,
"CurrentScore": 0.080035,
"ScoreVersion": 23
}]
validation_str = json.dumps(scores)
verify = {"Verify": verify_code(self.user.email, validation_str),
"VerifyMethod": "FoldItVerify"}
verify = {
"Verify": verify_code(self.user.email, validation_str),
"VerifyMethod": "FoldItVerify"
}
# change the real string -- should get an error
scores[0]['ScoreVersion'] = 22
scores_str = json.dumps(scores)
data = {'SetPlayerPuzzleScoresVerify': json.dumps(verify),
'SetPlayerPuzzleScores': scores_str}
data = {
'SetPlayerPuzzleScoresVerify': json.dumps(verify),
'SetPlayerPuzzleScores': scores_str
}
request = self.make_request(data)
response = foldit_ops(request)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response.content,
json.dumps([{
"OperationID": "SetPlayerPuzzleScores",
......@@ -261,7 +258,6 @@ class FolditTestCase(TestCase):
"ErrorString": "Verification failed",
"ErrorCode": "VerifyFailed"}]))
def make_puzzles_complete_request(self, puzzles):
"""
Make a puzzles complete request, given an array of
......@@ -272,11 +268,15 @@ class FolditTestCase(TestCase):
"""
puzzles_str = json.dumps(puzzles)
verify = {"Verify": verify_code(self.user.email, puzzles_str),
"VerifyMethod":"FoldItVerify"}
verify = {
"Verify": verify_code(self.user.email, puzzles_str),
"VerifyMethod": "FoldItVerify"
}
data = {'SetPuzzlesCompleteVerify': json.dumps(verify),
'SetPuzzlesComplete': puzzles_str}
data = {
'SetPuzzlesCompleteVerify': json.dumps(verify),
'SetPuzzlesComplete': puzzles_str
}
request = self.make_request(data)
......@@ -286,56 +286,64 @@ class FolditTestCase(TestCase):
@staticmethod
def set_puzzle_complete_response(values):
return json.dumps([{"OperationID":"SetPuzzlesComplete",
"""Returns a json response of a Puzzle Complete message"""
return json.dumps([{"OperationID": "SetPuzzlesComplete",
"Value": values}])
def test_SetPlayerPuzzlesComplete(self): # pylint: disable=invalid-name
def test_SetPlayerPuzzlesComplete(self):
puzzles = [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
{"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]
puzzles = [
{"PuzzleID": 13, "Set": 1, "SubSet": 2},
{"PuzzleID": 53524, "Set": 1, "SubSet": 1}
]
response = self.make_puzzles_complete_request(puzzles)
self.assertEqual(response.content,
self.set_puzzle_complete_response([13, 53524]))
def test_SetPlayerPuzzlesComplete_multiple(self):
def test_SetPlayerPuzzlesComplete_multiple(self): # pylint: disable=invalid-name
"""Check that state is stored properly"""
puzzles = [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
{"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]
puzzles = [
{"PuzzleID": 13, "Set": 1, "SubSet": 2},
{"PuzzleID": 53524, "Set": 1, "SubSet": 1}
]
response = self.make_puzzles_complete_request(puzzles)
self.assertEqual(response.content,
self.set_puzzle_complete_response([13, 53524]))
puzzles = [ {"PuzzleID": 14, "Set": 1, "SubSet": 3},
{"PuzzleID": 15, "Set": 1, "SubSet": 1} ]
puzzles = [
{"PuzzleID": 14, "Set": 1, "SubSet": 3},
{"PuzzleID": 15, "Set": 1, "SubSet": 1}
]
response = self.make_puzzles_complete_request(puzzles)
self.assertEqual(response.content,
self.set_puzzle_complete_response([13, 14, 15, 53524]))
self.assertEqual(
response.content,
self.set_puzzle_complete_response([13, 14, 15, 53524])
)
def test_SetPlayerPuzzlesComplete_level_complete(self):
def test_SetPlayerPuzzlesComplete_level_complete(self): # pylint: disable=invalid-name
"""Check that the level complete function works"""
puzzles = [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
{"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]
puzzles = [
{"PuzzleID": 13, "Set": 1, "SubSet": 2},
{"PuzzleID": 53524, "Set": 1, "SubSet": 1}
]
response = self.make_puzzles_complete_request(puzzles)
self.assertEqual(response.content,
self.set_puzzle_complete_response([13, 53524]))
puzzles = [ {"PuzzleID": 14, "Set": 1, "SubSet": 3},
{"PuzzleID": 15, "Set": 1, "SubSet": 1} ]
puzzles = [
{"PuzzleID": 14, "Set": 1, "SubSet": 3},
{"PuzzleID": 15, "Set": 1, "SubSet": 1}
]
response = self.make_puzzles_complete_request(puzzles)
......@@ -350,7 +358,7 @@ class FolditTestCase(TestCase):
self.assertTrue(is_complete(1, 2))
self.assertFalse(is_complete(4, 5))
puzzles = [ {"PuzzleID": 74, "Set": 4, "SubSet": 5} ]
puzzles = [{"PuzzleID": 74, "Set": 4, "SubSet": 5}]
response = self.make_puzzles_complete_request(puzzles)
......@@ -361,28 +369,30 @@ class FolditTestCase(TestCase):
self.assertTrue(is_complete(1, 1, due=self.tomorrow))
self.assertFalse(is_complete(1, 1, due=self.yesterday))
def test_SetPlayerPuzzlesComplete_error(self): # pylint: disable=invalid-name
def test_SetPlayerPuzzlesComplete_error(self):
puzzles = [ {"PuzzleID": 13, "Set": 1, "SubSet": 2},
{"PuzzleID": 53524, "Set": 1, "SubSet": 1} ]
puzzles = [
{"PuzzleID": 13, "Set": 1, "SubSet": 2},
{"PuzzleID": 53524, "Set": 1, "SubSet": 1}
]
puzzles_str = json.dumps(puzzles)
verify = {"Verify": verify_code(self.user.email, puzzles_str + "x"),
"VerifyMethod":"FoldItVerify"}
verify = {
"Verify": verify_code(self.user.email, puzzles_str + "x"),
"VerifyMethod": "FoldItVerify"
}
data = {'SetPuzzlesCompleteVerify': json.dumps(verify),
'SetPuzzlesComplete': puzzles_str}
data = {
'SetPuzzlesCompleteVerify': json.dumps(verify),
'SetPuzzlesComplete': puzzles_str
}
request = self.make_request(data)
response = foldit_ops(request)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertEqual(response.content,
json.dumps([{
"OperationID": "SetPuzzlesComplete",
......
......@@ -3,7 +3,7 @@ Instructor Views
"""
## NOTE: This is the code for the legacy instructor dashboard
## We are no longer supporting this file or accepting changes into it.
# pylint: skip-file
from contextlib import contextmanager
import csv
import json
......@@ -1427,6 +1427,7 @@ def get_student_grade_summary_data(request, course, get_grades=True, get_raw_sco
# Gradebook has moved to instructor.api.spoc_gradebook #
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def grade_summary(request, course_key):
"""Display the grade summary for a course."""
......
......@@ -24,7 +24,7 @@ from xmodule.modulestore.django import modulestore
from course_modes.models import CourseMode
from edxmako.shortcuts import render_to_string
from student.models import CourseEnrollment, unenroll_done
from student.models import CourseEnrollment, UNENROLL_DONE
from util.query import use_read_replica_if_available
from xmodule_django.models import CourseKeyField
......@@ -639,7 +639,7 @@ class CertificateItem(OrderItem):
course_enrollment = models.ForeignKey(CourseEnrollment)
mode = models.SlugField()
@receiver(unenroll_done)
@receiver(UNENROLL_DONE)
def refund_cert_callback(sender, course_enrollment=None, **kwargs):
"""
When a CourseEnrollment object calls its unenroll method, this function checks to see if that unenrollment
......
# Import other classes here so they can be imported from here.
# pylint: disable=W0611
"""Import other classes here so they can be imported from here."""
# pylint: disable=unused-import
from .comment import Comment
from .thread import Thread
from .user import User
......
"""Provides base Commentable model class"""
import models
import settings
class Commentable(models.Model):
base_url = "{prefix}/commentables".format(prefix=settings.PREFIX)
......
Markdown is supported
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