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
344ff8c1
Commit
344ff8c1
authored
Aug 11, 2015
by
Peter Fogg
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #9267 from edx/release
2015-08-11 release
parents
2b93e246
5b4d0702
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
641 additions
and
198 deletions
+641
-198
common/djangoapps/student/models.py
+6
-0
common/djangoapps/student/views.py
+18
-0
lms/djangoapps/ccx/migrations/0002_convert_memberships.py
+0
-98
lms/djangoapps/ccx/models.py
+47
-0
lms/djangoapps/ccx/tests/factories.py
+11
-0
lms/djangoapps/ccx/tests/test_field_override_performance.py
+11
-10
lms/djangoapps/ccx/tests/test_models.py
+115
-0
lms/djangoapps/ccx/tests/test_utils.py
+0
-0
lms/djangoapps/ccx/tests/test_views.py
+34
-27
lms/djangoapps/ccx/utils.py
+273
-13
lms/djangoapps/ccx/views.py
+21
-27
lms/djangoapps/commerce/api/v0/views.py
+1
-1
lms/djangoapps/courseware/user_state_client.py
+1
-1
lms/djangoapps/courseware/views.py
+9
-4
lms/templates/ccx/_dashboard_ccx_listing.html
+80
-0
lms/templates/ccx/enrollment.html
+2
-2
lms/templates/dashboard.html
+8
-0
openedx/core/djangoapps/content/course_overviews/models.py
+4
-15
No files found.
common/djangoapps/student/models.py
View file @
344ff8c1
...
@@ -1200,6 +1200,12 @@ class CourseEnrollment(models.Model):
...
@@ -1200,6 +1200,12 @@ 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 @
344ff8c1
...
@@ -655,6 +655,13 @@ def dashboard(request):
...
@@ -655,6 +655,13 @@ 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'
]
...
@@ -690,6 +697,7 @@ def dashboard(request):
...
@@ -690,6 +697,7 @@ 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
,
}
}
...
@@ -1896,6 +1904,16 @@ def activate_account(request, key):
...
@@ -1896,6 +1904,16 @@ 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"
,
{
{
...
...
lms/djangoapps/ccx/migrations/0002_convert_memberships.py
deleted
100644 → 0
View file @
2b93e246
# -*- 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
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
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
,
)
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 @
344ff8c1
...
@@ -9,6 +9,7 @@ from django.db import models
...
@@ -9,6 +9,7 @@ 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
...
@@ -95,6 +96,52 @@ class CustomCourseForEdX(models.Model):
...
@@ -95,6 +96,52 @@ 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 @
344ff8c1
...
@@ -5,6 +5,8 @@ from factory import SubFactory
...
@@ -5,6 +5,8 @@ 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
...
@@ -12,3 +14,12 @@ class CcxFactory(DjangoModelFactory): # pylint: disable=missing-docstring
...
@@ -12,3 +14,12 @@ 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 @
344ff8c1
...
@@ -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
from
ccx.tests.factories
import
CcxFactory
,
CcxMembershipFactory
@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
,
view_as_ccx
):
def
setup_course
(
self
,
size
,
enable_ccx
):
"""
"""
Build a gradable course where each node has `size` children.
Build a gradable course where each node has `size` children.
"""
"""
...
@@ -112,17 +112,18 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
...
@@ -112,17 +112,18 @@ 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
,
course_key
self
.
course
.
id
)
)
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.
...
@@ -155,7 +156,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
...
@@ -155,7 +156,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
,
view_as_ccx
)
self
.
setup_course
(
course_width
,
enable_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 @
344ff8c1
...
@@ -5,9 +5,12 @@ from datetime import datetime, timedelta
...
@@ -5,9 +5,12 @@ 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
...
@@ -18,11 +21,123 @@ from xmodule.modulestore.tests.factories import (
...
@@ -18,11 +21,123 @@ 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 @
344ff8c1
This diff is collapsed.
Click to expand it.
lms/djangoapps/ccx/tests/test_views.py
View file @
344ff8c1
...
@@ -20,12 +20,9 @@ from django.utils.timezone import UTC
...
@@ -20,12 +20,9 @@ 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
,
...
@@ -45,10 +42,14 @@ from ccx_keys.locator import CCXLocator
...
@@ -45,10 +42,14 @@ 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
,
)
)
...
@@ -279,7 +280,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -279,7 +280,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
ourseEnrollment
.
objects
.
filter
(
course_id
=
self
.
course
.
id
,
user
=
student
)
.
exists
()
C
cxMembership
.
objects
.
filter
(
ccx
=
ccx
,
student
=
student
)
.
exists
()
)
)
def
test_unenroll_member_student
(
self
):
def
test_unenroll_member_student
(
self
):
...
@@ -287,15 +288,16 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -287,15 +288,16 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
"""
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
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
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'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
)
)
data
=
{
data
=
{
'enrollment-button'
:
'Unenroll'
,
'enrollment-button'
:
'Unenroll'
,
...
@@ -309,6 +311,10 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -309,6 +311,10 @@ 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
...
@@ -316,13 +322,12 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -316,13 +322,12 @@ 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'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
)
)
data
=
{
data
=
{
'enrollment-button'
:
'Enroll'
,
'enrollment-button'
:
'Enroll'
,
...
@@ -337,8 +342,8 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -337,8 +342,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
ourseEnrollmentAllowed
.
objects
.
filter
(
C
cxFutureMembership
.
objects
.
filter
(
c
ourse_id
=
course_key
,
email
=
test_email
c
cx
=
ccx
,
email
=
test_email
)
.
exists
()
)
.
exists
()
)
)
...
@@ -347,16 +352,14 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -347,16 +352,14 @@ 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
ourseEnrollmentAllowed
(
course_id
=
course_key
,
email
=
test_email
)
C
cxFutureMembershipFactory
(
ccx
=
ccx
,
email
=
test_email
)
self
.
assertEqual
(
outbox
,
[])
self
.
assertEqual
(
outbox
,
[])
url
=
reverse
(
url
=
reverse
(
'ccx_invite'
,
'ccx_invite'
,
kwargs
=
{
'course_id'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
)
)
data
=
{
data
=
{
'enrollment-button'
:
'Unenroll'
,
'enrollment-button'
:
'Unenroll'
,
...
@@ -368,9 +371,11 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -368,9 +371,11 @@ 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
ourseEnrollmentAllowed
.
objects
.
filter
(
C
cxFutureMembership
.
objects
.
filter
(
c
ourse_id
=
course_key
,
email
=
test_email
c
cx
=
ccx
,
email
=
test_email
)
.
exists
()
)
.
exists
()
)
)
...
@@ -379,8 +384,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -379,8 +384,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
"""
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
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
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
()
...
@@ -388,7 +392,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -388,7 +392,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
url
=
reverse
(
url
=
reverse
(
'ccx_manage_student'
,
'ccx_manage_student'
,
kwargs
=
{
'course_id'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
)
)
data
=
{
data
=
{
'student-action'
:
'add'
,
'student-action'
:
'add'
,
...
@@ -402,7 +406,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -402,7 +406,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
ourseEnrollment
.
objects
.
filter
(
course_id
=
course_key
,
user
=
student
)
.
exists
()
C
cxMembership
.
objects
.
filter
(
ccx
=
ccx
,
student
=
student
)
.
exists
()
)
)
def
test_manage_remove_single_student
(
self
):
def
test_manage_remove_single_student
(
self
):
...
@@ -410,9 +414,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -410,9 +414,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
"""
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
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
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
,
[])
...
@@ -431,6 +435,10 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -431,6 +435,10 @@ 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
...
@@ -517,6 +525,7 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
...
@@ -517,6 +525,7 @@ 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
():
...
@@ -665,14 +674,12 @@ class TestStudentDashboardWithCCX(ModuleStoreTestCase):
...
@@ -665,14 +674,12 @@ 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().
course_key
=
CCXLocator
.
from_course_locator
(
self
.
split_course
.
id
,
self
.
ccx
.
id
)
CcxMembershipFactory
(
ccx
=
self
.
ccx
,
student
=
self
.
student
,
active
=
True
)
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 @
344ff8c1
This diff is collapsed.
Click to expand it.
lms/djangoapps/ccx/views.py
View file @
344ff8c1
...
@@ -36,24 +36,22 @@ from courseware.module_render import get_module_for_descriptor
...
@@ -36,24 +36,22 @@ 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
# pylint: disable=import-error
from
student.roles
import
CourseCcxCoachRole
from
student.models
import
CourseEnrollment
from
instructor.offline_gradecalc
import
student_grades
# pylint: disable=import-error
from
instructor.offline_gradecalc
import
student_grades
from
instructor.views.api
import
_split_input_list
# pylint: disable=import-error
from
instructor.views.api
import
_split_input_list
from
instructor.views.tools
import
get_student_from_identifier
# pylint: disable=import-error
from
instructor.views.tools
import
get_student_from_identifier
from
instructor.enrollment
import
(
enroll_email
,
unenroll_email
,
get_email_params
,
)
from
.models
import
CustomCourseForEdX
from
.models
import
CustomCourseForEdX
,
CcxMembership
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__
)
...
@@ -129,7 +127,7 @@ def dashboard(request, course, ccx=None):
...
@@ -129,7 +127,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
ourseEnrollment
.
objects
.
filter
(
course_id
=
ccx_locator
)
context
[
'ccx_members'
]
=
C
cxMembership
.
objects
.
filter
(
ccx
=
ccx
)
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
(
...
@@ -158,7 +156,7 @@ def create_ccx(request, course, ccx=None):
...
@@ -158,7 +156,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
(
...
@@ -409,18 +407,15 @@ def ccx_invite(request, course, ccx=None):
...
@@ -409,18 +407,15 @@ 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
ourse_key
,
c
cx
,
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
ourse_key
,
email
,
email_students
=
email_students
,
email_params
=
email_param
s
)
unenroll_email
(
c
cx
,
email
,
email_students
=
email_student
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
(
...
@@ -449,21 +444,20 @@ def ccx_student_management(request, course, ccx=None):
...
@@ -449,21 +444,20 @@ 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
ourse_key
,
email
,
auto_enroll
=
True
,
email_students
=
False
)
enroll_email
(
c
cx
,
email
,
auto_enroll
=
True
,
email_students
=
False
)
elif
action
==
'revoke'
:
elif
action
==
'revoke'
:
unenroll_email
(
c
ourse_key
,
email
,
email_students
=
False
)
unenroll_email
(
c
cx
,
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'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
}
)
)
return
redirect
(
url
)
return
redirect
(
url
)
...
@@ -502,8 +496,8 @@ def ccx_gradebook(request, course, ccx=None):
...
@@ -502,8 +496,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
ourseenrollment__course_id
=
ccx_key
,
c
cxmembership__ccx
=
ccx
,
c
ourseenrollment__is
_active
=
1
c
cxmembership_
_active
=
1
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
student_info
=
[
student_info
=
[
...
@@ -541,8 +535,8 @@ def ccx_grades_csv(request, course, ccx=None):
...
@@ -541,8 +535,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
ourseenrollment__course_id
=
ccx_key
,
c
cxmembership__ccx
=
ccx
,
c
ourseenrollment__is
_active
=
1
c
cxmembership_
_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/commerce/api/v0/views.py
View file @
344ff8c1
...
@@ -111,7 +111,7 @@ class BasketsView(APIView):
...
@@ -111,7 +111,7 @@ class BasketsView(APIView):
# If there are no course modes with SKUs, enroll the user without contacting the external API.
# If there are no course modes with SKUs, enroll the user without contacting the external API.
msg
=
Messages
.
NO_SKU_ENROLLED
.
format
(
enrollment_mode
=
CourseMode
.
HONOR
,
course_id
=
course_id
,
msg
=
Messages
.
NO_SKU_ENROLLED
.
format
(
enrollment_mode
=
CourseMode
.
HONOR
,
course_id
=
course_id
,
username
=
user
.
username
)
username
=
user
.
username
)
log
.
debug
(
msg
)
log
.
info
(
msg
)
self
.
_enroll
(
course_key
,
user
)
self
.
_enroll
(
course_key
,
user
)
self
.
_handle_marketing_opt_in
(
request
,
course_key
,
user
)
self
.
_handle_marketing_opt_in
(
request
,
course_key
,
user
)
return
DetailResponse
(
msg
)
return
DetailResponse
(
msg
)
...
...
lms/djangoapps/courseware/user_state_client.py
View file @
344ff8c1
...
@@ -253,7 +253,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
...
@@ -253,7 +253,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
student_modules
=
self
.
_get_student_modules
(
username
,
block_keys
)
student_modules
=
self
.
_get_student_modules
(
username
,
block_keys
)
for
student_module
,
_
in
student_modules
:
for
student_module
,
_
in
student_modules
:
if
fields
is
None
:
if
fields
is
None
:
student_module
.
state
=
None
student_module
.
state
=
"{}"
else
:
else
:
current_state
=
json
.
loads
(
student_module
.
state
)
current_state
=
json
.
loads
(
student_module
.
state
)
for
field
in
fields
:
for
field
in
fields
:
...
...
lms/djangoapps/courseware/views.py
View file @
344ff8c1
...
@@ -1212,20 +1212,25 @@ def submission_history(request, course_id, student_username, location):
...
@@ -1212,20 +1212,25 @@ def submission_history(request, course_id, student_username, location):
# This is ugly, but until we have a proper submissions API that we can use to provide
# This is ugly, but until we have a proper submissions API that we can use to provide
# the scores instead, it will have to do.
# the scores instead, it will have to do.
scores
=
list
(
StudentModuleHistory
.
objects
.
filter
(
scores
=
list
(
StudentModuleHistory
.
objects
.
filter
(
student_module__module_state_key
=
usage_key
student_module__module_state_key
=
usage_key
,
student_module__student__username
=
student_username
,
student_module__course_id
=
course_key
)
.
order_by
(
'-id'
))
)
.
order_by
(
'-id'
))
if
len
(
scores
)
!=
len
(
history_entries
):
if
len
(
scores
)
!=
len
(
history_entries
):
log
.
warning
(
log
.
warning
(
"Mismatch when fetching scores for student "
"Mismatch when fetching scores for student "
"history for course
%
s, user
%
s, xblock
%
s. "
"history for course
%
s, user
%
s, xblock
%
s. "
"Matching scores by date for display."
,
"
%
d scores were found, and
%
d history entries were found. "
"Matching scores to history entries by date for display."
,
course_id
,
course_id
,
student_username
,
student_username
,
location
location
,
len
(
scores
),
len
(
history_entries
),
)
)
scores_by_date
=
{
scores_by_date
=
{
score
.
modifi
ed
:
score
score
.
creat
ed
:
score
for
score
in
scores
for
score
in
scores
}
}
scores
=
[
scores
=
[
...
...
lms/templates/ccx/_dashboard_ccx_listing.html
0 → 100644
View file @
344ff8c1
<
%
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 @
344ff8c1
...
@@ -63,8 +63,8 @@
...
@@ -63,8 +63,8 @@
<tbody>
<tbody>
%for member in ccx_members:
%for member in ccx_members:
<tr>
<tr>
<td>
${member.
user
}
</td>
<td>
${member.
student
}
</td>
<td>
${member.
user
.email}
</td>
<td>
${member.
student
.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 @
344ff8c1
...
@@ -97,6 +97,14 @@ import json
...
@@ -97,6 +97,14 @@ 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 @
344ff8c1
...
@@ -16,8 +16,6 @@ from xmodule.error_module import ErrorDescriptor
...
@@ -16,8 +16,6 @@ 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
):
"""
"""
...
@@ -103,26 +101,17 @@ class CourseOverview(TimeStampedModel):
...
@@ -103,26 +101,17 @@ 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
=
display_name
,
display_name
=
course
.
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
=
start
,
start
=
course
.
start
,
end
=
end
,
end
=
course
.
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
),
...
...
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