Merge pull request #7530 from edx/aamir-khan/ECOM-912-in-course-reverification
Aamir khan/ecom 912 in course reverification
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from ratelimitbackend import admin
|
||||
from verify_student.models import SoftwareSecurePhotoVerification
|
||||
from verify_student.models import SoftwareSecurePhotoVerification, InCourseReverificationConfiguration
|
||||
|
||||
admin.site.register(SoftwareSecurePhotoVerification)
|
||||
admin.site.register(InCourseReverificationConfiguration)
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# -*- 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):
|
||||
# Adding model 'VerificationCheckpoint'
|
||||
db.create_table('verify_student_verificationcheckpoint', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)),
|
||||
('checkpoint_name', self.gf('django.db.models.fields.CharField')(max_length=32)),
|
||||
))
|
||||
db.send_create_signal('verify_student', ['VerificationCheckpoint'])
|
||||
|
||||
# Adding M2M table for field photo_verification on 'VerificationCheckpoint'
|
||||
m2m_table_name = db.shorten_name('verify_student_verificationcheckpoint_photo_verification')
|
||||
db.create_table(m2m_table_name, (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
('verificationcheckpoint', models.ForeignKey(orm['verify_student.verificationcheckpoint'], null=False)),
|
||||
('softwaresecurephotoverification', models.ForeignKey(orm['verify_student.softwaresecurephotoverification'], null=False))
|
||||
))
|
||||
db.create_unique(m2m_table_name, ['verificationcheckpoint_id', 'softwaresecurephotoverification_id'])
|
||||
|
||||
# Adding unique constraint on 'VerificationCheckpoint', fields ['course_id', 'checkpoint_name']
|
||||
db.create_unique('verify_student_verificationcheckpoint', ['course_id', 'checkpoint_name'])
|
||||
|
||||
# Adding model 'VerificationStatus'
|
||||
db.create_table('verify_student_verificationstatus', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('checkpoint', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['verify_student.VerificationCheckpoint'])),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('status', self.gf('django.db.models.fields.CharField')(max_length=32, db_index=True)),
|
||||
('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('response', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
|
||||
('error', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('verify_student', ['VerificationStatus'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'VerificationCheckpoint', fields ['course_id', 'checkpoint_name']
|
||||
db.delete_unique('verify_student_verificationcheckpoint', ['course_id', 'checkpoint_name'])
|
||||
|
||||
# Deleting model 'VerificationCheckpoint'
|
||||
db.delete_table('verify_student_verificationcheckpoint')
|
||||
|
||||
# Removing M2M table for field photo_verification on 'VerificationCheckpoint'
|
||||
db.delete_table(db.shorten_name('verify_student_verificationcheckpoint_photo_verification'))
|
||||
|
||||
# Deleting model 'VerificationStatus'
|
||||
db.delete_table('verify_student_verificationstatus')
|
||||
|
||||
|
||||
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.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': "'4f091843-1377-4d3b-af5d-3a4ae3d17943'", '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_name'),)", 'object_name': 'VerificationCheckpoint'},
|
||||
'checkpoint_name': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
|
||||
'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', [], {'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']
|
||||
@@ -0,0 +1,118 @@
|
||||
# -*- 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):
|
||||
# Adding model 'InCourseReverificationConfiguration'
|
||||
db.create_table('verify_student_incoursereverificationconfiguration', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
|
||||
('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)),
|
||||
('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
))
|
||||
db.send_create_signal('verify_student', ['InCourseReverificationConfiguration'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'InCourseReverificationConfiguration'
|
||||
db.delete_table('verify_student_incoursereverificationconfiguration')
|
||||
|
||||
|
||||
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.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': "'8a3c9d8a-b885-480e-8e1e-ca111326db42'", '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_name'),)", 'object_name': 'VerificationCheckpoint'},
|
||||
'checkpoint_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
|
||||
'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', [], {'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']
|
||||
|
||||
@@ -35,8 +35,11 @@ from verify_student.ssencrypt import (
|
||||
|
||||
from reverification.models import MidcourseReverificationWindow
|
||||
|
||||
from xmodule_django.models import CourseKeyField
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from config_models.models import ConfigurationModel
|
||||
|
||||
|
||||
def generateUUID(): # pylint: disable=invalid-name
|
||||
""" Utility function; generates UUIDs """
|
||||
@@ -640,6 +643,17 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
query = cls.objects.filter(user=user, window=None).order_by('-updated_at')
|
||||
return query[0]
|
||||
|
||||
@classmethod
|
||||
def get_initial_verification(cls, user):
|
||||
"""Get initial verification for a user
|
||||
Arguments:
|
||||
user(User): user object
|
||||
Return:
|
||||
SoftwareSecurePhotoVerification (object)
|
||||
"""
|
||||
init_verification = cls.objects.filter(user=user, status__in=["submitted", "approved"], window=None)
|
||||
return init_verification.latest('created_at') if init_verification.exists() else None
|
||||
|
||||
@status_before_must_be("created")
|
||||
def upload_face_image(self, img_data):
|
||||
"""
|
||||
@@ -881,3 +895,131 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
log.debug("Return message:\n\n{}\n\n".format(response.text))
|
||||
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def submit_faceimage(cls, user, face_image, photo_id_key):
|
||||
"""Submit the faceimage to SoftwareSecurePhotoVerification
|
||||
|
||||
Arguments:
|
||||
user(User): user object
|
||||
face_image (bytestream): raw bytestream image data
|
||||
photo_id_key (str) : SoftwareSecurePhotoVerification attribute
|
||||
Returns:
|
||||
SoftwareSecurePhotoVerification Object
|
||||
"""
|
||||
b64_face_image = face_image.split(",")[1]
|
||||
attempt = SoftwareSecurePhotoVerification(user=user)
|
||||
attempt.upload_face_image(b64_face_image.decode('base64'))
|
||||
attempt.photo_id_key = photo_id_key
|
||||
attempt.mark_ready()
|
||||
attempt.save()
|
||||
attempt.submit()
|
||||
return attempt
|
||||
|
||||
|
||||
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"),
|
||||
)
|
||||
|
||||
course_id = CourseKeyField(max_length=255, db_index=True)
|
||||
checkpoint_name = models.CharField(max_length=32, choices=CHECKPOINT_CHOICES)
|
||||
photo_verification = models.ManyToManyField(SoftwareSecurePhotoVerification)
|
||||
|
||||
class Meta: # pylint: disable=missing-docstring, old-style-class
|
||||
unique_together = (('course_id', 'checkpoint_name'),)
|
||||
|
||||
def add_verification_attempt(self, verification_attempt):
|
||||
""" Add the verification attempt in M2M relation of photo_verification
|
||||
|
||||
Arguments:
|
||||
verification_attempt(SoftwareSecurePhotoVerification): SoftwareSecurePhotoVerification object
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.photo_verification.add(verification_attempt) # pylint: disable=no-member
|
||||
|
||||
@classmethod
|
||||
def get_verification_checkpoint(cls, course_id, checkpoint_name):
|
||||
"""Get the verification checkpoint for given course_id and checkpoint name
|
||||
|
||||
Arguments:
|
||||
course_id(CourseKey): CourseKey
|
||||
checkpoint_name(str): checkpoint name
|
||||
|
||||
Returns:
|
||||
VerificationCheckpoint object if exists otherwise None
|
||||
"""
|
||||
try:
|
||||
return cls.objects.get(course_id=course_id, checkpoint_name=checkpoint_name)
|
||||
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
|
||||
"""
|
||||
|
||||
VERIFICATION_STATUS_CHOICES = (
|
||||
("submitted", "submitted"),
|
||||
("approved", "approved"),
|
||||
("denied", "denied"),
|
||||
("error", "error")
|
||||
)
|
||||
|
||||
checkpoint = models.ForeignKey(VerificationCheckpoint)
|
||||
user = models.ForeignKey(User)
|
||||
status = models.CharField(choices=VERIFICATION_STATUS_CHOICES, db_index=True, max_length=32)
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
response = models.TextField(null=True, blank=True)
|
||||
error = models.TextField(null=True, blank=True)
|
||||
|
||||
@classmethod
|
||||
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
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
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
|
||||
|
||||
Arguments:
|
||||
checkpoints(list): list of VerificationCheckpoint objects
|
||||
user(User): user object
|
||||
status(str): String representing the status from VERIFICATION_STATUS_CHOICES
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
for checkpoint in checkpoints:
|
||||
cls.objects.create(checkpoint=checkpoint, user=user, status=status)
|
||||
|
||||
|
||||
class InCourseReverificationConfiguration(ConfigurationModel):
|
||||
"""Configure in-course re-verification.
|
||||
|
||||
Enable or disable in-course re-verification feature.
|
||||
When this flag is disabled, the "in-course re-verification" feature
|
||||
will be disabled.
|
||||
|
||||
When the flag is enabled, the "in-course re-verification" feature
|
||||
will be enabled.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import timedelta, datetime
|
||||
import ddt
|
||||
import json
|
||||
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
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
@@ -16,7 +18,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from verify_student.models import (
|
||||
SoftwareSecurePhotoVerification, VerificationException,
|
||||
SoftwareSecurePhotoVerification, VerificationException, VerificationCheckpoint, VerificationStatus
|
||||
)
|
||||
|
||||
FAKE_SETTINGS = {
|
||||
@@ -599,3 +601,111 @@ class TestMidcourseReverification(ModuleStoreTestCase):
|
||||
attempt.status = "approved"
|
||||
attempt.save()
|
||||
assert_true(SoftwareSecurePhotoVerification.user_has_valid_or_pending(user=self.user, window=window))
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
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()
|
||||
|
||||
@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.
|
||||
"""
|
||||
# create the VerificationCheckpoint checkpoint
|
||||
ver_check_point = VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=check_point)
|
||||
self.assertEqual(
|
||||
VerificationCheckpoint.get_verification_checkpoint(self.course.id, check_point),
|
||||
ver_check_point
|
||||
)
|
||||
|
||||
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.
|
||||
"""
|
||||
# create the VerificationCheckpoint checkpoint
|
||||
VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=self.MIDTERM)
|
||||
|
||||
# get verification for not existing checkpoint
|
||||
self.assertEqual(VerificationCheckpoint.get_verification_checkpoint(self.course.id, 'abc'), None)
|
||||
|
||||
def test_unique_together_constraint(self):
|
||||
"""testing the unique together contraint.
|
||||
"""
|
||||
# create the VerificationCheckpoint checkpoint
|
||||
VerificationCheckpoint.objects.create(course_id=self.course.id, checkpoint_name=self.MIDTERM)
|
||||
|
||||
# create 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)
|
||||
|
||||
def test_add_verification_attempt_software_secure(self):
|
||||
"""testing manytomany relationship. adding softwaresecure attempt to the verification 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)
|
||||
|
||||
# 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 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 new attempt and adding to the checkpoint2
|
||||
attempt = SoftwareSecurePhotoVerification.objects.create(user=self.user)
|
||||
check_point2.add_verification_attempt(attempt)
|
||||
self.assertEqual(check_point2.photo_verification.count(), 1)
|
||||
|
||||
# remove the attempt from checkpoint2
|
||||
check_point2.photo_verification.remove(attempt)
|
||||
self.assertEqual(check_point2.photo_verification.count(), 0)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class VerificationStatusTest(ModuleStoreTestCase):
|
||||
"""Tests for the VerificationStatus model. """
|
||||
|
||||
def setUp(self):
|
||||
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")
|
||||
|
||||
@ddt.data('submitted', "approved", "denied", "error")
|
||||
def test_add_verification_status(self, status):
|
||||
"""adding verfication status using the class method."""
|
||||
|
||||
# adding verification status
|
||||
VerificationStatus.add_verification_status(checkpoint=self.check_point1, user=self.user, status=status)
|
||||
|
||||
# getting the status from db
|
||||
result = VerificationStatus.objects.filter(checkpoint=self.check_point1)[0]
|
||||
self.assertEqual(result.status, status)
|
||||
self.assertEqual(result.user, self.user)
|
||||
|
||||
@ddt.data('submitted', "approved", "denied", "error")
|
||||
def test_add_status_from_checkpoints(self, status):
|
||||
"""adding verfication status for checkpoints list."""
|
||||
|
||||
# adding verification status with multiple points
|
||||
VerificationStatus.add_status_from_checkpoints(
|
||||
checkpoints=[self.check_point1, self.check_point2], user=self.user, status=status
|
||||
)
|
||||
|
||||
# getting the status from db.
|
||||
result = VerificationStatus.objects.filter(user=self.user)
|
||||
self.assertEqual(len(result), len([self.check_point1.checkpoint_name, self.check_point2.checkpoint_name]))
|
||||
self.assertEqual(result[0].checkpoint.checkpoint_name, self.check_point1.checkpoint_name)
|
||||
self.assertEqual(result[1].checkpoint.checkpoint_name, self.check_point2.checkpoint_name)
|
||||
|
||||
@@ -36,8 +36,14 @@ from course_modes.models import CourseMode
|
||||
from shoppingcart.models import Order, CertificateItem
|
||||
from embargo.test_utils import restrict_course
|
||||
from util.testing import UrlResetMixin
|
||||
from verify_student.views import render_to_response, PayAndVerifyView
|
||||
from verify_student.models import SoftwareSecurePhotoVerification
|
||||
from verify_student.views import (
|
||||
render_to_response, PayAndVerifyView, EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW,
|
||||
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY
|
||||
)
|
||||
from verify_student.models import (
|
||||
SoftwareSecurePhotoVerification, VerificationCheckpoint,
|
||||
InCourseReverificationConfiguration
|
||||
)
|
||||
from reverification.tests.factories import MidcourseReverificationWindowFactory
|
||||
|
||||
|
||||
@@ -1686,3 +1692,175 @@ class TestReverificationBanner(ModuleStoreTestCase):
|
||||
self.client.post(reverse('verify_student_toggle_failed_banner_off'))
|
||||
photo_verification = SoftwareSecurePhotoVerification.objects.get(user=self.user, window=self.window)
|
||||
self.assertFalse(photo_verification.display)
|
||||
|
||||
|
||||
class TestInCourseReverifyView(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the incourse reverification views.
|
||||
"""
|
||||
IMAGE_DATA = "abcd,1234"
|
||||
MIDTERM = "midterm"
|
||||
|
||||
def setUp(self):
|
||||
super(TestInCourseReverifyView, self).setUp()
|
||||
|
||||
self.user = UserFactory.create(username="rusty", password="test")
|
||||
self.client.login(username="rusty", password="test")
|
||||
|
||||
self.course_key = SlashSeparatedCourseKey("Robot", "999", "Test_Course")
|
||||
CourseFactory.create(org='Robot', number='999', display_name='Test Course')
|
||||
|
||||
# Create the course modes
|
||||
for mode in ('audit', 'honor', 'verified'):
|
||||
min_price = 0 if mode in ["honor", "audit"] else 1
|
||||
CourseModeFactory(mode_slug=mode, course_id=self.course_key, min_price=min_price)
|
||||
|
||||
# Enroll the user in the default mode (honor) to emulate
|
||||
CourseEnrollment.enroll(self.user, self.course_key, mode="verified")
|
||||
self.config = InCourseReverificationConfiguration(enabled=True)
|
||||
self.config.save()
|
||||
|
||||
# mocking and patching for bi events
|
||||
analytics_patcher = patch('verify_student.views.analytics')
|
||||
self.mock_tracker = analytics_patcher.start()
|
||||
self.addCleanup(analytics_patcher.stop)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
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))
|
||||
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))
|
||||
|
||||
self.assertEquals(response.status_code, 404)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
def test_incourse_reverify_invalid_checkpoint_get(self):
|
||||
response = self.client.get(self._get_url(self.course_key, "invalid_checkpoint"))
|
||||
self.assertEquals(response.status_code, 404)
|
||||
|
||||
@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))
|
||||
|
||||
url = reverse('verify_student_verify_later',
|
||||
kwargs={"course_id": unicode(self.course_key)})
|
||||
self.assertRedirects(response, url)
|
||||
|
||||
@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):
|
||||
self._create_checkpoint()
|
||||
self._create_initial_verification()
|
||||
|
||||
response = self.client.get(self._get_url(self.course_key, self.MIDTERM))
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
#Verify Google Analytics event fired after successfully submiting the picture
|
||||
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
|
||||
},
|
||||
|
||||
context={
|
||||
'Google Analytics':
|
||||
{'clientId': None}
|
||||
}
|
||||
)
|
||||
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'])
|
||||
|
||||
@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.MIDTERM))
|
||||
url = reverse('verify_student_verify_later',
|
||||
kwargs={"course_id": unicode(self.course_key)})
|
||||
|
||||
self.assertRedirects(response, url)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
def test_incourse_reverify_index_error_post(self):
|
||||
self._create_checkpoint()
|
||||
self._create_initial_verification()
|
||||
|
||||
response = self.client.post(self._get_url(self.course_key, self.MIDTERM), {"face_image": ""})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@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_post(self):
|
||||
self._create_checkpoint()
|
||||
self._create_initial_verification()
|
||||
|
||||
response = self.client.post(self._get_url(self.course_key, self.MIDTERM), {"face_image": self.IMAGE_DATA})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
#Verify Google Analytics event fired after successfully submiting the picture
|
||||
self.mock_tracker.track.assert_called_once_with( # pylint: disable=no-member
|
||||
self.user.id, # pylint: disable=no-member
|
||||
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY,
|
||||
{
|
||||
'category': "verification",
|
||||
'label': unicode(self.course_key),
|
||||
'checkpoint': self.MIDTERM
|
||||
},
|
||||
|
||||
context={
|
||||
'Google Analytics':
|
||||
{'clientId': None}
|
||||
}
|
||||
)
|
||||
self.mock_tracker.reset_mock()
|
||||
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
def test_incourse_reverify_feature_flag_post(self):
|
||||
self.config.enabled = False
|
||||
self.config.save()
|
||||
|
||||
response = self.client.post(self._get_url(self.course_key, self.MIDTERM))
|
||||
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)
|
||||
checkpoint.save()
|
||||
|
||||
def _create_initial_verification(self):
|
||||
"""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.
|
||||
|
||||
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})
|
||||
|
||||
@@ -139,4 +139,14 @@ urlpatterns = patterns(
|
||||
views.submit_photos_for_verification,
|
||||
name="verify_student_submit_photos"
|
||||
),
|
||||
# Endpoint for in-course reverification
|
||||
# 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}/$'.format(
|
||||
course_id=settings.COURSE_ID_PATTERN, checkpoint=settings.CHECKPOINT_PATTERN
|
||||
),
|
||||
views.InCourseReverifyView.as_view(),
|
||||
name="verify_student_incourse_reverify"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -43,7 +43,9 @@ from shoppingcart.processors import (
|
||||
)
|
||||
from verify_student.models import (
|
||||
SoftwareSecurePhotoVerification,
|
||||
)
|
||||
VerificationCheckpoint,
|
||||
VerificationStatus,
|
||||
InCourseReverificationConfiguration)
|
||||
from reverification.models import MidcourseReverificationWindow
|
||||
import ssencrypt
|
||||
from .exceptions import WindowExpiredException
|
||||
@@ -51,6 +53,8 @@ from microsite_configuration import microsite
|
||||
from embargo import api as embargo_api
|
||||
from util.json_request import JsonResponse
|
||||
from util.date_utils import get_default_time_display
|
||||
from eventtracking import tracker
|
||||
import analytics
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -59,6 +63,9 @@ EVENT_NAME_USER_ENTERED_MIDCOURSE_REVERIFY_VIEW = 'edx.course.enrollment.reverif
|
||||
EVENT_NAME_USER_SUBMITTED_MIDCOURSE_REVERIFY = 'edx.course.enrollment.reverify.submitted'
|
||||
EVENT_NAME_USER_REVERIFICATION_REVIEWED_BY_SOFTWARESECURE = 'edx.course.enrollment.reverify.reviewed'
|
||||
|
||||
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW = 'edx.bi.reverify.started'
|
||||
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY = 'edx.bi.reverify.submitted'
|
||||
|
||||
|
||||
class PayAndVerifyView(View):
|
||||
"""View for the "verify and pay" flow.
|
||||
@@ -845,15 +852,20 @@ def results_callback(request):
|
||||
log.error("Software Secure posted back for receipt_id {}, but not found".format(receipt_id))
|
||||
return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))
|
||||
|
||||
checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()
|
||||
|
||||
if result == "PASS":
|
||||
log.debug("Approving verification for {}".format(receipt_id))
|
||||
attempt.approve()
|
||||
status = "approved"
|
||||
elif result == "FAIL":
|
||||
log.debug("Denying verification for {}".format(receipt_id))
|
||||
attempt.deny(json.dumps(reason), error_code=error_code)
|
||||
status = "denied"
|
||||
elif result == "SYSTEM FAIL":
|
||||
log.debug("System failure for {} -- resetting to must_retry".format(receipt_id))
|
||||
attempt.system_error(json.dumps(reason), error_code=error_code)
|
||||
status = "error"
|
||||
log.error("Software Secure callback attempt for %s failed: %s", receipt_id, reason)
|
||||
else:
|
||||
log.error("Software Secure returned unknown result {}".format(result))
|
||||
@@ -866,7 +878,7 @@ def results_callback(request):
|
||||
course_id = attempt.window.course_id
|
||||
course_enrollment = CourseEnrollment.get_or_create_enrollment(attempt.user, course_id)
|
||||
course_enrollment.emit_event(EVENT_NAME_USER_REVERIFICATION_REVIEWED_BY_SOFTWARESECURE)
|
||||
|
||||
VerificationStatus.add_status_from_checkpoints(checkpoints=checkpoints, user=attempt.user, status=status)
|
||||
return HttpResponse("OK!")
|
||||
|
||||
|
||||
@@ -1064,3 +1076,138 @@ def reverification_window_expired(_request):
|
||||
"""
|
||||
# TODO need someone to review the copy for this template
|
||||
return render_to_response("verify_student/reverification_window_expired.html")
|
||||
|
||||
|
||||
class InCourseReverifyView(View):
|
||||
"""
|
||||
The in-course reverification view.
|
||||
Needs to perform these functions:
|
||||
- take new face photo
|
||||
- retrieve the old id photo
|
||||
- submit these photos to photo verification service
|
||||
|
||||
Does not need to worry about pricing
|
||||
"""
|
||||
@method_decorator(login_required)
|
||||
def get(self, request, course_id, checkpoint_name):
|
||||
""" Display the view for face photo submission"""
|
||||
# Check the in-course re-verification is enabled or not
|
||||
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
|
||||
if not incourse_reverify_enabled:
|
||||
raise Http404
|
||||
|
||||
user = request.user
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = modulestore().get_course(course_key)
|
||||
if course is None:
|
||||
raise Http404
|
||||
|
||||
checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name)
|
||||
if checkpoint is None:
|
||||
raise Http404
|
||||
|
||||
init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
|
||||
if not init_verification:
|
||||
return redirect(reverse('verify_student_verify_later', kwargs={'course_id': unicode(course_key)}))
|
||||
|
||||
# emit the reverification event
|
||||
self._track_reverification_events(
|
||||
EVENT_NAME_USER_ENTERED_INCOURSE_REVERIFY_VIEW, user.id, course_id, checkpoint_name
|
||||
)
|
||||
|
||||
context = {
|
||||
'course_key': unicode(course_key),
|
||||
'course_name': course.display_name_with_default,
|
||||
'checkpoint_name': checkpoint_name,
|
||||
'platform_name': settings.PLATFORM_NAME,
|
||||
}
|
||||
return render_to_response("verify_student/incourse_reverify.html", context)
|
||||
|
||||
@method_decorator(login_required)
|
||||
def post(self, request, course_id, checkpoint_name):
|
||||
"""Submits the re-verification attempt to SoftwareSecure
|
||||
|
||||
Args:
|
||||
request(HttpRequest): HttpRequest object
|
||||
course_id(str): Course Id
|
||||
checkpoint_name(str): Checkpoint name
|
||||
|
||||
Returns:
|
||||
HttpResponse with status_code 400 if photo is missing or any error
|
||||
or redirect to verify_student_verify_later url if initial verification doesn't exist otherwise
|
||||
HttpsResponse with status code 200
|
||||
"""
|
||||
# Check the in-course re-verification is enabled or not
|
||||
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
|
||||
if not incourse_reverify_enabled:
|
||||
raise Http404
|
||||
|
||||
user = request.user
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = modulestore().get_course(course_key)
|
||||
checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name)
|
||||
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,
|
||||
}
|
||||
return render_to_response("verify_student/incourse_reverify.html", context)
|
||||
init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
|
||||
if not init_verification:
|
||||
log.error("Could not submit verification attempt for user %s", request.user.id)
|
||||
return redirect(reverse('verify_student_verify_later', kwargs={'course_id': unicode(course_key)}))
|
||||
|
||||
try:
|
||||
attempt = SoftwareSecurePhotoVerification.submit_faceimage(
|
||||
request.user, request.POST['face_image'], init_verification.photo_id_key
|
||||
)
|
||||
checkpoint.add_verification_attempt(attempt)
|
||||
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
|
||||
)
|
||||
|
||||
return HttpResponse()
|
||||
except IndexError:
|
||||
log.exception("Invalid image data during photo verification.")
|
||||
return HttpResponseBadRequest(_("Invalid image data during photo verification."))
|
||||
except Exception: # pylint: disable=broad-except
|
||||
log.exception("Could not submit verification attempt for user {}.").format(request.user.id)
|
||||
msg = _("Could not submit photos")
|
||||
return HttpResponseBadRequest(msg)
|
||||
|
||||
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
|
||||
|
||||
Arguments:
|
||||
user_id (str): The ID of the user generting the certificate.
|
||||
course_id (unicode): id associated with the course
|
||||
checkpoint (str): checkpoint name
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
if settings.FEATURES.get('SEGMENT_IO_LMS') and hasattr(settings, 'SEGMENT_IO_LMS_KEY'):
|
||||
tracking_context = tracker.get_tracker().resolve_context()
|
||||
analytics.track(
|
||||
user_id,
|
||||
event_name,
|
||||
{
|
||||
'category': "verification",
|
||||
'label': unicode(course_id),
|
||||
'checkpoint': checkpoint
|
||||
},
|
||||
context={
|
||||
'Google Analytics': {
|
||||
'clientId': tracking_context.get('client_id')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user