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
fa7ed070
Commit
fa7ed070
authored
May 24, 2016
by
Ahsan Ulhaq
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12468 from edx/ahsan/ECOM-4398-Course-Dashboard-Visual-Update
Course Dashboard Visual Update
parents
f7e98062
a8f2de83
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
283 additions
and
203 deletions
+283
-203
common/djangoapps/student/views.py
+0
-35
lms/djangoapps/learner_dashboard/tests/test_programs.py
+25
-6
lms/djangoapps/learner_dashboard/views.py
+3
-2
lms/static/sass/multicourse/_dashboard.scss
+4
-6
lms/templates/dashboard.html
+0
-13
openedx/core/djangoapps/credentials/tests/factories.py
+41
-0
openedx/core/djangoapps/credentials/tests/mixins.py
+52
-91
openedx/core/djangoapps/credentials/tests/test_utils.py
+95
-5
openedx/core/djangoapps/credentials/utils.py
+32
-0
openedx/core/djangoapps/programs/tests/mixins.py
+0
-25
openedx/core/djangoapps/programs/tests/test_utils.py
+30
-6
themes/edx.org/lms/templates/dashboard.html
+1
-14
No files found.
common/djangoapps/student/views.py
View file @
fa7ed070
...
@@ -122,8 +122,6 @@ from eventtracking import tracker
...
@@ -122,8 +122,6 @@ from eventtracking import tracker
# Note that this lives in LMS, so this dependency should be refactored.
# Note that this lives in LMS, so this dependency should be refactored.
from
notification_prefs.views
import
enable_notifications
from
notification_prefs.views
import
enable_notifications
# Note that this lives in openedx, so this dependency should be refactored.
from
openedx.core.djangoapps.credentials.utils
import
get_user_program_credentials
from
openedx.core.djangoapps.credit.email_utils
import
get_credit_provider_display_names
,
make_providers_strings
from
openedx.core.djangoapps.credit.email_utils
import
get_credit_provider_display_names
,
make_providers_strings
from
openedx.core.djangoapps.user_api.preferences
import
api
as
preferences_api
from
openedx.core.djangoapps.user_api.preferences
import
api
as
preferences_api
from
openedx.core.djangoapps.programs.utils
import
get_programs_for_dashboard
,
get_display_category
from
openedx.core.djangoapps.programs.utils
import
get_programs_for_dashboard
,
get_display_category
...
@@ -615,7 +613,6 @@ def dashboard(request):
...
@@ -615,7 +613,6 @@ def dashboard(request):
# This is passed along in the template context to allow rendering of
# This is passed along in the template context to allow rendering of
# program-related information on the dashboard.
# program-related information on the dashboard.
course_programs
=
_get_course_programs
(
user
,
[
enrollment
.
course_id
for
enrollment
in
course_enrollments
])
course_programs
=
_get_course_programs
(
user
,
[
enrollment
.
course_id
for
enrollment
in
course_enrollments
])
xseries_credentials
=
_get_xseries_credentials
(
user
)
# Construct a dictionary of course mode information
# Construct a dictionary of course mode information
# used to render the course list. We re-use the course modes dict
# used to render the course list. We re-use the course modes dict
...
@@ -741,7 +738,6 @@ def dashboard(request):
...
@@ -741,7 +738,6 @@ def dashboard(request):
'nav_hidden'
:
True
,
'nav_hidden'
:
True
,
'course_programs'
:
course_programs
,
'course_programs'
:
course_programs
,
'disable_courseware_js'
:
True
,
'disable_courseware_js'
:
True
,
'xseries_credentials'
:
xseries_credentials
,
'show_program_listing'
:
ProgramsApiConfig
.
current
()
.
show_program_listing
,
'show_program_listing'
:
ProgramsApiConfig
.
current
()
.
show_program_listing
,
}
}
...
@@ -2483,34 +2479,3 @@ def _get_course_programs(user, user_enrolled_courses): # pylint: disable=invali
...
@@ -2483,34 +2479,3 @@ def _get_course_programs(user, user_enrolled_courses): # pylint: disable=invali
log
.
warning
(
'Program structure is invalid, skipping display:
%
r'
,
program
)
log
.
warning
(
'Program structure is invalid, skipping display:
%
r'
,
program
)
return
programs_data
return
programs_data
def
_get_xseries_credentials
(
user
):
"""Return program credentials data required for display on
the learner dashboard.
Given a user, find all programs for which certificates have been earned
and return list of dictionaries of required program data.
Arguments:
user (User): user object for getting programs credentials.
Returns:
list of dict, containing data corresponding to the programs for which
the user has been awarded a credential.
"""
programs_credentials
=
get_user_program_credentials
(
user
)
credentials_data
=
[]
for
program
in
programs_credentials
:
if
program
.
get
(
'category'
)
==
'xseries'
:
try
:
program_data
=
{
'display_name'
:
program
[
'name'
],
'subtitle'
:
program
[
'subtitle'
],
'credential_url'
:
program
[
'credential_url'
],
}
credentials_data
.
append
(
program_data
)
except
KeyError
:
log
.
warning
(
'Program structure is invalid:
%
r'
,
program
)
return
credentials_data
lms/djangoapps/learner_dashboard/tests/test_programs.py
View file @
fa7ed070
...
@@ -14,6 +14,7 @@ from opaque_keys.edx import locator
...
@@ -14,6 +14,7 @@ from opaque_keys.edx import locator
from
provider.constants
import
CONFIDENTIAL
from
provider.constants
import
CONFIDENTIAL
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.credentials.tests
import
factories
as
credentials_factories
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsDataMixin
,
CredentialsApiConfigMixin
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsDataMixin
,
CredentialsApiConfigMixin
from
openedx.core.djangoapps.programs.tests.mixins
import
(
from
openedx.core.djangoapps.programs.tests.mixins
import
(
ProgramsApiConfigMixin
,
ProgramsApiConfigMixin
,
...
@@ -155,18 +156,36 @@ class TestProgramListing(
...
@@ -155,18 +156,36 @@ class TestProgramListing(
'{}?next={}'
.
format
(
reverse
(
'signin_user'
),
self
.
url
)
'{}?next={}'
.
format
(
reverse
(
'signin_user'
),
self
.
url
)
)
)
# TODO: Use a factory to generate this data.
def
_expected_progam_credentials_data
(
self
):
"""
Dry method for getting expected program credentials response data.
"""
return
[
credentials_factories
.
UserCredential
(
id
=
1
,
username
=
'test'
,
credential
=
credentials_factories
.
ProgramCredential
()
),
credentials_factories
.
UserCredential
(
id
=
2
,
username
=
'test'
,
credential
=
credentials_factories
.
ProgramCredential
()
)
]
def
_expected_credentials_data
(
self
):
def
_expected_credentials_data
(
self
):
""" Dry method for getting expected credentials."""
""" Dry method for getting expected credentials."""
program_credentials_data
=
self
.
_expected_progam_credentials_data
()
return
[
return
[
{
{
"display_name"
:
"Test Program A"
,
'display_name'
:
self
.
PROGRAMS_API_RESPONSE
[
'results'
][
0
][
'name'
],
"credential_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-1/"
'subtitle'
:
self
.
PROGRAMS_API_RESPONSE
[
'results'
][
0
][
'subtitle'
],
'credential_url'
:
program_credentials_data
[
0
][
'certificate_url'
]
},
},
{
{
"display_name"
:
"Test Program B"
,
'display_name'
:
self
.
PROGRAMS_API_RESPONSE
[
'results'
][
1
][
'name'
],
"credential_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-2/"
'subtitle'
:
self
.
PROGRAMS_API_RESPONSE
[
'results'
][
1
][
'subtitle'
],
'credential_url'
:
program_credentials_data
[
1
][
'certificate_url'
]
}
}
]
]
...
...
lms/djangoapps/learner_dashboard/views.py
View file @
fa7ed070
...
@@ -7,9 +7,10 @@ from django.views.decorators.http import require_GET
...
@@ -7,9 +7,10 @@ from django.views.decorators.http import require_GET
from
django.http
import
Http404
from
django.http
import
Http404
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
openedx.core.djangoapps.credentials.utils
import
get_programs_credentials
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.utils
import
ProgramProgressMeter
,
get_display_category
from
openedx.core.djangoapps.programs.utils
import
ProgramProgressMeter
,
get_display_category
from
student.views
import
get_course_enrollments
,
_get_xseries_credentials
from
student.views
import
get_course_enrollments
@login_required
@login_required
...
@@ -39,7 +40,7 @@ def view_programs(request):
...
@@ -39,7 +40,7 @@ def view_programs(request):
'xseries_url'
:
marketing_root
if
ProgramsApiConfig
.
current
()
.
show_xseries_ad
else
None
,
'xseries_url'
:
marketing_root
if
ProgramsApiConfig
.
current
()
.
show_xseries_ad
else
None
,
'nav_hidden'
:
True
,
'nav_hidden'
:
True
,
'show_program_listing'
:
show_program_listing
,
'show_program_listing'
:
show_program_listing
,
'credentials'
:
_get_xseries_credentials
(
request
.
user
),
'credentials'
:
get_programs_credentials
(
request
.
user
,
category
=
'xseries'
),
'disable_courseware_js'
:
True
,
'disable_courseware_js'
:
True
,
'uses_pattern_library'
:
True
'uses_pattern_library'
:
True
}
}
...
...
lms/static/sass/multicourse/_dashboard.scss
View file @
fa7ed070
...
@@ -19,9 +19,6 @@
...
@@ -19,9 +19,6 @@
@include
float
(
right
);
@include
float
(
right
);
@include
margin-left
(
flex-gutter
());
@include
margin-left
(
flex-gutter
());
width
:
flex-grid
(
3
);
width
:
flex-grid
(
3
);
margin-top
:
(
$baseline
*
2
);
border-top
:
3px
solid
$blue
;
padding
:
$baseline
0
;
.course-advertise
{
.course-advertise
{
@include
clearfix
();
@include
clearfix
();
...
@@ -36,7 +33,7 @@
...
@@ -36,7 +33,7 @@
}
}
.ad-link
{
.ad-link
{
@include
text-align
(
center
);
@include
text-align
(
center
);
.btn-
find-courses
{
.btn-
neutral
{
padding-bottom
:
12px
;
padding-bottom
:
12px
;
padding-top
:
12px
;
padding-top
:
12px
;
}
}
...
@@ -57,6 +54,9 @@
...
@@ -57,6 +54,9 @@
span
{
span
{
@include
margin-left
(
$baseline
*
0
.25
);
@include
margin-left
(
$baseline
*
0
.25
);
}
}
.icon
{
@include
margin-right
(
$baseline
*
0
.25
);
}
}
}
}
}
}
}
...
@@ -94,8 +94,6 @@
...
@@ -94,8 +94,6 @@
@include
margin-left
(
flex-gutter
());
@include
margin-left
(
flex-gutter
());
width
:
flex-grid
(
3
);
width
:
flex-grid
(
3
);
margin-top
:
(
$baseline
*
2
);
margin-top
:
(
$baseline
*
2
);
border-top
:
3px
solid
$blue
;
padding
:
$baseline
0
;
.user-info
{
.user-info
{
@include
clearfix
();
@include
clearfix
();
...
...
lms/templates/dashboard.html
View file @
fa7ed070
...
@@ -175,19 +175,6 @@ from openedx.core.djangolib.markup import Text, HTML
...
@@ -175,19 +175,6 @@ from openedx.core.djangolib.markup import Text, HTML
</ul>
</ul>
</section>
</section>
</section>
</section>
% if xseries_credentials:
<div
class=
"wrapper-xseries-certificates"
>
<p
class=
"title"
>
${_("XSeries Program Certificates")}
</p>
<p
class=
"copy"
>
${_("You have received a certificate for the following XSeries programs:")}
</p>
<ul>
% for xseries_credential in xseries_credentials:
<li>
<a
class=
"copy"
href=
"${xseries_credential['credential_url']}"
>
${xseries_credential['display_name']}
</a>
</li>
% endfor
</ul>
</div>
% endif
</section>
</section>
</main>
</main>
...
...
openedx/core/djangoapps/credentials/tests/factories.py
0 → 100644
View file @
fa7ed070
"""Factories for generating fake credentials-related data."""
import
factory
from
factory.fuzzy
import
FuzzyText
class
UserCredential
(
factory
.
Factory
):
"""Factory for stubbing user credentials resources from the User Credentials
API (v1).
"""
class
Meta
(
object
):
model
=
dict
id
=
factory
.
Sequence
(
lambda
n
:
n
)
# pylint: disable=invalid-name
username
=
FuzzyText
(
prefix
=
'user_'
)
status
=
'awarded'
uuid
=
FuzzyText
(
prefix
=
'uuid_'
)
certificate_url
=
'http=//credentials.edx.org/credentials/dummy-uuid'
credential
=
{}
class
ProgramCredential
(
factory
.
Factory
):
"""Factory for stubbing program credentials resources from the Program
Credentials API (v1).
"""
class
Meta
(
object
):
model
=
dict
credential_id
=
factory
.
Sequence
(
lambda
n
:
n
)
program_id
=
factory
.
Sequence
(
lambda
n
:
n
)
class
CourseCredential
(
factory
.
Factory
):
"""Factory for stubbing course credentials resources from the Course
Credentials API (v1).
"""
class
Meta
(
object
):
model
=
dict
course_id
=
'edx/test01/2015'
credential_id
=
factory
.
Sequence
(
lambda
n
:
n
)
certificate_type
=
'verified'
openedx/core/djangoapps/credentials/tests/mixins.py
View file @
fa7ed070
...
@@ -4,6 +4,7 @@ import json
...
@@ -4,6 +4,7 @@ import json
import
httpretty
import
httpretty
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.credentials.tests
import
factories
class
CredentialsApiConfigMixin
(
object
):
class
CredentialsApiConfigMixin
(
object
):
...
@@ -33,103 +34,63 @@ class CredentialsDataMixin(object):
...
@@ -33,103 +34,63 @@ class CredentialsDataMixin(object):
CREDENTIALS_API_RESPONSE
=
{
CREDENTIALS_API_RESPONSE
=
{
"next"
:
None
,
"next"
:
None
,
"results"
:
[
"results"
:
[
{
factories
.
UserCredential
(
"id"
:
1
,
id
=
1
,
"username"
:
"test"
,
username
=
'test'
,
"credential"
:
{
credential
=
factories
.
ProgramCredential
(
"credential_id"
:
1
,
program_id
=
1
"program_id"
:
1
)
},
),
"status"
:
"awarded"
,
factories
.
UserCredential
(
"uuid"
:
"dummy-uuid-1"
,
id
=
2
,
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-1/"
username
=
'test'
,
},
credential
=
factories
.
ProgramCredential
(
{
program_id
=
2
"id"
:
2
,
)
"username"
:
"test"
,
),
"credential"
:
{
factories
.
UserCredential
(
"credential_id"
:
2
,
id
=
3
,
"program_id"
:
2
status
=
'revoked'
,
},
username
=
'test'
,
"status"
:
"awarded"
,
credential
=
factories
.
ProgramCredential
()
"uuid"
:
"dummy-uuid-2"
,
),
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-2/"
factories
.
UserCredential
(
},
id
=
4
,
{
username
=
'test'
,
"id"
:
3
,
credential
=
factories
.
CourseCredential
(
"username"
:
"test"
,
certificate_type
=
'honor'
"credential"
:
{
)
"credential_id"
:
3
,
),
"program_id"
:
3
factories
.
UserCredential
(
},
id
=
5
,
"status"
:
"revoked"
,
username
=
'test'
,
"uuid"
:
"dummy-uuid-3"
,
credential
=
factories
.
CourseCredential
(
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-3/"
course_id
=
'edx/test02/2015'
},
)
{
),
"id"
:
4
,
factories
.
UserCredential
(
"username"
:
"test"
,
id
=
6
,
"credential"
:
{
username
=
'test'
,
"course_id"
:
"edx/test01/2015"
,
credential
=
factories
.
CourseCredential
(
"credential_id"
:
4
,
course_id
=
'edx/test02/2015'
"certificate_type"
:
"honor"
)
},
),
"status"
:
"awarded"
,
"uuid"
:
"dummy-uuid-4"
,
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-4/"
},
{
"id"
:
5
,
"username"
:
"test"
,
"credential"
:
{
"course_id"
:
"edx/test02/2015"
,
"credential_id"
:
5
,
"certificate_type"
:
"verified"
},
"status"
:
"awarded"
,
"uuid"
:
"dummy-uuid-5"
,
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-5/"
},
{
"id"
:
6
,
"username"
:
"test"
,
"credential"
:
{
"course_id"
:
"edx/test03/2015"
,
"credential_id"
:
6
,
"certificate_type"
:
"honor"
},
"status"
:
"revoked"
,
"uuid"
:
"dummy-uuid-6"
,
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-6/"
}
]
]
}
}
CREDENTIALS_NEXT_API_RESPONSE
=
{
CREDENTIALS_NEXT_API_RESPONSE
=
{
"next"
:
None
,
"next"
:
None
,
"results"
:
[
"results"
:
[
{
factories
.
UserCredential
(
"id"
:
7
,
id
=
7
,
"username"
:
"test"
,
username
=
'test'
,
"credential"
:
{
credential
=
factories
.
ProgramCredential
()
"credential_id"
:
7
,
),
"program_id"
:
7
factories
.
UserCredential
(
},
id
=
8
,
"status"
:
"awarded"
,
username
=
'test'
,
"uuid"
:
"dummy-uuid-7"
,
credential
=
factories
.
ProgramCredential
()
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-7"
)
},
{
"id"
:
8
,
"username"
:
"test"
,
"credential"
:
{
"credential_id"
:
8
,
"program_id"
:
8
},
"status"
:
"awarded"
,
"uuid"
:
"dummy-uuid-8"
,
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-8/"
}
]
]
}
}
...
...
openedx/core/djangoapps/credentials/tests/test_utils.py
View file @
fa7ed070
...
@@ -3,17 +3,19 @@ import unittest
...
@@ -3,17 +3,19 @@ import unittest
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.core.cache
import
cache
from
django.test
import
TestCase
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
import
httpretty
import
httpretty
from
edx_oauth2_provider.tests.factories
import
ClientFactory
from
edx_oauth2_provider.tests.factories
import
ClientFactory
from
provider.constants
import
CONFIDENTIAL
from
provider.constants
import
CONFIDENTIAL
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsApiConfigMixin
,
CredentialsDataMixin
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsApiConfigMixin
,
CredentialsDataMixin
from
openedx.core.djangoapps.credentials.utils
import
(
from
openedx.core.djangoapps.credentials.utils
import
(
get_user_credentials
,
get_user_program_credentials
get_user_credentials
,
get_user_program_credentials
,
get_programs_credentials
)
)
from
openedx.core.djangoapps.credentials.tests
import
factories
from
openedx.core.djangoapps.programs.tests.mixins
import
ProgramsApiConfigMixin
,
ProgramsDataMixin
from
openedx.core.djangoapps.programs.tests.mixins
import
ProgramsApiConfigMixin
,
ProgramsDataMixin
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangolib.testing.utils
import
CacheIsolationTestCase
from
openedx.core.djangolib.testing.utils
import
CacheIsolationTestCase
...
@@ -39,6 +41,39 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
...
@@ -39,6 +41,39 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
cache
.
clear
()
cache
.
clear
()
def
_expected_progam_credentials_data
(
self
):
"""
Dry method for getting expected program credentials response data.
"""
return
[
factories
.
UserCredential
(
id
=
1
,
username
=
'test'
,
credential
=
factories
.
ProgramCredential
()
),
factories
.
UserCredential
(
id
=
2
,
username
=
'test'
,
credential
=
factories
.
ProgramCredential
()
)
]
def
expected_credentials_display_data
(
self
):
""" Returns expected credentials data to be represented. """
program_credentials_data
=
self
.
_expected_progam_credentials_data
()
return
[
{
'display_name'
:
self
.
PROGRAMS_API_RESPONSE
[
'results'
][
0
][
'name'
],
'subtitle'
:
self
.
PROGRAMS_API_RESPONSE
[
'results'
][
0
][
'subtitle'
],
'credential_url'
:
program_credentials_data
[
0
][
'certificate_url'
]
},
{
'display_name'
:
self
.
PROGRAMS_API_RESPONSE
[
'results'
][
1
][
'name'
],
'subtitle'
:
self
.
PROGRAMS_API_RESPONSE
[
'results'
][
1
][
'subtitle'
],
'credential_url'
:
program_credentials_data
[
1
][
'certificate_url'
]
}
]
@httpretty.activate
@httpretty.activate
def
test_get_user_credentials
(
self
):
def
test_get_user_credentials
(
self
):
"""Verify user credentials data can be retrieve."""
"""Verify user credentials data can be retrieve."""
...
@@ -98,9 +133,10 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
...
@@ -98,9 +133,10 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
self
.
mock_credentials_api
(
self
.
user
,
reset_url
=
False
)
self
.
mock_credentials_api
(
self
.
user
,
reset_url
=
False
)
actual
=
get_user_program_credentials
(
self
.
user
)
actual
=
get_user_program_credentials
(
self
.
user
)
program_credentials_data
=
self
.
_expected_progam_credentials_data
()
expected
=
self
.
PROGRAMS_API_RESPONSE
[
'results'
][:
2
]
expected
=
self
.
PROGRAMS_API_RESPONSE
[
'results'
][:
2
]
expected
[
0
][
'credential_url'
]
=
self
.
PROGRAMS_CREDENTIALS_DATA
[
0
][
'certificate_url'
]
expected
[
0
][
'credential_url'
]
=
program_credentials_data
[
0
][
'certificate_url'
]
expected
[
1
][
'credential_url'
]
=
self
.
PROGRAMS_CREDENTIALS_DATA
[
1
][
'certificate_url'
]
expected
[
1
][
'credential_url'
]
=
program_credentials_data
[
1
][
'certificate_url'
]
# checking response from API is as expected
# checking response from API is as expected
self
.
assertEqual
(
len
(
actual
),
2
)
self
.
assertEqual
(
len
(
actual
),
2
)
...
@@ -125,3 +161,57 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
...
@@ -125,3 +161,57 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
self
.
mock_credentials_api
(
self
.
user
,
data
=
credential_data
)
self
.
mock_credentials_api
(
self
.
user
,
data
=
credential_data
)
actual
=
get_user_program_credentials
(
self
.
user
)
actual
=
get_user_program_credentials
(
self
.
user
)
self
.
assertEqual
(
actual
,
[])
self
.
assertEqual
(
actual
,
[])
@httpretty.activate
def
test_get_programs_credentials
(
self
):
""" Verify that the program credentials data required for display can
be retrieved.
"""
# create credentials and program configuration
self
.
create_credentials_config
()
self
.
create_programs_config
()
# Mocking the API responses from programs and credentials
self
.
mock_programs_api
()
self
.
mock_credentials_api
(
self
.
user
,
reset_url
=
False
)
actual
=
get_programs_credentials
(
self
.
user
,
category
=
'xseries'
)
expected
=
self
.
expected_credentials_display_data
()
# Checking result is as expected
self
.
assertEqual
(
len
(
actual
),
2
)
self
.
assertEqual
(
actual
,
expected
)
@httpretty.activate
def
test_get_programs_credentials_category
(
self
):
""" Verify behaviour when program category is provided."""
# create credentials and program configuration
self
.
create_credentials_config
()
self
.
create_programs_config
()
# Mocking the API responses from programs and credentials
self
.
mock_programs_api
()
self
.
mock_credentials_api
(
self
.
user
,
reset_url
=
False
)
actual
=
get_programs_credentials
(
self
.
user
,
category
=
'dummy_category'
)
expected
=
self
.
expected_credentials_display_data
()
self
.
assertEqual
(
len
(
actual
),
0
)
actual
=
get_programs_credentials
(
self
.
user
,
category
=
'xseries'
)
self
.
assertEqual
(
len
(
actual
),
2
)
self
.
assertEqual
(
actual
,
expected
)
@httpretty.activate
def
test_get_programs_credentials_no_category
(
self
):
""" Verify behaviour when no program category is provided. """
self
.
create_credentials_config
()
self
.
create_programs_config
()
# Mocking the API responses from programs and credentials
self
.
mock_programs_api
()
self
.
mock_credentials_api
(
self
.
user
,
reset_url
=
False
)
actual
=
get_programs_credentials
(
self
.
user
)
expected
=
self
.
expected_credentials_display_data
()
self
.
assertEqual
(
len
(
actual
),
2
)
self
.
assertEqual
(
actual
,
expected
)
openedx/core/djangoapps/credentials/utils.py
View file @
fa7ed070
...
@@ -64,3 +64,35 @@ def get_user_program_credentials(user):
...
@@ -64,3 +64,35 @@ def get_user_program_credentials(user):
programs_credentials_data
=
get_programs_for_credentials
(
user
,
programs_credentials
)
programs_credentials_data
=
get_programs_for_credentials
(
user
,
programs_credentials
)
return
programs_credentials_data
return
programs_credentials_data
def
get_programs_credentials
(
user
,
category
=
None
):
"""Return program credentials data required for display.
Given a user, find all programs for which certificates have been earned
and return list of dictionaries of required program data.
Arguments:
user (User): user object for getting programs credentials.
category(str) : program category for getting credentials.
Returns:
list of dict, containing data corresponding to the programs for which
the user has been awarded a credential.
"""
programs_credentials
=
get_user_program_credentials
(
user
)
credentials_data
=
[]
for
program
in
programs_credentials
:
is_included
=
(
category
is
None
)
or
(
program
.
get
(
'category'
)
==
category
)
if
is_included
:
try
:
program_data
=
{
'display_name'
:
program
[
'name'
],
'subtitle'
:
program
[
'subtitle'
],
'credential_url'
:
program
[
'credential_url'
],
}
credentials_data
.
append
(
program_data
)
except
KeyError
:
log
.
warning
(
'Program structure is invalid:
%
r'
,
program
)
return
credentials_data
openedx/core/djangoapps/programs/tests/mixins.py
View file @
fa7ed070
...
@@ -100,31 +100,6 @@ class ProgramsDataMixin(object):
...
@@ -100,31 +100,6 @@ class ProgramsDataMixin(object):
]
]
}
}
PROGRAMS_CREDENTIALS_DATA
=
[
{
"id"
:
1
,
"username"
:
"test"
,
"credential"
:
{
"credential_id"
:
1
,
"program_id"
:
1
},
"status"
:
"awarded"
,
"uuid"
:
"dummy-uuid-1"
,
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-1/"
},
{
"id"
:
2
,
"username"
:
"test"
,
"credential"
:
{
"credential_id"
:
2
,
"program_id"
:
2
},
"status"
:
"awarded"
,
"uuid"
:
"dummy-uuid-2"
,
"certificate_url"
:
"http://credentials.edx.org/credentials/dummy-uuid-2/"
}
]
def
mock_programs_api
(
self
,
data
=
None
,
status_code
=
200
):
def
mock_programs_api
(
self
,
data
=
None
,
status_code
=
200
):
"""Utility for mocking out Programs API URLs."""
"""Utility for mocking out Programs API URLs."""
self
.
assertTrue
(
httpretty
.
is_enabled
(),
msg
=
'httpretty must be enabled to mock Programs API calls.'
)
self
.
assertTrue
(
httpretty
.
is_enabled
(),
msg
=
'httpretty must be enabled to mock Programs API calls.'
)
...
...
openedx/core/djangoapps/programs/tests/test_utils.py
View file @
fa7ed070
...
@@ -12,7 +12,8 @@ from edx_oauth2_provider.tests.factories import ClientFactory
...
@@ -12,7 +12,8 @@ from edx_oauth2_provider.tests.factories import ClientFactory
from
provider.constants
import
CONFIDENTIAL
from
provider.constants
import
CONFIDENTIAL
from
lms.djangoapps.certificates.api
import
MODES
from
lms.djangoapps.certificates.api
import
MODES
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsApiConfigMixin
from
openedx.core.djangoapps.credentials.tests
import
factories
as
credentials_factories
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsApiConfigMixin
,
CredentialsDataMixin
from
openedx.core.djangoapps.programs
import
utils
from
openedx.core.djangoapps.programs
import
utils
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.tests
import
factories
from
openedx.core.djangoapps.programs.tests
import
factories
...
@@ -26,7 +27,7 @@ UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
...
@@ -26,7 +27,7 @@ UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
@skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
@skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
@attr
(
'shard_2'
)
@attr
(
'shard_2'
)
class
TestProgramRetrieval
(
ProgramsApiConfigMixin
,
ProgramsDataMixin
,
class
TestProgramRetrieval
(
ProgramsApiConfigMixin
,
ProgramsDataMixin
,
CredentialsDataMixin
,
CredentialsApiConfigMixin
,
CacheIsolationTestCase
):
CredentialsApiConfigMixin
,
CacheIsolationTestCase
):
"""Tests covering the retrieval of programs from the Programs service."""
"""Tests covering the retrieval of programs from the Programs service."""
...
@@ -40,6 +41,27 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
...
@@ -40,6 +41,27 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
cache
.
clear
()
cache
.
clear
()
def
_expected_progam_credentials_data
(
self
):
"""
Dry method for getting expected program credentials response data.
"""
return
[
credentials_factories
.
UserCredential
(
id
=
1
,
username
=
'test'
,
credential
=
credentials_factories
.
ProgramCredential
(
program_id
=
1
)
),
credentials_factories
.
UserCredential
(
id
=
2
,
username
=
'test'
,
credential
=
credentials_factories
.
ProgramCredential
(
program_id
=
2
)
)
]
@httpretty.activate
@httpretty.activate
def
test_get_programs
(
self
):
def
test_get_programs
(
self
):
"""Verify programs data can be retrieved."""
"""Verify programs data can be retrieved."""
...
@@ -152,11 +174,12 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
...
@@ -152,11 +174,12 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
"""Verify programs data can be retrieved and parsed correctly for certificates."""
"""Verify programs data can be retrieved and parsed correctly for certificates."""
self
.
create_programs_config
()
self
.
create_programs_config
()
self
.
mock_programs_api
()
self
.
mock_programs_api
()
program_credentials_data
=
self
.
_expected_progam_credentials_data
()
actual
=
utils
.
get_programs_for_credentials
(
self
.
user
,
self
.
PROGRAMS_CREDENTIALS_DATA
)
actual
=
utils
.
get_programs_for_credentials
(
self
.
user
,
program_credentials_data
)
expected
=
self
.
PROGRAMS_API_RESPONSE
[
'results'
][:
2
]
expected
=
self
.
PROGRAMS_API_RESPONSE
[
'results'
][:
2
]
expected
[
0
][
'credential_url'
]
=
self
.
PROGRAMS_CREDENTIALS_DATA
[
0
][
'certificate_url'
]
expected
[
0
][
'credential_url'
]
=
program_credentials_data
[
0
][
'certificate_url'
]
expected
[
1
][
'credential_url'
]
=
self
.
PROGRAMS_CREDENTIALS_DATA
[
1
][
'certificate_url'
]
expected
[
1
][
'credential_url'
]
=
program_credentials_data
[
1
][
'certificate_url'
]
self
.
assertEqual
(
len
(
actual
),
2
)
self
.
assertEqual
(
len
(
actual
),
2
)
self
.
assertEqual
(
actual
,
expected
)
self
.
assertEqual
(
actual
,
expected
)
...
@@ -167,8 +190,9 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
...
@@ -167,8 +190,9 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
self
.
create_programs_config
()
self
.
create_programs_config
()
self
.
create_credentials_config
()
self
.
create_credentials_config
()
self
.
mock_programs_api
(
data
=
{
'results'
:
[]})
self
.
mock_programs_api
(
data
=
{
'results'
:
[]})
program_credentials_data
=
self
.
_expected_progam_credentials_data
()
actual
=
utils
.
get_programs_for_credentials
(
self
.
user
,
self
.
PROGRAMS_CREDENTIALS_DATA
)
actual
=
utils
.
get_programs_for_credentials
(
self
.
user
,
program_credentials_data
)
self
.
assertEqual
(
actual
,
[])
self
.
assertEqual
(
actual
,
[])
@httpretty.activate
@httpretty.activate
...
...
themes/edx.org/lms/templates/dashboard.html
View file @
fa7ed070
...
@@ -163,7 +163,7 @@ from openedx.core.djangolib.markup import Text, HTML
...
@@ -163,7 +163,7 @@ from openedx.core.djangolib.markup import Text, HTML
${_("Browse recently launched courses and see what's new in your favorite subjects.")}
${_("Browse recently launched courses and see what's new in your favorite subjects.")}
</div>
</div>
<div
class=
"ad-link"
>
<div
class=
"ad-link"
>
<a
class=
"btn-
find-courses
"
href=
"${marketing_link('COURSES')}"
>
<a
class=
"btn-
neutral
"
href=
"${marketing_link('COURSES')}"
>
<span
class=
"icon fa fa-search"
aria-hidden=
"true"
></span>
<span
class=
"icon fa fa-search"
aria-hidden=
"true"
></span>
${_("Explore New Courses")}
${_("Explore New Courses")}
</a>
</a>
...
@@ -193,19 +193,6 @@ from openedx.core.djangolib.markup import Text, HTML
...
@@ -193,19 +193,6 @@ from openedx.core.djangolib.markup import Text, HTML
</ul>
</ul>
</section>
</section>
</section>
</section>
% if xseries_credentials:
<div
class=
"wrapper-xseries-certificates"
>
<p
class=
"title"
>
${_("XSeries Program Certificates")}
</p>
<p
class=
"copy"
>
${_("You have received a certificate for the following XSeries programs:")}
</p>
<ul>
% for xseries_credential in xseries_credentials:
<li>
<a
class=
"copy"
href=
"${xseries_credential['credential_url']}"
>
${xseries_credential['display_name']}
</a>
</li>
% endfor
</ul>
</div>
% endif
</section>
</section>
<section
id=
"email-settings-modal"
class=
"modal"
aria-hidden=
"true"
>
<section
id=
"email-settings-modal"
class=
"modal"
aria-hidden=
"true"
>
...
...
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