Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
11fef201
Commit
11fef201
authored
Jun 02, 2015
by
chrisndodge
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #8165 from edx/muhhshoaib/SOL-236/make-manual-enrollments
SOL-236 Manual Enrollments
parents
1153edf8
65c4f1df
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
666 additions
and
47 deletions
+666
-47
common/djangoapps/student/migrations/0050_auto__add_manualenrollmentaudit.py
+216
-0
common/djangoapps/student/models.py
+75
-8
common/djangoapps/student/views.py
+11
-2
lms/djangoapps/instructor/enrollment.py
+3
-4
lms/djangoapps/instructor/paidcourse_enrollment_report.py
+8
-2
lms/djangoapps/instructor/tests/test_api.py
+240
-17
lms/djangoapps/instructor/tests/test_api_email_localization.py
+1
-1
lms/djangoapps/instructor/views/api.py
+65
-7
lms/djangoapps/instructor/views/instructor_dashboard.py
+5
-4
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
+23
-1
lms/static/coffee/src/instructor_dashboard/membership.coffee
+9
-0
lms/templates/instructor/instructor_dashboard_2/membership.html
+10
-1
No files found.
common/djangoapps/student/migrations/0050_auto__add_manualenrollmentaudit.py
0 → 100644
View file @
11fef201
# -*- 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 'ManualEnrollmentAudit'
db
.
create_table
(
'student_manualenrollmentaudit'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'enrollment'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'student.CourseEnrollment'
],
null
=
True
)),
(
'enrolled_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'auth.User'
],
null
=
True
)),
(
'enrolled_email'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
255
,
db_index
=
True
)),
(
'time_stamp'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now_add
=
True
,
null
=
True
,
blank
=
True
)),
(
'state_transition'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
255
)),
(
'reason'
,
self
.
gf
(
'django.db.models.fields.TextField'
)(
null
=
True
)),
))
db
.
send_create_signal
(
'student'
,
[
'ManualEnrollmentAudit'
])
def
backwards
(
self
,
orm
):
# Deleting model 'ManualEnrollmentAudit'
db
.
delete_table
(
'student_manualenrollmentaudit'
)
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'
:
'32'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'student.courseaccessrole'
:
{
'Meta'
:
{
'unique_together'
:
"(('user', 'org', 'course_id', 'role'),)"
,
'object_name'
:
'CourseAccessRole'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'org'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'role'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'db_index'
:
'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'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'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'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'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.dashboardconfiguration'
:
{
'Meta'
:
{
'object_name'
:
'DashboardConfiguration'
},
'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'
}),
'recent_enrollment_time_delta'
:
(
'django.db.models.fields.PositiveIntegerField'
,
[],
{
'default'
:
'0'
})
},
'student.entranceexamconfiguration'
:
{
'Meta'
:
{
'unique_together'
:
"(('user', 'course_id'),)"
,
'object_name'
:
'EntranceExamConfiguration'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'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'
}),
'skip_entrance_exam'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'updated'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'student.languageproficiency'
:
{
'Meta'
:
{
'unique_together'
:
"(('code', 'user_profile'),)"
,
'object_name'
:
'LanguageProficiency'
},
'code'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'16'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'user_profile'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'language_proficiencies'"
,
'to'
:
"orm['student.UserProfile']"
})
},
'student.linkedinaddtoprofileconfiguration'
:
{
'Meta'
:
{
'object_name'
:
'LinkedInAddToProfileConfiguration'
},
'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'
}),
'company_identifier'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'dashboard_tracking_code'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'blank'
:
'True'
}),
'enabled'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'trk_partner_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"''"
,
'max_length'
:
'10'
,
'blank'
:
'True'
})
},
'student.loginfailures'
:
{
'Meta'
:
{
'object_name'
:
'LoginFailures'
},
'failure_count'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'lockout_until'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'student.manualenrollmentaudit'
:
{
'Meta'
:
{
'object_name'
:
'ManualEnrollmentAudit'
},
'enrolled_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
,
'null'
:
'True'
}),
'enrolled_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'enrollment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['student.CourseEnrollment']"
,
'null'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'reason'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'state_transition'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'time_stamp'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
'student.passwordhistory'
:
{
'Meta'
:
{
'object_name'
:
'PasswordHistory'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'time_set'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'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.userprofile'
:
{
'Meta'
:
{
'object_name'
:
'UserProfile'
,
'db_table'
:
"'auth_userprofile'"
},
'allow_certificate'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'bio'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'3000'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'city'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'country'
:
(
'django_countries.fields.CountryField'
,
[],
{
'max_length'
:
'2'
,
'null'
:
'True'
,
'blank'
:
'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'
}),
'profile_image_uploaded_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'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.usersignupsource'
:
{
'Meta'
:
{
'object_name'
:
'UserSignupSource'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'site'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'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'
]
common/djangoapps/student/models.py
View file @
11fef201
...
@@ -20,7 +20,7 @@ from collections import defaultdict, OrderedDict
...
@@ -20,7 +20,7 @@ from collections import defaultdict, OrderedDict
import
dogstats_wrapper
as
dog_stats_api
import
dogstats_wrapper
as
dog_stats_api
from
urllib
import
urlencode
from
urllib
import
urlencode
from
django.utils.translation
import
ugettext
as
_
,
ugettext_lazy
from
django.utils.translation
import
ugettext
_lazy
as
_
from
django.conf
import
settings
from
django.conf
import
settings
from
django.utils
import
timezone
from
django.utils
import
timezone
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
...
@@ -59,6 +59,26 @@ log = logging.getLogger(__name__)
...
@@ -59,6 +59,26 @@ log = logging.getLogger(__name__)
AUDIT_LOG
=
logging
.
getLogger
(
"audit"
)
AUDIT_LOG
=
logging
.
getLogger
(
"audit"
)
SessionStore
=
import_module
(
settings
.
SESSION_ENGINE
)
.
SessionStore
# pylint: disable=invalid-name
SessionStore
=
import_module
(
settings
.
SESSION_ENGINE
)
.
SessionStore
# pylint: disable=invalid-name
UNENROLLED_TO_ALLOWEDTOENROLL
=
'from unenrolled to allowed to enroll'
ALLOWEDTOENROLL_TO_ENROLLED
=
'from allowed to enroll to enrolled'
ENROLLED_TO_ENROLLED
=
'from enrolled to enrolled'
ENROLLED_TO_UNENROLLED
=
'from enrolled to unenrolled'
UNENROLLED_TO_ENROLLED
=
'from unenrolled to enrolled'
ALLOWEDTOENROLL_TO_UNENROLLED
=
'from allowed to enroll to enrolled'
UNENROLLED_TO_UNENROLLED
=
'from unenrolled to unenrolled'
DEFAULT_TRANSITION_STATE
=
'N/A'
TRANSITION_STATES
=
(
(
UNENROLLED_TO_ALLOWEDTOENROLL
,
UNENROLLED_TO_ALLOWEDTOENROLL
),
(
ALLOWEDTOENROLL_TO_ENROLLED
,
ALLOWEDTOENROLL_TO_ENROLLED
),
(
ENROLLED_TO_ENROLLED
,
ENROLLED_TO_ENROLLED
),
(
ENROLLED_TO_UNENROLLED
,
ENROLLED_TO_UNENROLLED
),
(
UNENROLLED_TO_ENROLLED
,
UNENROLLED_TO_ENROLLED
),
(
ALLOWEDTOENROLL_TO_UNENROLLED
,
ALLOWEDTOENROLL_TO_UNENROLLED
),
(
UNENROLLED_TO_UNENROLLED
,
UNENROLLED_TO_UNENROLLED
),
(
DEFAULT_TRANSITION_STATE
,
DEFAULT_TRANSITION_STATE
)
)
class
AnonymousUserId
(
models
.
Model
):
class
AnonymousUserId
(
models
.
Model
):
"""
"""
...
@@ -1291,6 +1311,53 @@ class CourseEnrollment(models.Model):
...
@@ -1291,6 +1311,53 @@ class CourseEnrollment(models.Model):
return
CourseMode
.
is_verified_slug
(
self
.
mode
)
return
CourseMode
.
is_verified_slug
(
self
.
mode
)
class
ManualEnrollmentAudit
(
models
.
Model
):
"""
Table for tracking which enrollments were performed through manual enrollment.
"""
enrollment
=
models
.
ForeignKey
(
CourseEnrollment
,
null
=
True
)
enrolled_by
=
models
.
ForeignKey
(
User
,
null
=
True
)
enrolled_email
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
)
time_stamp
=
models
.
DateTimeField
(
auto_now_add
=
True
,
null
=
True
)
state_transition
=
models
.
CharField
(
max_length
=
255
,
choices
=
TRANSITION_STATES
)
reason
=
models
.
TextField
(
null
=
True
)
@classmethod
def
create_manual_enrollment_audit
(
cls
,
user
,
email
,
state_transition
,
reason
,
enrollment
=
None
):
"""
saves the student manual enrollment information
"""
cls
.
objects
.
create
(
enrolled_by
=
user
,
enrolled_email
=
email
,
state_transition
=
state_transition
,
reason
=
reason
,
enrollment
=
enrollment
)
@classmethod
def
get_manual_enrollment_by_email
(
cls
,
email
):
"""
if matches returns the most recent entry in the table filtered by email else returns None.
"""
try
:
manual_enrollment
=
cls
.
objects
.
filter
(
enrolled_email
=
email
)
.
latest
(
'time_stamp'
)
except
cls
.
DoesNotExist
:
manual_enrollment
=
None
return
manual_enrollment
@classmethod
def
get_manual_enrollment
(
cls
,
enrollment
):
"""
if matches returns the most recent entry in the table filtered by enrollment else returns None,
"""
try
:
manual_enrollment
=
cls
.
objects
.
filter
(
enrollment
=
enrollment
)
.
latest
(
'time_stamp'
)
except
cls
.
DoesNotExist
:
manual_enrollment
=
None
return
manual_enrollment
class
CourseEnrollmentAllowed
(
models
.
Model
):
class
CourseEnrollmentAllowed
(
models
.
Model
):
"""
"""
Table of users (specified by email address strings) who are allowed to enroll in a specified course.
Table of users (specified by email address strings) who are allowed to enroll in a specified course.
...
@@ -1536,16 +1603,16 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
...
@@ -1536,16 +1603,16 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
"""
"""
MODE_TO_CERT_NAME
=
{
MODE_TO_CERT_NAME
=
{
"honor"
:
ugettext_lazy
(
u"{platform_name} Honor Code Certificate for {course_name}"
),
"honor"
:
_
(
u"{platform_name} Honor Code Certificate for {course_name}"
),
"verified"
:
ugettext_lazy
(
u"{platform_name} Verified Certificate for {course_name}"
),
"verified"
:
_
(
u"{platform_name} Verified Certificate for {course_name}"
),
"professional"
:
ugettext_lazy
(
u"{platform_name} Professional Certificate for {course_name}"
),
"professional"
:
_
(
u"{platform_name} Professional Certificate for {course_name}"
),
"no-id-professional"
:
ugettext_lazy
(
"no-id-professional"
:
_
(
u"{platform_name} Professional Certificate for {course_name}"
u"{platform_name} Professional Certificate for {course_name}"
),
),
}
}
company_identifier
=
models
.
TextField
(
company_identifier
=
models
.
TextField
(
help_text
=
ugettext_lazy
(
help_text
=
_
(
u"The company identifier for the LinkedIn Add-to-Profile button "
u"The company identifier for the LinkedIn Add-to-Profile button "
u"e.g 0_0dPSPyS070e0HsE9HNz_13_d11_"
u"e.g 0_0dPSPyS070e0HsE9HNz_13_d11_"
)
)
...
@@ -1558,7 +1625,7 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
...
@@ -1558,7 +1625,7 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
max_length
=
10
,
max_length
=
10
,
default
=
""
,
default
=
""
,
blank
=
True
,
blank
=
True
,
help_text
=
ugettext_lazy
(
help_text
=
_
(
u"Short identifier for the LinkedIn partner used in the tracking code. "
u"Short identifier for the LinkedIn partner used in the tracking code. "
u"(Example: 'edx') "
u"(Example: 'edx') "
u"If no value is provided, tracking codes will not be sent to LinkedIn."
u"If no value is provided, tracking codes will not be sent to LinkedIn."
...
@@ -1699,5 +1766,5 @@ class LanguageProficiency(models.Model):
...
@@ -1699,5 +1766,5 @@ class LanguageProficiency(models.Model):
max_length
=
16
,
max_length
=
16
,
blank
=
False
,
blank
=
False
,
choices
=
settings
.
ALL_LANGUAGES
,
choices
=
settings
.
ALL_LANGUAGES
,
help_text
=
ugettext_lazy
(
"The ISO 639-1 language code for this language."
)
help_text
=
_
(
"The ISO 639-1 language code for this language."
)
)
)
common/djangoapps/student/views.py
View file @
11fef201
...
@@ -55,7 +55,7 @@ from student.models import (
...
@@ -55,7 +55,7 @@ from student.models import (
PendingEmailChange
,
CourseEnrollment
,
unique_id_for_user
,
PendingEmailChange
,
CourseEnrollment
,
unique_id_for_user
,
CourseEnrollmentAllowed
,
UserStanding
,
LoginFailures
,
CourseEnrollmentAllowed
,
UserStanding
,
LoginFailures
,
create_comments_service_user
,
PasswordHistory
,
UserSignupSource
,
create_comments_service_user
,
PasswordHistory
,
UserSignupSource
,
DashboardConfiguration
,
LinkedInAddToProfileConfiguration
)
DashboardConfiguration
,
LinkedInAddToProfileConfiguration
,
ManualEnrollmentAudit
,
ALLOWEDTOENROLL_TO_ENROLLED
)
from
student.forms
import
AccountCreationForm
,
PasswordResetFormNoActive
from
student.forms
import
AccountCreationForm
,
PasswordResetFormNoActive
from
verify_student.models
import
SoftwareSecurePhotoVerification
,
MidcourseReverificationWindow
from
verify_student.models
import
SoftwareSecurePhotoVerification
,
MidcourseReverificationWindow
...
@@ -1783,7 +1783,16 @@ def activate_account(request, key):
...
@@ -1783,7 +1783,16 @@ def activate_account(request, key):
ceas
=
CourseEnrollmentAllowed
.
objects
.
filter
(
email
=
student
[
0
]
.
email
)
ceas
=
CourseEnrollmentAllowed
.
objects
.
filter
(
email
=
student
[
0
]
.
email
)
for
cea
in
ceas
:
for
cea
in
ceas
:
if
cea
.
auto_enroll
:
if
cea
.
auto_enroll
:
CourseEnrollment
.
enroll
(
student
[
0
],
cea
.
course_id
)
enrollment
=
CourseEnrollment
.
enroll
(
student
[
0
],
cea
.
course_id
)
manual_enrollment_audit
=
ManualEnrollmentAudit
.
get_manual_enrollment_by_email
(
student
[
0
]
.
email
)
if
manual_enrollment_audit
is
not
None
:
# get the enrolled by user and reason from the ManualEnrollmentAudit table.
# then create a new ManualEnrollmentAudit table entry for the same email
# different transition state.
ManualEnrollmentAudit
.
create_manual_enrollment_audit
(
manual_enrollment_audit
.
enrolled_by
,
student
[
0
]
.
email
,
ALLOWEDTOENROLL_TO_ENROLLED
,
manual_enrollment_audit
.
reason
,
enrollment
)
# enroll student in any pending CCXs he/she may have if auto_enroll flag is set
# enroll student in any pending CCXs he/she may have if auto_enroll flag is set
if
settings
.
FEATURES
.
get
(
'CUSTOM_COURSES_EDX'
):
if
settings
.
FEATURES
.
get
(
'CUSTOM_COURSES_EDX'
):
...
...
lms/djangoapps/instructor/enrollment.py
View file @
11fef201
...
@@ -100,7 +100,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
...
@@ -100,7 +100,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
representing state before and after the action.
representing state before and after the action.
"""
"""
previous_state
=
EmailEnrollmentState
(
course_id
,
student_email
)
previous_state
=
EmailEnrollmentState
(
course_id
,
student_email
)
enrollment_obj
=
None
if
previous_state
.
user
:
if
previous_state
.
user
:
# if the student is currently unenrolled, don't enroll them in their
# if the student is currently unenrolled, don't enroll them in their
# previous mode
# previous mode
...
@@ -108,7 +108,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
...
@@ -108,7 +108,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
if
previous_state
.
enrollment
:
if
previous_state
.
enrollment
:
course_mode
=
previous_state
.
mode
course_mode
=
previous_state
.
mode
CourseEnrollment
.
enroll_by_email
(
student_email
,
course_id
,
course_mode
)
enrollment_obj
=
CourseEnrollment
.
enroll_by_email
(
student_email
,
course_id
,
course_mode
)
if
email_students
:
if
email_students
:
email_params
[
'message'
]
=
'enrolled_enroll'
email_params
[
'message'
]
=
'enrolled_enroll'
email_params
[
'email_address'
]
=
student_email
email_params
[
'email_address'
]
=
student_email
...
@@ -125,7 +125,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
...
@@ -125,7 +125,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
after_state
=
EmailEnrollmentState
(
course_id
,
student_email
)
after_state
=
EmailEnrollmentState
(
course_id
,
student_email
)
return
previous_state
,
after_state
return
previous_state
,
after_state
,
enrollment_obj
def
unenroll_email
(
course_id
,
student_email
,
email_students
=
False
,
email_params
=
None
,
language
=
None
):
def
unenroll_email
(
course_id
,
student_email
,
email_students
=
False
,
email_params
=
None
,
language
=
None
):
...
@@ -141,7 +141,6 @@ def unenroll_email(course_id, student_email, email_students=False, email_params=
...
@@ -141,7 +141,6 @@ def unenroll_email(course_id, student_email, email_students=False, email_params=
representing state before and after the action.
representing state before and after the action.
"""
"""
previous_state
=
EmailEnrollmentState
(
course_id
,
student_email
)
previous_state
=
EmailEnrollmentState
(
course_id
,
student_email
)
if
previous_state
.
enrollment
:
if
previous_state
.
enrollment
:
CourseEnrollment
.
unenroll_by_email
(
student_email
,
course_id
)
CourseEnrollment
.
unenroll_by_email
(
student_email
,
course_id
)
if
email_students
:
if
email_students
:
...
...
lms/djangoapps/instructor/paidcourse_enrollment_report.py
View file @
11fef201
...
@@ -11,7 +11,7 @@ from instructor.enrollment_report import BaseAbstractEnrollmentReportProvider
...
@@ -11,7 +11,7 @@ from instructor.enrollment_report import BaseAbstractEnrollmentReportProvider
from
microsite_configuration
import
microsite
from
microsite_configuration
import
microsite
from
shoppingcart.models
import
RegistrationCodeRedemption
,
PaidCourseRegistration
,
CouponRedemption
,
OrderItem
,
\
from
shoppingcart.models
import
RegistrationCodeRedemption
,
PaidCourseRegistration
,
CouponRedemption
,
OrderItem
,
\
InvoiceTransaction
InvoiceTransaction
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
,
ManualEnrollmentAudit
class
PaidCourseEnrollmentReportProvider
(
BaseAbstractEnrollmentReportProvider
):
class
PaidCourseEnrollmentReportProvider
(
BaseAbstractEnrollmentReportProvider
):
...
@@ -56,7 +56,13 @@ class PaidCourseEnrollmentReportProvider(BaseAbstractEnrollmentReportProvider):
...
@@ -56,7 +56,13 @@ class PaidCourseEnrollmentReportProvider(BaseAbstractEnrollmentReportProvider):
elif
paid_course_reg_item
is
not
None
:
elif
paid_course_reg_item
is
not
None
:
enrollment_source
=
_
(
'Credit Card - Individual'
)
enrollment_source
=
_
(
'Credit Card - Individual'
)
else
:
else
:
enrollment_source
=
_
(
'Manually Enrolled'
)
manual_enrollment
=
ManualEnrollmentAudit
.
get_manual_enrollment
(
course_enrollment
)
if
manual_enrollment
is
not
None
:
enrollment_source
=
_
(
'manually enrolled by user_id {user_id}, enrollment state transition: {transition}'
)
.
format
(
user_id
=
manual_enrollment
.
enrolled_by_id
,
transition
=
manual_enrollment
.
state_transition
)
else
:
enrollment_source
=
_
(
'Manually Enrolled'
)
enrollment_date
=
course_enrollment
.
created
.
strftime
(
"
%
B
%
d,
%
Y"
)
enrollment_date
=
course_enrollment
.
created
.
strftime
(
"
%
B
%
d,
%
Y"
)
currently_enrolled
=
course_enrollment
.
is_active
currently_enrolled
=
course_enrollment
.
is_active
...
...
lms/djangoapps/instructor/tests/test_api.py
View file @
11fef201
...
@@ -43,7 +43,10 @@ from shoppingcart.models import (
...
@@ -43,7 +43,10 @@ from shoppingcart.models import (
InvoiceTransaction
)
InvoiceTransaction
)
from
shoppingcart.pdf
import
PDFInvoice
from
shoppingcart.pdf
import
PDFInvoice
from
student.models
import
(
from
student.models
import
(
CourseEnrollment
,
CourseEnrollmentAllowed
,
NonExistentCourseError
CourseEnrollment
,
CourseEnrollmentAllowed
,
NonExistentCourseError
,
ManualEnrollmentAudit
,
UNENROLLED_TO_ENROLLED
,
ENROLLED_TO_UNENROLLED
,
ALLOWEDTOENROLL_TO_UNENROLLED
,
ENROLLED_TO_ENROLLED
,
UNENROLLED_TO_ALLOWEDTOENROLL
,
UNENROLLED_TO_UNENROLLED
,
ALLOWEDTOENROLL_TO_ENROLLED
)
)
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
,
AdminFactory
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
,
AdminFactory
from
student.roles
import
CourseBetaTesterRole
,
CourseSalesAdminRole
,
CourseFinanceAdminRole
,
CourseInstructorRole
from
student.roles
import
CourseBetaTesterRole
,
CourseSalesAdminRole
,
CourseFinanceAdminRole
,
CourseInstructorRole
...
@@ -185,7 +188,8 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -185,7 +188,8 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
# Endpoints that only Staff or Instructors can access
# Endpoints that only Staff or Instructors can access
self
.
staff_level_endpoints
=
[
self
.
staff_level_endpoints
=
[
(
'students_update_enrollment'
,
{
'identifiers'
:
'foo@example.org'
,
'action'
:
'enroll'
}),
(
'students_update_enrollment'
,
{
'identifiers'
:
'foo@example.org'
,
'action'
:
'enroll'
}),
(
'get_grading_config'
,
{}),
(
'get_grading_config'
,
{}),
(
'get_students_features'
,
{}),
(
'get_students_features'
,
{}),
(
'get_distribution'
,
{}),
(
'get_distribution'
,
{}),
...
@@ -355,6 +359,10 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -355,6 +359,10 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertEquals
(
len
(
data
[
'warnings'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'warnings'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
0
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ENROLLED
)
# test the log for email that's send to new created user.
# test the log for email that's send to new created user.
info_log
.
assert_called_with
(
'email sent to new created user at
%
s'
,
'test_student@example.com'
)
info_log
.
assert_called_with
(
'email sent to new created user at
%
s'
,
'test_student@example.com'
)
...
@@ -372,6 +380,10 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -372,6 +380,10 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertEquals
(
len
(
data
[
'warnings'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'warnings'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
0
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ENROLLED
)
# test the log for email that's send to new created user.
# test the log for email that's send to new created user.
info_log
.
assert_called_with
(
'email sent to new created user at
%
s'
,
'test_student@example.com'
)
info_log
.
assert_called_with
(
'email sent to new created user at
%
s'
,
'test_student@example.com'
)
...
@@ -391,6 +403,10 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -391,6 +403,10 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertEquals
(
len
(
data
[
'warnings'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'warnings'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
0
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ENROLLED
)
# test the log for email that's send to new created user.
# test the log for email that's send to new created user.
info_log
.
assert_called_with
(
info_log
.
assert_called_with
(
u"user already exists with username '
%
s' and email '
%
s'"
,
u"user already exists with username '
%
s' and email '
%
s'"
,
...
@@ -409,6 +425,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -409,6 +425,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertNotEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertNotEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertEquals
(
data
[
'general_errors'
][
0
][
'response'
],
'Make sure that the file you upload is in CSV format with no extraneous characters or rows.'
)
self
.
assertEquals
(
data
[
'general_errors'
][
0
][
'response'
],
'Make sure that the file you upload is in CSV format with no extraneous characters or rows.'
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
0
)
def
test_bad_file_upload_type
(
self
):
def
test_bad_file_upload_type
(
self
):
"""
"""
Try uploading some non-CSV file and verify that it is rejected
Try uploading some non-CSV file and verify that it is rejected
...
@@ -420,6 +439,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -420,6 +439,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertNotEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertNotEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertEquals
(
data
[
'general_errors'
][
0
][
'response'
],
'Could not read uploaded file.'
)
self
.
assertEquals
(
data
[
'general_errors'
][
0
][
'response'
],
'Could not read uploaded file.'
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
0
)
def
test_insufficient_data
(
self
):
def
test_insufficient_data
(
self
):
"""
"""
Try uploading a CSV file which does not have the exact four columns of data
Try uploading a CSV file which does not have the exact four columns of data
...
@@ -434,6 +456,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -434,6 +456,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
1
)
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
1
)
self
.
assertEquals
(
data
[
'general_errors'
][
0
][
'response'
],
'Data in row #1 must have exactly four columns: email, username, full name, and country'
)
self
.
assertEquals
(
data
[
'general_errors'
][
0
][
'response'
],
'Data in row #1 must have exactly four columns: email, username, full name, and country'
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
0
)
def
test_invalid_email_in_csv
(
self
):
def
test_invalid_email_in_csv
(
self
):
"""
"""
Test failure case of a poorly formatted email field
Test failure case of a poorly formatted email field
...
@@ -449,6 +474,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -449,6 +474,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertEquals
(
data
[
'row_errors'
][
0
][
'response'
],
'Invalid email {0}.'
.
format
(
'test_student.example.com'
))
self
.
assertEquals
(
data
[
'row_errors'
][
0
][
'response'
],
'Invalid email {0}.'
.
format
(
'test_student.example.com'
))
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
0
)
@patch
(
'instructor.views.api.log.info'
)
@patch
(
'instructor.views.api.log.info'
)
def
test_csv_user_exist_and_not_enrolled
(
self
,
info_log
):
def
test_csv_user_exist_and_not_enrolled
(
self
,
info_log
):
"""
"""
...
@@ -465,6 +493,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -465,6 +493,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
u'NotEnrolledStudent'
,
u'NotEnrolledStudent'
,
self
.
course
.
id
self
.
course
.
id
)
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertTrue
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ENROLLED
)
def
test_user_with_already_existing_email_in_csv
(
self
):
def
test_user_with_already_existing_email_in_csv
(
self
):
"""
"""
...
@@ -485,6 +516,10 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -485,6 +516,10 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
user
=
User
.
objects
.
get
(
email
=
'test_student@example.com'
)
user
=
User
.
objects
.
get
(
email
=
'test_student@example.com'
)
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
user
,
self
.
course
.
id
))
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
user
,
self
.
course
.
id
))
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertTrue
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ENROLLED
)
def
test_user_with_already_existing_username_in_csv
(
self
):
def
test_user_with_already_existing_username_in_csv
(
self
):
"""
"""
If the username already exists (but not the email),
If the username already exists (but not the email),
...
@@ -516,6 +551,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -516,6 +551,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertNotEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertNotEquals
(
len
(
data
[
'general_errors'
]),
0
)
self
.
assertEquals
(
data
[
'general_errors'
][
0
][
'response'
],
'File is not attached.'
)
self
.
assertEquals
(
data
[
'general_errors'
][
0
][
'response'
],
'File is not attached.'
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
0
)
def
test_raising_exception_in_auto_registration_and_enrollment_case
(
self
):
def
test_raising_exception_in_auto_registration_and_enrollment_case
(
self
):
"""
"""
Test that exceptions are handled well
Test that exceptions are handled well
...
@@ -533,6 +571,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -533,6 +571,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertNotEquals
(
len
(
data
[
'row_errors'
]),
0
)
self
.
assertNotEquals
(
len
(
data
[
'row_errors'
]),
0
)
self
.
assertEquals
(
data
[
'row_errors'
][
0
][
'response'
],
'NonExistentCourseError'
)
self
.
assertEquals
(
data
[
'row_errors'
][
0
][
'response'
],
'NonExistentCourseError'
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
0
)
def
test_generate_unique_password
(
self
):
def
test_generate_unique_password
(
self
):
"""
"""
generate_unique_password should generate a unique password string that excludes certain characters.
generate_unique_password should generate a unique password string that excludes certain characters.
...
@@ -558,6 +599,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -558,6 +599,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
self
.
assertTrue
(
User
.
objects
.
filter
(
username
=
'test_student_2'
,
email
=
'test_student2@example.com'
)
.
exists
())
self
.
assertTrue
(
User
.
objects
.
filter
(
username
=
'test_student_2'
,
email
=
'test_student2@example.com'
)
.
exists
())
self
.
assertFalse
(
User
.
objects
.
filter
(
email
=
'test_student3@example.com'
)
.
exists
())
self
.
assertFalse
(
User
.
objects
.
filter
(
email
=
'test_student3@example.com'
)
.
exists
())
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
2
)
@patch.object
(
instructor
.
views
.
api
,
'generate_random_string'
,
@patch.object
(
instructor
.
views
.
api
,
'generate_random_string'
,
Mock
(
side_effect
=
[
'first'
,
'first'
,
'second'
]))
Mock
(
side_effect
=
[
'first'
,
'first'
,
'second'
]))
def
test_generate_unique_password_no_reuse
(
self
):
def
test_generate_unique_password_no_reuse
(
self
):
...
@@ -575,6 +619,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
...
@@ -575,6 +619,9 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(ModuleStoreTestCase, Log
response
=
self
.
client
.
post
(
self
.
url
,
{
'students_list'
:
uploaded_file
})
response
=
self
.
client
.
post
(
self
.
url
,
{
'students_list'
:
uploaded_file
})
self
.
assertEquals
(
response
.
status_code
,
403
)
self
.
assertEquals
(
response
.
status_code
,
403
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
0
)
@attr
(
'shard_1'
)
@attr
(
'shard_1'
)
@ddt.ddt
@ddt.ddt
...
@@ -658,7 +705,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -658,7 +705,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_invalid_username
(
self
):
def
test_invalid_username
(
self
):
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
'percivaloctavius'
,
'action'
:
'enroll'
,
'email_students'
:
False
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
'percivaloctavius'
,
'action'
:
'enroll'
,
'email_students'
:
False
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# test the response data
# test the response data
...
@@ -678,7 +726,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -678,7 +726,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_enroll_with_username
(
self
):
def
test_enroll_with_username
(
self
):
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
notenrolled_student
.
username
,
'action'
:
'enroll'
,
'email_students'
:
False
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
notenrolled_student
.
username
,
'action'
:
'enroll'
,
'email_students'
:
False
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# test the response data
# test the response data
...
@@ -703,13 +752,16 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -703,13 +752,16 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
}
}
]
]
}
}
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ENROLLED
)
res_json
=
json
.
loads
(
response
.
content
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
expected
)
self
.
assertEqual
(
res_json
,
expected
)
def
test_enroll_without_email
(
self
):
def
test_enroll_without_email
(
self
):
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
notenrolled_student
.
email
,
'action'
:
'enroll'
,
'email_students'
:
False
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
notenrolled_student
.
email
,
'action'
:
'enroll'
,
'email_students'
:
False
})
print
"type(self.notenrolled_student.email): {}"
.
format
(
type
(
self
.
notenrolled_student
.
email
))
print
"type(self.notenrolled_student.email): {}"
.
format
(
type
(
self
.
notenrolled_student
.
email
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
...
@@ -740,6 +792,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -740,6 +792,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
]
]
}
}
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ENROLLED
)
res_json
=
json
.
loads
(
response
.
content
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
expected
)
self
.
assertEqual
(
res_json
,
expected
)
...
@@ -811,6 +866,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -811,6 +866,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
params
=
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'enroll'
,
'email_students'
:
True
}
params
=
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'enroll'
,
'email_students'
:
True
}
environ
=
{
'wsgi.url_scheme'
:
protocol
}
environ
=
{
'wsgi.url_scheme'
:
protocol
}
response
=
self
.
client
.
post
(
url
,
params
,
**
environ
)
response
=
self
.
client
.
post
(
url
,
params
,
**
environ
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ALLOWEDTOENROLL
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# Check the outbox
# Check the outbox
...
@@ -839,10 +897,14 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -839,10 +897,14 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
environ
=
{
'wsgi.url_scheme'
:
protocol
}
environ
=
{
'wsgi.url_scheme'
:
protocol
}
response
=
self
.
client
.
post
(
url
,
params
,
**
environ
)
response
=
self
.
client
.
post
(
url
,
params
,
**
environ
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ALLOWEDTOENROLL
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
self
.
assertEqual
(
mail
.
outbox
[
0
]
.
body
,
mail
.
outbox
[
0
]
.
body
,
"Dear student,
\n\n
You have been invited to join {display_name} at edx.org by a member of the course staff.
\n\n
"
"Dear student,
\n\n
You have been invited to join {display_name}"
" at edx.org by a member of the course staff.
\n\n
"
"To finish your registration, please visit {proto}://{site}/register and fill out the registration form "
"To finish your registration, please visit {proto}://{site}/register and fill out the registration form "
"making sure to use robot-not-an-email-yet@robot.org in the E-mail field.
\n
"
"making sure to use robot-not-an-email-yet@robot.org in the E-mail field.
\n
"
"You can then enroll in {display_name}.
\n\n
----
\n
"
"You can then enroll in {display_name}.
\n\n
----
\n
"
...
@@ -867,12 +929,17 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -867,12 +929,17 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
mail
.
outbox
[
0
]
.
subject
,
mail
.
outbox
[
0
]
.
subject
,
u'You have been invited to register for {}'
.
format
(
self
.
course
.
display_name
)
u'You have been invited to register for {}'
.
format
(
self
.
course
.
display_name
)
)
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ALLOWEDTOENROLL
)
self
.
assertEqual
(
self
.
assertEqual
(
mail
.
outbox
[
0
]
.
body
,
mail
.
outbox
[
0
]
.
body
,
"Dear student,
\n\n
You have been invited to join {display_name} at edx.org by a member of the course staff.
\n\n
"
"Dear student,
\n\n
You have been invited to join {display_name}"
" at edx.org by a member of the course staff.
\n\n
"
"To finish your registration, please visit {proto}://{site}/register and fill out the registration form "
"To finish your registration, please visit {proto}://{site}/register and fill out the registration form "
"making sure to use robot-not-an-email-yet@robot.org in the E-mail field.
\n
"
"making sure to use robot-not-an-email-yet@robot.org in the E-mail field.
\n
"
"Once you have registered and activated your account, you will see {display_name} listed on your dashboard.
\n\n
----
\n
"
"Once you have registered and activated your account,"
" you will see {display_name} listed on your dashboard.
\n\n
----
\n
"
"This email was automatically sent from edx.org to robot-not-an-email-yet@robot.org"
.
format
(
"This email was automatically sent from edx.org to robot-not-an-email-yet@robot.org"
.
format
(
proto
=
protocol
,
site
=
self
.
site_name
,
display_name
=
self
.
course
.
display_name
proto
=
protocol
,
site
=
self
.
site_name
,
display_name
=
self
.
course
.
display_name
)
)
...
@@ -880,7 +947,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -880,7 +947,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_unenroll_without_email
(
self
):
def
test_unenroll_without_email
(
self
):
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
enrolled_student
.
email
,
'action'
:
'unenroll'
,
'email_students'
:
False
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
enrolled_student
.
email
,
'action'
:
'unenroll'
,
'email_students'
:
False
})
print
"type(self.enrolled_student.email): {}"
.
format
(
type
(
self
.
enrolled_student
.
email
))
print
"type(self.enrolled_student.email): {}"
.
format
(
type
(
self
.
enrolled_student
.
email
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
...
@@ -911,6 +979,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -911,6 +979,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
]
]
}
}
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
ENROLLED_TO_UNENROLLED
)
res_json
=
json
.
loads
(
response
.
content
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
expected
)
self
.
assertEqual
(
res_json
,
expected
)
...
@@ -919,7 +990,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -919,7 +990,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_unenroll_with_email
(
self
):
def
test_unenroll_with_email
(
self
):
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
enrolled_student
.
email
,
'action'
:
'unenroll'
,
'email_students'
:
True
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
enrolled_student
.
email
,
'action'
:
'unenroll'
,
'email_students'
:
True
})
print
"type(self.enrolled_student.email): {}"
.
format
(
type
(
self
.
enrolled_student
.
email
))
print
"type(self.enrolled_student.email): {}"
.
format
(
type
(
self
.
enrolled_student
.
email
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
...
@@ -950,6 +1022,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -950,6 +1022,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
]
]
}
}
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
ENROLLED_TO_UNENROLLED
)
res_json
=
json
.
loads
(
response
.
content
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
expected
)
self
.
assertEqual
(
res_json
,
expected
)
...
@@ -972,7 +1047,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -972,7 +1047,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
def
test_unenroll_with_email_allowed_student
(
self
):
def
test_unenroll_with_email_allowed_student
(
self
):
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
allowed_email
,
'action'
:
'unenroll'
,
'email_students'
:
True
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
allowed_email
,
'action'
:
'unenroll'
,
'email_students'
:
True
})
print
"type(self.allowed_email): {}"
.
format
(
type
(
self
.
allowed_email
))
print
"type(self.allowed_email): {}"
.
format
(
type
(
self
.
allowed_email
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
...
@@ -999,6 +1075,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -999,6 +1075,9 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
]
]
}
}
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
ALLOWEDTOENROLL_TO_UNENROLLED
)
res_json
=
json
.
loads
(
response
.
content
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
expected
)
self
.
assertEqual
(
res_json
,
expected
)
...
@@ -1054,7 +1133,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -1054,7 +1133,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
# Try with marketing site enabled
# Try with marketing site enabled
with
patch
.
dict
(
'django.conf.settings.FEATURES'
,
{
'ENABLE_MKTG_SITE'
:
True
}):
with
patch
.
dict
(
'django.conf.settings.FEATURES'
,
{
'ENABLE_MKTG_SITE'
:
True
}):
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'enroll'
,
'email_students'
:
True
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'enroll'
,
'email_students'
:
True
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
self
.
assertEqual
(
...
@@ -1087,7 +1167,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -1087,7 +1167,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
self
.
assertEqual
(
mail
.
outbox
[
0
]
.
body
,
mail
.
outbox
[
0
]
.
body
,
"Dear student,
\n\n
You have been invited to join {display_name} at edx.org by a member of the course staff.
\n\n
"
"Dear student,
\n\n
You have been invited to join {display_name}"
" at edx.org by a member of the course staff.
\n\n
"
"To access the course visit {proto}://{site}{course_path} and login.
\n\n
----
\n
"
"To access the course visit {proto}://{site}{course_path} and login.
\n\n
----
\n
"
"This email was automatically sent from edx.org to robot-not-an-email-yet@robot.org"
.
format
(
"This email was automatically sent from edx.org to robot-not-an-email-yet@robot.org"
.
format
(
display_name
=
self
.
course
.
display_name
,
display_name
=
self
.
course
.
display_name
,
...
@@ -1115,8 +1196,143 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -1115,8 +1196,143 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
course_enrollment
=
CourseEnrollment
.
objects
.
get
(
course_enrollment
=
CourseEnrollment
.
objects
.
get
(
user
=
self
.
enrolled_student
,
course_id
=
self
.
course
.
id
user
=
self
.
enrolled_student
,
course_id
=
self
.
course
.
id
)
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
ENROLLED_TO_ENROLLED
)
self
.
assertEqual
(
course_enrollment
.
mode
,
u"verified"
)
self
.
assertEqual
(
course_enrollment
.
mode
,
u"verified"
)
def
create_paid_course
(
self
):
"""
create paid course mode.
"""
paid_course
=
CourseFactory
.
create
()
CourseModeFactory
.
create
(
course_id
=
paid_course
.
id
,
min_price
=
50
)
CourseInstructorRole
(
paid_course
.
id
)
.
add_users
(
self
.
instructor
)
return
paid_course
def
test_reason_field_should_not_be_empty
(
self
):
"""
test to check that reason field should not be empty when
manually enrolling the students for the paid courses.
"""
paid_course
=
self
.
create_paid_course
()
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
paid_course
.
id
.
to_deprecated_string
()})
params
=
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'enroll'
,
'email_students'
:
False
,
'auto_enroll'
:
False
}
response
=
self
.
client
.
post
(
url
,
params
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
0
)
# test the response data
expected
=
{
"action"
:
"enroll"
,
"auto_enroll"
:
False
,
"results"
:
[
{
"error"
:
True
}
]
}
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
expected
)
def
test_unenrolled_allowed_to_enroll_user
(
self
):
"""
test to unenroll allow to enroll user.
"""
paid_course
=
self
.
create_paid_course
()
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
paid_course
.
id
.
to_deprecated_string
()})
params
=
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'enroll'
,
'email_students'
:
False
,
'auto_enroll'
:
False
,
'reason'
:
'testing..'
}
response
=
self
.
client
.
post
(
url
,
params
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_ALLOWEDTOENROLL
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# now registered the user
UserFactory
(
email
=
self
.
notregistered_email
)
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
paid_course
.
id
.
to_deprecated_string
()})
params
=
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'enroll'
,
'email_students'
:
False
,
'auto_enroll'
:
False
,
'reason'
:
'testing'
}
response
=
self
.
client
.
post
(
url
,
params
)
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
2
)
self
.
assertEqual
(
manual_enrollments
[
1
]
.
state_transition
,
ALLOWEDTOENROLL_TO_ENROLLED
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# test the response data
expected
=
{
"action"
:
"enroll"
,
"auto_enroll"
:
False
,
"results"
:
[
{
"identifier"
:
self
.
notregistered_email
,
"before"
:
{
"enrollment"
:
False
,
"auto_enroll"
:
False
,
"user"
:
True
,
"allowed"
:
True
,
},
"after"
:
{
"enrollment"
:
True
,
"auto_enroll"
:
False
,
"user"
:
True
,
"allowed"
:
True
,
}
}
]
}
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
expected
)
def
test_unenrolled_already_not_enrolled_user
(
self
):
"""
test unenrolled user already not enrolled in a course.
"""
paid_course
=
self
.
create_paid_course
()
course_enrollment
=
CourseEnrollment
.
objects
.
filter
(
user__email
=
self
.
notregistered_email
,
course_id
=
paid_course
.
id
)
self
.
assertEqual
(
course_enrollment
.
count
(),
0
)
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
paid_course
.
id
.
to_deprecated_string
()})
params
=
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'unenroll'
,
'email_students'
:
False
,
'auto_enroll'
:
False
,
'reason'
:
'testing'
}
response
=
self
.
client
.
post
(
url
,
params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# test the response data
expected
=
{
"action"
:
"unenroll"
,
"auto_enroll"
:
False
,
"results"
:
[
{
"identifier"
:
self
.
notregistered_email
,
"before"
:
{
"enrollment"
:
False
,
"auto_enroll"
:
False
,
"user"
:
False
,
"allowed"
:
False
,
},
"after"
:
{
"enrollment"
:
False
,
"auto_enroll"
:
False
,
"user"
:
False
,
"allowed"
:
False
,
}
}
]
}
manual_enrollments
=
ManualEnrollmentAudit
.
objects
.
all
()
self
.
assertEqual
(
manual_enrollments
.
count
(),
1
)
self
.
assertEqual
(
manual_enrollments
[
0
]
.
state_transition
,
UNENROLLED_TO_UNENROLLED
)
res_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
res_json
,
expected
)
def
test_unenroll_and_enroll_verified
(
self
):
def
test_unenroll_and_enroll_verified
(
self
):
"""
"""
Test that unenrolling and enrolling a student from a verified track
Test that unenrolling and enrolling a student from a verified track
...
@@ -1152,6 +1368,7 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -1152,6 +1368,7 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
'identifiers'
:
user
.
email
,
'identifiers'
:
user
.
email
,
'action'
:
action
,
'action'
:
action
,
'email_students'
:
True
,
'email_students'
:
True
,
'reason'
:
'change user enrollment'
}
}
response
=
self
.
client
.
post
(
url
,
params
)
response
=
self
.
client
.
post
(
url
,
params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
...
@@ -1382,7 +1599,9 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe
...
@@ -1382,7 +1599,9 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe
def
test_enroll_with_email_not_registered
(
self
):
def
test_enroll_with_email_not_registered
(
self
):
# User doesn't exist
# User doesn't exist
url
=
reverse
(
'bulk_beta_modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'bulk_beta_modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'add'
,
'email_students'
:
True
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
notregistered_email
,
'action'
:
'add'
,
'email_students'
:
True
,
'reason'
:
'testing'
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# test the response data
# test the response data
expected
=
{
expected
=
{
...
@@ -1403,7 +1622,9 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe
...
@@ -1403,7 +1622,9 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe
def
test_remove_without_email
(
self
):
def
test_remove_without_email
(
self
):
url
=
reverse
(
'bulk_beta_modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'bulk_beta_modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
beta_tester
.
email
,
'action'
:
'remove'
,
'email_students'
:
False
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
beta_tester
.
email
,
'action'
:
'remove'
,
'email_students'
:
False
,
'reason'
:
'testing'
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# Works around a caching bug which supposedly can't happen in prod. The instance here is not ==
# Works around a caching bug which supposedly can't happen in prod. The instance here is not ==
...
@@ -1431,7 +1652,9 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe
...
@@ -1431,7 +1652,9 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe
def
test_remove_with_email
(
self
):
def
test_remove_with_email
(
self
):
url
=
reverse
(
'bulk_beta_modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'bulk_beta_modify_access'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
beta_tester
.
email
,
'action'
:
'remove'
,
'email_students'
:
True
})
response
=
self
.
client
.
post
(
url
,
{
'identifiers'
:
self
.
beta_tester
.
email
,
'action'
:
'remove'
,
'email_students'
:
True
,
'reason'
:
'testing'
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# Works around a caching bug which supposedly can't happen in prod. The instance here is not ==
# Works around a caching bug which supposedly can't happen in prod. The instance here is not ==
...
...
lms/djangoapps/instructor/tests/test_api_email_localization.py
View file @
11fef201
...
@@ -42,7 +42,7 @@ class TestInstructorAPIEnrollmentEmailLocalization(ModuleStoreTestCase):
...
@@ -42,7 +42,7 @@ class TestInstructorAPIEnrollmentEmailLocalization(ModuleStoreTestCase):
Update the current student enrollment status.
Update the current student enrollment status.
"""
"""
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
url
=
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
args
=
{
'identifiers'
:
student_email
,
'email_students'
:
'true'
,
'action'
:
action
}
args
=
{
'identifiers'
:
student_email
,
'email_students'
:
'true'
,
'action'
:
action
,
'reason'
:
'testing'
}
response
=
self
.
client
.
post
(
url
,
args
)
response
=
self
.
client
.
post
(
url
,
args
)
return
response
return
response
...
...
lms/djangoapps/instructor/views/api.py
View file @
11fef201
...
@@ -31,7 +31,10 @@ import urllib
...
@@ -31,7 +31,10 @@ import urllib
import
decimal
import
decimal
from
student
import
auth
from
student
import
auth
from
student.roles
import
GlobalStaff
,
CourseSalesAdminRole
,
CourseFinanceAdminRole
from
student.roles
import
GlobalStaff
,
CourseSalesAdminRole
,
CourseFinanceAdminRole
from
util.file
import
store_uploaded_file
,
course_and_time_based_filename_generator
,
FileValidationException
,
UniversalNewlineIterator
from
util.file
import
(
store_uploaded_file
,
course_and_time_based_filename_generator
,
FileValidationException
,
UniversalNewlineIterator
)
from
util.json_request
import
JsonResponse
from
util.json_request
import
JsonResponse
from
instructor.views.instructor_task_helpers
import
extract_email_features
,
extract_task_features
from
instructor.views.instructor_task_helpers
import
extract_email_features
,
extract_task_features
...
@@ -59,7 +62,10 @@ from shoppingcart.models import (
...
@@ -59,7 +62,10 @@ from shoppingcart.models import (
)
)
from
student.models
import
(
from
student.models
import
(
CourseEnrollment
,
unique_id_for_user
,
anonymous_id_for_user
,
CourseEnrollment
,
unique_id_for_user
,
anonymous_id_for_user
,
UserProfile
,
Registration
,
EntranceExamConfiguration
UserProfile
,
Registration
,
EntranceExamConfiguration
,
ManualEnrollmentAudit
,
UNENROLLED_TO_ALLOWEDTOENROLL
,
ALLOWEDTOENROLL_TO_ENROLLED
,
ENROLLED_TO_ENROLLED
,
ENROLLED_TO_UNENROLLED
,
UNENROLLED_TO_ENROLLED
,
UNENROLLED_TO_UNENROLLED
,
ALLOWEDTOENROLL_TO_UNENROLLED
,
DEFAULT_TRANSITION_STATE
)
)
import
instructor_task.api
import
instructor_task.api
from
instructor_task.api_helper
import
AlreadyRunningError
from
instructor_task.api_helper
import
AlreadyRunningError
...
@@ -413,7 +419,11 @@ def register_and_enroll_students(request, course_id): # pylint: disable=too-man
...
@@ -413,7 +419,11 @@ def register_and_enroll_students(request, course_id): # pylint: disable=too-man
# make sure user is enrolled in course
# make sure user is enrolled in course
if
not
CourseEnrollment
.
is_enrolled
(
user
,
course_id
):
if
not
CourseEnrollment
.
is_enrolled
(
user
,
course_id
):
CourseEnrollment
.
enroll
(
user
,
course_id
)
enrollment_obj
=
CourseEnrollment
.
enroll
(
user
,
course_id
)
reason
=
'Enrolling via csv upload'
ManualEnrollmentAudit
.
create_manual_enrollment_audit
(
request
.
user
,
email
,
UNENROLLED_TO_ENROLLED
,
reason
,
enrollment_obj
)
log
.
info
(
log
.
info
(
u'user
%
s enrolled in the course
%
s'
,
u'user
%
s enrolled in the course
%
s'
,
username
,
username
,
...
@@ -427,7 +437,11 @@ def register_and_enroll_students(request, course_id): # pylint: disable=too-man
...
@@ -427,7 +437,11 @@ def register_and_enroll_students(request, course_id): # pylint: disable=too-man
password
=
generate_unique_password
(
generated_passwords
)
password
=
generate_unique_password
(
generated_passwords
)
try
:
try
:
create_and_enroll_user
(
email
,
username
,
name
,
country
,
password
,
course_id
)
enrollment_obj
=
create_and_enroll_user
(
email
,
username
,
name
,
country
,
password
,
course_id
)
reason
=
'Enrolling via csv upload'
ManualEnrollmentAudit
.
create_manual_enrollment_audit
(
request
.
user
,
email
,
UNENROLLED_TO_ENROLLED
,
reason
,
enrollment_obj
)
except
IntegrityError
:
except
IntegrityError
:
row_errors
.
append
({
row_errors
.
append
({
'username'
:
username
,
'email'
:
email
,
'response'
:
_
(
'Username {user} already exists.'
)
.
format
(
user
=
username
)})
'username'
:
username
,
'email'
:
email
,
'response'
:
_
(
'Username {user} already exists.'
)
.
format
(
user
=
username
)})
...
@@ -496,7 +510,7 @@ def create_and_enroll_user(email, username, name, country, password, course_id):
...
@@ -496,7 +510,7 @@ def create_and_enroll_user(email, username, name, country, password, course_id):
profile
.
save
()
profile
.
save
()
# try to enroll the user in this course
# try to enroll the user in this course
CourseEnrollment
.
enroll
(
user
,
course_id
)
return
CourseEnrollment
.
enroll
(
user
,
course_id
)
@ensure_csrf_cookie
@ensure_csrf_cookie
...
@@ -546,6 +560,18 @@ def students_update_enrollment(request, course_id):
...
@@ -546,6 +560,18 @@ def students_update_enrollment(request, course_id):
identifiers
=
_split_input_list
(
identifiers_raw
)
identifiers
=
_split_input_list
(
identifiers_raw
)
auto_enroll
=
request
.
POST
.
get
(
'auto_enroll'
)
in
[
'true'
,
'True'
,
True
]
auto_enroll
=
request
.
POST
.
get
(
'auto_enroll'
)
in
[
'true'
,
'True'
,
True
]
email_students
=
request
.
POST
.
get
(
'email_students'
)
in
[
'true'
,
'True'
,
True
]
email_students
=
request
.
POST
.
get
(
'email_students'
)
in
[
'true'
,
'True'
,
True
]
is_white_label
=
CourseMode
.
is_white_label
(
course_id
)
reason
=
request
.
POST
.
get
(
'reason'
)
if
is_white_label
:
if
not
reason
:
return
JsonResponse
(
{
'action'
:
action
,
'results'
:
[{
'error'
:
True
}],
'auto_enroll'
:
auto_enroll
,
},
status
=
400
)
enrollment_obj
=
None
state_transition
=
DEFAULT_TRANSITION_STATE
email_params
=
{}
email_params
=
{}
if
email_students
:
if
email_students
:
...
@@ -571,15 +597,44 @@ def students_update_enrollment(request, course_id):
...
@@ -571,15 +597,44 @@ def students_update_enrollment(request, course_id):
# validity (obviously, cannot check if email actually /exists/,
# validity (obviously, cannot check if email actually /exists/,
# simply that it is plausibly valid)
# simply that it is plausibly valid)
validate_email
(
email
)
# Raises ValidationError if invalid
validate_email
(
email
)
# Raises ValidationError if invalid
if
action
==
'enroll'
:
if
action
==
'enroll'
:
before
,
after
=
enroll_email
(
before
,
after
,
enrollment_obj
=
enroll_email
(
course_id
,
email
,
auto_enroll
,
email_students
,
email_params
,
language
=
language
course_id
,
email
,
auto_enroll
,
email_students
,
email_params
,
language
=
language
)
)
before_enrollment
=
before
.
to_dict
()[
'enrollment'
]
before_user_registered
=
before
.
to_dict
()[
'user'
]
before_allowed
=
before
.
to_dict
()[
'allowed'
]
after_enrollment
=
after
.
to_dict
()[
'enrollment'
]
after_allowed
=
after
.
to_dict
()[
'allowed'
]
if
before_user_registered
:
if
after_enrollment
:
if
before_enrollment
:
state_transition
=
ENROLLED_TO_ENROLLED
else
:
if
before_allowed
:
state_transition
=
ALLOWEDTOENROLL_TO_ENROLLED
else
:
state_transition
=
UNENROLLED_TO_ENROLLED
else
:
if
after_allowed
:
state_transition
=
UNENROLLED_TO_ALLOWEDTOENROLL
elif
action
==
'unenroll'
:
elif
action
==
'unenroll'
:
before
,
after
=
unenroll_email
(
before
,
after
=
unenroll_email
(
course_id
,
email
,
email_students
,
email_params
,
language
=
language
course_id
,
email
,
email_students
,
email_params
,
language
=
language
)
)
before_enrollment
=
before
.
to_dict
()[
'enrollment'
]
before_allowed
=
before
.
to_dict
()[
'allowed'
]
if
before_enrollment
:
state_transition
=
ENROLLED_TO_UNENROLLED
else
:
if
before_allowed
:
state_transition
=
ALLOWEDTOENROLL_TO_UNENROLLED
else
:
state_transition
=
UNENROLLED_TO_UNENROLLED
else
:
else
:
return
HttpResponseBadRequest
(
strip_tags
(
return
HttpResponseBadRequest
(
strip_tags
(
"Unrecognized action '{}'"
.
format
(
action
)
"Unrecognized action '{}'"
.
format
(
action
)
...
@@ -604,6 +659,9 @@ def students_update_enrollment(request, course_id):
...
@@ -604,6 +659,9 @@ def students_update_enrollment(request, course_id):
})
})
else
:
else
:
ManualEnrollmentAudit
.
create_manual_enrollment_audit
(
request
.
user
,
email
,
state_transition
,
reason
,
enrollment_obj
)
results
.
append
({
results
.
append
({
'identifier'
:
identifier
,
'identifier'
:
identifier
,
'before'
:
before
.
to_dict
(),
'before'
:
before
.
to_dict
(),
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
11fef201
...
@@ -71,9 +71,11 @@ def instructor_dashboard_2(request, course_id):
...
@@ -71,9 +71,11 @@ def instructor_dashboard_2(request, course_id):
if
not
access
[
'staff'
]:
if
not
access
[
'staff'
]:
raise
Http404
()
raise
Http404
()
is_white_label
=
CourseMode
.
is_white_label
(
course_key
)
sections
=
[
sections
=
[
_section_course_info
(
course
,
access
),
_section_course_info
(
course
,
access
),
_section_membership
(
course
,
access
),
_section_membership
(
course
,
access
,
is_white_label
),
_section_cohort_management
(
course
,
access
),
_section_cohort_management
(
course
,
access
),
_section_student_admin
(
course
,
access
),
_section_student_admin
(
course
,
access
),
_section_data_download
(
course
,
access
),
_section_data_download
(
course
,
access
),
...
@@ -92,8 +94,6 @@ def instructor_dashboard_2(request, course_id):
...
@@ -92,8 +94,6 @@ def instructor_dashboard_2(request, course_id):
unicode
(
course_key
),
len
(
paid_modes
)
unicode
(
course_key
),
len
(
paid_modes
)
)
)
is_white_label
=
CourseMode
.
is_white_label
(
course_key
)
if
(
settings
.
FEATURES
.
get
(
'INDIVIDUAL_DUE_DATES'
)
and
access
[
'instructor'
]):
if
(
settings
.
FEATURES
.
get
(
'INDIVIDUAL_DUE_DATES'
)
and
access
[
'instructor'
]):
sections
.
insert
(
3
,
_section_extensions
(
course
))
sections
.
insert
(
3
,
_section_extensions
(
course
))
...
@@ -321,7 +321,7 @@ def _section_course_info(course, access):
...
@@ -321,7 +321,7 @@ def _section_course_info(course, access):
return
section_data
return
section_data
def
_section_membership
(
course
,
access
):
def
_section_membership
(
course
,
access
,
is_white_label
):
""" Provide data for the corresponding dashboard section """
""" Provide data for the corresponding dashboard section """
course_key
=
course
.
id
course_key
=
course
.
id
ccx_enabled
=
settings
.
FEATURES
.
get
(
'CUSTOM_COURSES_EDX'
,
False
)
and
course
.
enable_ccx
ccx_enabled
=
settings
.
FEATURES
.
get
(
'CUSTOM_COURSES_EDX'
,
False
)
and
course
.
enable_ccx
...
@@ -330,6 +330,7 @@ def _section_membership(course, access):
...
@@ -330,6 +330,7 @@ def _section_membership(course, access):
'section_display_name'
:
_
(
'Membership'
),
'section_display_name'
:
_
(
'Membership'
),
'access'
:
access
,
'access'
:
access
,
'ccx_is_enabled'
:
ccx_enabled
,
'ccx_is_enabled'
:
ccx_enabled
,
'is_white_label'
:
is_white_label
,
'enroll_button_url'
:
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'enroll_button_url'
:
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'unenroll_button_url'
:
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'unenroll_button_url'
:
reverse
(
'students_update_enrollment'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'upload_student_csv_button_url'
:
reverse
(
'register_and_enroll_students'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'upload_student_csv_button_url'
:
reverse
(
'register_and_enroll_students'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
...
...
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
View file @
11fef201
...
@@ -27,7 +27,7 @@ from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartiti
...
@@ -27,7 +27,7 @@ from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartiti
from
shoppingcart.models
import
Order
,
PaidCourseRegistration
,
CourseRegistrationCode
,
Invoice
,
\
from
shoppingcart.models
import
Order
,
PaidCourseRegistration
,
CourseRegistrationCode
,
Invoice
,
\
CourseRegistrationCodeInvoiceItem
,
InvoiceTransaction
CourseRegistrationCodeInvoiceItem
,
InvoiceTransaction
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
,
ManualEnrollmentAudit
,
ALLOWEDTOENROLL_TO_ENROLLED
from
verify_student.tests.factories
import
SoftwareSecurePhotoVerificationFactory
from
verify_student.tests.factories
import
SoftwareSecurePhotoVerificationFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
xmodule.partitions.partitions
import
Group
,
UserPartition
...
@@ -321,6 +321,28 @@ class TestInstructorDetailedEnrollmentReport(TestReportMixin, InstructorTaskCour
...
@@ -321,6 +321,28 @@ class TestInstructorDetailedEnrollmentReport(TestReportMixin, InstructorTaskCour
self
.
_verify_cell_data_in_csv
(
student
.
username
,
'Enrollment Source'
,
'Credit Card - Individual'
)
self
.
_verify_cell_data_in_csv
(
student
.
username
,
'Enrollment Source'
,
'Credit Card - Individual'
)
self
.
_verify_cell_data_in_csv
(
student
.
username
,
'Payment Status'
,
'purchased'
)
self
.
_verify_cell_data_in_csv
(
student
.
username
,
'Payment Status'
,
'purchased'
)
def
test_student_manually_enrolled_in_detailed_enrollment_source
(
self
):
"""
test to check the manually enrolled user enrollment report status
and enrollment source.
"""
student
=
UserFactory
()
enrollment
=
CourseEnrollment
.
enroll
(
student
,
self
.
course
.
id
)
ManualEnrollmentAudit
.
create_manual_enrollment_audit
(
self
.
instructor
,
student
.
email
,
ALLOWEDTOENROLL_TO_ENROLLED
,
'manually enrolling unenrolled user'
,
enrollment
)
task_input
=
{
'features'
:
[]}
with
patch
(
'instructor_task.tasks_helper._get_current_task'
):
result
=
upload_enrollment_report
(
None
,
None
,
self
.
course
.
id
,
task_input
,
'generating_enrollment_report'
)
enrollment_source
=
u'manually enrolled by user_id {user_id}, enrollment state transition: {transition}'
.
format
(
user_id
=
self
.
instructor
.
id
,
transition
=
ALLOWEDTOENROLL_TO_ENROLLED
)
# pylint: disable=no-member
self
.
assertDictContainsSubset
({
'attempted'
:
1
,
'succeeded'
:
1
,
'failed'
:
0
},
result
)
self
.
_verify_cell_data_in_csv
(
student
.
username
,
'Enrollment Source'
,
enrollment_source
)
self
.
_verify_cell_data_in_csv
(
student
.
username
,
'Payment Status'
,
'TBD'
)
def
test_student_used_enrollment_code_for_course_enrollment
(
self
):
def
test_student_used_enrollment_code_for_course_enrollment
(
self
):
"""
"""
test to check the user enrollment source and payment status in the
test to check the user enrollment source and payment status in the
...
...
lms/static/coffee/src/instructor_dashboard/membership.coffee
View file @
11fef201
...
@@ -367,6 +367,8 @@ class BatchEnrollment
...
@@ -367,6 +367,8 @@ class BatchEnrollment
# gather elements
# gather elements
@
$identifier_input
=
@
$container
.
find
(
"textarea[name='student-ids']"
)
@
$identifier_input
=
@
$container
.
find
(
"textarea[name='student-ids']"
)
@
$enrollment_button
=
@
$container
.
find
(
".enrollment-button"
)
@
$enrollment_button
=
@
$container
.
find
(
".enrollment-button"
)
@
$is_course_white_label
=
@
$container
.
find
(
"#is_course_white_label"
).
val
()
@
$reason_field
=
@
$container
.
find
(
"textarea[name='reason-field']"
)
@
$checkbox_autoenroll
=
@
$container
.
find
(
"input[name='auto-enroll']"
)
@
$checkbox_autoenroll
=
@
$container
.
find
(
"input[name='auto-enroll']"
)
@
$checkbox_emailstudents
=
@
$container
.
find
(
"input[name='email-students']"
)
@
$checkbox_emailstudents
=
@
$container
.
find
(
"input[name='email-students']"
)
@
$task_response
=
@
$container
.
find
(
".request-response"
)
@
$task_response
=
@
$container
.
find
(
".request-response"
)
...
@@ -374,12 +376,18 @@ class BatchEnrollment
...
@@ -374,12 +376,18 @@ class BatchEnrollment
# attach click handler for enrollment buttons
# attach click handler for enrollment buttons
@
$enrollment_button
.
click
(
event
)
=>
@
$enrollment_button
.
click
(
event
)
=>
if
@
$is_course_white_label
==
'True'
if
not
@
$reason_field
.
val
()
@
fail_with_error
gettext
"Reason field should not be left blank."
return
false
emailStudents
=
@
$checkbox_emailstudents
.
is
(
':checked'
)
emailStudents
=
@
$checkbox_emailstudents
.
is
(
':checked'
)
send_data
=
send_data
=
action
:
$
(
event
.
target
).
data
(
'action'
)
# 'enroll' or 'unenroll'
action
:
$
(
event
.
target
).
data
(
'action'
)
# 'enroll' or 'unenroll'
identifiers
:
@
$identifier_input
.
val
()
identifiers
:
@
$identifier_input
.
val
()
auto_enroll
:
@
$checkbox_autoenroll
.
is
(
':checked'
)
auto_enroll
:
@
$checkbox_autoenroll
.
is
(
':checked'
)
email_students
:
emailStudents
email_students
:
emailStudents
reason
:
@
$reason_field
.
val
()
$
.
ajax
$
.
ajax
dataType
:
'json'
dataType
:
'json'
...
@@ -393,6 +401,7 @@ class BatchEnrollment
...
@@ -393,6 +401,7 @@ class BatchEnrollment
# clear the input text field
# clear the input text field
clear_input
:
->
clear_input
:
->
@
$identifier_input
.
val
''
@
$identifier_input
.
val
''
@
$reason_field
.
val
''
# default for the checkboxes should be checked
# default for the checkboxes should be checked
@
$checkbox_emailstudents
.
attr
(
'checked'
,
true
)
@
$checkbox_emailstudents
.
attr
(
'checked'
,
true
)
@
$checkbox_autoenroll
.
attr
(
'checked'
,
true
)
@
$checkbox_autoenroll
.
attr
(
'checked'
,
true
)
...
...
lms/templates/instructor/instructor_dashboard_2/membership.html
View file @
11fef201
...
@@ -37,7 +37,16 @@
...
@@ -37,7 +37,16 @@
${_("You will not get notification for emails that bounce, so please double-check spelling.")}
</label>
${_("You will not get notification for emails that bounce, so please double-check spelling.")}
</label>
<textarea
rows=
"6"
name=
"student-ids"
placeholder=
"${_("
Email
Addresses
/
Usernames
")}"
spellcheck=
"false"
></textarea>
<textarea
rows=
"6"
name=
"student-ids"
placeholder=
"${_("
Email
Addresses
/
Usernames
")}"
spellcheck=
"false"
></textarea>
</p>
</p>
<input
type=
"hidden"
id=
"is_course_white_label"
value=
"${section_data['is_white_label']}"
>
% if section_data['is_white_label']:
<p>
<label
for=
"reason-field-id"
>
${_("Enter the reason why the students are to be manually enrolled or unenrolled.")}
${_("This cannot be left blank and will be recorded and presented in Enrollment Reports.")}
${_("Therefore, please given enough detail to account for this action.")}
</label>
<textarea
rows=
"2"
id=
"reason-field-id"
name=
"reason-field"
placeholder=
"${_('Reason')}"
spellcheck=
"false"
></textarea>
</p>
%endif
<div
class=
"enroll-option"
>
<div
class=
"enroll-option"
>
<input
type=
"checkbox"
name=
"auto-enroll"
value=
"Auto-Enroll"
checked=
"yes"
>
<input
type=
"checkbox"
name=
"auto-enroll"
value=
"Auto-Enroll"
checked=
"yes"
>
<label
for=
"auto-enroll"
>
${_("Auto Enroll")}
</label>
<label
for=
"auto-enroll"
>
${_("Auto Enroll")}
</label>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment