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
eafb0755
Commit
eafb0755
authored
Aug 12, 2015
by
Peter Fogg
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #9287 from edx/release
Release
parents
5b9b0a83
986564c4
Hide whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
250 additions
and
1280 deletions
+250
-1280
cms/djangoapps/contentstore/tests/test_contentstore.py
+2
-2
cms/djangoapps/contentstore/views/course.py
+1
-1
cms/djangoapps/contentstore/views/tests/test_import_export.py
+0
-35
cms/djangoapps/contentstore/views/tests/test_item.py
+1
-1
common/djangoapps/student/models.py
+0
-6
common/djangoapps/student/views.py
+0
-18
common/lib/xmodule/xmodule/course_module.py
+1
-1
lms/djangoapps/ccx/migrations/0002_convert_memberships.py
+102
-0
lms/djangoapps/ccx/models.py
+0
-47
lms/djangoapps/ccx/tests/factories.py
+0
-11
lms/djangoapps/ccx/tests/test_field_override_performance.py
+10
-11
lms/djangoapps/ccx/tests/test_models.py
+0
-115
lms/djangoapps/ccx/tests/test_utils.py
+18
-544
lms/djangoapps/ccx/tests/test_views.py
+27
-34
lms/djangoapps/ccx/utils.py
+13
-273
lms/djangoapps/ccx/views.py
+27
-21
lms/djangoapps/django_comment_client/utils.py
+1
-1
lms/djangoapps/mobile_api/users/tests.py
+2
-2
lms/djangoapps/oauth2_handler/handlers.py
+10
-17
lms/djangoapps/oauth2_handler/tests.py
+15
-43
lms/templates/ccx/_dashboard_ccx_listing.html
+0
-80
lms/templates/ccx/enrollment.html
+2
-2
lms/templates/dashboard.html
+0
-8
openedx/core/djangoapps/content/course_overviews/models.py
+15
-4
openedx/core/djangoapps/content/course_overviews/tests.py
+2
-2
requirements/edx/github.txt
+1
-1
No files found.
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
eafb0755
...
@@ -1903,8 +1903,8 @@ class RerunCourseTest(ContentStoreTestCase):
...
@@ -1903,8 +1903,8 @@ class RerunCourseTest(ContentStoreTestCase):
source_course
=
CourseFactory
.
create
(
advertised_start
=
"01-12-2015"
)
source_course
=
CourseFactory
.
create
(
advertised_start
=
"01-12-2015"
)
destination_course_key
=
self
.
post_rerun_request
(
source_course
.
id
)
destination_course_key
=
self
.
post_rerun_request
(
source_course
.
id
)
destination_course
=
self
.
store
.
get_course
(
destination_course_key
)
destination_course
=
self
.
store
.
get_course
(
destination_course_key
)
# Advertised_start is String field so it will return empty string if its not set
self
.
assertEqual
(
''
,
destination_course
.
advertised_start
)
self
.
assertEqual
(
None
,
destination_course
.
advertised_start
)
def
test_rerun_of_rerun
(
self
):
def
test_rerun_of_rerun
(
self
):
source_course
=
CourseFactory
.
create
()
source_course
=
CourseFactory
.
create
()
...
...
cms/djangoapps/contentstore/views/course.py
View file @
eafb0755
...
@@ -787,7 +787,7 @@ def _rerun_course(request, org, number, run, fields):
...
@@ -787,7 +787,7 @@ def _rerun_course(request, org, number, run, fields):
CourseRerunState
.
objects
.
initiated
(
source_course_key
,
destination_course_key
,
request
.
user
,
fields
[
'display_name'
])
CourseRerunState
.
objects
.
initiated
(
source_course_key
,
destination_course_key
,
request
.
user
,
fields
[
'display_name'
])
# Clear the fields that must be reset for the rerun
# Clear the fields that must be reset for the rerun
fields
[
'advertised_start'
]
=
''
fields
[
'advertised_start'
]
=
None
# Rerun the course as a new celery task
# Rerun the course as a new celery task
json_fields
=
json
.
dumps
(
fields
,
cls
=
EdxJSONEncoder
)
json_fields
=
json
.
dumps
(
fields
,
cls
=
EdxJSONEncoder
)
...
...
cms/djangoapps/contentstore/views/tests/test_import_export.py
View file @
eafb0755
...
@@ -451,41 +451,6 @@ class ExportTestCase(CourseTestCase):
...
@@ -451,41 +451,6 @@ class ExportTestCase(CourseTestCase):
finally
:
finally
:
shutil
.
rmtree
(
root_dir
/
name
)
shutil
.
rmtree
(
root_dir
/
name
)
def
test_library_import_then_export
(
self
):
"""
Verify that a library exports successfully after being imported.
"""
library
=
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
lib_key
=
library
.
location
.
library_key
name
=
library
.
url_name
# import the library
extract_dir
=
path
(
tempfile
.
mkdtemp
(
dir
=
settings
.
DATA_DIR
))
extract_dir_relative
=
path
.
relpath
(
extract_dir
,
settings
.
DATA_DIR
)
try
:
with
tarfile
.
open
(
path
(
TEST_DATA_DIR
)
/
'imports'
/
'library.HhJfPD.tar.gz'
)
as
tar
:
safetar_extractall
(
tar
,
extract_dir
)
library_items
=
import_library_from_xml
(
self
.
store
,
self
.
user
.
id
,
settings
.
GITHUB_REPO_ROOT
,
[
extract_dir_relative
/
'library'
],
load_error_modules
=
False
,
static_content_store
=
contentstore
(),
target_id
=
lib_key
)
# verify library import correctly
self
.
assertEqual
(
lib_key
,
library_items
[
0
]
.
location
.
library_key
)
library
=
self
.
store
.
get_library
(
lib_key
)
self
.
assertEqual
(
len
(
library
.
children
),
3
)
# export library again
export_library_to_xml
(
self
.
store
,
contentstore
(),
lib_key
,
extract_dir
,
name
)
finally
:
shutil
.
rmtree
(
extract_dir
)
def
test_export_success_with_custom_tag
(
self
):
def
test_export_success_with_custom_tag
(
self
):
"""
"""
Verify that course export with customtag
Verify that course export with customtag
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
eafb0755
...
@@ -625,7 +625,7 @@ class TestEditItem(TestEditItemSetup):
...
@@ -625,7 +625,7 @@ class TestEditItem(TestEditItemSetup):
data
=
{
'nullout'
:
[
'markdown'
]}
data
=
{
'nullout'
:
[
'markdown'
]}
)
)
problem
=
self
.
get_item_from_modulestore
(
self
.
problem_usage_key
,
verify_is_draft
=
True
)
problem
=
self
.
get_item_from_modulestore
(
self
.
problem_usage_key
,
verify_is_draft
=
True
)
self
.
assert
Equal
(
problem
.
markdown
,
''
)
self
.
assert
IsNone
(
problem
.
markdown
)
def
test_date_fields
(
self
):
def
test_date_fields
(
self
):
"""
"""
...
...
common/djangoapps/student/models.py
View file @
eafb0755
...
@@ -1200,12 +1200,6 @@ class CourseEnrollment(models.Model):
...
@@ -1200,12 +1200,6 @@ class CourseEnrollment(models.Model):
if
not
user
.
is_authenticated
():
if
not
user
.
is_authenticated
():
return
False
return
False
# unwrap CCXLocators so that we use the course as the access control
# source
from
ccx_keys.locator
import
CCXLocator
if
isinstance
(
course_key
,
CCXLocator
):
course_key
=
course_key
.
to_course_locator
()
try
:
try
:
record
=
CourseEnrollment
.
objects
.
get
(
user
=
user
,
course_id
=
course_key
)
record
=
CourseEnrollment
.
objects
.
get
(
user
=
user
,
course_id
=
course_key
)
return
record
.
is_active
return
record
.
is_active
...
...
common/djangoapps/student/views.py
View file @
eafb0755
...
@@ -655,13 +655,6 @@ def dashboard(request):
...
@@ -655,13 +655,6 @@ def dashboard(request):
)
)
courses_requirements_not_met
=
get_pre_requisite_courses_not_completed
(
user
,
courses_having_prerequisites
)
courses_requirements_not_met
=
get_pre_requisite_courses_not_completed
(
user
,
courses_having_prerequisites
)
ccx_membership_triplets
=
[]
if
settings
.
FEATURES
.
get
(
'CUSTOM_COURSES_EDX'
,
False
):
from
ccx.utils
import
get_ccx_membership_triplets
ccx_membership_triplets
=
get_ccx_membership_triplets
(
user
,
course_org_filter
,
org_filter_out_set
)
if
'notlive'
in
request
.
GET
:
if
'notlive'
in
request
.
GET
:
redirect_message
=
_
(
"The course you are looking for does not start until {date}."
)
.
format
(
redirect_message
=
_
(
"The course you are looking for does not start until {date}."
)
.
format
(
date
=
request
.
GET
[
'notlive'
]
date
=
request
.
GET
[
'notlive'
]
...
@@ -697,7 +690,6 @@ def dashboard(request):
...
@@ -697,7 +690,6 @@ def dashboard(request):
'provider_states'
:
[],
'provider_states'
:
[],
'order_history_list'
:
order_history_list
,
'order_history_list'
:
order_history_list
,
'courses_requirements_not_met'
:
courses_requirements_not_met
,
'courses_requirements_not_met'
:
courses_requirements_not_met
,
'ccx_membership_triplets'
:
ccx_membership_triplets
,
'nav_hidden'
:
True
,
'nav_hidden'
:
True
,
}
}
...
@@ -1904,16 +1896,6 @@ def activate_account(request, key):
...
@@ -1904,16 +1896,6 @@ def activate_account(request, key):
manual_enrollment_audit
.
reason
,
enrollment
manual_enrollment_audit
.
reason
,
enrollment
)
)
# enroll student in any pending CCXs he/she may have if auto_enroll flag is set
if
settings
.
FEATURES
.
get
(
'CUSTOM_COURSES_EDX'
):
from
ccx.models
import
CcxMembership
,
CcxFutureMembership
ccxfms
=
CcxFutureMembership
.
objects
.
filter
(
email
=
student
[
0
]
.
email
)
for
ccxfm
in
ccxfms
:
if
ccxfm
.
auto_enroll
:
CcxMembership
.
auto_enroll
(
student
[
0
],
ccxfm
)
resp
=
render_to_response
(
resp
=
render_to_response
(
"registration/activation_complete.html"
,
"registration/activation_complete.html"
,
{
{
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
eafb0755
...
@@ -948,7 +948,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
...
@@ -948,7 +948,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
super
(
CourseDescriptor
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
super
(
CourseDescriptor
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
if
not
self
.
wiki_slug
:
if
self
.
wiki_slug
is
None
:
self
.
wiki_slug
=
self
.
location
.
course
self
.
wiki_slug
=
self
.
location
.
course
if
self
.
due_date_display_format
is
None
and
self
.
show_timezone
is
False
:
if
self
.
due_date_display_format
is
None
and
self
.
show_timezone
is
False
:
...
...
lms/djangoapps/ccx/migrations/0002_convert_memberships.py
0 → 100644
View file @
eafb0755
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
DataMigration
from
django.db
import
models
from
opaque_keys
import
InvalidKeyError
class
Migration
(
DataMigration
):
def
forwards
(
self
,
orm
):
"Convert CCX Memberships to Course Enrollments."
from
ccx_keys.locator
import
CCXLocator
memberships
=
orm
[
'ccx.CcxMembership'
]
.
objects
.
select_related
(
'ccx'
,
'student'
)
.
all
()
for
membership
in
memberships
:
ccx
=
membership
.
ccx
try
:
course_key
=
CCXLocator
.
from_course_locator
(
ccx
.
course_id
,
ccx
.
id
)
enrollment
,
created
=
orm
[
'student.CourseEnrollment'
]
.
objects
.
get_or_create
(
user
=
membership
.
student
,
course_id
=
course_key
,
)
except
InvalidKeyError
:
membership
.
delete
()
def
backwards
(
self
,
orm
):
"""In the future, here we will convert back CCX Course Enrollments to CCX
Memberships.
"""
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'
})
},
'ccx.ccxfieldoverride'
:
{
'Meta'
:
{
'unique_together'
:
"(('ccx', 'location', 'field'),)"
,
'object_name'
:
'CcxFieldOverride'
},
'ccx'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['ccx.CustomCourseForEdX']"
}),
'field'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'location'
:
(
'xmodule_django.models.LocationKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'value'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"'null'"
})
},
'ccx.customcourseforedx'
:
{
'Meta'
:
{
'object_name'
:
'CustomCourseForEdX'
},
'coach'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'display_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'ccx.ccxmembership'
:
{
'Meta'
:
{
'object_name'
:
'CcxMembership'
},
'active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
False
}),
'ccx'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['ccx.CustomCourseForEdX']"
}),
'student'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'student.courseenrollment'
:
{
'Meta'
:
{
'object_name'
:
'CourseEnrollment'
},
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
True
}),
'mode'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'default'
:
'"honor"'
}),
'created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'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'
})
}
}
complete_apps
=
[
'ccx'
,
'ccx'
]
symmetrical
=
True
lms/djangoapps/ccx/models.py
View file @
eafb0755
...
@@ -9,7 +9,6 @@ from django.db import models
...
@@ -9,7 +9,6 @@ from django.db import models
from
django.utils.timezone
import
UTC
from
django.utils.timezone
import
UTC
from
lazy
import
lazy
from
lazy
import
lazy
from
student.models
import
CourseEnrollment
,
AlreadyEnrolledError
# pylint: disable=import-error
from
xmodule_django.models
import
CourseKeyField
,
LocationKeyField
# pylint: disable=import-error
from
xmodule_django.models
import
CourseKeyField
,
LocationKeyField
# pylint: disable=import-error
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
...
@@ -96,52 +95,6 @@ class CustomCourseForEdX(models.Model):
...
@@ -96,52 +95,6 @@ class CustomCourseForEdX(models.Model):
return
value
return
value
class
CcxMembership
(
models
.
Model
):
"""
Which students are in a CCX?
"""
ccx
=
models
.
ForeignKey
(
CustomCourseForEdX
,
db_index
=
True
)
student
=
models
.
ForeignKey
(
User
,
db_index
=
True
)
active
=
models
.
BooleanField
(
default
=
False
)
@classmethod
def
auto_enroll
(
cls
,
student
,
future_membership
):
"""convert future_membership to an active membership
"""
if
not
future_membership
.
auto_enroll
:
msg
=
"auto enrollment not allowed for {}"
raise
ValueError
(
msg
.
format
(
future_membership
))
membership
=
cls
(
ccx
=
future_membership
.
ccx
,
student
=
student
,
active
=
True
)
try
:
CourseEnrollment
.
enroll
(
student
,
future_membership
.
ccx
.
course_id
,
check_access
=
True
)
except
AlreadyEnrolledError
:
# if the user is already enrolled in the course, great!
pass
membership
.
save
()
future_membership
.
delete
()
@classmethod
def
memberships_for_user
(
cls
,
user
,
active
=
True
):
"""
active memberships for a user
"""
return
cls
.
objects
.
filter
(
student
=
user
,
active__exact
=
active
)
class
CcxFutureMembership
(
models
.
Model
):
"""
Which emails for non-users are waiting to be added to CCX on registration
"""
ccx
=
models
.
ForeignKey
(
CustomCourseForEdX
,
db_index
=
True
)
email
=
models
.
CharField
(
max_length
=
255
)
auto_enroll
=
models
.
BooleanField
(
default
=
0
)
class
CcxFieldOverride
(
models
.
Model
):
class
CcxFieldOverride
(
models
.
Model
):
"""
"""
Field overrides for custom courses.
Field overrides for custom courses.
...
...
lms/djangoapps/ccx/tests/factories.py
View file @
eafb0755
...
@@ -5,8 +5,6 @@ from factory import SubFactory
...
@@ -5,8 +5,6 @@ from factory import SubFactory
from
factory.django
import
DjangoModelFactory
from
factory.django
import
DjangoModelFactory
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
ccx.models
import
CustomCourseForEdX
# pylint: disable=import-error
from
ccx.models
import
CustomCourseForEdX
# pylint: disable=import-error
from
ccx.models
import
CcxMembership
# pylint: disable=import-error
from
ccx.models
import
CcxFutureMembership
# pylint: disable=import-error
class
CcxFactory
(
DjangoModelFactory
):
# pylint: disable=missing-docstring
class
CcxFactory
(
DjangoModelFactory
):
# pylint: disable=missing-docstring
...
@@ -14,12 +12,3 @@ class CcxFactory(DjangoModelFactory): # pylint: disable=missing-docstring
...
@@ -14,12 +12,3 @@ class CcxFactory(DjangoModelFactory): # pylint: disable=missing-docstring
display_name
=
"Test CCX"
display_name
=
"Test CCX"
id
=
None
# pylint: disable=redefined-builtin, invalid-name
id
=
None
# pylint: disable=redefined-builtin, invalid-name
coach
=
SubFactory
(
UserFactory
)
coach
=
SubFactory
(
UserFactory
)
class
CcxMembershipFactory
(
DjangoModelFactory
):
# pylint: disable=missing-docstring
FACTORY_FOR
=
CcxMembership
active
=
False
class
CcxFutureMembershipFactory
(
DjangoModelFactory
):
# pylint: disable=missing-docstring
FACTORY_FOR
=
CcxFutureMembership
lms/djangoapps/ccx/tests/test_field_override_performance.py
View file @
eafb0755
...
@@ -27,7 +27,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, \
...
@@ -27,7 +27,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, \
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
,
CourseFactory
,
check_sum_of_calls
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
,
CourseFactory
,
check_sum_of_calls
from
xmodule.modulestore.tests.utils
import
ProceduralCourseTestMixin
from
xmodule.modulestore.tests.utils
import
ProceduralCourseTestMixin
from
ccx_keys.locator
import
CCXLocator
from
ccx_keys.locator
import
CCXLocator
from
ccx.tests.factories
import
CcxFactory
,
CcxMembershipFactory
from
ccx.tests.factories
import
CcxFactory
@attr
(
'shard_1'
)
@attr
(
'shard_1'
)
...
@@ -65,7 +65,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
...
@@ -65,7 +65,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
MakoMiddleware
()
.
process_request
(
self
.
request
)
MakoMiddleware
()
.
process_request
(
self
.
request
)
def
setup_course
(
self
,
size
,
enable_ccx
):
def
setup_course
(
self
,
size
,
enable_ccx
,
view_as_ccx
):
"""
"""
Build a gradable course where each node has `size` children.
Build a gradable course where each node has `size` children.
"""
"""
...
@@ -112,18 +112,17 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
...
@@ -112,18 +112,17 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
)
)
self
.
populate_course
(
size
)
self
.
populate_course
(
size
)
course_key
=
self
.
course
.
id
if
enable_ccx
:
self
.
ccx
=
CcxFactory
.
create
(
course_id
=
self
.
course
.
id
)
if
view_as_ccx
:
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
self
.
ccx
.
id
)
CourseEnrollment
.
enroll
(
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
student
,
self
.
course
.
id
course_key
)
)
if
enable_ccx
:
self
.
ccx
=
CcxFactory
.
create
()
CcxMembershipFactory
.
create
(
student
=
self
.
student
,
ccx
=
self
.
ccx
)
def
grade_course
(
self
,
course
,
view_as_ccx
):
def
grade_course
(
self
,
course
,
view_as_ccx
):
"""
"""
Renders the progress page for the given course.
Renders the progress page for the given course.
...
@@ -156,7 +155,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
...
@@ -156,7 +155,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
"""
"""
Renders the progress page, instrumenting Mongo reads and SQL queries.
Renders the progress page, instrumenting Mongo reads and SQL queries.
"""
"""
self
.
setup_course
(
course_width
,
enable_ccx
)
self
.
setup_course
(
course_width
,
enable_ccx
,
view_as_ccx
)
# Switch to published-only mode to simulate the LMS
# Switch to published-only mode to simulate the LMS
with
self
.
settings
(
MODULESTORE_BRANCH
=
'published-only'
):
with
self
.
settings
(
MODULESTORE_BRANCH
=
'published-only'
):
...
...
lms/djangoapps/ccx/tests/test_models.py
View file @
eafb0755
...
@@ -5,12 +5,9 @@ from datetime import datetime, timedelta
...
@@ -5,12 +5,9 @@ from datetime import datetime, timedelta
from
django.utils.timezone
import
UTC
from
django.utils.timezone
import
UTC
from
mock
import
patch
from
mock
import
patch
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
from
student.models
import
CourseEnrollment
# pylint: disable=import-error
from
student.roles
import
CourseCcxCoachRole
# pylint: disable=import-error
from
student.roles
import
CourseCcxCoachRole
# pylint: disable=import-error
from
student.tests.factories
import
(
# pylint: disable=import-error
from
student.tests.factories
import
(
# pylint: disable=import-error
AdminFactory
,
AdminFactory
,
CourseEnrollmentFactory
,
UserFactory
,
)
)
from
util.tests.test_date_utils
import
fake_ugettext
from
util.tests.test_date_utils
import
fake_ugettext
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
@@ -21,123 +18,11 @@ from xmodule.modulestore.tests.factories import (
...
@@ -21,123 +18,11 @@ from xmodule.modulestore.tests.factories import (
from
.factories
import
(
from
.factories
import
(
CcxFactory
,
CcxFactory
,
CcxFutureMembershipFactory
,
)
from
..models
import
(
CcxMembership
,
CcxFutureMembership
,
)
)
from
..overrides
import
override_field_for_ccx
from
..overrides
import
override_field_for_ccx
@attr
(
'shard_1'
)
@attr
(
'shard_1'
)
class
TestCcxMembership
(
ModuleStoreTestCase
):
"""Unit tests for the CcxMembership model
"""
def
setUp
(
self
):
"""common setup for all tests"""
super
(
TestCcxMembership
,
self
)
.
setUp
()
self
.
course
=
course
=
CourseFactory
.
create
()
coach
=
AdminFactory
.
create
()
role
=
CourseCcxCoachRole
(
course
.
id
)
role
.
add_users
(
coach
)
self
.
ccx
=
CcxFactory
(
course_id
=
course
.
id
,
coach
=
coach
)
enrollment
=
CourseEnrollmentFactory
.
create
(
course_id
=
course
.
id
)
self
.
enrolled_user
=
enrollment
.
user
self
.
unenrolled_user
=
UserFactory
.
create
()
def
create_future_enrollment
(
self
,
user
,
auto_enroll
=
True
):
"""
utility method to create future enrollment
"""
pfm
=
CcxFutureMembershipFactory
.
create
(
ccx
=
self
.
ccx
,
email
=
user
.
email
,
auto_enroll
=
auto_enroll
)
return
pfm
def
has_course_enrollment
(
self
,
user
):
"""
utility method to create future enrollment
"""
enrollment
=
CourseEnrollment
.
objects
.
filter
(
user
=
user
,
course_id
=
self
.
course
.
id
)
return
enrollment
.
exists
()
def
has_ccx_membership
(
self
,
user
):
"""
verify ccx membership
"""
membership
=
CcxMembership
.
objects
.
filter
(
student
=
user
,
ccx
=
self
.
ccx
,
active
=
True
)
return
membership
.
exists
()
def
has_ccx_future_membership
(
self
,
user
):
"""
verify future ccx membership
"""
future_membership
=
CcxFutureMembership
.
objects
.
filter
(
email
=
user
.
email
,
ccx
=
self
.
ccx
)
return
future_membership
.
exists
()
def
call_mut
(
self
,
student
,
future_membership
):
"""
Call the method undser test
"""
CcxMembership
.
auto_enroll
(
student
,
future_membership
)
def
test_ccx_auto_enroll_unregistered_user
(
self
):
"""verify auto_enroll works when user is not enrolled in the MOOC
n.b. After auto_enroll, user will have both a MOOC enrollment and a
CCX membership
"""
user
=
self
.
unenrolled_user
pfm
=
self
.
create_future_enrollment
(
user
)
self
.
assertTrue
(
self
.
has_ccx_future_membership
(
user
))
self
.
assertFalse
(
self
.
has_course_enrollment
(
user
))
# auto_enroll user
self
.
call_mut
(
user
,
pfm
)
self
.
assertTrue
(
self
.
has_course_enrollment
(
user
))
self
.
assertTrue
(
self
.
has_ccx_membership
(
user
))
self
.
assertFalse
(
self
.
has_ccx_future_membership
(
user
))
def
test_ccx_auto_enroll_registered_user
(
self
):
"""verify auto_enroll works when user is enrolled in the MOOC
"""
user
=
self
.
enrolled_user
pfm
=
self
.
create_future_enrollment
(
user
)
self
.
assertTrue
(
self
.
has_ccx_future_membership
(
user
))
self
.
assertTrue
(
self
.
has_course_enrollment
(
user
))
self
.
call_mut
(
user
,
pfm
)
self
.
assertTrue
(
self
.
has_course_enrollment
(
user
))
self
.
assertTrue
(
self
.
has_ccx_membership
(
user
))
self
.
assertFalse
(
self
.
has_ccx_future_membership
(
user
))
def
test_future_membership_disallows_auto_enroll
(
self
):
"""verify that the CcxFutureMembership can veto auto_enroll
"""
user
=
self
.
unenrolled_user
pfm
=
self
.
create_future_enrollment
(
user
,
auto_enroll
=
False
)
self
.
assertTrue
(
self
.
has_ccx_future_membership
(
user
))
self
.
assertFalse
(
self
.
has_course_enrollment
(
user
))
self
.
assertRaises
(
ValueError
,
self
.
call_mut
,
user
,
pfm
)
self
.
assertFalse
(
self
.
has_course_enrollment
(
user
))
self
.
assertFalse
(
self
.
has_ccx_membership
(
user
))
self
.
assertTrue
(
self
.
has_ccx_future_membership
(
user
))
@attr
(
'shard_1'
)
class
TestCCX
(
ModuleStoreTestCase
):
class
TestCCX
(
ModuleStoreTestCase
):
"""Unit tests for the CustomCourseForEdX model
"""Unit tests for the CustomCourseForEdX model
"""
"""
...
...
lms/djangoapps/ccx/tests/test_utils.py
View file @
eafb0755
...
@@ -3,575 +3,49 @@ test utils
...
@@ -3,575 +3,49 @@ test utils
"""
"""
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
from
ccx.models
import
(
# pylint: disable=import-error
CcxMembership
,
CcxFutureMembership
,
)
from
ccx.tests.factories
import
(
# pylint: disable=import-error
from
ccx.tests.factories
import
(
# pylint: disable=import-error
CcxFactory
,
CcxFactory
,
CcxMembershipFactory
,
CcxFutureMembershipFactory
,
)
)
from
student.roles
import
CourseCcxCoachRole
# pylint: disable=import-error
from
student.roles
import
CourseCcxCoachRole
# pylint: disable=import-error
from
student.tests.factories
import
(
# pylint: disable=import-error
from
student.tests.factories
import
(
# pylint: disable=import-error
AdminFactory
,
AdminFactory
,
UserFactory
,
)
)
from
xmodule.modulestore.tests.django_utils
import
(
from
xmodule.modulestore.tests.django_utils
import
(
ModuleStoreTestCase
,
ModuleStoreTestCase
,
TEST_DATA_SPLIT_MODULESTORE
)
TEST_DATA_SPLIT_MODULESTORE
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
ccx_keys.locator
import
CCXLocator
@attr
(
'shard_1'
)
class
TestEmailEnrollmentState
(
ModuleStoreTestCase
):
"""unit tests for the EmailEnrollmentState class
"""
def
setUp
(
self
):
"""
Set up tests
"""
super
(
TestEmailEnrollmentState
,
self
)
.
setUp
()
# remove user provided by the parent test case so we can make our own
# when needed.
self
.
user
=
None
course
=
CourseFactory
.
create
()
coach
=
AdminFactory
.
create
()
role
=
CourseCcxCoachRole
(
course
.
id
)
role
.
add_users
(
coach
)
self
.
ccx
=
CcxFactory
(
course_id
=
course
.
id
,
coach
=
coach
)
def
create_user
(
self
):
"""provide a legitimate django user for testing
"""
if
getattr
(
self
,
'user'
,
None
)
is
None
:
self
.
user
=
UserFactory
()
def
register_user_in_ccx
(
self
):
"""create registration of self.user in self.ccx
registration will be inactive
"""
self
.
create_user
()
CcxMembershipFactory
(
ccx
=
self
.
ccx
,
student
=
self
.
user
)
def
create_one
(
self
,
email
=
None
):
"""Create a single EmailEnrollmentState object and return it
"""
from
ccx.utils
import
EmailEnrollmentState
# pylint: disable=import-error
if
email
is
None
:
email
=
self
.
user
.
email
return
EmailEnrollmentState
(
self
.
ccx
,
email
)
def
test_enrollment_state_for_non_user
(
self
):
"""verify behavior for non-user email address
"""
ee_state
=
self
.
create_one
(
email
=
'nobody@nowhere.com'
)
for
attribute
in
[
'user'
,
'member'
,
'full_name'
,
'in_ccx'
]:
value
=
getattr
(
ee_state
,
attribute
,
'missing attribute'
)
self
.
assertFalse
(
value
,
"{}: {}"
.
format
(
value
,
attribute
))
def
test_enrollment_state_for_non_member_user
(
self
):
"""verify behavior for email address of user who is not a ccx memeber
"""
self
.
create_user
()
ee_state
=
self
.
create_one
()
self
.
assertTrue
(
ee_state
.
user
)
self
.
assertFalse
(
ee_state
.
in_ccx
)
self
.
assertEqual
(
ee_state
.
member
,
self
.
user
)
self
.
assertEqual
(
ee_state
.
full_name
,
self
.
user
.
profile
.
name
)
def
test_enrollment_state_for_member_user
(
self
):
"""verify behavior for email address of user who is a ccx member
"""
self
.
create_user
()
self
.
register_user_in_ccx
()
ee_state
=
self
.
create_one
()
for
attribute
in
[
'user'
,
'in_ccx'
]:
self
.
assertTrue
(
getattr
(
ee_state
,
attribute
,
False
),
"attribute {} is missing or False"
.
format
(
attribute
)
)
self
.
assertEqual
(
ee_state
.
member
,
self
.
user
)
self
.
assertEqual
(
ee_state
.
full_name
,
self
.
user
.
profile
.
name
)
def
test_enrollment_state_to_dict
(
self
):
"""verify dict representation of EmailEnrollmentState
"""
self
.
create_user
()
self
.
register_user_in_ccx
()
ee_state
=
self
.
create_one
()
ee_dict
=
ee_state
.
to_dict
()
expected
=
{
'user'
:
True
,
'member'
:
self
.
user
,
'in_ccx'
:
True
,
}
for
expected_key
,
expected_value
in
expected
.
iteritems
():
self
.
assertTrue
(
expected_key
in
ee_dict
)
self
.
assertEqual
(
expected_value
,
ee_dict
[
expected_key
])
def
test_enrollment_state_repr
(
self
):
self
.
create_user
()
self
.
register_user_in_ccx
()
ee_state
=
self
.
create_one
()
representation
=
repr
(
ee_state
)
self
.
assertTrue
(
'user=True'
in
representation
)
self
.
assertTrue
(
'in_ccx=True'
in
representation
)
member
=
'member={}'
.
format
(
self
.
user
)
self
.
assertTrue
(
member
in
representation
)
@attr
(
'shard_1'
)
# TODO: deal with changes in behavior for auto_enroll
class
TestGetEmailParams
(
ModuleStoreTestCase
):
"""tests for ccx.utils.get_email_params
"""
MODULESTORE
=
TEST_DATA_SPLIT_MODULESTORE
def
setUp
(
self
):
"""
Set up tests
"""
super
(
TestGetEmailParams
,
self
)
.
setUp
()
course
=
CourseFactory
.
create
()
coach
=
AdminFactory
.
create
()
role
=
CourseCcxCoachRole
(
course
.
id
)
role
.
add_users
(
coach
)
self
.
ccx
=
CcxFactory
(
course_id
=
course
.
id
,
coach
=
coach
)
self
.
all_keys
=
[
'site_name'
,
'course'
,
'course_url'
,
'registration_url'
,
'course_about_url'
,
'auto_enroll'
]
self
.
url_keys
=
[
k
for
k
in
self
.
all_keys
if
'url'
in
k
]
self
.
course_keys
=
[
k
for
k
in
self
.
url_keys
if
'course'
in
k
]
def
call_fut
(
self
,
auto_enroll
=
False
,
secure
=
False
):
"""
call function under test
"""
from
ccx.utils
import
get_email_params
# pylint: disable=import-error
return
get_email_params
(
self
.
ccx
,
auto_enroll
,
secure
)
def
test_params_have_expected_keys
(
self
):
params
=
self
.
call_fut
()
self
.
assertFalse
(
set
(
params
.
keys
())
-
set
(
self
.
all_keys
))
def
test_ccx_id_in_params
(
self
):
expected_course_id
=
unicode
(
CCXLocator
.
from_course_locator
(
self
.
ccx
.
course_id
,
self
.
ccx
.
id
))
params
=
self
.
call_fut
()
self
.
assertEqual
(
params
[
'course'
],
self
.
ccx
)
for
url_key
in
self
.
url_keys
:
self
.
assertTrue
(
'http://'
in
params
[
url_key
])
for
url_key
in
self
.
course_keys
:
self
.
assertTrue
(
expected_course_id
in
params
[
url_key
])
def
test_security_respected
(
self
):
secure
=
self
.
call_fut
(
secure
=
True
)
for
url_key
in
self
.
url_keys
:
self
.
assertTrue
(
'https://'
in
secure
[
url_key
])
insecure
=
self
.
call_fut
(
secure
=
False
)
for
url_key
in
self
.
url_keys
:
self
.
assertTrue
(
'http://'
in
insecure
[
url_key
])
def
test_auto_enroll_passed_correctly
(
self
):
not_auto
=
self
.
call_fut
(
auto_enroll
=
False
)
self
.
assertFalse
(
not_auto
[
'auto_enroll'
])
auto
=
self
.
call_fut
(
auto_enroll
=
True
)
self
.
assertTrue
(
auto
[
'auto_enroll'
])
@attr
(
'shard_1'
)
# TODO: deal with changes in behavior for auto_enroll
class
TestEnrollEmail
(
ModuleStoreTestCase
):
"""tests for the enroll_email function from ccx.utils
"""
MODULESTORE
=
TEST_DATA_SPLIT_MODULESTORE
def
setUp
(
self
):
super
(
TestEnrollEmail
,
self
)
.
setUp
()
# unbind the user created by the parent, so we can create our own when
# needed.
self
.
user
=
None
course
=
CourseFactory
.
create
()
coach
=
AdminFactory
.
create
()
role
=
CourseCcxCoachRole
(
course
.
id
)
role
.
add_users
(
coach
)
self
.
ccx
=
CcxFactory
(
course_id
=
course
.
id
,
coach
=
coach
)
self
.
outbox
=
self
.
get_outbox
()
def
create_user
(
self
):
"""provide a legitimate django user for testing
"""
if
getattr
(
self
,
'user'
,
None
)
is
None
:
self
.
user
=
UserFactory
()
def
register_user_in_ccx
(
self
):
"""create registration of self.user in self.ccx
registration will be inactive
"""
self
.
create_user
()
CcxMembershipFactory
(
ccx
=
self
.
ccx
,
student
=
self
.
user
)
def
get_outbox
(
self
):
"""Return the django mail outbox"""
from
django.core
import
mail
return
mail
.
outbox
def
check_membership
(
self
,
email
=
None
,
user
=
None
,
future
=
False
):
"""Verify tjat an appropriate CCX Membership exists"""
if
not
email
and
not
user
:
self
.
fail
(
"must provide user or email address to check CCX Membership"
)
if
future
and
email
:
membership
=
CcxFutureMembership
.
objects
.
filter
(
ccx
=
self
.
ccx
,
email
=
email
)
elif
not
future
:
if
not
user
:
user
=
self
.
user
membership
=
CcxMembership
.
objects
.
filter
(
ccx
=
self
.
ccx
,
student
=
user
)
self
.
assertTrue
(
membership
.
exists
())
def
check_enrollment_state
(
self
,
state
,
in_ccx
,
member
,
user
):
"""Verify an enrollment state object against provided arguments
state.in_ccx will always be a boolean
from
ccx_keys.locator
import
CCXLocator
state.user will always be a boolean
state.member will be a Django user object or None
"""
self
.
assertEqual
(
in_ccx
,
state
.
in_ccx
)
self
.
assertEqual
(
member
,
state
.
member
)
self
.
assertEqual
(
user
,
state
.
user
)
def
call_fut
(
self
,
student_email
=
None
,
auto_enroll
=
False
,
email_students
=
False
,
email_params
=
None
):
"""Call function under test"""
from
ccx.utils
import
enroll_email
# pylint: disable=import-error
if
student_email
is
None
:
student_email
=
self
.
user
.
email
before
,
after
=
enroll_email
(
self
.
ccx
,
student_email
,
auto_enroll
,
email_students
,
email_params
)
return
before
,
after
def
test_enroll_non_user_sending_email
(
self
):
"""enroll a non-user email and send an enrollment email to them
"""
# ensure no emails are in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
test_email
=
"nobody@nowhere.com"
before
,
after
=
self
.
call_fut
(
student_email
=
test_email
,
email_students
=
True
)
# there should be a future membership set for this email address now
self
.
check_membership
(
email
=
test_email
,
future
=
True
)
for
state
in
[
before
,
after
]:
self
.
check_enrollment_state
(
state
,
False
,
None
,
False
)
# mail was sent and to the right person
self
.
assertEqual
(
len
(
self
.
outbox
),
1
)
msg
=
self
.
outbox
[
0
]
self
.
assertTrue
(
test_email
in
msg
.
recipients
())
def
test_enroll_non_member_sending_email
(
self
):
"""register a non-member and send an enrollment email to them
"""
self
.
create_user
()
# ensure no emails are in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
before
,
after
=
self
.
call_fut
(
email_students
=
True
)
# there should be a membership set for this email address now
self
.
check_membership
(
email
=
self
.
user
.
email
)
self
.
check_enrollment_state
(
before
,
False
,
self
.
user
,
True
)
self
.
check_enrollment_state
(
after
,
True
,
self
.
user
,
True
)
# mail was sent and to the right person
self
.
assertEqual
(
len
(
self
.
outbox
),
1
)
msg
=
self
.
outbox
[
0
]
self
.
assertTrue
(
self
.
user
.
email
in
msg
.
recipients
())
def
test_enroll_member_sending_email
(
self
):
"""register a member and send an enrollment email to them
"""
self
.
register_user_in_ccx
()
# ensure no emails are in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
before
,
after
=
self
.
call_fut
(
email_students
=
True
)
# there should be a membership set for this email address now
self
.
check_membership
(
email
=
self
.
user
.
email
)
for
state
in
[
before
,
after
]:
self
.
check_enrollment_state
(
state
,
True
,
self
.
user
,
True
)
# mail was sent and to the right person
self
.
assertEqual
(
len
(
self
.
outbox
),
1
)
msg
=
self
.
outbox
[
0
]
self
.
assertTrue
(
self
.
user
.
email
in
msg
.
recipients
())
def
test_enroll_non_user_no_email
(
self
):
"""register a non-user via email address but send no email
"""
# ensure no emails are in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
test_email
=
"nobody@nowhere.com"
before
,
after
=
self
.
call_fut
(
student_email
=
test_email
,
email_students
=
False
)
# there should be a future membership set for this email address now
self
.
check_membership
(
email
=
test_email
,
future
=
True
)
for
state
in
[
before
,
after
]:
self
.
check_enrollment_state
(
state
,
False
,
None
,
False
)
# ensure there are still no emails in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
def
test_enroll_non_member_no_email
(
self
):
"""register a non-member but send no email"""
self
.
create_user
()
# ensure no emails are in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
before
,
after
=
self
.
call_fut
(
email_students
=
False
)
# there should be a membership set for this email address now
self
.
check_membership
(
email
=
self
.
user
.
email
)
self
.
check_enrollment_state
(
before
,
False
,
self
.
user
,
True
)
self
.
check_enrollment_state
(
after
,
True
,
self
.
user
,
True
)
# ensure there are still no emails in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
def
test_enroll_member_no_email
(
self
):
"""enroll a member but send no email
"""
self
.
register_user_in_ccx
()
# ensure no emails are in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
before
,
after
=
self
.
call_fut
(
email_students
=
False
)
# there should be a membership set for this email address now
self
.
check_membership
(
email
=
self
.
user
.
email
)
for
state
in
[
before
,
after
]:
self
.
check_enrollment_state
(
state
,
True
,
self
.
user
,
True
)
# ensure there are still no emails in the outbox now
self
.
assertEqual
(
self
.
outbox
,
[])
@attr
(
'shard_1'
)
# TODO: deal with changes in behavior for auto_enroll
class
TestUnenrollEmail
(
ModuleStoreTestCase
):
"""Tests for the unenroll_email function from ccx.utils"""
MODULESTORE
=
TEST_DATA_SPLIT_MODULESTORE
def
setUp
(
self
):
super
(
TestUnenrollEmail
,
self
)
.
setUp
()
# unbind the user created by the parent, so we can create our own when
# needed.
self
.
user
=
None
course
=
CourseFactory
.
create
()
coach
=
AdminFactory
.
create
()
role
=
CourseCcxCoachRole
(
course
.
id
)
role
.
add_users
(
coach
)
self
.
ccx
=
CcxFactory
(
course_id
=
course
.
id
,
coach
=
coach
)
self
.
outbox
=
self
.
get_outbox
()
self
.
email
=
"nobody@nowhere.com"
def
get_outbox
(
self
):
"""Return the django mail outbox"""
from
django.core
import
mail
return
mail
.
outbox
def
create_user
(
self
):
"""provide a legitimate django user for testing
"""
if
getattr
(
self
,
'user'
,
None
)
is
None
:
self
.
user
=
UserFactory
()
def
make_ccx_membership
(
self
):
"""create registration of self.user in self.ccx
registration will be inactive
"""
self
.
create_user
()
CcxMembershipFactory
.
create
(
ccx
=
self
.
ccx
,
student
=
self
.
user
)
def
make_ccx_future_membership
(
self
):
"""create future registration for email in self.ccx"""
CcxFutureMembershipFactory
.
create
(
ccx
=
self
.
ccx
,
email
=
self
.
email
)
def
check_enrollment_state
(
self
,
state
,
in_ccx
,
member
,
user
):
"""Verify an enrollment state object against provided arguments
state.in_ccx will always be a boolean
state.user will always be a boolean
state.member will be a Django user object or None
"""
self
.
assertEqual
(
in_ccx
,
state
.
in_ccx
)
self
.
assertEqual
(
member
,
state
.
member
)
self
.
assertEqual
(
user
,
state
.
user
)
def
check_membership
(
self
,
future
=
False
):
"""
check membership
"""
if
future
:
membership
=
CcxFutureMembership
.
objects
.
filter
(
ccx
=
self
.
ccx
,
email
=
self
.
email
)
else
:
membership
=
CcxMembership
.
objects
.
filter
(
ccx
=
self
.
ccx
,
student
=
self
.
user
)
return
membership
.
exists
()
def
call_fut
(
self
,
email_students
=
False
):
"""call function under test"""
from
ccx.utils
import
unenroll_email
# pylint: disable=import-error
email
=
getattr
(
self
,
'user'
,
None
)
and
self
.
user
.
email
or
self
.
email
return
unenroll_email
(
self
.
ccx
,
email
,
email_students
=
email_students
)
def
test_unenroll_future_member_with_email
(
self
):
"""unenroll a future member and send an email
"""
self
.
make_ccx_future_membership
()
# assert that a membership exists and that no emails have been sent
self
.
assertTrue
(
self
.
check_membership
(
future
=
True
))
self
.
assertEqual
(
self
.
outbox
,
[])
# unenroll the student
before
,
after
=
self
.
call_fut
(
email_students
=
True
)
# assert that membership is now gone
self
.
assertFalse
(
self
.
check_membership
(
future
=
True
))
# validate the before and after enrollment states
for
state
in
[
before
,
after
]:
self
.
check_enrollment_state
(
state
,
False
,
None
,
False
)
# check that mail was sent and to the right person
self
.
assertEqual
(
len
(
self
.
outbox
),
1
)
msg
=
self
.
outbox
[
0
]
self
.
assertTrue
(
self
.
email
in
msg
.
recipients
())
def
test_unenroll_member_with_email
(
self
):
"""unenroll a current member and send an email"""
self
.
make_ccx_membership
()
# assert that a membership exists and that no emails have been sent
self
.
assertTrue
(
self
.
check_membership
())
self
.
assertEqual
(
self
.
outbox
,
[])
# unenroll the student
before
,
after
=
self
.
call_fut
(
email_students
=
True
)
# assert that membership is now gone
self
.
assertFalse
(
self
.
check_membership
())
# validate the before and after enrollment state
self
.
check_enrollment_state
(
after
,
False
,
self
.
user
,
True
)
self
.
check_enrollment_state
(
before
,
True
,
self
.
user
,
True
)
# check that mail was sent and to the right person
self
.
assertEqual
(
len
(
self
.
outbox
),
1
)
msg
=
self
.
outbox
[
0
]
self
.
assertTrue
(
self
.
user
.
email
in
msg
.
recipients
())
def
test_unenroll_future_member_no_email
(
self
):
"""unenroll a future member but send no email
"""
self
.
make_ccx_future_membership
()
# assert that a membership exists and that no emails have been sent
self
.
assertTrue
(
self
.
check_membership
(
future
=
True
))
self
.
assertEqual
(
self
.
outbox
,
[])
# unenroll the student
before
,
after
=
self
.
call_fut
()
# assert that membership is now gone
self
.
assertFalse
(
self
.
check_membership
(
future
=
True
))
# validate the before and after enrollment states
for
state
in
[
before
,
after
]:
self
.
check_enrollment_state
(
state
,
False
,
None
,
False
)
# no email was sent to the student
self
.
assertEqual
(
self
.
outbox
,
[])
def
test_unenroll_member_no_email
(
self
):
"""unenroll a current member but send no email
"""
self
.
make_ccx_membership
()
# assert that a membership exists and that no emails have been sent
self
.
assertTrue
(
self
.
check_membership
())
self
.
assertEqual
(
self
.
outbox
,
[])
# unenroll the student
before
,
after
=
self
.
call_fut
()
# assert that membership is now gone
self
.
assertFalse
(
self
.
check_membership
())
# validate the before and after enrollment state
self
.
check_enrollment_state
(
after
,
False
,
self
.
user
,
True
)
self
.
check_enrollment_state
(
before
,
True
,
self
.
user
,
True
)
# no email was sent to the student
self
.
assertEqual
(
self
.
outbox
,
[])
@attr
(
'shard_1'
)
@attr
(
'shard_1'
)
class
TestGet
MembershipTriplets
(
ModuleStoreTestCase
):
class
TestGet
CCXFromCCXLocator
(
ModuleStoreTestCase
):
"""Verify that get_ccx_
membership_triplets
functions properly"""
"""Verify that get_ccx_
from_ccx_locator
functions properly"""
MODULESTORE
=
TEST_DATA_SPLIT_MODULESTORE
MODULESTORE
=
TEST_DATA_SPLIT_MODULESTORE
def
setUp
(
self
):
def
setUp
(
self
):
"""Set up a course, coach, ccx and user"""
"""Set up a course, coach, ccx and user"""
super
(
TestGet
MembershipTriplets
,
self
)
.
setUp
()
super
(
TestGet
CCXFromCCXLocator
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
self
.
course
=
CourseFactory
.
create
()
coach
=
AdminFactory
.
create
()
coach
=
self
.
coach
=
AdminFactory
.
create
()
role
=
CourseCcxCoachRole
(
self
.
course
.
id
)
role
=
CourseCcxCoachRole
(
self
.
course
.
id
)
role
.
add_users
(
coach
)
role
.
add_users
(
coach
)
self
.
ccx
=
CcxFactory
(
course_id
=
self
.
course
.
id
,
coach
=
coach
)
def
make_ccx_membership
(
self
,
active
=
True
):
def
call_fut
(
self
,
course_id
):
"""create registration of self.user in self.ccx
registration will be inactive
"""
CcxMembershipFactory
.
create
(
ccx
=
self
.
ccx
,
student
=
self
.
user
,
active
=
active
)
def
call_fut
(
self
,
org_filter
=
None
,
org_filter_out
=
()):
"""call the function under test in this test case"""
"""call the function under test in this test case"""
from
ccx.utils
import
get_ccx_membership_triplets
from
ccx.utils
import
get_ccx_from_ccx_locator
return
list
(
return
get_ccx_from_ccx_locator
(
course_id
)
get_ccx_membership_triplets
(
self
.
user
,
org_filter
,
org_filter_out
)
)
def
test_no
_membership
(
self
):
def
test_no
n_ccx_locator
(
self
):
"""verify that no
triplets are returned if there are no memberships
"""verify that no
thing is returned if locator is not a ccx locator
"""
"""
triplets
=
self
.
call_fut
(
)
result
=
self
.
call_fut
(
self
.
course
.
id
)
self
.
assertEqual
(
len
(
triplets
),
0
)
self
.
assertEqual
(
result
,
None
)
def
test_
has_membership
(
self
):
def
test_
ccx_locator
(
self
):
"""verify that
a triplet is returned when a membership exists
"""verify that
the ccx is retuned if using a ccx locator
"""
"""
self
.
make_ccx_membership
()
ccx
=
CcxFactory
(
course_id
=
self
.
course
.
id
,
coach
=
self
.
coach
)
triplets
=
self
.
call_fut
()
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
self
.
assertEqual
(
len
(
triplets
),
1
)
result
=
self
.
call_fut
(
course_key
)
ccx
,
membership
,
course_overview
=
triplets
[
0
]
self
.
assertEqual
(
result
,
ccx
)
self
.
assertEqual
(
ccx
.
id
,
self
.
ccx
.
id
)
self
.
assertEqual
(
unicode
(
course_overview
.
id
),
unicode
(
self
.
course
.
id
))
self
.
assertEqual
(
membership
.
student
,
self
.
user
)
def
test_has_membership_org_filtered
(
self
):
"""verify that microsite org filter prevents seeing microsite ccx"""
self
.
make_ccx_membership
()
bad_org
=
self
.
course
.
location
.
org
+
'foo'
triplets
=
self
.
call_fut
(
org_filter
=
bad_org
)
self
.
assertEqual
(
len
(
triplets
),
0
)
def
test_has_membership_org_filtered_out
(
self
):
"""verify that microsite ccxs not seen in non-microsite view"""
self
.
make_ccx_membership
()
filter_list
=
[
self
.
course
.
location
.
org
]
triplets
=
self
.
call_fut
(
org_filter_out
=
filter_list
)
self
.
assertEqual
(
len
(
triplets
),
0
)
lms/djangoapps/ccx/tests/test_views.py
View file @
eafb0755
...
@@ -20,9 +20,12 @@ from django.utils.timezone import UTC
...
@@ -20,9 +20,12 @@ from django.utils.timezone import UTC
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
from
django.test
import
RequestFactory
from
django.test
import
RequestFactory
from
edxmako.shortcuts
import
render_to_response
# pylint: disable=import-error
from
edxmako.shortcuts
import
render_to_response
# pylint: disable=import-error
from
student.models
import
CourseEnrollment
from
request_cache.middleware
import
RequestCache
from
request_cache.middleware
import
RequestCache
from
student.roles
import
CourseCcxCoachRole
# pylint: disable=import-error
from
student.roles
import
CourseCcxCoachRole
# pylint: disable=import-error
from
student.models
import
(
CourseEnrollment
,
CourseEnrollmentAllowed
,
)
from
student.tests.factories
import
(
# pylint: disable=import-error
from
student.tests.factories
import
(
# pylint: disable=import-error
AdminFactory
,
AdminFactory
,
CourseEnrollmentFactory
,
CourseEnrollmentFactory
,
...
@@ -42,14 +45,10 @@ from ccx_keys.locator import CCXLocator
...
@@ -42,14 +45,10 @@ from ccx_keys.locator import CCXLocator
from
..models
import
(
from
..models
import
(
CustomCourseForEdX
,
CustomCourseForEdX
,
CcxMembership
,
CcxFutureMembership
,
)
)
from
..overrides
import
get_override_for_ccx
,
override_field_for_ccx
from
..overrides
import
get_override_for_ccx
,
override_field_for_ccx
from
.factories
import
(
from
.factories
import
(
CcxFactory
,
CcxFactory
,
CcxMembershipFactory
,
CcxFutureMembershipFactory
,
)
)
...
@@ -280,7 +279,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -280,7 +279,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertTrue
(
student
.
email
in
outbox
[
0
]
.
recipients
())
# pylint: disable=no-member
self
.
assertTrue
(
student
.
email
in
outbox
[
0
]
.
recipients
())
# pylint: disable=no-member
# a CcxMembership exists for this student
# a CcxMembership exists for this student
self
.
assertTrue
(
self
.
assertTrue
(
C
cxMembership
.
objects
.
filter
(
ccx
=
ccx
,
student
=
student
)
.
exists
()
C
ourseEnrollment
.
objects
.
filter
(
course_id
=
self
.
course
.
id
,
user
=
student
)
.
exists
()
)
)
def
test_unenroll_member_student
(
self
):
def
test_unenroll_member_student
(
self
):
...
@@ -288,16 +287,15 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -288,16 +287,15 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
"""
self
.
make_coach
()
self
.
make_coach
()
ccx
=
self
.
make_ccx
()
ccx
=
self
.
make_ccx
()
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
)
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course_key
)
student
=
enrollment
.
user
student
=
enrollment
.
user
outbox
=
self
.
get_outbox
()
outbox
=
self
.
get_outbox
()
self
.
assertEqual
(
outbox
,
[])
self
.
assertEqual
(
outbox
,
[])
# student is member of CCX:
CcxMembershipFactory
(
ccx
=
ccx
,
student
=
student
)
url
=
reverse
(
url
=
reverse
(
'ccx_invite'
,
'ccx_invite'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
)
data
=
{
data
=
{
'enrollment-button'
:
'Unenroll'
,
'enrollment-button'
:
'Unenroll'
,
...
@@ -311,10 +309,6 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -311,10 +309,6 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertEqual
(
len
(
outbox
),
1
)
self
.
assertEqual
(
len
(
outbox
),
1
)
self
.
assertTrue
(
student
.
email
in
outbox
[
0
]
.
recipients
())
# pylint: disable=no-member
self
.
assertTrue
(
student
.
email
in
outbox
[
0
]
.
recipients
())
# pylint: disable=no-member
# the membership for this student is gone
self
.
assertFalse
(
CcxMembership
.
objects
.
filter
(
ccx
=
ccx
,
student
=
student
)
.
exists
()
)
def
test_enroll_non_user_student
(
self
):
def
test_enroll_non_user_student
(
self
):
"""enroll a list of students who are not users yet
"""enroll a list of students who are not users yet
...
@@ -322,12 +316,13 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -322,12 +316,13 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
test_email
=
"nobody@nowhere.com"
test_email
=
"nobody@nowhere.com"
self
.
make_coach
()
self
.
make_coach
()
ccx
=
self
.
make_ccx
()
ccx
=
self
.
make_ccx
()
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
outbox
=
self
.
get_outbox
()
outbox
=
self
.
get_outbox
()
self
.
assertEqual
(
outbox
,
[])
self
.
assertEqual
(
outbox
,
[])
url
=
reverse
(
url
=
reverse
(
'ccx_invite'
,
'ccx_invite'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
)
data
=
{
data
=
{
'enrollment-button'
:
'Enroll'
,
'enrollment-button'
:
'Enroll'
,
...
@@ -342,8 +337,8 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -342,8 +337,8 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
len
(
outbox
),
1
)
self
.
assertEqual
(
len
(
outbox
),
1
)
self
.
assertTrue
(
test_email
in
outbox
[
0
]
.
recipients
())
self
.
assertTrue
(
test_email
in
outbox
[
0
]
.
recipients
())
self
.
assertTrue
(
self
.
assertTrue
(
C
cxFutureMembership
.
objects
.
filter
(
C
ourseEnrollmentAllowed
.
objects
.
filter
(
c
cx
=
ccx
,
email
=
test_email
c
ourse_id
=
course_key
,
email
=
test_email
)
.
exists
()
)
.
exists
()
)
)
...
@@ -352,14 +347,16 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -352,14 +347,16 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
"""
test_email
=
"nobody@nowhere.com"
test_email
=
"nobody@nowhere.com"
self
.
make_coach
()
self
.
make_coach
()
course
=
CourseFactory
.
create
()
ccx
=
self
.
make_ccx
()
ccx
=
self
.
make_ccx
()
course_key
=
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
outbox
=
self
.
get_outbox
()
outbox
=
self
.
get_outbox
()
C
cxFutureMembershipFactory
(
ccx
=
ccx
,
email
=
test_email
)
C
ourseEnrollmentAllowed
(
course_id
=
course_key
,
email
=
test_email
)
self
.
assertEqual
(
outbox
,
[])
self
.
assertEqual
(
outbox
,
[])
url
=
reverse
(
url
=
reverse
(
'ccx_invite'
,
'ccx_invite'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
)
data
=
{
data
=
{
'enrollment-button'
:
'Unenroll'
,
'enrollment-button'
:
'Unenroll'
,
...
@@ -371,11 +368,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -371,11 +368,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
# we were redirected to our current location
# we were redirected to our current location
self
.
assertEqual
(
len
(
response
.
redirect_chain
),
1
)
self
.
assertEqual
(
len
(
response
.
redirect_chain
),
1
)
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertEqual
(
len
(
outbox
),
1
)
self
.
assertTrue
(
test_email
in
outbox
[
0
]
.
recipients
())
self
.
assertFalse
(
self
.
assertFalse
(
C
cxFutureMembership
.
objects
.
filter
(
C
ourseEnrollmentAllowed
.
objects
.
filter
(
c
cx
=
ccx
,
email
=
test_email
c
ourse_id
=
course_key
,
email
=
test_email
)
.
exists
()
)
.
exists
()
)
)
...
@@ -384,7 +379,8 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -384,7 +379,8 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
"""
self
.
make_coach
()
self
.
make_coach
()
ccx
=
self
.
make_ccx
()
ccx
=
self
.
make_ccx
()
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
)
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course_key
)
student
=
enrollment
.
user
student
=
enrollment
.
user
# no emails have been sent so far
# no emails have been sent so far
outbox
=
self
.
get_outbox
()
outbox
=
self
.
get_outbox
()
...
@@ -392,7 +388,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -392,7 +388,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
url
=
reverse
(
url
=
reverse
(
'ccx_manage_student'
,
'ccx_manage_student'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
)
data
=
{
data
=
{
'student-action'
:
'add'
,
'student-action'
:
'add'
,
...
@@ -406,7 +402,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -406,7 +402,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
outbox
,
[])
self
.
assertEqual
(
outbox
,
[])
# a CcxMembership exists for this student
# a CcxMembership exists for this student
self
.
assertTrue
(
self
.
assertTrue
(
C
cxMembership
.
objects
.
filter
(
ccx
=
ccx
,
student
=
student
)
.
exists
()
C
ourseEnrollment
.
objects
.
filter
(
course_id
=
course_key
,
user
=
student
)
.
exists
()
)
)
def
test_manage_remove_single_student
(
self
):
def
test_manage_remove_single_student
(
self
):
...
@@ -414,9 +410,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -414,9 +410,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
"""
self
.
make_coach
()
self
.
make_coach
()
ccx
=
self
.
make_ccx
()
ccx
=
self
.
make_ccx
()
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
)
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course_key
)
student
=
enrollment
.
user
student
=
enrollment
.
user
CcxMembershipFactory
(
ccx
=
ccx
,
student
=
student
)
# no emails have been sent so far
# no emails have been sent so far
outbox
=
self
.
get_outbox
()
outbox
=
self
.
get_outbox
()
self
.
assertEqual
(
outbox
,
[])
self
.
assertEqual
(
outbox
,
[])
...
@@ -435,10 +431,6 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -435,10 +431,6 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
len
(
response
.
redirect_chain
),
1
)
self
.
assertEqual
(
len
(
response
.
redirect_chain
),
1
)
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertEqual
(
outbox
,
[])
self
.
assertEqual
(
outbox
,
[])
# a CcxMembership exists for this student
self
.
assertFalse
(
CcxMembership
.
objects
.
filter
(
ccx
=
ccx
,
student
=
student
)
.
exists
()
)
GET_CHILDREN
=
XModuleMixin
.
get_children
GET_CHILDREN
=
XModuleMixin
.
get_children
...
@@ -525,7 +517,6 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -525,7 +517,6 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
student
=
student
=
UserFactory
.
create
()
self
.
student
=
student
=
UserFactory
.
create
()
CourseEnrollmentFactory
.
create
(
user
=
student
,
course_id
=
self
.
course
.
id
)
CourseEnrollmentFactory
.
create
(
user
=
student
,
course_id
=
self
.
course
.
id
)
CcxMembershipFactory
(
ccx
=
ccx
,
student
=
student
,
active
=
True
)
# create grades for self.student as if they'd submitted the ccx
# create grades for self.student as if they'd submitted the ccx
for
chapter
in
self
.
course
.
get_children
():
for
chapter
in
self
.
course
.
get_children
():
...
@@ -674,12 +665,14 @@ class TestStudentDashboardWithCCX(ModuleStoreTestCase):
...
@@ -674,12 +665,14 @@ class TestStudentDashboardWithCCX(ModuleStoreTestCase):
self
.
ccx
=
CcxFactory
(
course_id
=
self
.
split_course
.
id
,
coach
=
self
.
coach
)
self
.
ccx
=
CcxFactory
(
course_id
=
self
.
split_course
.
id
,
coach
=
self
.
coach
)
last_week
=
datetime
.
datetime
.
now
(
UTC
())
-
datetime
.
timedelta
(
days
=
7
)
last_week
=
datetime
.
datetime
.
now
(
UTC
())
-
datetime
.
timedelta
(
days
=
7
)
override_field_for_ccx
(
self
.
ccx
,
self
.
split_course
,
'start'
,
last_week
)
# Required by self.ccx.has_started().
override_field_for_ccx
(
self
.
ccx
,
self
.
split_course
,
'start'
,
last_week
)
# Required by self.ccx.has_started().
CcxMembershipFactory
(
ccx
=
self
.
ccx
,
student
=
self
.
student
,
active
=
True
)
course_key
=
CCXLocator
.
from_course_locator
(
self
.
split_course
.
id
,
self
.
ccx
.
id
)
CourseEnrollment
.
enroll
(
self
.
student
,
course_key
)
def
test_load_student_dashboard
(
self
):
def
test_load_student_dashboard
(
self
):
self
.
client
.
login
(
username
=
self
.
student
.
username
,
password
=
self
.
student_password
)
self
.
client
.
login
(
username
=
self
.
student
.
username
,
password
=
self
.
student_password
)
response
=
self
.
client
.
get
(
reverse
(
'dashboard'
))
response
=
self
.
client
.
get
(
reverse
(
'dashboard'
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
re
.
search
(
'Test CCX'
,
response
.
content
))
def
flatten
(
seq
):
def
flatten
(
seq
):
...
...
lms/djangoapps/ccx/utils.py
View file @
eafb0755
...
@@ -4,283 +4,23 @@ CCX Enrollment operations for use by Coach APIs.
...
@@ -4,283 +4,23 @@ CCX Enrollment operations for use by Coach APIs.
Does not include any access control, be sure to check access before calling.
Does not include any access control, be sure to check access before calling.
"""
"""
import
logging
import
logging
from
django.contrib.auth.models
import
User
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.core.mail
import
send_mail
from
edxmako.shortcuts
import
render_to_string
# pylint: disable=import-error
from
microsite_configuration
import
microsite
# pylint: disable=import-error
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
xmodule.modulestore.django
import
modulestore
from
xmodule.error_module
import
ErrorDescriptor
from
ccx_keys.locator
import
CCXLocator
from
.models
import
(
from
.models
import
CustomCourseForEdX
CcxMembership
,
CcxFutureMembership
,
)
log
=
logging
.
getLogger
(
"edx.ccx"
)
log
=
logging
.
getLogger
(
"edx.ccx"
)
class
EmailEnrollmentState
(
object
):
def
get_ccx_from_ccx_locator
(
course_id
):
""" Store the complete enrollment state of an email in a class """
""" helper function to allow querying ccx fields from templates """
def
__init__
(
self
,
ccx
,
email
):
ccx_id
=
getattr
(
course_id
,
'ccx'
,
None
)
exists_user
=
User
.
objects
.
filter
(
email
=
email
)
.
exists
()
ccx
=
None
if
exists_user
:
if
ccx_id
:
user
=
User
.
objects
.
get
(
email
=
email
)
ccx
=
CustomCourseForEdX
.
objects
.
filter
(
id
=
ccx_id
)
ccx_member
=
CcxMembership
.
objects
.
filter
(
ccx
=
ccx
,
student
=
user
)
if
not
ccx
:
in_ccx
=
ccx_member
.
exists
()
log
.
warning
(
full_name
=
user
.
profile
.
name
"CCX does not exist for course with id
%
s"
,
else
:
course_id
user
=
None
in_ccx
=
False
full_name
=
None
self
.
user
=
exists_user
self
.
member
=
user
self
.
full_name
=
full_name
self
.
in_ccx
=
in_ccx
def
__repr__
(
self
):
return
"{}(user={}, member={}, in_ccx={})"
.
format
(
self
.
__class__
.
__name__
,
self
.
user
,
self
.
member
,
self
.
in_ccx
,
)
def
to_dict
(
self
):
""" return dict with membership and ccx info """
return
{
'user'
:
self
.
user
,
'member'
:
self
.
member
,
'in_ccx'
:
self
.
in_ccx
,
}
def
enroll_email
(
ccx
,
student_email
,
auto_enroll
=
False
,
email_students
=
False
,
email_params
=
None
):
"""
Send email to newly enrolled student
"""
if
email_params
is
None
:
email_params
=
get_email_params
(
ccx
,
True
)
previous_state
=
EmailEnrollmentState
(
ccx
,
student_email
)
if
previous_state
.
user
:
user
=
User
.
objects
.
get
(
email
=
student_email
)
if
not
previous_state
.
in_ccx
:
membership
=
CcxMembership
(
ccx
=
ccx
,
student
=
user
,
active
=
True
)
membership
.
save
()
elif
auto_enroll
:
# activate existing memberships
membership
=
CcxMembership
.
objects
.
get
(
student
=
user
,
ccx
=
ccx
)
membership
.
active
=
True
membership
.
save
()
if
email_students
:
email_params
[
'message'
]
=
'enrolled_enroll'
email_params
[
'email_address'
]
=
student_email
email_params
[
'full_name'
]
=
previous_state
.
full_name
send_mail_to_student
(
student_email
,
email_params
)
else
:
membership
=
CcxFutureMembership
(
ccx
=
ccx
,
auto_enroll
=
auto_enroll
,
email
=
student_email
)
membership
.
save
()
if
email_students
:
email_params
[
'message'
]
=
'allowed_enroll'
email_params
[
'email_address'
]
=
student_email
send_mail_to_student
(
student_email
,
email_params
)
after_state
=
EmailEnrollmentState
(
ccx
,
student_email
)
return
previous_state
,
after_state
def
unenroll_email
(
ccx
,
student_email
,
email_students
=
False
,
email_params
=
None
):
"""
send email to unenrolled students
"""
if
email_params
is
None
:
email_params
=
get_email_params
(
ccx
,
True
)
previous_state
=
EmailEnrollmentState
(
ccx
,
student_email
)
if
previous_state
.
in_ccx
:
CcxMembership
.
objects
.
get
(
ccx
=
ccx
,
student
=
previous_state
.
member
)
.
delete
()
if
email_students
:
email_params
[
'message'
]
=
'enrolled_unenroll'
email_params
[
'email_address'
]
=
student_email
email_params
[
'full_name'
]
=
previous_state
.
full_name
send_mail_to_student
(
student_email
,
email_params
)
else
:
if
CcxFutureMembership
.
objects
.
filter
(
ccx
=
ccx
,
email
=
student_email
)
.
exists
():
CcxFutureMembership
.
objects
.
get
(
ccx
=
ccx
,
email
=
student_email
)
.
delete
()
if
email_students
:
email_params
[
'message'
]
=
'allowed_unenroll'
email_params
[
'email_address'
]
=
student_email
send_mail_to_student
(
student_email
,
email_params
)
after_state
=
EmailEnrollmentState
(
ccx
,
student_email
)
return
previous_state
,
after_state
def
get_email_params
(
ccx
,
auto_enroll
,
secure
=
True
):
"""
get parameters for enrollment emails
"""
protocol
=
'https'
if
secure
else
'http'
stripped_site_name
=
microsite
.
get_value
(
'SITE_NAME'
,
settings
.
SITE_NAME
)
registration_url
=
u'{proto}://{site}{path}'
.
format
(
proto
=
protocol
,
site
=
stripped_site_name
,
path
=
reverse
(
'register_user'
)
)
course_url
=
u'{proto}://{site}{path}'
.
format
(
proto
=
protocol
,
site
=
stripped_site_name
,
path
=
reverse
(
'course_root'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
ccx
.
course_id
,
ccx
.
id
)}
)
)
)
return
None
return
ccx
[
0
]
course_about_url
=
None
if
not
settings
.
FEATURES
.
get
(
'ENABLE_MKTG_SITE'
,
False
):
course_about_url
=
u'{proto}://{site}{path}'
.
format
(
proto
=
protocol
,
site
=
stripped_site_name
,
path
=
reverse
(
'about_course'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
ccx
.
course_id
,
ccx
.
id
)}
)
)
email_params
=
{
'site_name'
:
stripped_site_name
,
'registration_url'
:
registration_url
,
'course'
:
ccx
,
'auto_enroll'
:
auto_enroll
,
'course_url'
:
course_url
,
'course_about_url'
:
course_about_url
,
}
return
email_params
def
send_mail_to_student
(
student
,
param_dict
):
"""
Check parameters, set text template and send email to student
"""
if
'course'
in
param_dict
:
param_dict
[
'course_name'
]
=
param_dict
[
'course'
]
.
display_name
param_dict
[
'site_name'
]
=
microsite
.
get_value
(
'SITE_NAME'
,
param_dict
[
'site_name'
]
)
subject
=
None
message
=
None
message_type
=
param_dict
[
'message'
]
email_template_dict
=
{
'allowed_enroll'
:
(
'ccx/enroll_email_allowedsubject.txt'
,
'ccx/enroll_email_allowedmessage.txt'
),
'enrolled_enroll'
:
(
'ccx/enroll_email_enrolledsubject.txt'
,
'ccx/enroll_email_enrolledmessage.txt'
),
'allowed_unenroll'
:
(
'ccx/unenroll_email_subject.txt'
,
'ccx/unenroll_email_allowedmessage.txt'
),
'enrolled_unenroll'
:
(
'ccx/unenroll_email_subject.txt'
,
'ccx/unenroll_email_enrolledmessage.txt'
),
}
subject_template
,
message_template
=
email_template_dict
.
get
(
message_type
,
(
None
,
None
)
)
if
subject_template
is
not
None
and
message_template
is
not
None
:
subject
=
render_to_string
(
subject_template
,
param_dict
)
message
=
render_to_string
(
message_template
,
param_dict
)
if
subject
and
message
:
message
=
message
.
strip
()
subject
=
''
.
join
(
subject
.
splitlines
())
from_address
=
microsite
.
get_value
(
'email_from_address'
,
settings
.
DEFAULT_FROM_EMAIL
)
send_mail
(
subject
,
message
,
from_address
,
[
student
],
fail_silently
=
False
)
def
get_ccx_membership_triplets
(
user
,
course_org_filter
,
org_filter_out_set
):
"""
Get the relevant set of (CustomCourseForEdX, CcxMembership, CourseOverview)
triplets to be displayed on a student's dashboard.
"""
error_message_format
=
"User {0} enrolled in {2} course {1}"
# only active memberships for now
for
membership
in
CcxMembership
.
memberships_for_user
(
user
):
ccx
=
membership
.
ccx
try
:
course_overview
=
CourseOverview
.
get_from_id
(
ccx
.
course_id
)
except
CourseOverview
.
DoesNotExist
:
log
.
error
(
error_message_format
.
format
(
# pylint: disable=logging-format-interpolation
user
.
username
,
ccx
.
course_id
,
"non-existent"
))
continue
except
IOError
:
log
.
error
(
error_message_format
.
format
(
# pylint: disable=logging-format-interpolation
user
.
username
,
ccx
.
course_id
,
"broken"
))
continue
# if we are in a Microsite, then filter out anything that is not
# attributed (by ORG) to that Microsite
if
course_org_filter
and
course_org_filter
!=
course_overview
.
location
.
org
:
continue
# Conversely, if we are not in a Microsite, then let's filter out any enrollments
# with courses attributed (by ORG) to Microsites
elif
course_overview
.
location
.
org
in
org_filter_out_set
:
continue
# If, somehow, we've got a ccx that has been created for a
# course with a deprecated ID, we must filter it out. Emit a
# warning to the log so we can clean up.
if
course_overview
.
location
.
deprecated
:
log
.
warning
(
"CCX
%
s exists for course
%
s with deprecated id"
,
ccx
,
ccx
.
course_id
)
continue
yield
(
ccx
,
membership
,
course_overview
)
lms/djangoapps/ccx/views.py
View file @
eafb0755
...
@@ -36,22 +36,24 @@ from courseware.module_render import get_module_for_descriptor
...
@@ -36,22 +36,24 @@ from courseware.module_render import get_module_for_descriptor
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
ccx_keys.locator
import
CCXLocator
from
ccx_keys.locator
import
CCXLocator
from
student.roles
import
CourseCcxCoachRole
from
student.roles
import
CourseCcxCoachRole
# pylint: disable=import-error
from
student.models
import
CourseEnrollment
from
instructor.offline_gradecalc
import
student_grades
from
instructor.offline_gradecalc
import
student_grades
# pylint: disable=import-error
from
instructor.views.api
import
_split_input_list
from
instructor.views.api
import
_split_input_list
# pylint: disable=import-error
from
instructor.views.tools
import
get_student_from_identifier
from
instructor.views.tools
import
get_student_from_identifier
# pylint: disable=import-error
from
instructor.enrollment
import
(
enroll_email
,
unenroll_email
,
get_email_params
,
)
from
.models
import
CustomCourseForEdX
,
CcxMembership
from
.models
import
CustomCourseForEdX
from
.overrides
import
(
from
.overrides
import
(
clear_override_for_ccx
,
clear_override_for_ccx
,
get_override_for_ccx
,
get_override_for_ccx
,
override_field_for_ccx
,
override_field_for_ccx
,
)
)
from
.utils
import
(
enroll_email
,
unenroll_email
,
)
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -127,7 +129,7 @@ def dashboard(request, course, ccx=None):
...
@@ -127,7 +129,7 @@ def dashboard(request, course, ccx=None):
context
[
'schedule'
]
=
json
.
dumps
(
schedule
,
indent
=
4
)
context
[
'schedule'
]
=
json
.
dumps
(
schedule
,
indent
=
4
)
context
[
'save_url'
]
=
reverse
(
context
[
'save_url'
]
=
reverse
(
'save_ccx'
,
kwargs
=
{
'course_id'
:
ccx_locator
})
'save_ccx'
,
kwargs
=
{
'course_id'
:
ccx_locator
})
context
[
'ccx_members'
]
=
C
cxMembership
.
objects
.
filter
(
ccx
=
ccx
)
context
[
'ccx_members'
]
=
C
ourseEnrollment
.
objects
.
filter
(
course_id
=
ccx_locator
)
context
[
'gradebook_url'
]
=
reverse
(
context
[
'gradebook_url'
]
=
reverse
(
'ccx_gradebook'
,
kwargs
=
{
'course_id'
:
ccx_locator
})
'ccx_gradebook'
,
kwargs
=
{
'course_id'
:
ccx_locator
})
context
[
'grades_csv_url'
]
=
reverse
(
context
[
'grades_csv_url'
]
=
reverse
(
...
@@ -156,7 +158,7 @@ def create_ccx(request, course, ccx=None):
...
@@ -156,7 +158,7 @@ def create_ccx(request, course, ccx=None):
"You cannot create a CCX from a course using a deprecated id. "
"You cannot create a CCX from a course using a deprecated id. "
"Please create a rerun of this course in the studio to allow "
"Please create a rerun of this course in the studio to allow "
"this action."
))
"this action."
))
url
=
reverse
(
'ccx_coach_dashboard'
,
kwargs
=
{
'course_id'
,
course
.
id
})
url
=
reverse
(
'ccx_coach_dashboard'
,
kwargs
=
{
'course_id'
:
course
.
id
})
return
redirect
(
url
)
return
redirect
(
url
)
ccx
=
CustomCourseForEdX
(
ccx
=
CustomCourseForEdX
(
...
@@ -407,15 +409,18 @@ def ccx_invite(request, course, ccx=None):
...
@@ -407,15 +409,18 @@ def ccx_invite(request, course, ccx=None):
email
=
user
.
email
email
=
user
.
email
try
:
try
:
validate_email
(
email
)
validate_email
(
email
)
course_key
=
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
email_params
=
get_email_params
(
course
,
auto_enroll
)
if
action
==
'Enroll'
:
if
action
==
'Enroll'
:
enroll_email
(
enroll_email
(
c
cx
,
c
ourse_key
,
email
,
email
,
auto_enroll
=
auto_enroll
,
auto_enroll
=
auto_enroll
,
email_students
=
email_students
email_students
=
email_students
,
email_params
=
email_params
)
)
if
action
==
"Unenroll"
:
if
action
==
"Unenroll"
:
unenroll_email
(
c
cx
,
email
,
email_students
=
email_student
s
)
unenroll_email
(
c
ourse_key
,
email
,
email_students
=
email_students
,
email_params
=
email_param
s
)
except
ValidationError
:
except
ValidationError
:
log
.
info
(
'Invalid user name or email when trying to invite students:
%
s'
,
email
)
log
.
info
(
'Invalid user name or email when trying to invite students:
%
s'
,
email
)
url
=
reverse
(
url
=
reverse
(
...
@@ -444,20 +449,21 @@ def ccx_student_management(request, course, ccx=None):
...
@@ -444,20 +449,21 @@ def ccx_student_management(request, course, ccx=None):
else
:
else
:
email
=
user
.
email
email
=
user
.
email
course_key
=
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
try
:
try
:
validate_email
(
email
)
validate_email
(
email
)
if
action
==
'add'
:
if
action
==
'add'
:
# by decree, no emails sent to students added this way
# by decree, no emails sent to students added this way
# by decree, any students added this way are auto_enrolled
# by decree, any students added this way are auto_enrolled
enroll_email
(
c
cx
,
email
,
auto_enroll
=
True
,
email_students
=
False
)
enroll_email
(
c
ourse_key
,
email
,
auto_enroll
=
True
,
email_students
=
False
)
elif
action
==
'revoke'
:
elif
action
==
'revoke'
:
unenroll_email
(
c
cx
,
email
,
email_students
=
False
)
unenroll_email
(
c
ourse_key
,
email
,
email_students
=
False
)
except
ValidationError
:
except
ValidationError
:
log
.
info
(
'Invalid user name or email when trying to enroll student:
%
s'
,
email
)
log
.
info
(
'Invalid user name or email when trying to enroll student:
%
s'
,
email
)
url
=
reverse
(
url
=
reverse
(
'ccx_coach_dashboard'
,
'ccx_coach_dashboard'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
)
return
redirect
(
url
)
return
redirect
(
url
)
...
@@ -496,8 +502,8 @@ def ccx_gradebook(request, course, ccx=None):
...
@@ -496,8 +502,8 @@ def ccx_gradebook(request, course, ccx=None):
prep_course_for_grading
(
course
,
request
)
prep_course_for_grading
(
course
,
request
)
enrolled_students
=
User
.
objects
.
filter
(
enrolled_students
=
User
.
objects
.
filter
(
c
cxmembership__ccx
=
ccx
,
c
ourseenrollment__course_id
=
ccx_key
,
c
cxmembership_
_active
=
1
c
ourseenrollment__is
_active
=
1
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
student_info
=
[
student_info
=
[
...
@@ -535,8 +541,8 @@ def ccx_grades_csv(request, course, ccx=None):
...
@@ -535,8 +541,8 @@ def ccx_grades_csv(request, course, ccx=None):
prep_course_for_grading
(
course
,
request
)
prep_course_for_grading
(
course
,
request
)
enrolled_students
=
User
.
objects
.
filter
(
enrolled_students
=
User
.
objects
.
filter
(
c
cxmembership__ccx
=
ccx
,
c
ourseenrollment__course_id
=
ccx_key
,
c
cxmembership_
_active
=
1
c
ourseenrollment__is
_active
=
1
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
grades
=
iterate_grades_for
(
course
,
enrolled_students
)
grades
=
iterate_grades_for
(
course
,
enrolled_students
)
...
...
lms/djangoapps/django_comment_client/utils.py
View file @
eafb0755
...
@@ -87,7 +87,7 @@ def has_forum_access(uname, course_id, rolename):
...
@@ -87,7 +87,7 @@ def has_forum_access(uname, course_id, rolename):
def
has_required_keys
(
module
):
def
has_required_keys
(
module
):
"""Returns True iff module has the proper attributes for generating metadata with get_discussion_id_map_entry()"""
"""Returns True iff module has the proper attributes for generating metadata with get_discussion_id_map_entry()"""
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
if
not
getattr
(
module
,
key
,
None
)
:
if
getattr
(
module
,
key
,
None
)
is
None
:
log
.
debug
(
"Required key '
%
s' not in discussion
%
s, leaving out of category map"
,
key
,
module
.
location
)
log
.
debug
(
"Required key '
%
s' not in discussion
%
s, leaving out of category map"
,
key
,
module
.
location
)
return
False
return
False
return
True
return
True
...
...
lms/djangoapps/mobile_api/users/tests.py
View file @
eafb0755
...
@@ -146,9 +146,9 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin):
...
@@ -146,9 +146,9 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin):
@ddt.data
(
@ddt.data
(
(
NEXT_WEEK
,
ADVERTISED_START
,
ADVERTISED_START
,
"string"
),
(
NEXT_WEEK
,
ADVERTISED_START
,
ADVERTISED_START
,
"string"
),
(
NEXT_WEEK
,
None
,
''
,
"string
"
),
(
NEXT_WEEK
,
None
,
defaultfilters
.
date
(
NEXT_WEEK
,
"DATE_FORMAT"
),
"timestamp
"
),
(
DEFAULT_START_DATE
,
ADVERTISED_START
,
ADVERTISED_START
,
"string"
),
(
DEFAULT_START_DATE
,
ADVERTISED_START
,
ADVERTISED_START
,
"string"
),
(
DEFAULT_START_DATE
,
None
,
''
,
"string
"
)
(
DEFAULT_START_DATE
,
None
,
None
,
"empty
"
)
)
)
@ddt.unpack
@ddt.unpack
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'DISABLE_START_DATES'
:
False
})
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'DISABLE_START_DATES'
:
False
})
...
...
lms/djangoapps/oauth2_handler/handlers.py
View file @
eafb0755
...
@@ -4,11 +4,12 @@ from django.conf import settings
...
@@ -4,11 +4,12 @@ from django.conf import settings
from
django.core.cache
import
cache
from
django.core.cache
import
cache
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
courseware.access
import
has_access
from
openedx.core.djangoapps.user_api.models
import
UserPreference
from
openedx.core.djangoapps.user_api.models
import
UserPreference
from
student.models
import
anonymous_id_for_user
from
student.models
import
anonymous_id_for_user
from
student.models
import
UserProfile
from
student.models
import
UserProfile
from
lang_pref
import
LANGUAGE_KEY
from
lang_pref
import
LANGUAGE_KEY
from
student.roles
import
(
GlobalStaff
,
CourseStaffRole
,
CourseInstructorRole
,
UserBasedRole
)
from
student.roles
import
GlobalStaff
,
CourseStaffRole
,
CourseInstructorRole
class
OpenIDHandler
(
object
):
class
OpenIDHandler
(
object
):
...
@@ -189,31 +190,23 @@ class CourseAccessHandler(object):
...
@@ -189,31 +190,23 @@ class CourseAccessHandler(object):
return
course_ids
return
course_ids
# pylint: disable=missing-docstring
def
_get_courses_with_access_type
(
self
,
user
,
access_type
):
def
_get_courses_with_access_type
(
self
,
user
,
access_type
):
"""
If global staff, returns all courses. Otherwise, returns list of courses
based on role access (e.g. courses that course staff has access to).
"""
# Check the application cache and update if not present. The application
# Check the application cache and update if not present. The application
# cache is useful since there are calls to different endpoints in close
# cache is useful since there are calls to different endpoints in close
# succession, for example the id_token and user_info endpoints.
# succession, for example the id_token and user_info endpoints.
key
=
'-'
.
join
([
str
(
self
.
__class__
),
str
(
user
.
id
),
access_type
])
key
=
'-'
.
join
([
str
(
self
.
__class__
),
str
(
user
.
id
),
access_type
])
course_ids
=
cache
.
get
(
key
)
course_ids
=
cache
.
get
(
key
)
if
not
course_ids
:
if
not
course_ids
:
courses
=
_get_all_courses
()
# Global staff have access to all courses. Filter courses for non-global staff.
if
not
GlobalStaff
()
.
has_user
(
user
):
courses
=
[
course
for
course
in
courses
if
has_access
(
user
,
access_type
,
course
)]
if
GlobalStaff
()
.
has_user
(
user
):
course_ids
=
[
unicode
(
course
.
id
)
for
course
in
courses
]
# TODO: This code should be optimized in the future to caching
# the list of all courses in the system.
# The modulestore has all courses, while the roles table only has courses
# with roles. Thus, we'll use the modulestore to fetch all courses.
courses
=
_get_all_courses
()
course_ids
=
[
unicode
(
course
.
id
)
for
course
in
courses
]
else
:
# Getting courses based on roles avoid querying mongo and thus faster
courses
=
UserBasedRole
(
user
,
access_type
)
.
courses_with_role
()
course_ids
=
[
unicode
(
course
.
course_id
)
for
course
in
courses
]
cache
.
set
(
key
,
course_ids
,
self
.
COURSE_CACHE_TIMEOUT
)
cache
.
set
(
key
,
course_ids
,
self
.
COURSE_CACHE_TIMEOUT
)
...
...
lms/djangoapps/oauth2_handler/tests.py
View file @
eafb0755
...
@@ -5,10 +5,9 @@ from lang_pref import LANGUAGE_KEY
...
@@ -5,10 +5,9 @@ from lang_pref import LANGUAGE_KEY
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
xmodule.modulestore.tests.django_utils
import
TEST_DATA_MIXED_TOY_MODULESTORE
from
xmodule.modulestore.tests.django_utils
import
TEST_DATA_MIXED_TOY_MODULESTORE
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
,
check_mongo_calls_range
from
student.models
import
anonymous_id_for_user
from
student.models
import
anonymous_id_for_user
from
student.models
import
UserProfile
from
student.models
import
UserProfile
from
student.roles
import
CourseStaffRole
,
CourseInstructorRole
,
GlobalStaff
from
student.roles
import
CourseStaffRole
,
CourseInstructorRole
from
student.tests.factories
import
UserFactory
,
UserProfileFactory
from
student.tests.factories
import
UserFactory
,
UserProfileFactory
from
openedx.core.djangoapps.user_api.preferences.api
import
set_user_preference
from
openedx.core.djangoapps.user_api.preferences.api
import
set_user_preference
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
@@ -78,8 +77,7 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
...
@@ -78,8 +77,7 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
self
.
assertEqual
(
language
,
locale
)
self
.
assertEqual
(
language
,
locale
)
def
test_no_special_course_access
(
self
):
def
test_no_special_course_access
(
self
):
with
check_mongo_calls
(
0
):
scopes
,
claims
=
self
.
get_id_token_values
(
'openid course_instructor course_staff'
)
scopes
,
claims
=
self
.
get_id_token_values
(
'openid course_instructor course_staff'
)
self
.
assertNotIn
(
'course_staff'
,
scopes
)
self
.
assertNotIn
(
'course_staff'
,
scopes
)
self
.
assertNotIn
(
'staff_courses'
,
claims
)
self
.
assertNotIn
(
'staff_courses'
,
claims
)
...
@@ -88,15 +86,17 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
...
@@ -88,15 +86,17 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
def
test_course_staff_courses
(
self
):
def
test_course_staff_courses
(
self
):
CourseStaffRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
CourseStaffRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
with
check_mongo_calls
(
0
):
scopes
,
claims
=
self
.
get_id_token_values
(
'openid course_staff'
)
scopes
,
claims
=
self
.
get_id_token_values
(
'openid course_staff'
)
self
.
assertIn
(
'course_staff'
,
scopes
)
self
.
assertIn
(
'course_staff'
,
scopes
)
self
.
assertNotIn
(
'staff_courses'
,
claims
)
# should not return courses in id_token
self
.
assertNotIn
(
'staff_courses'
,
claims
)
# should not return courses in id_token
def
test_course_instructor_courses
(
self
):
def
test_course_instructor_courses
(
self
):
CourseInstructorRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
CourseInstructorRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
with
check_mongo_calls
(
0
):
scopes
,
claims
=
self
.
get_id_token_values
(
'openid course_instructor'
)
scopes
,
claims
=
self
.
get_id_token_values
(
'openid course_instructor'
)
self
.
assertIn
(
'course_instructor'
,
scopes
)
self
.
assertIn
(
'course_instructor'
,
scopes
)
self
.
assertNotIn
(
'instructor_courses'
,
claims
)
# should not return courses in id_token
self
.
assertNotIn
(
'instructor_courses'
,
claims
)
# should not return courses in id_token
...
@@ -113,8 +113,7 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
...
@@ -113,8 +113,7 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
}
}
}
}
with
check_mongo_calls
(
0
):
scopes
,
claims
=
self
.
get_id_token_values
(
scope
=
'openid course_staff'
,
claims
=
claims
)
scopes
,
claims
=
self
.
get_id_token_values
(
scope
=
'openid course_staff'
,
claims
=
claims
)
self
.
assertIn
(
'course_staff'
,
scopes
)
self
.
assertIn
(
'course_staff'
,
scopes
)
self
.
assertIn
(
'staff_courses'
,
claims
)
self
.
assertIn
(
'staff_courses'
,
claims
)
...
@@ -134,11 +133,6 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
...
@@ -134,11 +133,6 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
class
UserInfoTest
(
BaseTestMixin
,
UserInfoTestCase
):
class
UserInfoTest
(
BaseTestMixin
,
UserInfoTestCase
):
def
setUp
(
self
):
super
(
UserInfoTest
,
self
)
.
setUp
()
# clear the course ID cache
cache
.
clear
()
def
token_for_scope
(
self
,
scope
):
def
token_for_scope
(
self
,
scope
):
full_scope
=
'openid
%
s'
%
scope
full_scope
=
'openid
%
s'
%
scope
self
.
set_access_token_scope
(
full_scope
)
self
.
set_access_token_scope
(
full_scope
)
...
@@ -164,39 +158,19 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
...
@@ -164,39 +158,19 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
self
.
assertEqual
(
result
.
status_code
,
200
)
self
.
assertEqual
(
result
.
status_code
,
200
)
return
claims
return
claims
def
test_request_global_staff_courses_using_scope
(
self
):
GlobalStaff
()
.
add_users
(
self
.
user
)
with
check_mongo_calls_range
(
min_finds
=
1
):
claims
=
self
.
get_with_scope
(
'course_staff'
)
courses
=
claims
[
'staff_courses'
]
self
.
assertIn
(
self
.
course_id
,
courses
)
self
.
assertEqual
(
len
(
courses
),
1
)
def
test_request_staff_courses_using_scope
(
self
):
def
test_request_staff_courses_using_scope
(
self
):
CourseStaffRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
CourseStaffRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
with
check_mongo_calls
(
0
):
claims
=
self
.
get_with_scope
(
'course_staff'
)
claims
=
self
.
get_with_scope
(
'course_staff'
)
courses
=
claims
[
'staff_courses'
]
courses
=
claims
[
'staff_courses'
]
self
.
assertIn
(
self
.
course_id
,
courses
)
self
.
assertIn
(
self
.
course_id
,
courses
)
self
.
assertEqual
(
len
(
courses
),
1
)
self
.
assertEqual
(
len
(
courses
),
1
)
def
test_request_instructor_courses_using_scope
(
self
):
def
test_request_instructor_courses_using_scope
(
self
):
CourseInstructorRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
CourseInstructorRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
with
check_mongo_calls
(
0
):
claims
=
self
.
get_with_scope
(
'course_instructor'
)
claims
=
self
.
get_with_scope
(
'course_instructor'
)
courses
=
claims
[
'instructor_courses'
]
self
.
assertIn
(
self
.
course_id
,
courses
)
self
.
assertEqual
(
len
(
courses
),
1
)
def
test_request_global_staff_courses_with_claims
(
self
):
GlobalStaff
()
.
add_users
(
self
.
user
)
values
=
[
self
.
course_id
,
'some_invalid_course'
]
courses
=
claims
[
'instructor_courses'
]
with
check_mongo_calls_range
(
min_finds
=
1
):
claims
=
self
.
get_with_claim_value
(
'course_staff'
,
'staff_courses'
,
values
)
self
.
assertEqual
(
len
(
claims
),
2
)
courses
=
claims
[
'staff_courses'
]
self
.
assertIn
(
self
.
course_id
,
courses
)
self
.
assertIn
(
self
.
course_id
,
courses
)
self
.
assertEqual
(
len
(
courses
),
1
)
self
.
assertEqual
(
len
(
courses
),
1
)
...
@@ -204,8 +178,7 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
...
@@ -204,8 +178,7 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
CourseStaffRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
CourseStaffRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
values
=
[
self
.
course_id
,
'some_invalid_course'
]
values
=
[
self
.
course_id
,
'some_invalid_course'
]
with
check_mongo_calls
(
0
):
claims
=
self
.
get_with_claim_value
(
'course_staff'
,
'staff_courses'
,
values
)
claims
=
self
.
get_with_claim_value
(
'course_staff'
,
'staff_courses'
,
values
)
self
.
assertEqual
(
len
(
claims
),
2
)
self
.
assertEqual
(
len
(
claims
),
2
)
courses
=
claims
[
'staff_courses'
]
courses
=
claims
[
'staff_courses'
]
...
@@ -216,8 +189,7 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
...
@@ -216,8 +189,7 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
CourseInstructorRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
CourseInstructorRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
values
=
[
'edX/toy/TT_2012_Fall'
,
self
.
course_id
,
'invalid_course_id'
]
values
=
[
'edX/toy/TT_2012_Fall'
,
self
.
course_id
,
'invalid_course_id'
]
with
check_mongo_calls
(
0
):
claims
=
self
.
get_with_claim_value
(
'course_instructor'
,
'instructor_courses'
,
values
)
claims
=
self
.
get_with_claim_value
(
'course_instructor'
,
'instructor_courses'
,
values
)
self
.
assertEqual
(
len
(
claims
),
2
)
self
.
assertEqual
(
len
(
claims
),
2
)
courses
=
claims
[
'instructor_courses'
]
courses
=
claims
[
'instructor_courses'
]
...
...
lms/templates/ccx/_dashboard_ccx_listing.html
deleted
100644 → 0
View file @
5b9b0a83
<
%
page
args=
"ccx, membership, course_overview, show_courseware_link, is_course_blocked"
/>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
core
.
urlresolvers
import
reverse
from
courseware
.
courses
import
course_image_url
,
get_course_about_section
from
ccx_keys
.
locator
import
CCXLocator
%
>
<
%
ccx_target =
reverse('info',
args=
[CCXLocator.from_course_locator(course_overview.id,
ccx
.
id
)])
%
>
<li
class=
"course-item"
>
<article
class=
"course"
>
<section
class=
"details"
>
<div
class=
"wrapper-course-image"
aria-hidden=
"true"
>
% if show_courseware_link:
% if not is_course_blocked:
<a
href=
"${ccx_target}"
class=
"cover"
>
<img
src=
"${course_overview.course_image_url}"
class=
"course-image"
alt=
"${_('{course_number} {ccx_name} Cover Image').format(course_number=course_overview.number, ccx_name=ccx.display_name) |h}"
/>
</a>
% else:
<a
class=
"fade-cover"
>
<img
src=
"${course_overview.course_image_url}"
class=
"course-image"
alt=
"${_('{course_number} {ccx_name} Cover Image').format(course_number=course_overview.number, ccx_name=ccx.display_name) |h}"
/>
</a>
% endif
% else:
<a
class=
"cover"
>
<img
src=
"${course_overview.course_image_url}"
class=
"course-image"
alt=
"${_('{course_number} {ccx_name} Cover Image').format(course_number=course_overview.number, ccx_name=ccx.display_name) |h}"
/>
</a>
% endif
</div>
<div
class=
"wrapper-course-details"
>
<h3
class=
"course-title"
>
% if show_courseware_link:
% if not is_course_blocked:
<a
href=
"${ccx_target}"
>
${ccx.display_name}
</a>
% else:
<a
class=
"disable-look"
>
${ccx.display_name}
</a>
% endif
% else:
<span>
${ccx.display_name}
</span>
% endif
</h3>
<div
class=
"course-info"
>
<span
class=
"info-university"
>
${get_course_about_section(course_overview, 'university')} -
</span>
<span
class=
"info-course-id"
>
${course_overview.display_number_with_default | h}
</span>
<span
class=
"info-date-block"
data-tooltip=
"Hi"
>
% if ccx.has_ended():
${_("Ended - {end_date}").format(end_date=ccx.end_datetime_text("SHORT_DATE"))}
% elif ccx.has_started():
${_("Started - {start_date}").format(start_date=ccx.start_datetime_text("SHORT_DATE"))}
% else: # hasn't started yet
${_("Starts - {start_date}").format(start_date=ccx.start_datetime_text("SHORT_DATE"))}
% endif
</span>
</div>
% if show_courseware_link:
<div
class=
"wrapper-course-actions"
>
<div
class=
"course-actions"
>
% if ccx.has_ended():
% if not is_course_blocked:
<a
href=
"${ccx_target}"
class=
"enter-course archived"
>
${_('View Archived Custom Course')}
<span
class=
"sr"
>
${ccx.display_name}
</span></a>
% else:
<a
class=
"enter-course-blocked archived"
>
${_('View Archived Custom Course')}
<span
class=
"sr"
>
${ccx.display_name}
</span></a>
% endif
% else:
% if not is_course_blocked:
<a
href=
"${ccx_target}"
class=
"enter-course"
>
${_('View Custom Course')}
<span
class=
"sr"
>
${ccx.display_name}
</span></a>
% else:
<a
class=
"enter-course-blocked"
>
${_('View Custom Course')}
<span
class=
"sr"
>
${ccx.display_name}
</span></a>
% endif
% endif
</div>
</div>
% endif
</div>
</section>
</article>
</li>
lms/templates/ccx/enrollment.html
View file @
eafb0755
...
@@ -63,8 +63,8 @@
...
@@ -63,8 +63,8 @@
<tbody>
<tbody>
%for member in ccx_members:
%for member in ccx_members:
<tr>
<tr>
<td>
${member.
student
}
</td>
<td>
${member.
user
}
</td>
<td>
${member.
student
.email}
</td>
<td>
${member.
user
.email}
</td>
<td><div
class=
"revoke"
><i
class=
"fa fa-times-circle"
></i>
Revoke access
</div></td>
<td><div
class=
"revoke"
><i
class=
"fa fa-times-circle"
></i>
Revoke access
</div></td>
</tr>
</tr>
%endfor
%endfor
...
...
lms/templates/dashboard.html
View file @
eafb0755
...
@@ -97,14 +97,6 @@ import json
...
@@ -97,14 +97,6 @@ import json
<
%
include
file=
'dashboard/_dashboard_course_listing.html'
args=
"course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option=show_refund_option, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user"
/>
<
%
include
file=
'dashboard/_dashboard_course_listing.html'
args=
"course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option=show_refund_option, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user"
/>
% endfor
% endfor
% if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
% for ccx, membership, course_overview in ccx_membership_triplets:
<
%
show_courseware_link =
ccx.has_started()
%
>
<
%
is_course_blocked =
False
%
>
<
%
include
file=
'ccx/_dashboard_ccx_listing.html'
args=
"ccx=ccx, membership=membership, course_overview=course_overview, show_courseware_link=show_courseware_link, is_course_blocked=is_course_blocked"
/>
% endfor
% endif
</ul>
</ul>
% else:
% else:
<section
class=
"empty-dashboard-message"
>
<section
class=
"empty-dashboard-message"
>
...
...
openedx/core/djangoapps/content/course_overviews/models.py
View file @
eafb0755
...
@@ -16,6 +16,8 @@ from xmodule.error_module import ErrorDescriptor
...
@@ -16,6 +16,8 @@ from xmodule.error_module import ErrorDescriptor
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule_django.models
import
CourseKeyField
,
UsageKeyField
from
xmodule_django.models
import
CourseKeyField
,
UsageKeyField
from
ccx_keys.locator
import
CCXLocator
class
CourseOverview
(
TimeStampedModel
):
class
CourseOverview
(
TimeStampedModel
):
"""
"""
...
@@ -101,17 +103,26 @@ class CourseOverview(TimeStampedModel):
...
@@ -101,17 +103,26 @@ class CourseOverview(TimeStampedModel):
except
ValueError
:
except
ValueError
:
lowest_passing_grade
=
None
lowest_passing_grade
=
None
display_name
=
course
.
display_name
start
=
course
.
start
end
=
course
.
end
if
isinstance
(
course
.
id
,
CCXLocator
):
from
ccx.utils
import
get_ccx_from_ccx_locator
# pylint: disable=import-error
ccx
=
get_ccx_from_ccx_locator
(
course
.
id
)
display_name
=
ccx
.
display_name
start
=
ccx
.
start
end
=
ccx
.
due
return
cls
(
return
cls
(
version
=
cls
.
VERSION
,
version
=
cls
.
VERSION
,
id
=
course
.
id
,
id
=
course
.
id
,
_location
=
course
.
location
,
_location
=
course
.
location
,
display_name
=
course
.
display_name
,
display_name
=
display_name
,
display_number_with_default
=
course
.
display_number_with_default
,
display_number_with_default
=
course
.
display_number_with_default
,
display_org_with_default
=
course
.
display_org_with_default
,
display_org_with_default
=
course
.
display_org_with_default
,
start
=
course
.
start
,
start
=
start
,
end
=
course
.
end
,
end
=
end
,
advertised_start
=
course
.
advertised_start
,
advertised_start
=
course
.
advertised_start
,
course_image_url
=
course_image_url
(
course
),
course_image_url
=
course_image_url
(
course
),
...
...
openedx/core/djangoapps/content/course_overviews/tests.py
View file @
eafb0755
...
@@ -191,7 +191,7 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
...
@@ -191,7 +191,7 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
"display_name"
:
""
,
# Empty display name
"display_name"
:
""
,
# Empty display name
"start"
:
LAST_MONTH
,
# Course already ended
"start"
:
LAST_MONTH
,
# Course already ended
"end"
:
LAST_WEEK
,
"end"
:
LAST_WEEK
,
"advertised_start"
:
''
,
# No advertised start
"advertised_start"
:
None
,
# No advertised start
"pre_requisite_courses"
:
[],
# No pre-requisites
"pre_requisite_courses"
:
[],
# No pre-requisites
"static_asset_path"
:
""
,
# Empty asset path
"static_asset_path"
:
""
,
# Empty asset path
"certificates_show_before_end"
:
False
,
"certificates_show_before_end"
:
False
,
...
@@ -200,7 +200,7 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
...
@@ -200,7 +200,7 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
# # Don't set display name
# # Don't set display name
"start"
:
DEFAULT_START_DATE
,
# Default start and end dates
"start"
:
DEFAULT_START_DATE
,
# Default start and end dates
"end"
:
None
,
"end"
:
None
,
"advertised_start"
:
''
,
# No advertised start
"advertised_start"
:
None
,
# No advertised start
"pre_requisite_courses"
:
[],
# No pre-requisites
"pre_requisite_courses"
:
[],
# No pre-requisites
"static_asset_path"
:
None
,
# No asset path
"static_asset_path"
:
None
,
# No asset path
"certificates_show_before_end"
:
False
,
"certificates_show_before_end"
:
False
,
...
...
requirements/edx/github.txt
View file @
eafb0755
...
@@ -34,7 +34,7 @@ git+https://github.com/hmarr/django-debug-toolbar-mongo.git@b0686a76f1ce3532088c
...
@@ -34,7 +34,7 @@ git+https://github.com/hmarr/django-debug-toolbar-mongo.git@b0686a76f1ce3532088c
git+https://github.com/edx/rfc6266.git@v0.0.5-edx#egg=rfc6266==0.0.5-edx
git+https://github.com/edx/rfc6266.git@v0.0.5-edx#egg=rfc6266==0.0.5-edx
# Our libraries:
# Our libraries:
-e git+https://github.com/edx/XBlock.git@
0d3d43bd1ef0f882ef70d19d4f652d35dd37eb01
#egg=XBlock
-e git+https://github.com/edx/XBlock.git@
d1ff8cf31a9b94916ce06ba06d4176bd72e15768
#egg=XBlock
-e git+https://github.com/edx/codejail.git@6b17c33a89bef0ac510926b1d7fea2748b73aadd#egg=codejail
-e git+https://github.com/edx/codejail.git@6b17c33a89bef0ac510926b1d7fea2748b73aadd#egg=codejail
-e git+https://github.com/edx/js-test-tool.git@v0.1.6#egg=js_test_tool
-e git+https://github.com/edx/js-test-tool.git@v0.1.6#egg=js_test_tool
-e git+https://github.com/edx/event-tracking.git@0.2.0#egg=event-tracking
-e git+https://github.com/edx/event-tracking.git@0.2.0#egg=event-tracking
...
...
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