Commit c6439b53 by zubair-arbi

Merge pull request #8231 from edx/zub/story/ecom-1612-change-icrv-block-fields

use reverification xblock location as an identifier of reverification…
parents f4250271 803bc553
......@@ -23,9 +23,9 @@ class VerificationStatusAdmin(admin.ModelAdmin):
"""
Admin for the VerificationStatus table.
"""
list_display = ('timestamp', 'user', 'status', 'checkpoint', 'location_id')
list_display = ('timestamp', 'user', 'status', 'checkpoint')
readonly_fields = ()
search_fields = ('checkpoint', 'user')
search_fields = ('checkpoint__checkpoint_location', 'user__username')
def get_readonly_fields(self, request, obj=None):
"""When editing an existing record, all fields should be read-only.
......@@ -36,7 +36,7 @@ class VerificationStatusAdmin(admin.ModelAdmin):
"""
if obj:
return self.readonly_fields + ('status', 'checkpoint', 'user', 'location_id', 'response', 'error')
return self.readonly_fields + ('status', 'checkpoint', 'user', 'response', 'error')
return self.readonly_fields
def has_delete_permission(self, request, obj=None):
......@@ -48,7 +48,7 @@ class SkippedReverificationAdmin(admin.ModelAdmin):
"""Admin for the SkippedReverification table. """
list_display = ('created_at', 'user', 'course_id', 'checkpoint')
readonly_fields = ('user', 'course_id')
search_fields = ('user', 'course_id', 'checkpoint')
search_fields = ('user__username', 'course_id', 'checkpoint__checkpoint_location')
def has_add_permission(self, request):
"""Skipped verifications can't be created in Django admin. """
......
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Removing unique constraint on 'VerificationCheckpoint', fields ['course_id', 'checkpoint_name']
db.delete_unique('verify_student_verificationcheckpoint', ['course_id', 'checkpoint_name'])
# Deleting field 'VerificationCheckpoint.checkpoint_name'
db.delete_column('verify_student_verificationcheckpoint', 'checkpoint_name')
# Adding field 'VerificationCheckpoint.checkpoint_location'
db.add_column('verify_student_verificationcheckpoint', 'checkpoint_location',
self.gf('django.db.models.fields.CharField')(default='', max_length=255),
keep_default=False)
# Adding unique constraint on 'VerificationCheckpoint', fields ['course_id', 'checkpoint_location']
db.create_unique('verify_student_verificationcheckpoint', ['course_id', 'checkpoint_location'])
# Deleting field 'VerificationStatus.location_id'
db.delete_column('verify_student_verificationstatus', 'location_id')
def backwards(self, orm):
# Removing unique constraint on 'VerificationCheckpoint', fields ['course_id', 'checkpoint_location']
db.delete_unique('verify_student_verificationcheckpoint', ['course_id', 'checkpoint_location'])
# User chose to not deal with backwards NULL issues for 'VerificationCheckpoint.checkpoint_name'
raise RuntimeError("Cannot reverse this migration. 'VerificationCheckpoint.checkpoint_name' and its values cannot be restored.")
# The following code is provided here to aid in writing a correct migration # Adding field 'VerificationCheckpoint.checkpoint_name'
db.add_column('verify_student_verificationcheckpoint', 'checkpoint_name',
self.gf('django.db.models.fields.CharField')(max_length=32),
keep_default=False)
# Deleting field 'VerificationCheckpoint.checkpoint_location'
db.delete_column('verify_student_verificationcheckpoint', 'checkpoint_location')
# Adding unique constraint on 'VerificationCheckpoint', fields ['course_id', 'checkpoint_name']
db.create_unique('verify_student_verificationcheckpoint', ['course_id', 'checkpoint_name'])
# Adding field 'VerificationStatus.location_id'
db.add_column('verify_student_verificationstatus', 'location_id',
self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
keep_default=False)
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'reverification.midcoursereverificationwindow': {
'Meta': {'object_name': 'MidcourseReverificationWindow'},
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'end_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'start_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'})
},
'verify_student.incoursereverificationconfiguration': {
'Meta': {'object_name': 'InCourseReverificationConfiguration'},
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'verify_student.skippedreverification': {
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'SkippedReverification'},
'checkpoint': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'skipped_checkpoint'", 'to': "orm['verify_student.VerificationCheckpoint']"}),
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'verify_student.softwaresecurephotoverification': {
'Meta': {'ordering': "['-created_at']", 'object_name': 'SoftwareSecurePhotoVerification'},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'display': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
'error_code': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'error_msg': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'face_image_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'photo_id_image_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
'photo_id_key': ('django.db.models.fields.TextField', [], {'max_length': '1024'}),
'receipt_id': ('django.db.models.fields.CharField', [], {'default': "'4ae40fdd-c39a-4a23-a593-41beec90013b'", 'max_length': '255', 'db_index': 'True'}),
'reviewing_service': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'reviewing_user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'photo_verifications_reviewed'", 'null': 'True', 'to': "orm['auth.User']"}),
'status': ('model_utils.fields.StatusField', [], {'default': "'created'", 'max_length': '100', u'no_check_for_status': 'True'}),
'status_changed': ('model_utils.fields.MonitorField', [], {'default': 'datetime.datetime.now', u'monitor': "u'status'"}),
'submitted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'window': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reverification.MidcourseReverificationWindow']", 'null': 'True'})
},
'verify_student.verificationcheckpoint': {
'Meta': {'unique_together': "(('course_id', 'checkpoint_location'),)", 'object_name': 'VerificationCheckpoint'},
'checkpoint_location': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'photo_verification': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['verify_student.SoftwareSecurePhotoVerification']", 'symmetrical': 'False'})
},
'verify_student.verificationstatus': {
'Meta': {'object_name': 'VerificationStatus'},
'checkpoint': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'checkpoint_status'", 'to': "orm['verify_student.VerificationCheckpoint']"}),
'error': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'response': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
}
}
complete_apps = ['verify_student']
\ No newline at end of file
......@@ -8,16 +8,17 @@ of a student over a period of time. Right now, the only models are the abstract
`SoftwareSecurePhotoVerification`. The hope is to keep as much of the
photo verification process as generic as possible.
"""
from datetime import datetime, timedelta
from email.utils import formatdate
import functools
import json
import logging
from datetime import datetime, timedelta
from email.utils import formatdate
from course_modes.models import CourseMode
import pytz
import requests
import uuid
from lazy import lazy
from opaque_keys.edx.keys import UsageKey
from django.conf import settings
from django.contrib.auth.models import User
......@@ -29,6 +30,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from boto.s3.connection import S3Connection
from boto.s3.key import Key
from config_models.models import ConfigurationModel
from course_modes.models import CourseMode
from model_utils.models import StatusModel
from model_utils import Choices
from reverification.models import MidcourseReverificationWindow
......@@ -36,6 +38,8 @@ from verify_student.ssencrypt import (
random_aes_key, encrypt_and_encode,
generate_signed_message, rsa_encrypt
)
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule_django.models import CourseKeyField
......@@ -852,12 +856,13 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
@classmethod
def submit_faceimage(cls, user, face_image, photo_id_key):
"""Submit the faceimage to SoftwareSecurePhotoVerification
"""Submit the face image to SoftwareSecurePhotoVerification.
Arguments:
user(User): user object
face_image (bytestream): raw bytestream image data
photo_id_key (str) : SoftwareSecurePhotoVerification attribute
Returns:
SoftwareSecurePhotoVerification Object
"""
......@@ -887,42 +892,60 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
class VerificationCheckpoint(models.Model):
"""Represents a point at which a user is challenged to reverify his or her identity.
Each checkpoint is uniquely identified by a (course_id, checkpoint_name) tuple.
"""
CHECKPOINT_CHOICES = (
("midterm", "midterm"),
("final", "final"),
)
"""Represents a point at which a user is asked to re-verify his/her
identity.
Each checkpoint is uniquely identified by a
(course_id, checkpoint_location) tuple.
"""
course_id = CourseKeyField(max_length=255, db_index=True)
checkpoint_name = models.CharField(max_length=32, choices=CHECKPOINT_CHOICES)
checkpoint_location = models.CharField(max_length=255)
photo_verification = models.ManyToManyField(SoftwareSecurePhotoVerification)
class Meta: # pylint: disable=missing-docstring, old-style-class
unique_together = (('course_id', 'checkpoint_name'),)
unique_together = ('course_id', 'checkpoint_location')
def __unicode__(self):
"""Unicode representation of the checkpoint. """
"""
Unicode representation of the checkpoint.
"""
return u"{checkpoint} in {course}".format(
checkpoint=self.checkpoint_name,
course=self.course_id
)
@lazy
def checkpoint_name(self):
"""Lazy method for getting checkpoint name of reverification block.
Return location of the checkpoint if no related assessment found in
database.
"""
checkpoint_key = UsageKey.from_string(self.checkpoint_location)
try:
checkpoint_name = modulestore().get_item(checkpoint_key).related_assessment
except ItemNotFoundError:
log.warning(
u"Verification checkpoint block with location '%s' and course id '%s' "
u"not found in database.", self.checkpoint_location, unicode(self.course_id)
)
checkpoint_name = self.checkpoint_location
return checkpoint_name
def add_verification_attempt(self, verification_attempt):
""" Add the verification attempt in M2M relation of photo_verification
"""Add the verification attempt in M2M relation of photo_verification.
Arguments:
verification_attempt(SoftwareSecurePhotoVerification): SoftwareSecurePhotoVerification object
verification_attempt(object): SoftwareSecurePhotoVerification object
Returns:
None
"""
self.photo_verification.add(verification_attempt) # pylint: disable=no-member
self.photo_verification.add(verification_attempt) # pylint: disable=no-member
def get_user_latest_status(self, user_id):
""" Return the latest status of the given checkpoint attempt by user
"""Get the status of the latest checkpoint attempt of the given user.
Args:
user_id(str): Id of user
......@@ -931,34 +954,35 @@ class VerificationCheckpoint(models.Model):
VerificationStatus object if found any else None
"""
try:
return self.checkpoint_status.filter(user_id=user_id).latest() # pylint: disable=E1101
return self.checkpoint_status.filter(user_id=user_id).latest() # pylint: disable=no-member
except ObjectDoesNotExist:
return None
@classmethod
def get_verification_checkpoint(cls, course_id, checkpoint_name):
"""Get the verification checkpoint for given course_id and checkpoint name
def get_verification_checkpoint(cls, course_id, checkpoint_location):
"""Get the verification checkpoint for given 'course_id' and
checkpoint name.
Arguments:
course_id(CourseKey): CourseKey
checkpoint_name(str): checkpoint name
checkpoint_location(str): Verification checkpoint location
Returns:
VerificationCheckpoint object if exists otherwise None
"""
try:
return cls.objects.get(course_id=course_id, checkpoint_name=checkpoint_name)
return cls.objects.get(course_id=course_id, checkpoint_location=checkpoint_location)
except cls.DoesNotExist:
return None
class VerificationStatus(models.Model):
"""A verification status represents a user’s progress
through the verification process for a particular checkpoint
Model is an append-only table that represents the user status changes in
verification process
"""
"""This model is an append-only table that represents user status changes
during the verification process.
A verification status represents a user’s progress through the verification
process for a particular checkpoint.
"""
VERIFICATION_STATUS_CHOICES = (
("submitted", "submitted"),
("approved", "approved"),
......@@ -973,56 +997,43 @@ class VerificationStatus(models.Model):
response = models.TextField(null=True, blank=True)
error = models.TextField(null=True, blank=True)
# This field is used to save location of Reverification module in courseware
location_id = models.CharField(
null=True,
blank=True,
max_length=255,
help_text=ugettext_lazy("Usage id of Reverification XBlock.")
)
class Meta(object): # pylint: disable=missing-docstring
get_latest_by = "timestamp"
verbose_name = "Verification Status"
verbose_name_plural = "Verification Statuses"
@classmethod
def add_verification_status(cls, checkpoint, user, status, location_id=None):
""" Create new verification status object
def add_verification_status(cls, checkpoint, user, status):
"""Create new verification status object.
Arguments:
checkpoint(VerificationCheckpoint): VerificationCheckpoint object
user(User): user object
status(str): String representing the status from VERIFICATION_STATUS_CHOICES
location_id(str): Usage key of Reverification XBlock
status(str): Status from VERIFICATION_STATUS_CHOICES
Returns:
None
"""
cls.objects.create(checkpoint=checkpoint, user=user, status=status, location_id=location_id)
cls.objects.create(checkpoint=checkpoint, user=user, status=status)
@classmethod
def add_status_from_checkpoints(cls, checkpoints, user, status):
""" Create new verification status objects against the given checkpoints
"""Create new verification status objects for a user against the given
checkpoints.
Arguments:
checkpoints(list): list of VerificationCheckpoint objects
user(User): user object
status(str): String representing the status from VERIFICATION_STATUS_CHOICES
status(str): Status from VERIFICATION_STATUS_CHOICES
Returns:
None
"""
for checkpoint in checkpoints:
# get 'location_id' from last entry (if it exists) and add it in
# new entry
try:
location_id = cls.objects.filter(checkpoint=checkpoint).latest().location_id
except cls.DoesNotExist:
location_id = None
cls.objects.create(checkpoint=checkpoint, user=user, status=status, location_id=location_id)
cls.objects.create(checkpoint=checkpoint, user=user, status=status)
@classmethod
def get_user_attempts(cls, user_id, course_key, related_assessment, location_id):
def get_user_attempts(cls, user_id, course_key, related_assessment_location):
"""
Get re-verification attempts against a user for a given 'checkpoint'
and 'course_id'.
......@@ -1030,34 +1041,32 @@ class VerificationStatus(models.Model):
Arguments:
user_id(str): User Id string
course_key(str): A CourseKey of a course
related_assessment(str): Verification checkpoint name
location_id(str): Location of Reverification XBlock in courseware
related_assessment_location(str): Verification checkpoint location
Returns:
count of re-verification attempts
Count of re-verification attempts
"""
return cls.objects.filter(
user_id=user_id,
checkpoint__course_id=course_key,
checkpoint__checkpoint_name=related_assessment,
location_id=location_id,
checkpoint__checkpoint_location=related_assessment_location,
status="submitted"
).count()
@classmethod
def get_location_id(cls, photo_verification):
""" Return the location id of xblock
"""Get the location ID of reverification XBlock.
Args:
photo_verification(SoftwareSecurePhotoVerification): SoftwareSecurePhotoVerification object
photo_verification(object): SoftwareSecurePhotoVerification object
Return:
Location Id of xblock if any else empty string
Location Id of XBlock if any else empty string
"""
try:
ver_status = cls.objects.filter(checkpoint__photo_verification=photo_verification).latest()
return ver_status.location_id
verification_status = cls.objects.filter(checkpoint__photo_verification=photo_verification).latest()
return verification_status.checkpoint.checkpoint_location
except cls.DoesNotExist:
return ""
......@@ -1071,17 +1080,16 @@ class InCourseReverificationConfiguration(ConfigurationModel):
When the flag is enabled, the "in-course re-verification" feature
will be enabled.
"""
pass
class SkippedReverification(models.Model):
"""
Model for tracking skipped Reverification of a user against a specific
"""Model for tracking skipped Reverification of a user against a specific
course.
If user skipped the Reverification for a specific course then in future
user cannot see the reverification link.
If a user skipped a Reverification checkpoint for a specific course then in
future that user cannot see the reverification link.
"""
user = models.ForeignKey(User)
course_id = CourseKeyField(max_length=255, db_index=True)
......@@ -1093,12 +1101,13 @@ class SkippedReverification(models.Model):
@classmethod
def add_skipped_reverification_attempt(cls, checkpoint, user_id, course_id):
""" Create skipped reverification object
"""Create skipped reverification object.
Arguments:
checkpoint(VerificationCheckpoint): VerificationCheckpoint object
user_id(str): User Id of currently logged in user
course_id(CourseKey): CourseKey
Returns:
None
"""
......@@ -1106,11 +1115,13 @@ class SkippedReverification(models.Model):
@classmethod
def check_user_skipped_reverification_exists(cls, user, course_id):
"""Check user skipped re-verification attempt exists against specific course
"""Check existence of a user's skipped re-verification attempt for a
specific course.
Arguments:
user(User): user object
course_id(CourseKey): CourseKey
Returns:
Boolean
"""
......
......@@ -9,6 +9,7 @@ from django.core.urlresolvers import reverse
from django.db import IntegrityError
from opaque_keys.edx.keys import CourseKey
from verify_student.models import VerificationCheckpoint, VerificationStatus, SkippedReverification
......@@ -20,19 +21,19 @@ class ReverificationService(object):
Reverification XBlock service
"""
def get_status(self, user_id, course_id, related_assessment):
"""
Get verification attempt status against a user for a given 'checkpoint'
and 'course_id'.
def get_status(self, user_id, course_id, related_assessment_location):
"""Get verification attempt status against a user for a given
'checkpoint' and 'course_id'.
Args:
user_id(str): User Id string
course_id(str): A string of course id
related_assessment(str): Verification checkpoint name
related_assessment_location(str): Location of Reverification XBlock
Returns:
"skipped" if has skip the re-verification or Verification Status string if
any attempt submitted by user else None
"skipped" if the user has skipped the re-verification or
Verification Status string if the user has submitted photo
verification attempt else None
"""
course_key = CourseKey.from_string(course_id)
has_skipped = SkippedReverification.check_user_skipped_reverification_exists(user_id, course_key)
......@@ -42,69 +43,73 @@ class ReverificationService(object):
checkpoint_status = VerificationStatus.objects.filter(
user_id=user_id,
checkpoint__course_id=course_key,
checkpoint__checkpoint_name=related_assessment
checkpoint__checkpoint_location=related_assessment_location
).latest()
return checkpoint_status.status
except ObjectDoesNotExist:
return None
def start_verification(self, course_id, related_assessment, item_id):
"""
Create re-verification link against a verification checkpoint.
def start_verification(self, course_id, related_assessment_location):
"""Create re-verification link against a verification checkpoint.
Args:
course_id(str): A string of course id
related_assessment(str): Verification checkpoint name
related_assessment_location(str): Location of Reverification XBlock
Returns:
Re-verification link
"""
course_key = CourseKey.from_string(course_id)
VerificationCheckpoint.objects.get_or_create(course_id=course_key, checkpoint_name=related_assessment)
VerificationCheckpoint.objects.get_or_create(
course_id=course_key,
checkpoint_location=related_assessment_location
)
re_verification_link = reverse(
'verify_student_incourse_reverify',
args=(
unicode(course_key),
unicode(related_assessment),
unicode(item_id)
unicode(related_assessment_location)
)
)
return re_verification_link
def skip_verification(self, checkpoint_name, user_id, course_id):
"""
Add skipped verification attempt entry against a given 'checkpoint'
def skip_verification(self, user_id, course_id, related_assessment_location):
"""Add skipped verification attempt entry for a user against a given
'checkpoint'.
Args:
checkpoint_name(str): Verification checkpoint name
user_id(str): User Id string
course_id(str): A string of course_id
related_assessment_location(str): Location of Reverification XBlock
Returns:
None
"""
course_key = CourseKey.from_string(course_id)
checkpoint = VerificationCheckpoint.objects.get(course_id=course_key, checkpoint_name=checkpoint_name)
checkpoint = VerificationCheckpoint.objects.get(
course_id=course_key,
checkpoint_location=related_assessment_location
)
# if user do not already skipped the attempt for this course only then he can skip
# user can skip a reverification attempt only if that user has not already
# skipped an attempt
try:
SkippedReverification.add_skipped_reverification_attempt(checkpoint, user_id, course_key)
except IntegrityError:
log.exception("Skipped attempt already exists for user %s: with course %s:", user_id, unicode(course_id))
def get_attempts(self, user_id, course_id, related_assessment, location_id):
"""
Get re-verification attempts against a user for a given 'checkpoint'
def get_attempts(self, user_id, course_id, related_assessment_location):
"""Get re-verification attempts against a user for a given 'checkpoint'
and 'course_id'.
Args:
user_id(str): User Id string
course_id(str): A string of course id
related_assessment(str): Verification checkpoint name
location_id(str): Location of Reverification XBlock in courseware
related_assessment_location(str): Location of Reverification XBlock
Returns:
Number of re-verification attempts of a user
"""
course_key = CourseKey.from_string(course_id)
return VerificationStatus.get_user_attempts(user_id, course_key, related_assessment, location_id)
return VerificationStatus.get_user_attempts(user_id, course_key, related_assessment_location)
......@@ -6,7 +6,6 @@ import requests.exceptions
import pytz
from django.conf import settings
from django.test import TestCase
from django.db.utils import IntegrityError
from mock import patch
from nose.tools import assert_is_none, assert_equals, assert_raises, assert_true, assert_false # pylint: disable=E0611
......@@ -486,69 +485,92 @@ class TestPhotoVerification(ModuleStoreTestCase):
class VerificationCheckpointTest(ModuleStoreTestCase):
"""Tests for the VerificationCheckpoint model. """
MIDTERM = "midterm"
FINAL = "final"
def setUp(self):
super(VerificationCheckpointTest, self).setUp()
self.user = UserFactory.create()
self.course = CourseFactory.create()
self.checkpoint_midterm = u'i4x://{org}/{course}/edx-reverification-block/midterm_uuid'.format(
org=self.course.id.org, course=self.course.id.course
)
self.checkpoint_final = u'i4x://{org}/{course}/edx-reverification-block/final_uuid'.format(
org=self.course.id.org, course=self.course.id.course
)
@ddt.data(MIDTERM, FINAL)
def test_get_verification_checkpoint(self, check_point):
"""testing class method of VerificationCheckpoint. create the object and then uses the class method to get the
verification check point.
@ddt.data('midterm', 'final')
def test_get_verification_checkpoint(self, checkpoint):
"""
# create the VerificationCheckpoint checkpoint
ver_check_point = VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=check_point)
Test that a reverification checkpoint is created properly.
"""
checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/{checkpoint}'.format(
org=self.course.id.org, course=self.course.id.course, checkpoint=checkpoint
)
# create the 'VerificationCheckpoint' checkpoint
verification_checkpoint = VerificationCheckpoint.objects.create(
course_id=self.course.id,
checkpoint_location=checkpoint_location
)
self.assertEqual(
VerificationCheckpoint.get_verification_checkpoint(self.course.id, check_point),
ver_check_point
VerificationCheckpoint.get_verification_checkpoint(self.course.id, checkpoint_location),
verification_checkpoint
)
def test_get_verification_checkpoint_for_not_existing_values(self):
"""testing class method of VerificationCheckpoint. create the object and then uses the class method to get the
verification check point.
"""Test that 'get_verification_checkpoint' method returns None if user
tries to access a checkpoint with an invalid location.
"""
# create the VerificationCheckpoint checkpoint
VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=self.MIDTERM)
VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_location=self.checkpoint_midterm)
# get verification for not existing checkpoint
self.assertEqual(VerificationCheckpoint.get_verification_checkpoint(self.course.id, 'abc'), None)
# get verification for a non existing checkpoint
self.assertEqual(
VerificationCheckpoint.get_verification_checkpoint(
self.course.id,
u'i4x://edX/DemoX/edx-reverification-block/invalid_location'
),
None
)
def test_unique_together_constraint(self):
"""testing the unique together contraint.
"""
Test the unique together constraint.
"""
# create the VerificationCheckpoint checkpoint
VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=self.MIDTERM)
VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_location=self.checkpoint_midterm)
# create the VerificationCheckpoint checkpoint with same course id and checkpoint name
# test creating the VerificationCheckpoint checkpoint with same course
# id and checkpoint name
with self.assertRaises(IntegrityError):
VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=self.MIDTERM)
VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_location=self.checkpoint_midterm)
def test_add_verification_attempt_software_secure(self):
"""testing manytomany relationship. adding softwaresecure attempt to the verification checkpoints.
"""
Test adding Software Secure photo verification attempts for the
reverification checkpoints.
"""
# adding two check points.
check_point1 = VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=self.MIDTERM)
check_point2 = VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=self.FINAL)
first_checkpoint = VerificationCheckpoint.objects.create(
course_id=self.course.id, checkpoint_location=self.checkpoint_midterm
)
second_checkpoint = VerificationCheckpoint.objects.create(
course_id=self.course.id, checkpoint_location=self.checkpoint_final
)
# Make an attempt and added to the checkpoint1.
check_point1.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
self.assertEqual(check_point1.photo_verification.count(), 1)
# make an attempt for the 'first_checkpoint'
first_checkpoint.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
self.assertEqual(first_checkpoint.photo_verification.count(), 1)
# Make an other attempt and added to the checkpoint1.
check_point1.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
self.assertEqual(check_point1.photo_verification.count(), 2)
# make another attempt for the 'first_checkpoint'
first_checkpoint.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
self.assertEqual(first_checkpoint.photo_verification.count(), 2)
# make new attempt and adding to the checkpoint2
# make new attempt for the 'second_checkpoint'
attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
check_point2.add_verification_attempt(attempt)
self.assertEqual(check_point2.photo_verification.count(), 1)
second_checkpoint.add_verification_attempt(attempt)
self.assertEqual(second_checkpoint.photo_verification.count(), 1)
# remove the attempt from checkpoint2
check_point2.photo_verification.remove(attempt)
self.assertEqual(check_point2.photo_verification.count(), 0)
# remove the attempt from 'second_checkpoint'
second_checkpoint.photo_verification.remove(attempt)
self.assertEqual(second_checkpoint.photo_verification.count(), 0)
@ddt.ddt
......@@ -559,15 +581,20 @@ class VerificationStatusTest(ModuleStoreTestCase):
super(VerificationStatusTest, self).setUp()
self.user = UserFactory.create()
self.course = CourseFactory.create()
self.check_point1 = VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name="midterm")
self.check_point2 = VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name="final")
self.dummy_reverification_item_id_1 = 'i4x://{}/{}/edx-reverification-block/related_assessment_1'.format(
self.course.location.org,
self.course.location.course
self.first_checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/first_checkpoint_uuid'.format(
org=self.course.id.org, course=self.course.id.course
)
self.dummy_reverification_item_id_2 = 'i4x://{}/{}/edx-reverification-block/related_assessment_2'.format(
self.course.location.org,
self.course.location.course
self.first_checkpoint = VerificationCheckpoint.objects.create(
course_id=self.course.id,
checkpoint_location=self.first_checkpoint_location
)
self.second_checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/second_checkpoint_uuid'.\
format(org=self.course.id.org, course=self.course.id.course)
self.second_checkpoint = VerificationCheckpoint.objects.create(
course_id=self.course.id,
checkpoint_location=self.second_checkpoint_location
)
@ddt.data('submitted', "approved", "denied", "error")
......@@ -576,149 +603,159 @@ class VerificationStatusTest(ModuleStoreTestCase):
# adding verification status
VerificationStatus.add_verification_status(
checkpoint=self.check_point1,
checkpoint=self.first_checkpoint,
user=self.user,
status=status,
location_id=self.dummy_reverification_item_id_1
status=status
)
# getting the status from db
result = VerificationStatus.objects.filter(checkpoint=self.check_point1)[0]
# test the status from database
result = VerificationStatus.objects.filter(checkpoint=self.first_checkpoint)[0]
self.assertEqual(result.status, status)
self.assertEqual(result.user, self.user)
@ddt.data("approved", "denied", "error")
def test_add_status_from_checkpoints(self, status):
""" Adding verification status for checkpoints list after submitting sspv. """
"""Test verification status for reverification checkpoints after
submitting software secure photo verification.
"""
# add initial verification status for checkpoints
initial_status = "submitted"
VerificationStatus.add_verification_status(
checkpoint=self.check_point1,
checkpoint=self.first_checkpoint,
user=self.user,
status=initial_status,
location_id=self.dummy_reverification_item_id_1
status=initial_status
)
VerificationStatus.add_verification_status(
checkpoint=self.check_point2,
checkpoint=self.second_checkpoint,
user=self.user,
status=initial_status,
location_id=self.dummy_reverification_item_id_2
status=initial_status
)
# now add verification status for multiple checkpoint points
VerificationStatus.add_status_from_checkpoints(
checkpoints=[self.check_point1, self.check_point2], user=self.user, status=status
checkpoints=[self.first_checkpoint, self.second_checkpoint], user=self.user, status=status
)
# test that verification status entries with new status have been added
# for both checkpoints and all entries have related 'location_id'.
result = VerificationStatus.objects.filter(user=self.user, checkpoint=self.check_point1)
self.assertEqual(len(result), len(self.check_point1.checkpoint_status.all()))
# for both checkpoints
result = VerificationStatus.objects.filter(user=self.user, checkpoint=self.first_checkpoint)
self.assertEqual(len(result), len(self.first_checkpoint.checkpoint_status.all()))
self.assertEqual(
list(result.values_list('location_id', flat=True)),
list(self.check_point1.checkpoint_status.all().values_list('location_id', flat=True))
list(result.values_list('checkpoint__checkpoint_location', flat=True)),
list(self.first_checkpoint.checkpoint_status.values_list('checkpoint__checkpoint_location', flat=True))
)
result = VerificationStatus.objects.filter(user=self.user, checkpoint=self.check_point2)
self.assertEqual(len(result), len(self.check_point2.checkpoint_status.all()))
result = VerificationStatus.objects.filter(user=self.user, checkpoint=self.second_checkpoint)
self.assertEqual(len(result), len(self.second_checkpoint.checkpoint_status.all()))
self.assertEqual(
list(result.values_list('location_id', flat=True)),
list(self.check_point2.checkpoint_status.all().values_list('location_id', flat=True))
list(result.values_list('checkpoint__checkpoint_location', flat=True)),
list(self.second_checkpoint.checkpoint_status.values_list('checkpoint__checkpoint_location', flat=True))
)
def test_get_location_id(self):
""" Getting location id for a specific checkpoint """
"""
Getting location id for a specific checkpoint.
"""
# creating software secure attempt against checkpoint
self.check_point1.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
self.first_checkpoint.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
# add initial verification status for checkpoint
VerificationStatus.add_verification_status(
checkpoint=self.check_point1,
checkpoint=self.first_checkpoint,
user=self.user,
status='submitted',
location_id=self.dummy_reverification_item_id_1
)
attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)
self.assertIsNotNone(VerificationStatus.get_location_id(attempt))
self.assertEqual(VerificationStatus.get_location_id(None), '')
def test_get_user_attempts(self):
# adding verification status
"""
Test adding verification status.
"""
VerificationStatus.add_verification_status(
checkpoint=self.check_point1,
checkpoint=self.first_checkpoint,
user=self.user,
status='submitted',
location_id=self.dummy_reverification_item_id_1
status='submitted'
)
self.assertEqual(VerificationStatus.get_user_attempts(
course_key=self.course.id,
user_id=self.user.id,
related_assessment='midterm', location_id=self.dummy_reverification_item_id_1), 1)
self.assertEqual(
VerificationStatus.get_user_attempts(
user_id=self.user.id,
course_key=self.course.id,
related_assessment_location=self.first_checkpoint_location
),
1
)
class SkippedReverificationTest(ModuleStoreTestCase):
"""Tests for the SkippedReverification model. """
"""
Tests for the SkippedReverification model.
"""
def setUp(self):
super(SkippedReverificationTest, self).setUp()
self.user = UserFactory.create()
self.course = CourseFactory.create()
self.checkpoint = VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name="midterm")
dummy_checkpoint_location = u'i4x://edX/DemoX/edx-reverification-block/midterm_uuid'
self.checkpoint = VerificationCheckpoint.objects.create(
course_id=self.course.id,
checkpoint_location=dummy_checkpoint_location
)
def test_add_skipped_attempts(self):
"""adding skipped re-verification object using class method."""
"""
Test 'add_skipped_reverification_attempt' method.
"""
# adding verification status
# add verification status
SkippedReverification.add_skipped_reverification_attempt(
checkpoint=self.checkpoint, user_id=self.user.id, course_id=unicode(self.course.id)
)
# getting the status from db
# test the status of skipped reverification from database
result = SkippedReverification.objects.filter(course_id=self.course.id)[0]
self.assertEqual(result.checkpoint, self.checkpoint)
self.assertEqual(result.user, self.user)
self.assertEqual(result.course_id, self.course.id)
def test_unique_constraint(self):
"""adding skipped re-verification with same user and course id will
raise integrity exception
"""Test that adding skipped re-verification with same user and course
id will raise 'IntegrityError' exception.
"""
# adding verification object
# add verification object
SkippedReverification.add_skipped_reverification_attempt(
checkpoint=self.checkpoint, user_id=self.user.id, course_id=unicode(self.course.id)
)
with self.assertRaises(IntegrityError):
SkippedReverification.add_skipped_reverification_attempt(
checkpoint=self.checkpoint, user_id=self.user.id, course_id=unicode(self.course.id)
)
# Create skipped attempt for different user
# create skipped attempt for different user
user2 = UserFactory.create()
SkippedReverification.add_skipped_reverification_attempt(
checkpoint=self.checkpoint, user_id=user2.id, course_id=unicode(self.course.id)
)
# getting the status from db
# test the status of skipped reverification from database
result = SkippedReverification.objects.filter(user=user2)[0]
self.assertEqual(result.checkpoint, self.checkpoint)
self.assertEqual(result.user, user2)
self.assertEqual(result.course_id, self.course.id)
def test_check_user_skipped_reverification_exists(self):
"""Checking check_user_skipped_reverification_exists method returns boolean status"""
# adding verification status
"""
Test the 'check_user_skipped_reverification_exists' method's response.
"""
# add verification status
SkippedReverification.add_skipped_reverification_attempt(
checkpoint=self.checkpoint, user_id=self.user.id, course_id=unicode(self.course.id)
)
self.assertTrue(
SkippedReverification.check_user_skipped_reverification_exists(course_id=self.course.id, user=self.user)
)
......
......@@ -31,89 +31,101 @@ class TestReverificationService(ModuleStoreTestCase):
min_price=100,
)
self.item = ItemFactory.create(parent=course, category='chapter', display_name='Test Section')
self.final_checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/final_uuid'.format(
org=self.course_key.org, course=self.course_key.course
)
@ddt.data("final_term", "mid_term")
@ddt.data('final', 'midterm')
def test_start_verification(self, checkpoint_name):
"""
Test the 'start_verification' service method. If checkpoint exists for
a specific course then return the checkpoint otherwise create that
checkpoint.
"""Test the 'start_verification' service method.
Check that if a reverification checkpoint exists for a specific course
then 'start_verification' method returns that checkpoint otherwise it
creates that checkpoint.
"""
reverification_service = ReverificationService()
reverification_service.start_verification(unicode(self.course_key), checkpoint_name, self.item.location)
checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/{checkpoint}'.format(
org=self.course_key.org, course=self.course_key.course, checkpoint=checkpoint_name
)
expected_url = (
'/verify_student/reverify'
'/{course_key}'
'/{checkpoint_name}'
'/{usage_id}/'
).format(course_key=unicode(self.course_key), checkpoint_name=checkpoint_name, usage_id=self.item.location)
'/{checkpoint_location}/'
).format(course_key=unicode(self.course_key), checkpoint_location=checkpoint_location)
self.assertEqual(
expected_url,
reverification_service.start_verification(unicode(self.course_key), checkpoint_name, self.item.location)
reverification_service.start_verification(unicode(self.course_key), checkpoint_location),
expected_url
)
def test_get_status(self):
"""Test the verification statuses of a user for a given 'checkpoint'
and 'course_id'.
"""
Test the verification statuses of a user for a given 'checkpoint' and
'course_id'.
"""
checkpoint_name = 'final_term'
reverification_service = ReverificationService()
self.assertIsNone(reverification_service.get_status(self.user.id, unicode(self.course_key), checkpoint_name))
self.assertIsNone(
reverification_service.get_status(self.user.id, unicode(self.course_key), self.final_checkpoint_location)
)
checkpoint_obj = VerificationCheckpoint.objects.create(
course_id=unicode(self.course_key),
checkpoint_name=checkpoint_name
checkpoint_location=self.final_checkpoint_location
)
VerificationStatus.objects.create(checkpoint=checkpoint_obj, user=self.user, status='submitted')
self.assertEqual(
reverification_service.get_status(self.user.id, unicode(self.course_key), checkpoint_name),
reverification_service.get_status(self.user.id, unicode(self.course_key), self.final_checkpoint_location),
'submitted'
)
VerificationStatus.objects.create(checkpoint=checkpoint_obj, user=self.user, status='submitted')
VerificationStatus.objects.create(checkpoint=checkpoint_obj, user=self.user, status='approved')
self.assertEqual(
reverification_service.get_status(self.user.id, unicode(self.course_key), checkpoint_name),
'submitted'
reverification_service.get_status(self.user.id, unicode(self.course_key), self.final_checkpoint_location),
'approved'
)
def test_skip_verification(self):
"""
Adding the test skip verification attempt for the user
Test adding skip attempt of a user for a reverification checkpoint.
"""
checkpoint_name = 'final_term'
reverification_service = ReverificationService()
VerificationCheckpoint.objects.create(
course_id=unicode(self.course_key),
checkpoint_name=checkpoint_name
checkpoint_location=self.final_checkpoint_location
)
reverification_service.skip_verification(checkpoint_name, self.user.id, unicode(self.course_key))
self.assertEqual(1, SkippedReverification.objects.filter(user=self.user, course_id=self.course_key).count())
reverification_service.skip_verification(self.user.id, unicode(self.course_key), self.final_checkpoint_location)
self.assertEqual(
SkippedReverification.objects.filter(user=self.user, course_id=self.course_key).count(),
1
)
reverification_service.skip_verification(checkpoint_name, self.user.id, unicode(self.course_key))
self.assertEqual(1, SkippedReverification.objects.filter(user=self.user, course_id=self.course_key).count())
# now test that a user can have only one entry for a skipped
# reverification for a course
reverification_service.skip_verification(self.user.id, unicode(self.course_key), self.final_checkpoint_location)
self.assertEqual(
SkippedReverification.objects.filter(user=self.user, course_id=self.course_key).count(),
1
)
def test_get_attempts(self):
"""
Check verification attempts count against a user for a given
"""Check verification attempts count against a user for a given
'checkpoint' and 'course_id'.
"""
checkpoint_name = 'final_term'
reverification_service = ReverificationService()
course_id = unicode(self.course_key)
self.assertEqual(
reverification_service.get_attempts(self.user.id, course_id, checkpoint_name, location_id=None),
reverification_service.get_attempts(self.user.id, course_id, self.final_checkpoint_location),
0
)
# now create a checkpoint and add user's entry against it then test
# that the 'get_attempts' service method returns count accordingly
checkpoint_obj = VerificationCheckpoint.objects.create(course_id=course_id, checkpoint_name=checkpoint_name)
# that the 'get_attempts' service method returns correct count
checkpoint_obj = VerificationCheckpoint.objects.create(
course_id=course_id,
checkpoint_location=self.final_checkpoint_location
)
VerificationStatus.objects.create(checkpoint=checkpoint_obj, user=self.user, status='submitted')
self.assertEqual(
reverification_service.get_attempts(self.user.id, course_id, checkpoint_name, location_id=None),
reverification_service.get_attempts(self.user.id, course_id, self.final_checkpoint_location),
1
)
......@@ -2,42 +2,41 @@
"""
Tests of verify_student views.
"""
import json
import urllib
from datetime import timedelta, datetime
from uuid import uuid4
from django.test.utils import override_settings
import ddt
import httpretty
import mock
from mock import patch, Mock, ANY
from django.utils import timezone
import pytz
import ddt
from django.test.client import Client
from django.test import TestCase
from bs4 import BeautifulSoup
from mock import patch, Mock, ANY
from django.conf import settings
from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.core import mail
import httpretty
from bs4 import BeautifulSoup
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.factories import check_mongo_calls
from django.test import TestCase
from django.test.client import Client
from django.test.utils import override_settings
from django.utils import timezone
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.locator import CourseLocator
from microsite_configuration import microsite
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from commerce.tests import TEST_PAYMENT_DATA, TEST_API_URL, TEST_API_SIGNING_KEY
from embargo.test_utils import restrict_course
from microsite_configuration import microsite
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
from shoppingcart.models import Order, CertificateItem
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from student.models import CourseEnrollment
from course_modes.tests.factories import CourseModeFactory
from course_modes.models import CourseMode
from shoppingcart.models import Order, CertificateItem
from embargo.test_utils import restrict_course
from util.date_utils import get_default_time_display
from util.testing import UrlResetMixin
from verify_student.views import (
checkout_with_ecommerce_service,
......@@ -48,7 +47,11 @@ from verify_student.models import (
SoftwareSecurePhotoVerification, VerificationCheckpoint,
InCourseReverificationConfiguration, VerificationStatus
)
from util.date_utils import get_default_time_display
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.factories import check_mongo_calls
def mock_render_to_response(*args, **kwargs):
......@@ -1598,16 +1601,9 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
mock_send_email.assert_called_once_with(self.user.id, subject, ANY)
def create_reverification_xblock(self):
""" Create the reverification xblock
"""
# Create checkpoint
checkpoint = VerificationCheckpoint(course_id=self.course_id, checkpoint_name="midterm")
checkpoint.save()
# Add a re-verification attempt
checkpoint.add_verification_attempt(self.attempt)
Create the reverification XBlock.
"""
# Create the 'edx-reverification-block' in course tree
section = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
subsection = ItemFactory.create(parent=section, category='sequential', display_name='Test Subsection')
......@@ -1618,13 +1614,20 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
display_name='Test Verification Block'
)
# Create checkpoint
checkpoint = VerificationCheckpoint(course_id=self.course_id, checkpoint_location=reverification.location)
checkpoint.save()
# Add a re-verification attempt
checkpoint.add_verification_attempt(self.attempt)
# Add a re-verification attempt status for the user
VerificationStatus.add_verification_status(checkpoint, self.user, "submitted", reverification.location)
VerificationStatus.add_verification_status(checkpoint, self.user, "submitted")
class TestReverifyView(ModuleStoreTestCase):
"""
Tests for the reverification views
Tests for the reverification views.
"""
def setUp(self):
super(TestReverifyView, self).setUp()
......@@ -1675,7 +1678,6 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
Tests for the incourse reverification views.
"""
IMAGE_DATA = "abcd,1234"
MIDTERM = "midterm"
def build_course(self):
"""
......@@ -1695,7 +1697,7 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
section = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
subsection = ItemFactory.create(parent=section, category='sequential', display_name='Test Subsection')
vertical = ItemFactory.create(parent=subsection, category='vertical', display_name='Test Unit')
reverification = ItemFactory.create(
self.reverification = ItemFactory.create(
parent=vertical,
category='edx-reverification-block',
display_name='Test Verification Block'
......@@ -1703,7 +1705,8 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
self.section_location = section.location
self.subsection_location = subsection.location
self.vertical_location = vertical.location
self.reverification_location = reverification.location
self.reverification_location = unicode(self.reverification.location)
self.reverification_assessment = self.reverification.related_assessment
def setUp(self):
super(TestInCourseReverifyView, self).setUp()
......@@ -1727,12 +1730,12 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
def test_incourse_reverify_feature_flag_get(self):
self.config.enabled = False
self.config.save()
response = self.client.get(self._get_url(self.course_key, self.MIDTERM))
response = self.client.get(self._get_url(self.course_key, self.reverification_location))
self.assertEquals(response.status_code, 404)
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
def test_incourse_reverify_invalid_course_get(self):
response = self.client.get(self._get_url("invalid/course/key", self.MIDTERM))
response = self.client.get(self._get_url("invalid/course/key", self.reverification_location))
self.assertEquals(response.status_code, 404)
......@@ -1744,7 +1747,7 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
def test_incourse_reverify_initial_redirect_get(self):
self._create_checkpoint()
response = self.client.get(self._get_url(self.course_key, self.MIDTERM))
response = self.client.get(self._get_url(self.course_key, self.reverification_location))
url = reverse('verify_student_verify_now', kwargs={"course_id": unicode(self.course_key)})
self.assertRedirects(response, url)
......@@ -1752,20 +1755,24 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
@override_settings(SEGMENT_IO_LMS_KEY="foobar")
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True, 'SEGMENT_IO_LMS': True})
def test_incourse_reverify_get(self):
"""
Test incourse reverification.
"""
self._create_checkpoint()
self._create_initial_verification()
response = self.client.get(self._get_url(self.course_key, self.MIDTERM))
response = self.client.get(self._get_url(self.course_key, self.reverification_location))
self.assertEquals(response.status_code, 200)
#Verify Google Analytics event fired after successfully submiting the picture
# verify that Google Analytics event fires after successfully
# submitting the photo verification
self.mock_tracker.track.assert_called_once_with( # pylint: disable=no-member
self.user.id, # pylint: disable=no-member
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW,
{
'category': "verification",
'label': unicode(self.course_key),
'checkpoint': self.MIDTERM
'checkpoint': self.reverification_assessment
},
context={
......@@ -1776,22 +1783,19 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
self.mock_tracker.reset_mock()
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
@patch('verify_student.views.render_to_response', render_mock)
def test_invalid_checkpoint_post(self):
response = self.client.post(self._get_url(self.course_key, self.MIDTERM))
self.assertEquals(response.status_code, 200)
((template, context), _kwargs) = render_mock.call_args # pylint: disable=unpacking-non-sequence
self.assertIn('incourse_reverify', template)
self.assertTrue(context['error'])
def test_checkpoint_post(self):
"""Verify that POST requests including an invalid checkpoint location
results in a 400 response.
"""
response = self.client.post(self._get_url(self.course_key, self.reverification_location))
self.assertEquals(response.status_code, 400)
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
def test_incourse_reverify_initial_redirect_post(self):
self._create_checkpoint()
response = self.client.post(self._get_url(self.course_key, self.reverification_location))
response = self.client.post(self._get_url(self.course_key, self.MIDTERM))
url = reverse('verify_student_verify_now', kwargs={"course_id": unicode(self.course_key)})
self.assertRedirects(response, url)
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
......@@ -1799,7 +1803,7 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
self._create_checkpoint()
self._create_initial_verification()
response = self.client.post(self._get_url(self.course_key, self.MIDTERM), {"face_image": ""})
response = self.client.post(self._get_url(self.course_key, self.reverification_location), {"face_image": ""})
self.assertEqual(response.status_code, 400)
@override_settings(SEGMENT_IO_LMS_KEY="foobar")
......@@ -1808,19 +1812,22 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
self._create_checkpoint()
self._create_initial_verification()
response = self.client.post(self._get_url(self.course_key, self.MIDTERM), {"face_image": self.IMAGE_DATA})
response = self.client.post(
self._get_url(self.course_key, self.reverification_location),
{"face_image": self.IMAGE_DATA}
)
self.assertEqual(response.status_code, 200)
#Verify Google Analytics event fired after successfully submiting the picture
# test that Google Analytics event firs after successfully submitting
# photo verification
self.mock_tracker.track.assert_called_once_with( # pylint: disable=no-member
self.user.id, # pylint: disable=no-member
self.user.id,
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY,
{
'category': "verification",
'label': unicode(self.course_key),
'checkpoint': self.MIDTERM
'checkpoint': self.reverification_assessment
},
context={
'Google Analytics':
{'clientId': None}
......@@ -1833,37 +1840,43 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
self.config.enabled = False
self.config.save()
response = self.client.post(self._get_url(self.course_key, self.MIDTERM))
response = self.client.post(self._get_url(self.course_key, self.reverification_location))
self.assertEquals(response.status_code, 404)
def _create_checkpoint(self):
"""helper method for creating checkpoint"""
checkpoint = VerificationCheckpoint(course_id=self.course_key, checkpoint_name=self.MIDTERM)
"""
Helper method for creating a reverification checkpoint.
"""
checkpoint = VerificationCheckpoint(course_id=self.course_key, checkpoint_location=self.reverification_location)
checkpoint.save()
def _create_initial_verification(self):
"""helper method for initial verification"""
"""
Helper method for initial verification.
"""
attempt = SoftwareSecurePhotoVerification(user=self.user)
attempt.mark_ready()
attempt.save()
attempt.submit()
def _get_url(self, course_key, checkpoint):
"""contruct the url.
def _get_url(self, course_key, checkpoint_location):
"""
Construct the reverification url.
Arguments:
course_key (unicode): The ID of the course
checkpoint_location (str): Location of verification checkpoint
Arguments:
course_key (unicode): The ID of the course.
checkpoint (str): The verification checkpoint
Returns:
url
"""
return reverse('verify_student_incourse_reverify',
kwargs={
"course_id": unicode(course_key),
"checkpoint_name": checkpoint,
"usage_id": unicode(self.reverification_location)
})
return reverse(
'verify_student_incourse_reverify',
kwargs={
"course_id": unicode(course_key),
"usage_id": checkpoint_location
}
)
class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
......@@ -1875,8 +1888,6 @@ class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
"""
Build up a course tree with a Reverificaiton xBlock.
"""
# pylint: disable=attribute-defined-outside-init
self.course_key = SlashSeparatedCourseKey("Robot", "999", "Test_Course")
self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
self.due_date = datetime(2015, 6, 22, tzinfo=pytz.UTC)
......@@ -1901,40 +1912,41 @@ class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
self.section_location = section.location
self.subsection_location = subsection.location
self.vertical_location = vertical.location
self.reverification_location = self.reverification.location
self.assessment = "midterm"
self.reverification_location = unicode(self.reverification.location)
self.assessment = self.reverification.related_assessment
self.re_verification_link = reverse(
'verify_student_incourse_reverify',
args=(
unicode(self.course_key),
unicode(self.assessment),
unicode(self.reverification_location)
self.reverification_location
)
)
def setUp(self):
"""
Setup method for testing photo verification email messages.
"""
super(TestEmailMessageWithCustomICRVBlock, self).setUp()
self.build_course()
self.check_point = VerificationCheckpoint.objects.create(
course_id=self.course.id, checkpoint_name=self.assessment
course_id=self.course.id, checkpoint_location=self.reverification_location
)
self.check_point.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
VerificationStatus.add_verification_status(
checkpoint=self.check_point,
user=self.user,
status='submitted',
location_id=self.reverification_location
status='submitted'
)
self.attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)
def test_approved_email_message(self):
"""
Test email message for approved photo verification.
"""
subject, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "approved", True
self.course.id, self.user.id, self.reverification_location, "approved", True
)
self.assertIn(
......@@ -1944,13 +1956,12 @@ class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
),
body
)
self.assertIn("Re-verification Status", subject)
def test_denied_email_message_with_valid_due_date_and_attempts_allowed(self):
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
self.course.id, self.user.id, self.reverification_location, "denied", True
)
self.assertIn(
......@@ -1975,7 +1986,7 @@ class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
return_value = datetime(2016, 1, 1, tzinfo=timezone.utc)
with patch.object(timezone, 'now', return_value=return_value):
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
self.course.id, self.user.id, self.reverification_location, "denied", True
)
self.assertIn(
......@@ -1991,7 +2002,7 @@ class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
def test_check_num_queries(self):
# Get the re-verification block to check the call made
with check_mongo_calls(2):
ver_block = modulestore().get_item(self.reverification_location)
ver_block = modulestore().get_item(self.reverification.location)
# Expect that the verification block is fetched
self.assertIsNotNone(ver_block)
......@@ -2006,8 +2017,6 @@ class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
"""
Build up a course tree with a Reverificaiton xBlock.
"""
# pylint: disable=attribute-defined-outside-init
self.course_key = SlashSeparatedCourseKey("Robot", "999", "Test_Course")
self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
......@@ -2030,15 +2039,14 @@ class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
self.section_location = section.location
self.subsection_location = subsection.location
self.vertical_location = vertical.location
self.reverification_location = self.reverification.location
self.assessment = "midterm"
self.reverification_location = unicode(self.reverification.location)
self.assessment = self.reverification.related_assessment
self.re_verification_link = reverse(
'verify_student_incourse_reverify',
args=(
unicode(self.course_key),
unicode(self.assessment),
unicode(self.reverification_location)
self.reverification_location
)
)
......@@ -2047,7 +2055,7 @@ class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
self.build_course()
self.check_point = VerificationCheckpoint.objects.create(
course_id=self.course.id, checkpoint_name=self.assessment
course_id=self.course.id, checkpoint_location=self.reverification_location
)
self.check_point.add_verification_attempt(SoftwareSecurePhotoVerification.objects.create(user=self.user))
self.attempt = SoftwareSecurePhotoVerification.objects.filter(user=self.user)
......@@ -2057,12 +2065,11 @@ class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
VerificationStatus.add_verification_status(
checkpoint=self.check_point,
user=self.user,
status='submitted',
location_id=self.reverification_location
status='submitted'
)
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
self.course.id, self.user.id, self.reverification_location, "denied", True
)
self.assertIn(
......@@ -2082,11 +2089,10 @@ class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
VerificationStatus.add_verification_status(
checkpoint=self.check_point,
user=self.user,
status='submitted',
location_id=self.reverification_location
status='submitted'
)
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
self.course.id, self.user.id, self.reverification_location, "denied", True
)
self.assertIn(
......@@ -2104,12 +2110,11 @@ class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
VerificationStatus.add_verification_status(
checkpoint=self.check_point,
user=self.user,
status='error',
location_id=self.reverification_location
status='error'
)
__, body = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
self.course.id, self.user.id, self.reverification_location, "denied", True
)
self.assertIn(
......@@ -2131,6 +2136,6 @@ class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
def test_error_on_compose_email(self):
resp = _compose_message_reverification_email(
self.course.id, self.user.id, "midterm", self.attempt, "denied", True
self.course.id, self.user.id, u'i4x://edX/DemoX/edx-reverification-block/invalid_location', "denied", True
)
self.assertIsNone(resp)
......@@ -116,9 +116,8 @@ urlpatterns = patterns(
# Users are sent to this end-point from within courseware
# to re-verify their identities by re-submitting face photos.
url(
r'^reverify/{course_id}/{checkpoint}/{usage_id}/$'.format(
r'^reverify/{course_id}/{usage_id}/$'.format(
course_id=settings.COURSE_ID_PATTERN,
checkpoint=settings.CHECKPOINT_PATTERN,
usage_id=settings.USAGE_ID_PATTERN
),
views.InCourseReverifyView.as_view(),
......
"""
Views for the verification flow
"""
import datetime
import decimal
import json
import logging
import decimal
import datetime
from collections import namedtuple
from pytz import UTC
from django.utils import timezone
from ipware.ip import get_ip
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
from django.http import (
HttpResponse, HttpResponseBadRequest,
HttpResponseRedirect, Http404
)
from django.contrib.auth.models import User
from django.shortcuts import redirect
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _, ugettext_lazy
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.views.generic.base import View, RedirectView
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.auth.decorators import login_required
from django.core.mail import send_mail
from ecommerce_api_client.exceptions import SlumberBaseException
import analytics
from eventtracking import tracker
from opaque_keys.edx.keys import CourseKey, UsageKey
from opaque_keys import InvalidKeyError
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from commerce import ecommerce_api_client
from course_modes.models import CourseMode
from courseware.url_helpers import get_redirect_url
from ecommerce_api_client.exceptions import SlumberBaseException
from edxmako.shortcuts import render_to_response, render_to_string
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings, update_account_settings
from embargo import api as embargo_api
from microsite_configuration import microsite
from openedx.core.djangoapps.user_api.accounts import NAME_MIN_LENGTH
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings, update_account_settings
from openedx.core.djangoapps.user_api.errors import UserNotFound, AccountValidationError
from commerce import ecommerce_api_client
from course_modes.models import CourseMode
from student.models import CourseEnrollment
from student.views import reverification_info
from shoppingcart.models import Order, CertificateItem
from shoppingcart.processors import (
get_signed_purchase_params, get_purchase_endpoint
)
from verify_student.ssencrypt import has_valid_signature
from verify_student.models import (
SoftwareSecurePhotoVerification,
VerificationCheckpoint,
VerificationStatus,
InCourseReverificationConfiguration)
from reverification.models import MidcourseReverificationWindow
import ssencrypt
from .exceptions import WindowExpiredException
from microsite_configuration import microsite
from embargo import api as embargo_api
InCourseReverificationConfiguration
)
from util.json_request import JsonResponse
from util.date_utils import get_default_time_display
from eventtracking import tracker
import analytics
from courseware.url_helpers import get_redirect_url
from django.contrib.auth.models import User
log = logging.getLogger(__name__)
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
log = logging.getLogger(__name__)
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW = 'edx.bi.reverify.started'
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY = 'edx.bi.reverify.submitted'
......@@ -859,40 +856,40 @@ def submit_photos_for_verification(request):
def _compose_message_reverification_email(
course_key, user_id, relates_assessment, photo_verification, status, is_secure
course_key, user_id, related_assessment_location, status, is_secure
): # pylint: disable=invalid-name
""" Composes subject and message for email
"""
Compose subject and message for photo reverification email.
Args:
course_key(CourseKey): CourseKey object
user_id(str): User Id
relates_assessment(str): related assessment name
photo_verification(QuerySet/SoftwareSecure): A query set of SoftwareSecure objects or SoftwareSecure objec
status(str): approval status
related_assessment_location(str): Location of reverification XBlock
photo_verification(QuerySet): Queryset of SoftwareSecure objects
status(str): Approval status
is_secure(Bool): Is running on secure protocol or not
Returns:
None if any error occurred else Tuple of subject and message strings
"""
try:
location_id = VerificationStatus.get_location_id(photo_verification)
usage_key = UsageKey.from_string(location_id)
usage_key = UsageKey.from_string(related_assessment_location)
reverification_block = modulestore().get_item(usage_key)
course = modulestore().get_course(course_key)
redirect_url = get_redirect_url(course_key, usage_key.replace(course_key=course_key))
subject = "Re-verification Status"
context = {
"status": status,
"course_name": course.display_name_with_default,
"assessment": relates_assessment,
"assessment": reverification_block.related_assessment,
"courseware_url": redirect_url
}
reverification_block = modulestore().get_item(usage_key)
# Allowed attempts is 1 if not set on verification block
allowed_attempts = 1 if reverification_block.attempts == 0 else reverification_block.attempts
user_attempts = VerificationStatus.get_user_attempts(user_id, course_key, relates_assessment, location_id)
user_attempts = VerificationStatus.get_user_attempts(user_id, course_key, related_assessment_location)
left_attempts = allowed_attempts - user_attempts
is_attempt_allowed = left_attempts > 0
verification_open = True
......@@ -911,8 +908,7 @@ def _compose_message_reverification_email(
'verify_student_incourse_reverify',
args=(
unicode(course_key),
unicode(relates_assessment),
unicode(location_id)
related_assessment_location
)
)
context["reverify_link"] = re_verification_link
......@@ -972,7 +968,7 @@ def results_callback(request):
"Date": request.META.get("HTTP_DATE", "")
}
sig_valid = ssencrypt.has_valid_signature(
has_valid_signature(
"POST",
headers,
body_dict,
......@@ -1029,10 +1025,10 @@ def results_callback(request):
if checkpoints:
user_id = attempt.user.id
course_key = checkpoints[0].course_id
relates_assessment = checkpoints[0].checkpoint_name
related_assessment_location = checkpoints[0].checkpoint_location
subject, message = _compose_message_reverification_email(
course_key, user_id, relates_assessment, attempt, status, request.is_secure()
course_key, user_id, related_assessment_location, status, request.is_secure()
)
_send_email(user_id, subject, message)
......@@ -1122,11 +1118,20 @@ class InCourseReverifyView(View):
Does not need to worry about pricing
"""
@method_decorator(login_required)
def get(self, request, course_id, checkpoint_name, usage_id):
""" Display the view for face photo submission"""
# Check the in-course re-verification is enabled or not
def get(self, request, course_id, usage_id):
"""Display the view for face photo submission.
Args:
request(HttpRequest): HttpRequest object
course_id(str): A string of course id
usage_id(str): Location of Reverification XBlock in courseware
Returns:
HttpResponse
"""
# Check that in-course re-verification is enabled or not
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
if not incourse_reverify_enabled:
log.error(
u"In-course reverification is not enabled. "
......@@ -1139,49 +1144,49 @@ class InCourseReverifyView(View):
course_key = CourseKey.from_string(course_id)
course = modulestore().get_course(course_key)
if course is None:
log.error(u"Could not find course %s for in-course reverification.", course_key)
log.error(u"Could not find course '%s' for in-course reverification.", course_key)
raise Http404
checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name)
checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, usage_id)
if checkpoint is None:
log.error(
u"No verification checkpoint exists for the "
u"course %s and checkpoint name %s.",
course_key, checkpoint_name
u"course '%s' and checkpoint location '%s'.",
course_key, usage_id
)
raise Http404
init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
if not init_verification:
initial_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
if not initial_verification:
return self._redirect_no_initial_verification(user, course_key)
# emit the reverification event
self._track_reverification_events(
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, user.id, course_id, checkpoint_name
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, user.id, course_id, checkpoint.checkpoint_name
)
context = {
'course_key': unicode(course_key),
'course_name': course.display_name_with_default,
'checkpoint_name': checkpoint_name,
'checkpoint_name': checkpoint.checkpoint_name,
'platform_name': settings.PLATFORM_NAME,
'usage_id': usage_id
}
return render_to_response("verify_student/incourse_reverify.html", context)
@method_decorator(login_required)
def post(self, request, course_id, checkpoint_name, usage_id):
"""Submits the re-verification attempt to SoftwareSecure
def post(self, request, course_id, usage_id):
"""Submits the re-verification attempt to SoftwareSecure.
Args:
request(HttpRequest): HttpRequest object
course_id(str): Course Id
checkpoint_name(str): Checkpoint name
usage_id(str): Location of Reverification XBlock in courseware
Returns:
HttpResponse with status_code 400 if photo is missing or any error
or redirect to the verification flow if initial verification doesn't exist otherwise
HttpsResponse with status code 200
or redirect to the verification flow if initial verification
doesn't exist otherwise HttpResponse with status code 200
"""
# Check the in-course re-verification is enabled or not
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
......@@ -1196,20 +1201,18 @@ class InCourseReverifyView(View):
raise Http404(u"Invalid course_key or usage_key")
course = modulestore().get_course(course_key)
checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name)
if course is None:
log.error(u"Invalid course id '%s'", course_id)
return HttpResponseBadRequest(_("Invalid course location."))
checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, usage_id)
if checkpoint is None:
log.error("Checkpoint is not defined. Could not submit verification attempt for user %s",
request.user.id)
context = {
'course_key': unicode(course_key),
'course_name': course.display_name_with_default,
'checkpoint_name': checkpoint_name,
'error': True,
'errorMsg': _("No checkpoint found"),
'platform_name': settings.PLATFORM_NAME,
'usage_id': usage_id
}
return render_to_response("verify_student/incourse_reverify.html", context)
log.error(
u"Checkpoint is not defined. Could not submit verification attempt"
u" for user '%s', course '%s' and checkpoint location '%s'.",
request.user.id, course_key, usage_id
)
return HttpResponseBadRequest(_("Invalid checkpoint location."))
init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
if not init_verification:
......@@ -1220,47 +1223,51 @@ class InCourseReverifyView(View):
request.user, request.POST['face_image'], init_verification.photo_id_key
)
checkpoint.add_verification_attempt(attempt)
VerificationStatus.add_verification_status(checkpoint, user, "submitted", usage_id)
VerificationStatus.add_verification_status(checkpoint, user, "submitted")
# emit the reverification event
self._track_reverification_events(
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, user.id, course_id, checkpoint_name
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, user.id, course_id, checkpoint.checkpoint_name
)
try:
redirect_url = get_redirect_url(course_key, usage_key)
except (ItemNotFoundError, NoPathToItem):
log.warning(
u"Could not find redirect URL for location %s in course %s",
course_key, usage_key
)
redirect_url = reverse("courseware", args=(unicode(course_key),))
redirect_url = get_redirect_url(course_key, usage_key)
response = JsonResponse({'url': redirect_url})
except (ItemNotFoundError, NoPathToItem):
log.warning(u"Could not find redirect URL for location %s in course %s", course_key, usage_key)
redirect_url = reverse("courseware", args=(unicode(course_key),))
response = JsonResponse({'url': redirect_url})
return JsonResponse({'url': redirect_url})
except Http404 as expt:
log.exception("Invalid location during photo verification.")
return HttpResponseBadRequest(expt.message)
response = HttpResponseBadRequest(expt.message)
except IndexError:
log.exception("Invalid image data during photo verification.")
return HttpResponseBadRequest(_("Invalid image data during photo verification."))
response = HttpResponseBadRequest(_("Invalid image data during photo verification."))
except Exception: # pylint: disable=broad-except
log.exception("Could not submit verification attempt for user %s.", request.user.id)
msg = _("Could not submit photos")
return HttpResponseBadRequest(msg)
response = HttpResponseBadRequest(msg)
return response
def _track_reverification_events(self, event_name, user_id, course_id, checkpoint): # pylint: disable=invalid-name
"""Track re-verification events for user against course checkpoints
"""Track re-verification events for a user against a reverification
checkpoint of a course.
Arguments:
user_id (str): The ID of the user generting the certificate.
course_id (unicode): id associated with the course
checkpoint (str): checkpoint name
event_name (str): Name of event being tracked
user_id (str): The ID of the user
course_id (unicode): ID associated with the course
checkpoint (str): Checkpoint name
Returns:
None
"""
log.info(
u"In-course reverification: event %s occurred for user %s in course %s at checkpoint %s",
u"In-course reverification: event %s occurred for user '%s' in course '%s' at checkpoint '%s'",
event_name, user_id, course_id, checkpoint
)
......
......@@ -20,7 +20,6 @@
return new edx.verify_student.InCourseReverifyView({
courseKey: el.data('course-key'),
checkpointName: el.data('checkpoint-name'),
platformName: el.data('platform-name'),
usageId: el.data('usage-id'),
errorModel: errorView.model
......
......@@ -16,7 +16,6 @@ var edx = edx || {};
defaults: {
courseKey: '',
checkpointName: '',
faceImage: '',
usageId: ''
},
......@@ -28,9 +27,8 @@ var edx = edx || {};
face_image: model.get( 'faceImage' )
},
url = _.str.sprintf(
'/verify_student/reverify/%(courseKey)s/%(checkpointName)s/%(usageId)s/', {
'/verify_student/reverify/%(courseKey)s/%(usageId)s/', {
courseKey: model.get('courseKey'),
checkpointName: model.get('checkpointName'),
usageId: model.get('usageId')
}
);
......
......@@ -26,14 +26,12 @@
this.errorModel = obj.errorModel || null;
this.courseKey = obj.courseKey || null;
this.checkpointName = obj.checkpointName || null;
this.platformName = obj.platformName || null;
this.usageId = obj.usageId || null;
this.model = new edx.verify_student.ReverificationModel({
courseKey: this.courseKey,
checkpointName: this.checkpointName,
usageId: this.usageId
});
......@@ -47,7 +45,6 @@
$( this.templateId ).html(),
{
courseKey: this.courseKey,
checkpointName: this.checkpointName,
platformName: this.platformName
}
);
......
......@@ -43,7 +43,6 @@ from verify_student.views import PayAndVerifyView
<div id="incourse-reverify-container"
class="incourse-reverify"
data-course-key='${course_key}'
data-checkpoint-name='${checkpoint_name}'
data-platform-name='${platform_name}'
data-usage-id='${usage_id}'
></div>
......
......@@ -50,7 +50,7 @@ git+https://github.com/hmarr/django-debug-toolbar-mongo.git@b0686a76f1ce3532088c
git+https://github.com/edx/edx-lint.git@8bf82a32ecb8598c415413df66f5232ab8d974e9#egg=edx_lint==0.2.1
-e git+https://github.com/edx/xblock-utils.git@db22bc40fd2a75458a3c66d057f88aff5a7383e6#egg=xblock-utils
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
-e git+https://github.com/edx/edx-reverification-block.git@2ff0d21f6614874067168bd244e68d8215041f3b#egg=edx-reverification-block
-e git+https://github.com/edx/edx-reverification-block.git@03da85753d5f563a22c1282c0e89fcb2e828b8c1#egg=edx-reverification-block
git+https://github.com/edx/ecommerce-api-client.git@1.0.0#egg=ecommerce-api-client==1.0.0
# Third Party XBlocks
......
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