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
e44e1859
Commit
e44e1859
authored
Nov 23, 2016
by
Afzal Wali
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Programs list conditionally added to the context of index and courses page.
parent
bde0f7b2
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
242 additions
and
16 deletions
+242
-16
common/djangoapps/student/views.py
+12
-0
lms/djangoapps/branding/tests/test_page.py
+35
-0
lms/djangoapps/courseware/views/views.py
+22
-11
lms/envs/common.py
+5
-1
openedx/core/djangoapps/catalog/migrations/0002_catalogintegration_username.py
+19
-0
openedx/core/djangoapps/catalog/models.py
+10
-0
openedx/core/djangoapps/catalog/tests/factories.py
+11
-0
openedx/core/djangoapps/catalog/tests/test_utils.py
+69
-2
openedx/core/djangoapps/catalog/utils.py
+59
-2
No files found.
common/djangoapps/student/views.py
View file @
e44e1859
...
...
@@ -127,6 +127,7 @@ from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.theming
import
helpers
as
theming_helpers
from
openedx.core.djangoapps.user_api.preferences
import
api
as
preferences_api
from
openedx.core.djangoapps.catalog.utils
import
get_programs_data
log
=
logging
.
getLogger
(
"edx.student"
)
...
...
@@ -173,6 +174,7 @@ def index(request, extra_context=None, user=AnonymousUser()):
if
extra_context
is
None
:
extra_context
=
{}
programs_list
=
[]
courses
=
get_courses
(
user
)
if
configuration_helpers
.
get_value
(
...
...
@@ -206,6 +208,16 @@ def index(request, extra_context=None, user=AnonymousUser()):
# Insert additional context for use in the template
context
.
update
(
extra_context
)
# Getting all the programs from course-catalog service. The programs_list is being added to the context but it's
# not being used currently in lms/templates/index.html. To use this list, you need to create a custom theme that
# overrides index.html. The modifications to index.html to display the programs will be done after the support
# for edx-pattern-library is added.
if
configuration_helpers
.
get_value
(
"DISPLAY_PROGRAMS_ON_MARKETING_PAGES"
,
settings
.
FEATURES
.
get
(
"DISPLAY_PROGRAMS_ON_MARKETING_PAGES"
)):
programs_list
=
get_programs_data
(
user
)
context
[
"programs_list"
]
=
programs_list
return
render_to_response
(
'index.html'
,
context
)
...
...
lms/djangoapps/branding/tests/test_page.py
View file @
e44e1859
...
...
@@ -2,6 +2,7 @@
Tests for branding page
"""
import
mock
import
datetime
from
django.conf
import
settings
...
...
@@ -287,3 +288,37 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
self
.
assertEqual
(
context
[
'courses'
][
0
]
.
id
,
self
.
starting_later
.
id
)
self
.
assertEqual
(
context
[
'courses'
][
1
]
.
id
,
self
.
starting_earlier
.
id
)
self
.
assertEqual
(
context
[
'courses'
][
2
]
.
id
,
self
.
course_with_default_start_date
.
id
)
@attr
(
shard
=
1
)
class
IndexPageProgramsTests
(
ModuleStoreTestCase
):
"""
Tests for Programs List in Marketing Pages.
"""
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'DISPLAY_PROGRAMS_ON_MARKETING_PAGES'
:
False
})
def
test_get_programs_not_called
(
self
):
with
mock
.
patch
(
"student.views.get_programs_data"
)
as
patched_get_programs_data
:
# check the /dashboard
response
=
self
.
client
.
get
(
'/'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
patched_get_programs_data
.
call_count
,
0
)
with
mock
.
patch
(
"courseware.views.views.get_programs_data"
)
as
patched_get_programs_data
:
# check the /courses view
response
=
self
.
client
.
get
(
reverse
(
'branding.views.courses'
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
patched_get_programs_data
.
call_count
,
0
)
@patch.dict
(
'django.conf.settings.FEATURES'
,
{
'DISPLAY_PROGRAMS_ON_MARKETING_PAGES'
:
True
})
def
test_get_programs_called
(
self
):
with
mock
.
patch
(
"student.views.get_programs_data"
)
as
patched_get_programs_data
:
# check the /dashboard
response
=
self
.
client
.
get
(
'/'
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
patched_get_programs_data
.
call_count
,
1
)
with
mock
.
patch
(
"courseware.views.views.get_programs_data"
)
as
patched_get_programs_data
:
# check the /courses view
response
=
self
.
client
.
get
(
reverse
(
'branding.views.courses'
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
patched_get_programs_data
.
call_count
,
1
)
lms/djangoapps/courseware/views/views.py
View file @
e44e1859
...
...
@@ -34,18 +34,22 @@ from opaque_keys.edx.keys import CourseKey, UsageKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
rest_framework
import
status
from
lms.djangoapps.instructor.views.api
import
require_global_staff
from
lms.djangoapps.ccx.utils
import
prep_course_for_grading
from
lms.djangoapps.grades.new.course_grade
import
CourseGradeFactory
from
lms.djangoapps.instructor.enrollment
import
uses_shib
from
lms.djangoapps.verify_student.models
import
SoftwareSecurePhotoVerification
from
lms.djangoapps.ccx.custom_exception
import
CCXLocatorValidationException
from
openedx.core.djangoapps.catalog.utils
import
get_programs_data
import
shoppingcart
import
survey.utils
import
survey.views
from
lms.djangoapps.ccx.utils
import
prep_course_for_grading
from
certificates
import
api
as
certs_api
from
certificates.models
import
CertificateStatuses
from
openedx.core.djangoapps.models.course_details
import
CourseDetails
from
commerce.utils
import
EcommerceService
from
enrollment.api
import
add_enrollment
from
course_modes.models
import
CourseMode
from
lms.djangoapps.grades.new.course_grade
import
CourseGradeFactory
from
courseware.access
import
has_access
,
has_ccx_coach_role
,
_adjust_start_date_for_beta_testers
from
courseware.access_response
import
StartDateError
from
courseware.access_utils
import
in_preview_mode
...
...
@@ -67,8 +71,6 @@ from courseware.models import StudentModule, BaseStudentModuleHistory
from
courseware.url_helpers
import
get_redirect_url
,
get_redirect_url_for_global_staff
from
courseware.user_state_client
import
DjangoXBlockUserStateClient
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
,
marketing_link
from
lms.djangoapps.instructor.enrollment
import
uses_shib
from
lms.djangoapps.verify_student.models
import
SoftwareSecurePhotoVerification
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.coursetalk.helpers
import
inject_coursetalk_keys_into_context
from
openedx.core.djangoapps.credit.api
import
(
...
...
@@ -91,11 +93,9 @@ from xmodule.modulestore.django import modulestore
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
NoPathToItem
from
xmodule.tabs
import
CourseTabList
from
xmodule.x_module
import
STUDENT_VIEW
from
lms.djangoapps.ccx.custom_exception
import
CCXLocatorValidationException
from
..entrance_exams
import
user_must_complete_entrance_exam
from
..module_render
import
get_module_for_descriptor
,
get_module
,
get_module_by_usage_id
log
=
logging
.
getLogger
(
"edx.courseware"
)
...
...
@@ -136,21 +136,32 @@ def courses(request):
Render "find courses" page. The course selection work is done in courseware.courses.
"""
courses_list
=
[]
programs_list
=
[]
course_discovery_meanings
=
getattr
(
settings
,
'COURSE_DISCOVERY_MEANINGS'
,
{})
if
not
settings
.
FEATURES
.
get
(
'ENABLE_COURSE_DISCOVERY'
):
courses_list
=
get_courses
(
request
.
user
)
if
configuration_helpers
.
get_value
(
"ENABLE_COURSE_SORTING_BY_START_DATE"
,
settings
.
FEATURES
[
"ENABLE_COURSE_SORTING_BY_START_DATE"
]
):
if
configuration_helpers
.
get_value
(
"ENABLE_COURSE_SORTING_BY_START_DATE"
,
settings
.
FEATURES
[
"ENABLE_COURSE_SORTING_BY_START_DATE"
]):
courses_list
=
sort_by_start_date
(
courses_list
)
else
:
courses_list
=
sort_by_announcement
(
courses_list
)
# Getting all the programs from course-catalog service. The programs_list is being added to the context but it's
# not being used currently in courseware/courses.html. To use this list, you need to create a custom theme that
# overrides courses.html. The modifications to courses.html to display the programs will be done after the support
# for edx-pattern-library is added.
if
configuration_helpers
.
get_value
(
"DISPLAY_PROGRAMS_ON_MARKETING_PAGES"
,
settings
.
FEATURES
.
get
(
"DISPLAY_PROGRAMS_ON_MARKETING_PAGES"
)):
programs_list
=
get_programs_data
(
request
.
user
)
return
render_to_response
(
"courseware/courses.html"
,
{
'courses'
:
courses_list
,
'course_discovery_meanings'
:
course_discovery_meanings
}
{
'courses'
:
courses_list
,
'course_discovery_meanings'
:
course_discovery_meanings
,
'programs_list'
:
programs_list
}
)
...
...
lms/envs/common.py
View file @
e44e1859
...
...
@@ -249,11 +249,15 @@ FEATURES = {
# False to not redirect the user
'ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER'
:
True
,
# When a user goes to the homepage ('/') the user see the
# When a user goes to the homepage ('/') the user see
s
the
# courses listed in the announcement dates order - this is default Open edX behavior.
# Set to True to change the course sorting behavior by their start dates, latest first.
'ENABLE_COURSE_SORTING_BY_START_DATE'
:
True
,
# When set to True, a list of programs is displayed along with the list of courses
# when the user visits the homepage or the find courses page.
'DISPLAY_PROGRAMS_ON_MARKETING_PAGES'
:
False
,
# Expose Mobile REST API. Note that if you use this, you must also set
# ENABLE_OAUTH2_PROVIDER to True
'ENABLE_MOBILE_REST_API'
:
False
,
...
...
openedx/core/djangoapps/catalog/migrations/0002_catalogintegration_username.py
0 → 100644
View file @
e44e1859
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'catalog'
,
'0001_initial'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'catalogintegration'
,
name
=
'service_username'
,
field
=
models
.
CharField
(
default
=
b
'lms_catalog_service_user'
,
help_text
=
'Username created for Course Catalog Integration, e.g. lms_catalog_service_user.'
,
max_length
=
100
),
),
]
openedx/core/djangoapps/catalog/models.py
View file @
e44e1859
...
...
@@ -25,6 +25,16 @@ class CatalogIntegration(ConfigurationModel):
)
)
service_username
=
models
.
CharField
(
max_length
=
100
,
default
=
"lms_catalog_service_user"
,
null
=
False
,
blank
=
False
,
help_text
=
_
(
'Username created for Course Catalog Integration, e.g. lms_catalog_service_user.'
)
)
@property
def
is_cache_enabled
(
self
):
"""Whether responses from the catalog API will be cached."""
...
...
openedx/core/djangoapps/catalog/tests/factories.py
View file @
e44e1859
...
...
@@ -70,3 +70,14 @@ class Program(factory.Factory):
banner_image
=
{
size
:
BannerImage
()
for
size
in
[
'large'
,
'medium'
,
'small'
,
'x-small'
]
}
class
ProgramType
(
factory
.
Factory
):
"""
Factory for stubbing ProgramType resources from the catalog API.
"""
class
Meta
(
object
):
model
=
dict
name
=
FuzzyText
()
logo_image
=
FuzzyText
(
prefix
=
'https://example.com/program/logo'
)
openedx/core/djangoapps/catalog/tests/test_utils.py
View file @
e44e1859
...
...
@@ -2,6 +2,7 @@
Tests covering utilities for integrating with the catalog service.
"""
import
uuid
import
copy
from
django.core.cache
import
cache
from
django.test
import
TestCase
...
...
@@ -12,9 +13,8 @@ from opaque_keys.edx.keys import CourseKey
from
openedx.core.djangoapps.catalog
import
utils
from
openedx.core.djangoapps.catalog.models
import
CatalogIntegration
from
openedx.core.djangoapps.catalog.tests
import
factories
,
mixins
from
student.tests.factories
import
UserFactory
,
AnonymousUserFactory
from
openedx.core.djangolib.testing.utils
import
CacheIsolationTestCase
from
student.tests.factories
import
UserFactory
UTILS_MODULE
=
'openedx.core.djangoapps.catalog.utils'
...
...
@@ -76,6 +76,73 @@ class TestGetPrograms(mixins.CatalogIntegrationMixin, TestCase):
self
.
assert_contract
(
mock_get_catalog_data
.
call_args
)
self
.
assertEqual
(
data
,
programs
)
def
test_get_programs_anonymous_user
(
self
,
_mock_cache
,
mock_get_catalog_data
):
programs
=
[
factories
.
Program
()
for
__
in
range
(
3
)]
mock_get_catalog_data
.
return_value
=
programs
anonymous_user
=
AnonymousUserFactory
()
# The user is an Anonymous user but the Catalog Service User has not been created yet.
data
=
utils
.
get_programs
(
anonymous_user
)
# This should not return programs.
self
.
assertEqual
(
data
,
[])
UserFactory
(
username
=
'lms_catalog_service_user'
)
# After creating the service user above,
data
=
utils
.
get_programs
(
anonymous_user
)
# the programs should be returned successfully.
self
.
assertEqual
(
data
,
programs
)
def
test_get_program_types
(
self
,
_mock_cache
,
mock_get_catalog_data
):
program_types
=
[
factories
.
ProgramType
()
for
__
in
range
(
3
)]
mock_get_catalog_data
.
return_value
=
program_types
# Creating Anonymous user but the Catalog Service User has not been created yet.
anonymous_user
=
AnonymousUserFactory
()
data
=
utils
.
get_program_types
(
anonymous_user
)
# This should not return programs.
self
.
assertEqual
(
data
,
[])
# Creating Catalog Service User user
UserFactory
(
username
=
'lms_catalog_service_user'
)
data
=
utils
.
get_program_types
(
anonymous_user
)
# the programs should be returned successfully.
self
.
assertEqual
(
data
,
program_types
)
# Catalog integration is disabled now.
self
.
catalog_integration
=
self
.
create_catalog_integration
(
enabled
=
False
)
data
=
utils
.
get_program_types
(
anonymous_user
)
# This should not return programs.
self
.
assertEqual
(
data
,
[])
def
test_get_programs_data
(
self
,
_mock_cache
,
mock_get_catalog_data
):
# pylint: disable=unused-argument
programs
=
[]
program_types
=
[]
programs_data
=
[]
for
index
in
range
(
3
):
# Creating the Programs and their corresponding program types.
type_name
=
"type_name_{postfix}"
.
format
(
postfix
=
index
)
program
=
factories
.
Program
(
type
=
type_name
)
program_type
=
factories
.
ProgramType
(
name
=
type_name
)
# Maintaining the programs, program types and program data(program+logo_image) lists.
programs
.
append
(
program
)
program_types
.
append
(
program_type
)
programs_data
.
append
(
copy
.
deepcopy
(
program
))
# Adding the logo image in program data.
programs_data
[
-
1
][
'logo_image'
]
=
program_type
[
"logo_image"
]
with
mock
.
patch
(
"openedx.core.djangoapps.catalog.utils.get_programs"
)
as
patched_get_programs
:
with
mock
.
patch
(
"openedx.core.djangoapps.catalog.utils.get_program_types"
)
as
patched_get_program_types
:
# Mocked the "get_programs" and "get_program_types"
patched_get_programs
.
return_value
=
programs
patched_get_program_types
.
return_value
=
program_types
programs_data
=
utils
.
get_programs_data
()
self
.
assertEqual
(
programs_data
,
programs
)
def
test_get_one_program
(
self
,
_mock_cache
,
mock_get_catalog_data
):
program
=
factories
.
Program
()
mock_get_catalog_data
.
return_value
=
program
...
...
openedx/core/djangoapps/catalog/utils.py
View file @
e44e1859
...
...
@@ -4,6 +4,7 @@ import logging
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.contrib.auth.models
import
User
from
edx_rest_api_client.client
import
EdxRestApiClient
from
opaque_keys.edx.keys
import
CourseKey
...
...
@@ -24,7 +25,20 @@ def create_catalog_api_client(user, catalog_integration):
return
EdxRestApiClient
(
catalog_integration
.
internal_api_url
,
jwt
=
jwt
)
def
get_programs
(
user
,
uuid
=
None
,
type
=
None
):
# pylint: disable=redefined-builtin
def
_get_service_user
(
user
,
service_username
):
"""
Retrieve and return the Catalog Integration Service User Object
if the passed user is None or anonymous
"""
if
not
user
or
user
.
is_anonymous
():
try
:
user
=
User
.
objects
.
get
(
username
=
service_username
)
except
User
.
DoesNotExist
:
user
=
None
return
user
def
get_programs
(
user
=
None
,
uuid
=
None
,
type
=
None
):
# pylint: disable=redefined-builtin
"""Retrieve marketable programs from the catalog service.
Keyword Arguments:
...
...
@@ -36,8 +50,11 @@ def get_programs(user, uuid=None, type=None): # pylint: disable=redefined-built
dict, if a specific program is requested.
"""
catalog_integration
=
CatalogIntegration
.
current
()
if
catalog_integration
.
enabled
:
user
=
_get_service_user
(
user
,
catalog_integration
.
service_username
)
if
not
user
:
return
[]
api
=
create_catalog_api_client
(
user
,
catalog_integration
)
cache_key
=
'{base}.programs{type}'
.
format
(
...
...
@@ -66,6 +83,46 @@ def get_programs(user, uuid=None, type=None): # pylint: disable=redefined-built
return
[]
def
get_program_types
(
user
=
None
):
# pylint: disable=redefined-builtin
"""Retrieve all program types from the catalog service.
Returns:
list of dict, representing program types.
"""
catalog_integration
=
CatalogIntegration
.
current
()
if
catalog_integration
.
enabled
:
user
=
_get_service_user
(
user
,
catalog_integration
.
service_username
)
if
not
user
:
return
[]
api
=
create_catalog_api_client
(
user
,
catalog_integration
)
cache_key
=
'{base}.program_types'
.
format
(
base
=
catalog_integration
.
CACHE_KEY
)
return
get_edx_api_data
(
catalog_integration
,
user
,
'program_types'
,
cache_key
=
cache_key
if
catalog_integration
.
is_cache_enabled
else
None
,
api
=
api
)
else
:
return
[]
def
get_programs_data
(
user
=
None
):
"""Return the list of Programs after adding the ProgramType Logo Image"""
programs_list
=
get_programs
(
user
)
program_types
=
get_program_types
(
user
)
program_types_lookup_dict
=
{
program_type
[
"name"
]:
program_type
for
program_type
in
program_types
}
for
program
in
programs_list
:
program
[
"logo_image"
]
=
program_types_lookup_dict
[
program
[
"type"
]][
"logo_image"
]
return
programs_list
def
munge_catalog_program
(
catalog_program
):
"""Make a program from the catalog service look like it came from the programs service.
...
...
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