Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
def deadlines_for_courses(cls, course_keys):
"""
Retrieve verification deadlines for particular courses.
Arguments:
course_keys (list): List of `CourseKey`s.
Returns:
dict: Map of course keys to datetimes (verification deadlines)
"""
all_deadlines = cache.get(cls.ALL_DEADLINES_CACHE_KEY)
if all_deadlines is None:
all_deadlines = {
deadline.course_key: deadline.deadline
for deadline in VerificationDeadline.objects.all()
}
cache.set(cls.ALL_DEADLINES_CACHE_KEY, all_deadlines)
return {
course_key: all_deadlines[course_key]
for course_key in course_keys
if course_key in all_deadlines
}
@classmethod
def deadline_for_course(cls, course_key):
"""
Retrieve the verification deadline for a particular course.
Arguments:
course_key (CourseKey): The identifier for the course.
Returns:
datetime or None
"""
try:
deadline = cls.objects.get(course_key=course_key)
return deadline.deadline
except cls.DoesNotExist:
return None
@receiver(models.signals.post_save, sender=VerificationDeadline)
@receiver(models.signals.post_delete, sender=VerificationDeadline)
def invalidate_deadline_caches(sender, **kwargs): # pylint: disable=unused-argument
"""Invalidate the cached verification deadline information. """
cache.delete(VerificationDeadline.ALL_DEADLINES_CACHE_KEY)
zubair-arbi
committed
"""Represents a point at which a user is asked to re-verify his/her
identity.
zubair-arbi
committed
Each checkpoint is uniquely identified by a
(course_id, checkpoint_location) tuple.
"""
course_id = CourseKeyField(max_length=255, db_index=True)
zubair-arbi
committed
checkpoint_location = models.CharField(max_length=255)
photo_verification = models.ManyToManyField(SoftwareSecurePhotoVerification)
zubair-arbi
committed
unique_together = ('course_id', 'checkpoint_location')
zubair-arbi
committed
"""
Unicode representation of the checkpoint.
"""
return u"{checkpoint} in {course}".format(
checkpoint=self.checkpoint_name,
course=self.course_id
)
zubair-arbi
committed
@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):
zubair-arbi
committed
"""Add the verification attempt in M2M relation of photo_verification.
zubair-arbi
committed
verification_attempt(object): SoftwareSecurePhotoVerification object
zubair-arbi
committed
self.photo_verification.add(verification_attempt) # pylint: disable=no-member
aamir-khan
committed
def get_user_latest_status(self, user_id):
zubair-arbi
committed
"""Get the status of the latest checkpoint attempt of the given user.
aamir-khan
committed
Args:
user_id(str): Id of user
Returns:
VerificationStatus object if found any else None
"""
try:
return self.checkpoint_status.filter(user_id=user_id).latest()
aamir-khan
committed
except ObjectDoesNotExist:
return None
def get_or_create_verification_checkpoint(cls, course_id, checkpoint_location):
"""
Get or create the verification checkpoint for given 'course_id' and
zubair-arbi
committed
checkpoint name.
course_id (CourseKey): CourseKey
checkpoint_location (str): Verification checkpoint location
Raises:
IntegrityError if create fails due to concurrent create.
Returns:
VerificationCheckpoint object if exists otherwise None
"""
checkpoint, __ = cls.objects.get_or_create(course_id=course_id, checkpoint_location=checkpoint_location)
return checkpoint
class VerificationStatus(models.Model):
zubair-arbi
committed
"""This model is an append-only table that represents user status changes
during the verification process.
zubair-arbi
committed
A verification status represents a user’s progress through the verification
process for a particular checkpoint.
"""
SUBMITTED_STATUS = "submitted"
APPROVED_STATUS = "approved"
DENIED_STATUS = "denied"
ERROR_STATUS = "error"
(SUBMITTED_STATUS, SUBMITTED_STATUS),
(APPROVED_STATUS, APPROVED_STATUS),
(DENIED_STATUS, DENIED_STATUS),
(ERROR_STATUS, ERROR_STATUS)
aamir-khan
committed
checkpoint = models.ForeignKey(VerificationCheckpoint, related_name="checkpoint_status")
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)
aamir-khan
committed
get_latest_by = "timestamp"
verbose_name = "Verification Status"
verbose_name_plural = "Verification Statuses"
aamir-khan
committed
zubair-arbi
committed
def add_verification_status(cls, checkpoint, user, status):
"""Create new verification status object.
Arguments:
checkpoint(VerificationCheckpoint): VerificationCheckpoint object
user(User): user object
zubair-arbi
committed
status(str): Status from VERIFICATION_STATUS_CHOICES
zubair-arbi
committed
cls.objects.create(checkpoint=checkpoint, user=user, status=status)
@classmethod
def add_status_from_checkpoints(cls, checkpoints, user, status):
zubair-arbi
committed
"""Create new verification status objects for a user against the given
checkpoints.
Arguments:
checkpoints(list): list of VerificationCheckpoint objects
user(User): user object
zubair-arbi
committed
status(str): Status from VERIFICATION_STATUS_CHOICES
Returns:
None
"""
for checkpoint in checkpoints:
zubair-arbi
committed
cls.objects.create(checkpoint=checkpoint, user=user, status=status)
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
@classmethod
def get_user_status_at_checkpoint(cls, user, course_key, location):
"""
Get the user's latest status at the checkpoint.
Arguments:
user (User): The user whose status we are retrieving.
course_key (CourseKey): The identifier for the course.
location (UsageKey): The location of the checkpoint in the course.
Returns:
unicode or None
"""
try:
return cls.objects.filter(
user=user,
checkpoint__course_id=course_key,
checkpoint__checkpoint_location=unicode(location),
).latest().status
except cls.DoesNotExist:
return None
def get_user_attempts(cls, user_id, course_key, checkpoint_location):
"""
Get re-verification attempts against a user for a given 'checkpoint'
and 'course_id'.
Arguments:
user_id (str): User Id string
course_key (str): A CourseKey of a course
checkpoint_location (str): Verification checkpoint location
zubair-arbi
committed
Count of re-verification attempts
"""
return cls.objects.filter(
user_id=user_id,
checkpoint__course_id=course_key,
checkpoint__checkpoint_location=checkpoint_location,
status=cls.SUBMITTED_STATUS
@classmethod
def get_location_id(cls, photo_verification):
zubair-arbi
committed
"""Get the location ID of reverification XBlock.
Args:
zubair-arbi
committed
photo_verification(object): SoftwareSecurePhotoVerification object
Return:
zubair-arbi
committed
Location Id of XBlock if any else empty string
"""
try:
zubair-arbi
committed
verification_status = cls.objects.filter(checkpoint__photo_verification=photo_verification).latest()
return verification_status.checkpoint.checkpoint_location
except cls.DoesNotExist:
return ""
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
@classmethod
def get_all_checkpoints(cls, user_id, course_key):
"""Return dict of all the checkpoints with their status.
Args:
user_id(int): Id of user.
course_key(unicode): Unicode of course key
Returns:
dict: {checkpoint:status}
"""
all_checks_points = cls.objects.filter(
user_id=user_id, checkpoint__course_id=course_key
)
check_points = {}
for check in all_checks_points:
check_points[check.checkpoint.checkpoint_location] = check.status
return check_points
@classmethod
def cache_key_name(cls, user_id, course_key):
"""Return the name of the key to use to cache the current configuration
Args:
user_id(int): Id of user.
course_key(unicode): Unicode of course key
Returns:
Unicode cache key
"""
return u"verification.{}.{}".format(user_id, unicode(course_key))
@receiver(models.signals.post_save, sender=VerificationStatus)
@receiver(models.signals.post_delete, sender=VerificationStatus)
def invalidate_verification_status_cache(sender, instance, **kwargs): # pylint: disable=unused-argument, invalid-name
"""Invalidate the cache of VerificationStatus model. """
cache_key = VerificationStatus.cache_key_name(
instance.user.id,
unicode(instance.checkpoint.course_id)
)
cache.delete(cache_key)
# DEPRECATED: this feature has been permanently enabled.
# Once the application code has been updated in production,
# this table can be safely deleted.
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
class IcrvStatusEmailsConfiguration(ConfigurationModel):
"""Toggle in-course reverification (ICRV) status emails
Disabled by default. When disabled, ICRV status emails will not be sent.
When enabled, ICRV status emails are sent.
"""
pass
class SkippedReverification(models.Model):
zubair-arbi
committed
"""Model for tracking skipped Reverification of a user against a specific
zubair-arbi
committed
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)
checkpoint = models.ForeignKey(VerificationCheckpoint, related_name="skipped_checkpoint")
created_at = models.DateTimeField(auto_now_add=True)
unique_together = (('user', 'course_id'),)
def add_skipped_reverification_attempt(cls, checkpoint, user_id, course_id):
zubair-arbi
committed
"""Create skipped reverification object.
Arguments:
checkpoint(VerificationCheckpoint): VerificationCheckpoint object
user_id(str): User Id of currently logged in user
course_id(CourseKey): CourseKey
zubair-arbi
committed
Returns:
None
"""
cls.objects.create(checkpoint=checkpoint, user_id=user_id, course_id=course_id)
@classmethod
def check_user_skipped_reverification_exists(cls, user_id, course_id):
zubair-arbi
committed
"""Check existence of a user's skipped re-verification attempt for a
specific course.
course_id(CourseKey): CourseKey
zubair-arbi
committed
Returns:
Boolean
"""
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
has_skipped = cls.objects.filter(user_id=user_id, course_id=course_id).exists()
return has_skipped
@classmethod
def cache_key_name(cls, user_id, course_key):
"""Return the name of the key to use to cache the current configuration
Arguments:
user(User): user object
course_key(CourseKey): CourseKey
Returns:
string: cache key name
"""
return u"skipped_reverification.{}.{}".format(user_id, unicode(course_key))
@receiver(models.signals.post_save, sender=SkippedReverification)
@receiver(models.signals.post_delete, sender=SkippedReverification)
def invalidate_skipped_verification_cache(sender, instance, **kwargs): # pylint: disable=unused-argument, invalid-name
"""Invalidate the cache of skipped verification model. """
cache_key = SkippedReverification.cache_key_name(
instance.user.id,
unicode(instance.course_id)
)
cache.delete(cache_key)