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):
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 @
344ff8c1
...
...
@@ -655,6 +655,13 @@ 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'
]
...
...
@@ -690,6 +697,7 @@ 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
,
}
...
...
@@ -1896,6 +1904,16 @@ 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"
,
{
...
...
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
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
...
...
@@ -95,6 +96,52 @@ 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 @
344ff8c1
...
...
@@ -5,6 +5,8 @@ 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
...
...
@@ -12,3 +14,12 @@ 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 @
344ff8c1
...
...
@@ -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
from
ccx.tests.factories
import
CcxFactory
,
CcxMembershipFactory
@attr
(
'shard_1'
)
...
...
@@ -65,7 +65,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
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.
"""
...
...
@@ -112,17 +112,18 @@ 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
,
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
):
"""
Renders the progress page for the given course.
...
...
@@ -155,7 +156,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
"""
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
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
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
...
...
@@ -18,11 +21,123 @@ 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 @
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
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
,
...
...
@@ -45,10 +42,14 @@ 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
,
)
...
...
@@ -279,7 +280,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
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
):
...
...
@@ -287,15 +288,16 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self
.
make_coach
()
ccx
=
self
.
make_ccx
()
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course_key
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
)
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'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
)
data
=
{
'enrollment-button'
:
'Unenroll'
,
...
...
@@ -309,6 +311,10 @@ 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
...
...
@@ -316,13 +322,12 @@ 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'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
)
data
=
{
'enrollment-button'
:
'Enroll'
,
...
...
@@ -337,8 +342,8 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
len
(
outbox
),
1
)
self
.
assertTrue
(
test_email
in
outbox
[
0
]
.
recipients
())
self
.
assertTrue
(
C
ourseEnrollmentAllowed
.
objects
.
filter
(
c
ourse_id
=
course_key
,
email
=
test_email
C
cxFutureMembership
.
objects
.
filter
(
c
cx
=
ccx
,
email
=
test_email
)
.
exists
()
)
...
...
@@ -347,16 +352,14 @@ 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
ourseEnrollmentAllowed
(
course_id
=
course_key
,
email
=
test_email
)
C
cxFutureMembershipFactory
(
ccx
=
ccx
,
email
=
test_email
)
self
.
assertEqual
(
outbox
,
[])
url
=
reverse
(
'ccx_invite'
,
kwargs
=
{
'course_id'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
)
data
=
{
'enrollment-button'
:
'Unenroll'
,
...
...
@@ -368,9 +371,11 @@ 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
ourseEnrollmentAllowed
.
objects
.
filter
(
c
ourse_id
=
course_key
,
email
=
test_email
C
cxFutureMembership
.
objects
.
filter
(
c
cx
=
ccx
,
email
=
test_email
)
.
exists
()
)
...
...
@@ -379,8 +384,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self
.
make_coach
()
ccx
=
self
.
make_ccx
()
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course_key
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
)
student
=
enrollment
.
user
# no emails have been sent so far
outbox
=
self
.
get_outbox
()
...
...
@@ -388,7 +392,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
url
=
reverse
(
'ccx_manage_student'
,
kwargs
=
{
'course_id'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
}
)
data
=
{
'student-action'
:
'add'
,
...
...
@@ -402,7 +406,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
self
.
assertEqual
(
outbox
,
[])
# a CcxMembership exists for this student
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
):
...
...
@@ -410,9 +414,9 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
self
.
make_coach
()
ccx
=
self
.
make_ccx
()
course_key
=
CCXLocator
.
from_course_locator
(
self
.
course
.
id
,
ccx
.
id
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course_key
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
self
.
course
.
id
)
student
=
enrollment
.
user
CcxMembershipFactory
(
ccx
=
ccx
,
student
=
student
)
# no emails have been sent so far
outbox
=
self
.
get_outbox
()
self
.
assertEqual
(
outbox
,
[])
...
...
@@ -431,6 +435,10 @@ 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
...
...
@@ -517,6 +525,7 @@ 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
():
...
...
@@ -665,14 +674,12 @@ 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().
course_key
=
CCXLocator
.
from_course_locator
(
self
.
split_course
.
id
,
self
.
ccx
.
id
)
CourseEnrollment
.
enroll
(
self
.
student
,
course_key
)
CcxMembershipFactory
(
ccx
=
self
.
ccx
,
student
=
self
.
student
,
active
=
True
)
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 @
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
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
# pylint: disable=import-error
from
student.models
import
CourseEnrollment
from
student.roles
import
CourseCcxCoachRole
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
instructor.offline_gradecalc
import
student_grades
from
instructor.views.api
import
_split_input_list
from
instructor.views.tools
import
get_student_from_identifier
from
.models
import
CustomCourseForEdX
from
.models
import
CustomCourseForEdX
,
CcxMembership
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__
)
...
...
@@ -129,7 +127,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
ourseEnrollment
.
objects
.
filter
(
course_id
=
ccx_locator
)
context
[
'ccx_members'
]
=
C
cxMembership
.
objects
.
filter
(
ccx
=
ccx
)
context
[
'gradebook_url'
]
=
reverse
(
'ccx_gradebook'
,
kwargs
=
{
'course_id'
:
ccx_locator
})
context
[
'grades_csv_url'
]
=
reverse
(
...
...
@@ -158,7 +156,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
(
...
...
@@ -409,18 +407,15 @@ 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
ourse_key
,
c
cx
,
email
,
auto_enroll
=
auto_enroll
,
email_students
=
email_students
,
email_params
=
email_params
email_students
=
email_students
)
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
:
log
.
info
(
'Invalid user name or email when trying to invite students:
%
s'
,
email
)
url
=
reverse
(
...
...
@@ -449,21 +444,20 @@ 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
ourse_key
,
email
,
auto_enroll
=
True
,
email_students
=
False
)
enroll_email
(
c
cx
,
email
,
auto_enroll
=
True
,
email_students
=
False
)
elif
action
==
'revoke'
:
unenroll_email
(
c
ourse_key
,
email
,
email_students
=
False
)
unenroll_email
(
c
cx
,
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'
:
course_key
}
kwargs
=
{
'course_id'
:
CCXLocator
.
from_course_locator
(
course
.
id
,
ccx
.
id
)
}
)
return
redirect
(
url
)
...
...
@@ -502,8 +496,8 @@ def ccx_gradebook(request, course, ccx=None):
prep_course_for_grading
(
course
,
request
)
enrolled_students
=
User
.
objects
.
filter
(
c
ourseenrollment__course_id
=
ccx_key
,
c
ourseenrollment__is
_active
=
1
c
cxmembership__ccx
=
ccx
,
c
cxmembership_
_active
=
1
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
student_info
=
[
...
...
@@ -541,8 +535,8 @@ def ccx_grades_csv(request, course, ccx=None):
prep_course_for_grading
(
course
,
request
)
enrolled_students
=
User
.
objects
.
filter
(
c
ourseenrollment__course_id
=
ccx_key
,
c
ourseenrollment__is
_active
=
1
c
cxmembership__ccx
=
ccx
,
c
cxmembership_
_active
=
1
)
.
order_by
(
'username'
)
.
select_related
(
"profile"
)
grades
=
iterate_grades_for
(
course
,
enrolled_students
)
...
...
lms/djangoapps/commerce/api/v0/views.py
View file @
344ff8c1
...
...
@@ -111,7 +111,7 @@ class BasketsView(APIView):
# 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
,
username
=
user
.
username
)
log
.
debug
(
msg
)
log
.
info
(
msg
)
self
.
_enroll
(
course_key
,
user
)
self
.
_handle_marketing_opt_in
(
request
,
course_key
,
user
)
return
DetailResponse
(
msg
)
...
...
lms/djangoapps/courseware/user_state_client.py
View file @
344ff8c1
...
...
@@ -253,7 +253,7 @@ class DjangoXBlockUserStateClient(XBlockUserStateClient):
student_modules
=
self
.
_get_student_modules
(
username
,
block_keys
)
for
student_module
,
_
in
student_modules
:
if
fields
is
None
:
student_module
.
state
=
None
student_module
.
state
=
"{}"
else
:
current_state
=
json
.
loads
(
student_module
.
state
)
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):
# 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.
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'
))
if
len
(
scores
)
!=
len
(
history_entries
):
log
.
warning
(
"Mismatch when fetching scores for student "
"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
,
student_username
,
location
location
,
len
(
scores
),
len
(
history_entries
),
)
scores_by_date
=
{
score
.
modifi
ed
:
score
score
.
creat
ed
:
score
for
score
in
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 @@
<tbody>
%for member in ccx_members:
<tr>
<td>
${member.
user
}
</td>
<td>
${member.
user
.email}
</td>
<td>
${member.
student
}
</td>
<td>
${member.
student
.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 @
344ff8c1
...
...
@@ -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"
/>
% 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 @
344ff8c1
...
...
@@ -16,8 +16,6 @@ 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
):
"""
...
...
@@ -103,26 +101,17 @@ 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
=
display_name
,
display_name
=
course
.
display_name
,
display_number_with_default
=
course
.
display_number_with_default
,
display_org_with_default
=
course
.
display_org_with_default
,
start
=
start
,
end
=
end
,
start
=
course
.
start
,
end
=
course
.
end
,
advertised_start
=
course
.
advertised_start
,
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