Commit e44e1859 by Afzal Wali

Programs list conditionally added to the context of index and courses page.

parent bde0f7b2
......@@ -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)
......
......@@ -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)
......@@ -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
}
)
......
......@@ -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 sees 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,
......
# -*- 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),
),
]
......@@ -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."""
......
......@@ -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')
......@@ -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
......
......@@ -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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment