Add a per-course anonymous student id
This does not yet replace the existing per-student anonymous id, but is intended to do so in the future. Co-author: Alexander Kryklia <kryklia@edx.org> Co-author: Ned Batchelder <ned@edx.org> Co-author: Oleg Marchev <oleg@edx.org> Co-author: Valera Rozuvan <valera@edx.org> Co-author: polesye
This commit is contained in:
@@ -1,21 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Dump username,unique_id_for_user pairs as CSV.
|
||||
"""Dump username, per-student anonymous id, and per-course anonymous id triples as CSV.
|
||||
|
||||
Give instructors easy access to the mapping from anonymized IDs to user IDs
|
||||
with a simple Django management command to generate a CSV mapping. To run, use
|
||||
the following:
|
||||
|
||||
rake django-admin[anonymized_id_mapping,x,y,z]
|
||||
|
||||
[Naturally, substitute the appropriate values for x, y, and z. (I.e.,
|
||||
lms, dev, and MITx/6.002x/Circuits)]"""
|
||||
./manage.py lms anonymized_id_mapping COURSE_ID
|
||||
"""
|
||||
|
||||
import csv
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from student.models import unique_id_for_user
|
||||
from student.models import anonymous_id_for_user
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -52,9 +50,17 @@ class Command(BaseCommand):
|
||||
try:
|
||||
with open(output_filename, 'wb') as output_file:
|
||||
csv_writer = csv.writer(output_file)
|
||||
csv_writer.writerow(("User ID", "Anonymized user ID"))
|
||||
csv_writer.writerow((
|
||||
"User ID",
|
||||
"Per-Student anonymized user ID",
|
||||
"Per-course anonymized user id"
|
||||
))
|
||||
for student in students:
|
||||
csv_writer.writerow((student.id, unique_id_for_user(student)))
|
||||
csv_writer.writerow((
|
||||
student.id,
|
||||
anonymous_id_for_user(student, ''),
|
||||
anonymous_id_for_user(student, course_id)
|
||||
))
|
||||
except IOError:
|
||||
raise CommandError("Error writing to file: %s" % output_filename)
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import 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 'AnonymousUserId'
|
||||
db.create_table('student_anonymoususerid', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('anonymous_user_id', self.gf('django.db.models.fields.CharField')(unique=True, max_length=16)),
|
||||
('course_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
|
||||
))
|
||||
db.send_create_signal('student', ['AnonymousUserId'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'AnonymousUserId'
|
||||
db.delete_table('student_anonymoususerid')
|
||||
|
||||
|
||||
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'})
|
||||
},
|
||||
'student.anonymoususerid': {
|
||||
'Meta': {'object_name': 'AnonymousUserId'},
|
||||
'anonymous_user_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '16'}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.courseenrollment': {
|
||||
'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'},
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.courseenrollmentallowed': {
|
||||
'Meta': {'unique_together': "(('email', 'course_id'),)", 'object_name': 'CourseEnrollmentAllowed'},
|
||||
'auto_enroll': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'student.pendingemailchange': {
|
||||
'Meta': {'object_name': 'PendingEmailChange'},
|
||||
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
|
||||
},
|
||||
'student.pendingnamechange': {
|
||||
'Meta': {'object_name': 'PendingNameChange'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
|
||||
},
|
||||
'student.registration': {
|
||||
'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"},
|
||||
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
|
||||
},
|
||||
'student.testcenterregistration': {
|
||||
'Meta': {'object_name': 'TestCenterRegistration'},
|
||||
'accommodation_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'accommodation_request': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||
'authorization_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'client_authorization_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
|
||||
'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'eligibility_appointment_date_first': ('django.db.models.fields.DateField', [], {'db_index': 'True'}),
|
||||
'eligibility_appointment_date_last': ('django.db.models.fields.DateField', [], {'db_index': 'True'}),
|
||||
'exam_series_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'processed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'testcenter_user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['student.TestCenterUser']"}),
|
||||
'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'upload_error_message': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
|
||||
'upload_status': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}),
|
||||
'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
|
||||
},
|
||||
'student.testcenteruser': {
|
||||
'Meta': {'object_name': 'TestCenterUser'},
|
||||
'address_1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
|
||||
'address_2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
|
||||
'address_3': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
|
||||
'candidate_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'city': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
|
||||
'client_candidate_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}),
|
||||
'company_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),
|
||||
'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'country': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'extension': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8', 'blank': 'True'}),
|
||||
'fax': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}),
|
||||
'fax_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
|
||||
'middle_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'phone': ('django.db.models.fields.CharField', [], {'max_length': '35'}),
|
||||
'phone_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}),
|
||||
'postal_code': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'blank': 'True'}),
|
||||
'processed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'salutation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
|
||||
'state': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}),
|
||||
'suffix': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'upload_error_message': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
|
||||
'upload_status': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}),
|
||||
'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'unique': 'True'}),
|
||||
'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
|
||||
},
|
||||
'student.userprofile': {
|
||||
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
|
||||
'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
|
||||
'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
|
||||
'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}),
|
||||
'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'student.userstanding': {
|
||||
'Meta': {'object_name': 'UserStanding'},
|
||||
'account_status': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'standing_last_changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'standing'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.usertestgroup': {
|
||||
'Meta': {'object_name': 'UserTestGroup'},
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['student']
|
||||
@@ -25,6 +25,7 @@ from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
import django.dispatch
|
||||
from django.forms import ModelForm, forms
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
import lms.lib.comment_client as cc
|
||||
@@ -42,6 +43,63 @@ log = logging.getLogger(__name__)
|
||||
AUDIT_LOG = logging.getLogger("audit")
|
||||
|
||||
|
||||
class AnonymousUserId(models.Model):
|
||||
"""
|
||||
This table contains user, course_Id and anonymous_user_id
|
||||
|
||||
Purpose of this table is to provide user by anonymous_user_id.
|
||||
|
||||
We are generating anonymous_user_id using md5 algorithm, so resulting length will always be 16 bytes.
|
||||
http://docs.python.org/2/library/md5.html#md5.digest_size
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True)
|
||||
anonymous_user_id = models.CharField(unique=True, max_length=16)
|
||||
course_id = models.CharField(db_index=True, max_length=255)
|
||||
unique_together = (user, course_id)
|
||||
|
||||
|
||||
def anonymous_id_for_user(user, course_id):
|
||||
"""
|
||||
Return a unique id for a (user, course) pair, suitable for inserting
|
||||
into e.g. personalized survey links.
|
||||
|
||||
If user is an `AnonymousUser`, returns `None`
|
||||
"""
|
||||
# This part is for ability to get xblock instance in xblock_noauth handlers, where user is unauthenticated.
|
||||
if user.is_anonymous():
|
||||
return None
|
||||
|
||||
# include the secret key as a salt, and to make the ids unique across different LMS installs.
|
||||
hasher = hashlib.md5()
|
||||
hasher.update(settings.SECRET_KEY)
|
||||
hasher.update(str(user.id))
|
||||
hasher.update(course_id)
|
||||
|
||||
return AnonymousUserId.objects.get_or_create(
|
||||
defaults={'anonymous_user_id': hasher.hexdigest()},
|
||||
user=user,
|
||||
course_id=course_id
|
||||
)[0].anonymous_user_id
|
||||
|
||||
|
||||
def user_by_anonymous_id(id):
|
||||
"""
|
||||
Return user by anonymous_user_id using AnonymousUserId lookup table.
|
||||
|
||||
Do not raise `django.ObjectDoesNotExist` exception,
|
||||
if there is no user for anonymous_student_id,
|
||||
because this function will be used inside xmodule w/o django access.
|
||||
"""
|
||||
|
||||
if id is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return User.objects.get(anonymoususerid__anonymous_user_id=id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class UserStanding(models.Model):
|
||||
"""
|
||||
This table contains a student's account's status.
|
||||
@@ -624,12 +682,9 @@ def unique_id_for_user(user):
|
||||
Return a unique id for a user, suitable for inserting into
|
||||
e.g. personalized survey links.
|
||||
"""
|
||||
# include the secret key as a salt, and to make the ids unique across
|
||||
# different LMS installs.
|
||||
h = hashlib.md5()
|
||||
h.update(settings.SECRET_KEY)
|
||||
h.update(str(user.id))
|
||||
return h.hexdigest()
|
||||
# Setting course_id to '' makes it not affect the generated hash,
|
||||
# and thus produce the old per-student anonymous id
|
||||
return anonymous_id_for_user(user, '')
|
||||
|
||||
|
||||
# TODO: Should be renamed to generic UserGroup, and possibly
|
||||
|
||||
@@ -15,7 +15,7 @@ from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.test.client import RequestFactory
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.utils.http import int_to_base36
|
||||
@@ -28,7 +28,7 @@ from courseware.tests.tests import TEST_DATA_MIXED_MODULESTORE
|
||||
from mock import Mock, patch, sentinel
|
||||
from textwrap import dedent
|
||||
|
||||
from student.models import unique_id_for_user, CourseEnrollment
|
||||
from student.models import anonymous_id_for_user, user_by_anonymous_id, CourseEnrollment, unique_id_for_user
|
||||
from student.views import (process_survey_link, _cert_info, password_reset, password_reset_confirm_wrapper,
|
||||
change_enrollment, complete_course_mode_info)
|
||||
from student.tests.factories import UserFactory, CourseModeFactory
|
||||
@@ -501,3 +501,37 @@ class PaidRegistrationTest(ModuleStoreTestCase):
|
||||
self.assertEqual(response.content, reverse('shoppingcart.views.show_cart'))
|
||||
self.assertTrue(shoppingcart.models.PaidCourseRegistration.contained_in_order(
|
||||
shoppingcart.models.Order.get_cart_for_user(self.user), self.course.id))
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
|
||||
class AnonymousLookupTable(TestCase):
|
||||
"""
|
||||
Tests for anonymous_id_functions
|
||||
"""
|
||||
# arbitrary constant
|
||||
COURSE_SLUG = "100"
|
||||
COURSE_NAME = "test_course"
|
||||
COURSE_ORG = "EDX"
|
||||
|
||||
def setUp(self):
|
||||
self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
|
||||
self.assertIsNotNone(self.course)
|
||||
self.user = UserFactory()
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
mode_slug='honor',
|
||||
mode_display_name='Honor Code',
|
||||
)
|
||||
patcher = patch('student.models.server_track')
|
||||
self.mock_server_track = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
def test_for_unregistered_user(self): # same path as for logged out user
|
||||
self.assertEqual(None, anonymous_id_for_user(AnonymousUser(), self.course.id))
|
||||
self.assertIsNone(user_by_anonymous_id(None))
|
||||
|
||||
def test_roundtrip_for_logged_user(self):
|
||||
enrollment = CourseEnrollment.enroll(self.user, self.course.id)
|
||||
anonymous_id = anonymous_id_for_user(self.user, self.course.id)
|
||||
real_user = user_by_anonymous_id(anonymous_id)
|
||||
self.assertEqual(self.user, real_user)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Test for lms courseware app, module render unit
|
||||
"""
|
||||
from ddt import ddt, data
|
||||
from mock import MagicMock, patch, Mock
|
||||
import json
|
||||
|
||||
@@ -11,10 +12,14 @@ from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from xblock.field_data import FieldData
|
||||
from xblock.runtime import Runtime
|
||||
from xblock.fields import ScopeIds
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
import courseware.module_render as render
|
||||
from courseware.tests.tests import LoginEnrollmentTestCase
|
||||
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
|
||||
@@ -515,3 +520,74 @@ class TestHtmlModifiers(ModuleStoreTestCase):
|
||||
'Staff Debug',
|
||||
result_fragment.content
|
||||
)
|
||||
|
||||
PER_COURSE_ANONYMIZED_DESCRIPTORS = ()
|
||||
|
||||
PER_STUDENT_ANONYMIZED_DESCRIPTORS = [
|
||||
class_ for (name, class_) in XModuleDescriptor.load_classes()
|
||||
if not issubclass(class_, PER_COURSE_ANONYMIZED_DESCRIPTORS)
|
||||
]
|
||||
|
||||
|
||||
@ddt
|
||||
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
|
||||
class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
Test that anonymous_student_id is set correctly across a variety of XBlock types
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = UserFactory()
|
||||
|
||||
@patch('courseware.module_render.has_access', Mock(return_value=True))
|
||||
def _get_anonymous_id(self, course_id, xblock_class):
|
||||
location = Location('dummy_org', 'dummy_course', 'dummy_category', 'dummy_name')
|
||||
descriptor = Mock(
|
||||
spec=xblock_class,
|
||||
_field_data=Mock(spec=FieldData),
|
||||
location=location,
|
||||
static_asset_path=None,
|
||||
runtime=Mock(
|
||||
spec=Runtime,
|
||||
resources_fs=None,
|
||||
mixologist=Mock(_mixins=())
|
||||
),
|
||||
scope_ids=Mock(spec=ScopeIds),
|
||||
)
|
||||
if hasattr(xblock_class, 'module_class'):
|
||||
descriptor.module_class = xblock_class.module_class
|
||||
|
||||
return render.get_module_for_descriptor_internal(
|
||||
self.user,
|
||||
descriptor,
|
||||
Mock(spec=FieldDataCache),
|
||||
course_id,
|
||||
Mock(), # Track Function
|
||||
Mock(), # XQueue Callback Url Prefix
|
||||
).xmodule_runtime.anonymous_student_id
|
||||
|
||||
@data(*PER_STUDENT_ANONYMIZED_DESCRIPTORS)
|
||||
def test_per_student_anonymized_id(self, descriptor_class):
|
||||
for course_id in ('MITx/6.00x/2012_Fall', 'MITx/6.00x/2013_Spring'):
|
||||
self.assertEquals(
|
||||
# This value is set by observation, so that later changes to the student
|
||||
# id computation don't break old data
|
||||
'5afe5d9bb03796557ee2614f5c9611fb',
|
||||
self._get_anonymous_id(course_id, descriptor_class)
|
||||
)
|
||||
|
||||
@data(*PER_COURSE_ANONYMIZED_DESCRIPTORS)
|
||||
def test_per_course_anonymized_id(self, descriptor_class):
|
||||
self.assertEquals(
|
||||
# This value is set by observation, so that later changes to the student
|
||||
# id computation don't break old data
|
||||
'e3b0b940318df9c14be59acb08e78af5',
|
||||
self._get_anonymous_id('MITx/6.00x/2012_Fall', descriptor_class)
|
||||
)
|
||||
|
||||
self.assertEquals(
|
||||
# This value is set by observation, so that later changes to the student
|
||||
# id computation don't break old data
|
||||
'f82b5416c9f54b5ce33989511bb5ef2e',
|
||||
self._get_anonymous_id('MITx/6.00x/2013_Spring', descriptor_class)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user