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
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
232 additions
and
736 deletions
+232
-736
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
+0
-0
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):
source_course
=
CourseFactory
.
create
(
advertised_start
=
"01-12-2015"
)
destination_course_key
=
self
.
post_rerun_request
(
source_course
.
id
)
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
):
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):
CourseRerunState
.
objects
.
initiated
(
source_course_key
,
destination_course_key
,
request
.
user
,
fields
[
'display_name'
])
# 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
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):
finally
:
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
):
"""
Verify that course export with customtag
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
eafb0755
...
...
@@ -625,7 +625,7 @@ class TestEditItem(TestEditItemSetup):
data
=
{
'nullout'
:
[
'markdown'
]}
)
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
):
"""
...
...
common/djangoapps/student/models.py
View file @
eafb0755
...
...
@@ -1200,12 +1200,6 @@ class CourseEnrollment(models.Model):
if
not
user
.
is_authenticated
():
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
:
record
=
CourseEnrollment
.
objects
.
get
(
user
=
user
,
course_id
=
course_key
)
return
record
.
is_active
...
...
common/djangoapps/student/views.py
View file @
eafb0755
...
...
@@ -655,13 +655,6 @@ def dashboard(request):
)
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
:
redirect_message
=
_
(
"The course you are looking for does not start until {date}."
)
.
format
(
date
=
request
.
GET
[
'notlive'
]
...
...
@@ -697,7 +690,6 @@ def dashboard(request):
'provider_states'
:
[],
'order_history_list'
:
order_history_list
,
'courses_requirements_not_met'
:
courses_requirements_not_met
,
'ccx_membership_triplets'
:
ccx_membership_triplets
,
'nav_hidden'
:
True
,
}
...
...
@@ -1904,16 +1896,6 @@ def activate_account(request, key):
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
(
"registration/activation_complete.html"
,
{
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
eafb0755
...
...
@@ -948,7 +948,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
super
(
CourseDescriptor
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
if
not
self
.
wiki_slug
:
if
self
.
wiki_slug
is
None
:
self
.
wiki_slug
=
self
.
location
.
course
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
from
django.utils.timezone
import
UTC
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.error_module
import
ErrorDescriptor
from
xmodule.modulestore.django
import
modulestore
...
...
@@ -96,52 +95,6 @@ class CustomCourseForEdX(models.Model):
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
):
"""
Field overrides for custom courses.
...
...
lms/djangoapps/ccx/tests/factories.py
View file @
eafb0755
...
...
@@ -5,8 +5,6 @@ from factory import SubFactory
from
factory.django
import
DjangoModelFactory
from
student.tests.factories
import
UserFactory
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
...
...
@@ -14,12 +12,3 @@ class CcxFactory(DjangoModelFactory): # pylint: disable=missing-docstring
display_name
=
"Test CCX"
id
=
None
# pylint: disable=redefined-builtin, invalid-name
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, \
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
,
CourseFactory
,
check_sum_of_calls
from
xmodule.modulestore.tests.utils
import
ProceduralCourseTestMixin
from
ccx_keys.locator
import
CCXLocator
from
ccx.tests.factories
import
CcxFactory
,
CcxMembershipFactory
from
ccx.tests.factories
import
CcxFactory
@attr
(
'shard_1'
)
...
...
@@ -65,7 +65,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
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.
"""
...
...
@@ -112,18 +112,17 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
)
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
(
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
):
"""
Renders the progress page for the given course.
...
...
@@ -156,7 +155,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
"""
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
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
from
django.utils.timezone
import
UTC
from
mock
import
patch
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.tests.factories
import
(
# pylint: disable=import-error
AdminFactory
,
CourseEnrollmentFactory
,
UserFactory
,
)
from
util.tests.test_date_utils
import
fake_ugettext
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -21,123 +18,11 @@ from xmodule.modulestore.tests.factories import (
from
.factories
import
(
CcxFactory
,
CcxFutureMembershipFactory
,
)
from
..models
import
(
CcxMembership
,
CcxFutureMembership
,
)
from
..overrides
import
override_field_for_ccx
@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
):
"""Unit tests for the CustomCourseForEdX model
"""
...
...
lms/djangoapps/ccx/tests/test_utils.py
View file @
eafb0755
This diff is collapsed.
Click to expand it.
lms/djangoapps/ccx/tests/test_views.py
View file @
eafb0755
...
...
@@ -20,9 +20,12 @@ from django.utils.timezone import UTC
from
django.test.utils
import
override_settings
from
django.test
import
RequestFactory
from
edxmako.shortcuts
import
render_to_response
# pylint: disable=import-error
from
student.models
import
CourseEnrollment
from
request_cache.middleware
import
RequestCache
from
student.roles
import
CourseCcxCoachRole
# pylint: disable=import-error
from
student.models
import
(
CourseEnrollment
,
CourseEnrollmentAllowed
,
)
from
student.tests.factories
import
(
# pylint: disable=import-error
AdminFactory
,
CourseEnrollmentFactory
,
...
...
@@ -42,14 +45,10 @@ from ccx_keys.locator import CCXLocator
from
..models
import
(
CustomCourseForEdX
,
CcxMembership
,
CcxFutureMembership
,
)
from
..overrides
import
get_override_for_ccx
,
override_field_for_ccx
from
.factories
import
(
CcxFactory
,
CcxMembershipFactory
,
CcxFutureMembershipFactory
,
)
...
...
@@ -280,7 +279,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertTrue
(
student
.
email
in
outbox
[
0
]
.
recipients
())
# pylint: disable=no-member
# a CcxMembership exists for this student
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
):
...
...
@@ -288,16 +287,15 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self
.
make_coach
()
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
outbox
=
self
.
get_outbox
()
self
.
assertEqual
(
outbox
,
[])
# student is member of CCX:
CcxMembershipFactory
(
ccx
=
ccx
,
student
=
student
)
url
=
reverse
(
'ccx_invite'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
data
=
{
'enrollment-button'
:
'Unenroll'
,
...
...
@@ -311,10 +309,6 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertEqual
(
len
(
outbox
),
1
)
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
):
"""enroll a list of students who are not users yet
...
...
@@ -322,12 +316,13 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
test_email
=
"nobody@nowhere.com"
self
.
make_coach
()
ccx
=
self
.
make_ccx
()
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
outbox
=
self
.
get_outbox
()
self
.
assertEqual
(
outbox
,
[])
url
=
reverse
(
'ccx_invite'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
data
=
{
'enrollment-button'
:
'Enroll'
,
...
...
@@ -342,8 +337,8 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
len
(
outbox
),
1
)
self
.
assertTrue
(
test_email
in
outbox
[
0
]
.
recipients
())
self
.
assertTrue
(
C
cxFutureMembership
.
objects
.
filter
(
c
cx
=
ccx
,
email
=
test_email
C
ourseEnrollmentAllowed
.
objects
.
filter
(
c
ourse_id
=
course_key
,
email
=
test_email
)
.
exists
()
)
...
...
@@ -352,14 +347,16 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
test_email
=
"nobody@nowhere.com"
self
.
make_coach
()
course
=
CourseFactory
.
create
()
ccx
=
self
.
make_ccx
()
course_key
=
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
outbox
=
self
.
get_outbox
()
C
cxFutureMembershipFactory
(
ccx
=
ccx
,
email
=
test_email
)
C
ourseEnrollmentAllowed
(
course_id
=
course_key
,
email
=
test_email
)
self
.
assertEqual
(
outbox
,
[])
url
=
reverse
(
'ccx_invite'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
data
=
{
'enrollment-button'
:
'Unenroll'
,
...
...
@@ -371,11 +368,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
# we were redirected to our current location
self
.
assertEqual
(
len
(
response
.
redirect_chain
),
1
)
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertEqual
(
len
(
outbox
),
1
)
self
.
assertTrue
(
test_email
in
outbox
[
0
]
.
recipients
())
self
.
assertFalse
(
C
cxFutureMembership
.
objects
.
filter
(
c
cx
=
ccx
,
email
=
test_email
C
ourseEnrollmentAllowed
.
objects
.
filter
(
c
ourse_id
=
course_key
,
email
=
test_email
)
.
exists
()
)
...
...
@@ -384,7 +379,8 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self
.
make_coach
()
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
# no emails have been sent so far
outbox
=
self
.
get_outbox
()
...
...
@@ -392,7 +388,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
url
=
reverse
(
'ccx_manage_student'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
data
=
{
'student-action'
:
'add'
,
...
...
@@ -406,7 +402,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
outbox
,
[])
# a CcxMembership exists for this student
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
):
...
...
@@ -414,9 +410,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self
.
make_coach
()
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
CcxMembershipFactory
(
ccx
=
ccx
,
student
=
student
)
# no emails have been sent so far
outbox
=
self
.
get_outbox
()
self
.
assertEqual
(
outbox
,
[])
...
...
@@ -435,10 +431,6 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
len
(
response
.
redirect_chain
),
1
)
self
.
assertTrue
(
302
in
response
.
redirect_chain
[
0
])
self
.
assertEqual
(
outbox
,
[])
# a CcxMembership exists for this student
self
.
assertFalse
(
CcxMembership
.
objects
.
filter
(
ccx
=
ccx
,
student
=
student
)
.
exists
()
)
GET_CHILDREN
=
XModuleMixin
.
get_children
...
...
@@ -525,7 +517,6 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
student
=
student
=
UserFactory
.
create
()
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
for
chapter
in
self
.
course
.
get_children
():
...
...
@@ -674,12 +665,14 @@ class TestStudentDashboardWithCCX(ModuleStoreTestCase):
self
.
ccx
=
CcxFactory
(
course_id
=
self
.
split_course
.
id
,
coach
=
self
.
coach
)
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().
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
):
self
.
client
.
login
(
username
=
self
.
student
.
username
,
password
=
self
.
student_password
)
response
=
self
.
client
.
get
(
reverse
(
'dashboard'
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
re
.
search
(
'Test CCX'
,
response
.
content
))
def
flatten
(
seq
):
...
...
lms/djangoapps/ccx/utils.py
View file @
eafb0755
This diff is collapsed.
Click to expand it.
lms/djangoapps/ccx/views.py
View file @
eafb0755
...
...
@@ -36,22 +36,24 @@ from courseware.module_render import get_module_for_descriptor
from
edxmako.shortcuts
import
render_to_response
from
opaque_keys.edx.keys
import
CourseKey
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.views.api
import
_split_input_list
from
instructor.views.tools
import
get_student_from_identifier
from
instructor.offline_gradecalc
import
student_grades
# pylint: disable=import-error
from
instructor.views.api
import
_split_input_list
# pylint: disable=import-error
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
(
clear_override_for_ccx
,
get_override_for_ccx
,
override_field_for_ccx
,
)
from
.utils
import
(
enroll_email
,
unenroll_email
,
)
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -127,7 +129,7 @@ def dashboard(request, course, ccx=None):
context
[
'schedule'
]
=
json
.
dumps
(
schedule
,
indent
=
4
)
context
[
'save_url'
]
=
reverse
(
'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
(
'ccx_gradebook'
,
kwargs
=
{
'course_id'
:
ccx_locator
})
context
[
'grades_csv_url'
]
=
reverse
(
...
...
@@ -156,7 +158,7 @@ def create_ccx(request, course, ccx=None):
"You cannot create a CCX from a course using a deprecated id. "
"Please create a rerun of this course in the studio to allow "
"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
)
ccx
=
CustomCourseForEdX
(
...
...
@@ -407,15 +409,18 @@ def ccx_invite(request, course, ccx=None):
email
=
user
.
email
try
:
validate_email
(
email
)
course_key
=
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
email_params
=
get_email_params
(
course
,
auto_enroll
)
if
action
==
'Enroll'
:
enroll_email
(
c
cx
,
c
ourse_key
,
email
,
auto_enroll
=
auto_enroll
,
email_students
=
email_students
email_students
=
email_students
,
email_params
=
email_params
)
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
:
log
.
info
(
'Invalid user name or email when trying to invite students:
%
s'
,
email
)
url
=
reverse
(
...
...
@@ -444,20 +449,21 @@ def ccx_student_management(request, course, ccx=None):
else
:
email
=
user
.
email
course_key
=
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
try
:
validate_email
(
email
)
if
action
==
'add'
:
# by decree, no emails sent to students added this way
# 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'
:
unenroll_email
(
c
cx
,
email
,
email_students
=
False
)
unenroll_email
(
c
ourse_key
,
email
,
email_students
=
False
)
except
ValidationError
:
log
.
info
(
'Invalid user name or email when trying to enroll student:
%
s'
,
email
)
url
=
reverse
(
'ccx_coach_dashboard'
,
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
}
kwargs
=
{
'course_id'
:
course_key
}
)
return
redirect
(
url
)
...
...
@@ -496,8 +502,8 @@ def ccx_gradebook(request, course, ccx=None):
prep_course_for_grading
(
course
,
request
)
enrolled_students
=
User
.
objects
.
filter
(
c
cxmembership__ccx
=
ccx
,
c
cxmembership_
_active
=
1
c
ourseenrollment__course_id
=
ccx_key
,
c
ourseenrollment__is
_active
=
1
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
student_info
=
[
...
...
@@ -535,8 +541,8 @@ def ccx_grades_csv(request, course, ccx=None):
prep_course_for_grading
(
course
,
request
)
enrolled_students
=
User
.
objects
.
filter
(
c
cxmembership__ccx
=
ccx
,
c
cxmembership_
_active
=
1
c
ourseenrollment__course_id
=
ccx_key
,
c
ourseenrollment__is
_active
=
1
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
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):
def
has_required_keys
(
module
):
"""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'
):
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
)
return
False
return
True
...
...
lms/djangoapps/mobile_api/users/tests.py
View file @
eafb0755
...
...
@@ -146,9 +146,9 @@ class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin):
@ddt.data
(
(
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
,
None
,
''
,
"string
"
)
(
DEFAULT_START_DATE
,
None
,
None
,
"empty
"
)
)
@ddt.unpack
@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
from
django.core.cache
import
cache
from
xmodule.modulestore.django
import
modulestore
from
courseware.access
import
has_access
from
openedx.core.djangoapps.user_api.models
import
UserPreference
from
student.models
import
anonymous_id_for_user
from
student.models
import
UserProfile
from
lang_pref
import
LANGUAGE_KEY
from
student.roles
import
(
GlobalStaff
,
CourseStaffRole
,
CourseInstructorRole
,
UserBasedRole
)
from
student.roles
import
GlobalStaff
,
CourseStaffRole
,
CourseInstructorRole
class
OpenIDHandler
(
object
):
...
...
@@ -189,31 +190,23 @@ class CourseAccessHandler(object):
return
course_ids
# pylint: disable=missing-docstring
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
# cache is useful since there are calls to different endpoints in close
# succession, for example the id_token and user_info endpoints.
key
=
'-'
.
join
([
str
(
self
.
__class__
),
str
(
user
.
id
),
access_type
])
course_ids
=
cache
.
get
(
key
)
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
):
# 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
]
course_ids
=
[
unicode
(
course
.
id
)
for
course
in
courses
]
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
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
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
UserProfile
from
student.roles
import
CourseStaffRole
,
CourseInstructorRole
,
GlobalStaff
from
student.roles
import
CourseStaffRole
,
CourseInstructorRole
from
student.tests.factories
import
UserFactory
,
UserProfileFactory
from
openedx.core.djangoapps.user_api.preferences.api
import
set_user_preference
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -78,8 +77,7 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
self
.
assertEqual
(
language
,
locale
)
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
(
'staff_courses'
,
claims
)
...
...
@@ -88,15 +86,17 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
def
test_course_staff_courses
(
self
):
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
.
assertNotIn
(
'staff_courses'
,
claims
)
# should not return courses in id_token
def
test_course_instructor_courses
(
self
):
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
.
assertNotIn
(
'instructor_courses'
,
claims
)
# should not return courses in id_token
...
...
@@ -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
(
'staff_courses'
,
claims
)
...
...
@@ -134,11 +133,6 @@ class IDTokenTest(BaseTestMixin, IDTokenTestCase):
class
UserInfoTest
(
BaseTestMixin
,
UserInfoTestCase
):
def
setUp
(
self
):
super
(
UserInfoTest
,
self
)
.
setUp
()
# clear the course ID cache
cache
.
clear
()
def
token_for_scope
(
self
,
scope
):
full_scope
=
'openid
%
s'
%
scope
self
.
set_access_token_scope
(
full_scope
)
...
...
@@ -164,39 +158,19 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
self
.
assertEqual
(
result
.
status_code
,
200
)
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
):
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'
]
self
.
assertIn
(
self
.
course_id
,
courses
)
self
.
assertEqual
(
len
(
courses
),
1
)
def
test_request_instructor_courses_using_scope
(
self
):
CourseInstructorRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
with
check_mongo_calls
(
0
):
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
)
claims
=
self
.
get_with_scope
(
'course_instructor'
)
values
=
[
self
.
course_id
,
'some_invalid_course'
]
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'
]
courses
=
claims
[
'instructor_courses'
]
self
.
assertIn
(
self
.
course_id
,
courses
)
self
.
assertEqual
(
len
(
courses
),
1
)
...
...
@@ -204,8 +178,7 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
CourseStaffRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
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
)
courses
=
claims
[
'staff_courses'
]
...
...
@@ -216,8 +189,7 @@ class UserInfoTest(BaseTestMixin, UserInfoTestCase):
CourseInstructorRole
(
self
.
course_key
)
.
add_users
(
self
.
user
)
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
)
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 @@
<tbody>
%for member in ccx_members:
<tr>
<td>
${member.
student
}
</td>
<td>
${member.
student
.email}
</td>
<td>
${member.
user
}
</td>
<td>
${member.
user
.email}
</td>
<td><div
class=
"revoke"
><i
class=
"fa fa-times-circle"
></i>
Revoke access
</div></td>
</tr>
%endfor
...
...
lms/templates/dashboard.html
View file @
eafb0755
...
...
@@ -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"
/>
% 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>
% else:
<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
from
xmodule.modulestore.django
import
modulestore
from
xmodule_django.models
import
CourseKeyField
,
UsageKeyField
from
ccx_keys.locator
import
CCXLocator
class
CourseOverview
(
TimeStampedModel
):
"""
...
...
@@ -101,17 +103,26 @@ class CourseOverview(TimeStampedModel):
except
ValueError
:
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
(
version
=
cls
.
VERSION
,
id
=
course
.
id
,
_location
=
course
.
location
,
display_name
=
course
.
display_name
,
display_name
=
display_name
,
display_number_with_default
=
course
.
display_number_with_default
,
display_org_with_default
=
course
.
display_org_with_default
,
start
=
course
.
start
,
end
=
course
.
end
,
start
=
start
,
end
=
end
,
advertised_start
=
course
.
advertised_start
,
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):
"display_name"
:
""
,
# Empty display name
"start"
:
LAST_MONTH
,
# Course already ended
"end"
:
LAST_WEEK
,
"advertised_start"
:
''
,
# No advertised start
"advertised_start"
:
None
,
# No advertised start
"pre_requisite_courses"
:
[],
# No pre-requisites
"static_asset_path"
:
""
,
# Empty asset path
"certificates_show_before_end"
:
False
,
...
...
@@ -200,7 +200,7 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
# # Don't set display name
"start"
:
DEFAULT_START_DATE
,
# Default start and end dates
"end"
:
None
,
"advertised_start"
:
''
,
# No advertised start
"advertised_start"
:
None
,
# No advertised start
"pre_requisite_courses"
:
[],
# No pre-requisites
"static_asset_path"
:
None
,
# No asset path
"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
git+https://github.com/edx/rfc6266.git@v0.0.5-edx#egg=rfc6266==0.0.5-edx
# 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/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
...
...
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