Commit 58f0154e by Douglas Hall

Add support for multi-org sites

WL-926
parent 66267257
...@@ -286,14 +286,14 @@ def reverification_info(statuses): ...@@ -286,14 +286,14 @@ def reverification_info(statuses):
return reverifications return reverifications
def get_course_enrollments(user, org_to_include, orgs_to_exclude): def get_course_enrollments(user, orgs_to_include, orgs_to_exclude):
""" """
Given a user, return a filtered set of his or her course enrollments. Given a user, return a filtered set of his or her course enrollments.
Arguments: Arguments:
user (User): the user in question. user (User): the user in question.
org_to_include (str): If not None, ONLY courses of this org will be returned. orgs_to_include (list[str]): If not None, ONLY courses of these orgs will be returned.
orgs_to_exclude (list[str]): If org_to_include is not None, this orgs_to_exclude (list[str]): If orgs_to_include is not None, this
argument is ignored. Else, courses of this org will be excluded. argument is ignored. Else, courses of this org will be excluded.
Returns: Returns:
...@@ -312,8 +312,8 @@ def get_course_enrollments(user, org_to_include, orgs_to_exclude): ...@@ -312,8 +312,8 @@ def get_course_enrollments(user, org_to_include, orgs_to_exclude):
) )
continue continue
# Filter out anything that is not attributed to the current ORG. # Filter out anything that is not attributed to the orgs to include.
if org_to_include and course_overview.location.org != org_to_include: if orgs_to_include and course_overview.location.org not in orgs_to_include:
continue continue
# Conversely, filter out any enrollments with courses attributed to current ORG. # Conversely, filter out any enrollments with courses attributed to current ORG.
...@@ -600,17 +600,16 @@ def dashboard(request): ...@@ -600,17 +600,16 @@ def dashboard(request):
settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True) settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True)
) )
# we want to filter and only show enrollments for courses within
# the 'ORG' defined in configuration.
course_org_filter = configuration_helpers.get_value('course_org_filter')
# Let's filter out any courses in an "org" that has been declared to be # Let's filter out any courses in an "org" that has been declared to be
# in a configuration # in a configuration
org_filter_out_set = configuration_helpers.get_all_orgs() org_filter_out_set = configuration_helpers.get_all_orgs()
# remove our current org from the "filter out" list, if applicable # Remove current site orgs from the "filter out" list, if applicable.
# We want to filter and only show enrollments for courses within
# the organizations defined in configuration for the current site.
course_org_filter = configuration_helpers.get_current_site_orgs()
if course_org_filter: if course_org_filter:
org_filter_out_set.remove(course_org_filter) org_filter_out_set = org_filter_out_set - set(course_org_filter)
# Build our (course, enrollment) list for the user, but ignore any courses that no # Build our (course, enrollment) list for the user, but ignore any courses that no
# longer exist (because the course IDs have changed). Still, we don't delete those # longer exist (because the course IDs have changed). Still, we don't delete those
......
...@@ -295,11 +295,11 @@ def _record_feedback_in_zendesk( ...@@ -295,11 +295,11 @@ def _record_feedback_in_zendesk(
# Tag all issues with LMS to distinguish channel in Zendesk; requested by student support team # Tag all issues with LMS to distinguish channel in Zendesk; requested by student support team
zendesk_tags = list(tags.values()) + ["LMS"] zendesk_tags = list(tags.values()) + ["LMS"]
# Per edX support, we would like to be able to route white label feedback items # Per edX support, we would like to be able to route feedback items by site via tagging
# via tagging current_site_orgs = configuration_helpers.get_current_site_orgs()
white_label_org = configuration_helpers.get_value('course_org_filter') if current_site_orgs:
if white_label_org: for org in current_site_orgs:
zendesk_tags = zendesk_tags + ["whitelabel_{org}".format(org=white_label_org)] zendesk_tags.append("whitelabel_{org}".format(org=org))
new_ticket = { new_ticket = {
"ticket": { "ticket": {
......
...@@ -27,25 +27,23 @@ def get_visible_courses(org=None, filter_=None): ...@@ -27,25 +27,23 @@ def get_visible_courses(org=None, filter_=None):
filter_ (dict): Optional parameter that allows custom filtering by filter_ (dict): Optional parameter that allows custom filtering by
fields on the course. fields on the course.
""" """
current_site_org = configuration_helpers.get_value('course_org_filter') courses = []
current_site_orgs = configuration_helpers.get_current_site_orgs()
if org and current_site_org: if org:
# Return an empty result if the org passed by the caller does not match the designated site org. # Check the current site's orgs to make sure the org's courses should be displayed
courses = CourseOverview.get_all_courses( if not current_site_orgs or org in current_site_orgs:
org=org, courses = CourseOverview.get_all_courses(orgs=[org], filter_=filter_)
filter_=filter_, elif current_site_orgs:
) if org == current_site_org else [] # Only display courses that should be displayed on this site
courses = CourseOverview.get_all_courses(orgs=current_site_orgs, filter_=filter_)
else: else:
# We only make it to this point if one of org or current_site_org is defined. courses = CourseOverview.get_all_courses(filter_=filter_)
# If both org and current_site_org were defined, the code would have fallen into the
# first branch of the conditional above, wherein an equality check is performed.
target_org = org or current_site_org
courses = CourseOverview.get_all_courses(org=target_org, filter_=filter_)
courses = sorted(courses, key=lambda course: course.number) courses = sorted(courses, key=lambda course: course.number)
# Filtering can stop here. # Filtering can stop here.
if current_site_org: if current_site_orgs:
return courses return courses
# See if we have filtered course listings in this domain # See if we have filtered course listings in this domain
......
...@@ -14,7 +14,6 @@ from django.contrib.staticfiles.storage import staticfiles_storage ...@@ -14,7 +14,6 @@ from django.contrib.staticfiles.storage import staticfiles_storage
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
import student.views import student.views
from student.models import CourseEnrollment
import courseware.views.views import courseware.views.views
from edxmako.shortcuts import marketing_link from edxmako.shortcuts import marketing_link
from util.cache import cache_if_anonymous from util.cache import cache_if_anonymous
...@@ -25,24 +24,6 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ ...@@ -25,24 +24,6 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def get_course_enrollments(user):
"""
Returns the course enrollments for the passed in user within the context of current org, that
is filtered by course_org_filter
"""
enrollments = CourseEnrollment.enrollments_for_user(user)
course_org = configuration_helpers.get_value('course_org_filter')
if course_org:
site_enrollments = [
enrollment for enrollment in enrollments if enrollment.course_id.org == course_org
]
else:
site_enrollments = [
enrollment for enrollment in enrollments
]
return site_enrollments
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_if_anonymous() @cache_if_anonymous()
def index(request): def index(request):
......
...@@ -11,8 +11,8 @@ def order_history(user, **kwargs): ...@@ -11,8 +11,8 @@ def order_history(user, **kwargs):
Returns the list of previously purchased orders for a user. Only the orders with Returns the list of previously purchased orders for a user. Only the orders with
PaidCourseRegistration and CourseRegCodeItem are returned. PaidCourseRegistration and CourseRegCodeItem are returned.
Params: Params:
course_org_filter: Current Microsite's ORG. course_org_filter: A list of the current Site's orgs.
org_filter_out_set: A list of all other Microsites' ORGs. org_filter_out_set: A list of all other Sites' orgs.
""" """
course_org_filter = kwargs['course_org_filter'] if 'course_org_filter' in kwargs else None course_org_filter = kwargs['course_org_filter'] if 'course_org_filter' in kwargs else None
org_filter_out_set = kwargs['org_filter_out_set'] if 'org_filter_out_set' in kwargs else [] org_filter_out_set = kwargs['org_filter_out_set'] if 'org_filter_out_set' in kwargs else []
...@@ -27,7 +27,7 @@ def order_history(user, **kwargs): ...@@ -27,7 +27,7 @@ def order_history(user, **kwargs):
# not attributed (by ORG) to any Microsite. # not attributed (by ORG) to any Microsite.
order_item_course_id = getattr(order_item, 'course_id', None) order_item_course_id = getattr(order_item, 'course_id', None)
if order_item_course_id: if order_item_course_id:
if (course_org_filter and course_org_filter == order_item_course_id.org) or \ if (course_org_filter and order_item_course_id.org in course_org_filter) or \
(course_org_filter is None and order_item_course_id.org not in org_filter_out_set): (course_org_filter is None and order_item_course_id.org not in org_filter_out_set):
order_history_list.append({ order_history_list.append({
'order_id': order_item.order.id, 'order_id': order_item.order.id,
......
...@@ -34,7 +34,7 @@ class LmsSearchFilterGenerator(SearchFilterGenerator): ...@@ -34,7 +34,7 @@ class LmsSearchFilterGenerator(SearchFilterGenerator):
field_dictionary['course'] = [unicode(enrollment.course_id) for enrollment in user_enrollments] field_dictionary['course'] = [unicode(enrollment.course_id) for enrollment in user_enrollments]
# if we have an org filter, only include results for this org filter # if we have an org filter, only include results for this org filter
course_org_filter = configuration_helpers.get_value('course_org_filter') course_org_filter = configuration_helpers.get_current_site_orgs()
if course_org_filter: if course_org_filter:
field_dictionary['org'] = course_org_filter field_dictionary['org'] = course_org_filter
...@@ -45,7 +45,7 @@ class LmsSearchFilterGenerator(SearchFilterGenerator): ...@@ -45,7 +45,7 @@ class LmsSearchFilterGenerator(SearchFilterGenerator):
Exclude any courses defined outside the current org. Exclude any courses defined outside the current org.
""" """
exclude_dictionary = super(LmsSearchFilterGenerator, self).exclude_dictionary(**kwargs) exclude_dictionary = super(LmsSearchFilterGenerator, self).exclude_dictionary(**kwargs)
course_org_filter = configuration_helpers.get_value('course_org_filter') course_org_filter = configuration_helpers.get_current_site_orgs()
# If we have a course filter we are ensuring that we only get those courses above # If we have a course filter we are ensuring that we only get those courses above
if not course_org_filter: if not course_org_filter:
org_filter_out_set = configuration_helpers.get_all_orgs() org_filter_out_set = configuration_helpers.get_all_orgs()
......
...@@ -109,7 +109,7 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase): ...@@ -109,7 +109,7 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
field_dictionary, _, exclude_dictionary = LmsSearchFilterGenerator.generate_field_filters(user=self.user) field_dictionary, _, exclude_dictionary = LmsSearchFilterGenerator.generate_field_filters(user=self.user)
self.assertNotIn('org', exclude_dictionary) self.assertNotIn('org', exclude_dictionary)
self.assertIn('org', field_dictionary) self.assertIn('org', field_dictionary)
self.assertEqual('TestSiteX', field_dictionary['org']) self.assertEqual(['TestSiteX'], field_dictionary['org'])
@patch( @patch(
'openedx.core.djangoapps.site_configuration.helpers.get_all_orgs', 'openedx.core.djangoapps.site_configuration.helpers.get_all_orgs',
...@@ -134,4 +134,4 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase): ...@@ -134,4 +134,4 @@ class LmsSearchFilterGeneratorTestCase(ModuleStoreTestCase):
field_dictionary, _, exclude_dictionary = LmsSearchFilterGenerator.generate_field_filters(user=self.user) field_dictionary, _, exclude_dictionary = LmsSearchFilterGenerator.generate_field_filters(user=self.user)
self.assertNotIn('org', exclude_dictionary) self.assertNotIn('org', exclude_dictionary)
self.assertIn('org', field_dictionary) self.assertIn('org', field_dictionary)
self.assertEqual('TestSite3', field_dictionary['org']) self.assertEqual(['TestSite3'], field_dictionary['org'])
...@@ -446,12 +446,12 @@ class CourseOverview(TimeStampedModel): ...@@ -446,12 +446,12 @@ class CourseOverview(TimeStampedModel):
return course_overviews return course_overviews
@classmethod @classmethod
def get_all_courses(cls, org=None, filter_=None): def get_all_courses(cls, orgs=None, filter_=None):
""" """
Returns all CourseOverview objects in the database. Returns all CourseOverview objects in the database.
Arguments: Arguments:
org (string): Optional parameter that allows case-insensitive orgs (list[string]): Optional parameter that allows case-insensitive
filtering by organization. filtering by organization.
filter_ (dict): Optional parameter that allows custom filtering. filter_ (dict): Optional parameter that allows custom filtering.
""" """
...@@ -460,11 +460,11 @@ class CourseOverview(TimeStampedModel): ...@@ -460,11 +460,11 @@ class CourseOverview(TimeStampedModel):
# created. For tests using CourseFactory, use emit_signals=True. # created. For tests using CourseFactory, use emit_signals=True.
course_overviews = CourseOverview.objects.all() course_overviews = CourseOverview.objects.all()
if org: if orgs:
# In rare cases, courses belonging to the same org may be accidentally assigned # In rare cases, courses belonging to the same org may be accidentally assigned
# an org code with a different casing (e.g., Harvardx as opposed to HarvardX). # an org code with a different casing (e.g., Harvardx as opposed to HarvardX).
# Case-insensitive exact matching allows us to deal with this kind of dirty data. # Case-insensitive matching allows us to deal with this kind of dirty data.
course_overviews = course_overviews.filter(org__iexact=org) course_overviews = course_overviews.filter(org__iregex=r'(' + '|'.join(orgs) + ')')
if filter_: if filter_:
course_overviews = course_overviews.filter(**filter_) course_overviews = course_overviews.filter(**filter_)
......
...@@ -470,7 +470,7 @@ class CourseOverviewTestCase(ModuleStoreTestCase): ...@@ -470,7 +470,7 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
def test_get_all_courses_by_org(self): def test_get_all_courses_by_org(self):
org_courses = [] # list of lists of courses org_courses = [] # list of lists of courses
for index in range(2): for index in range(3):
org_courses.append([ org_courses.append([
CourseFactory.create(org='test_org_' + unicode(index), emit_signals=True) CourseFactory.create(org='test_org_' + unicode(index), emit_signals=True)
for __ in range(3) for __ in range(3)
...@@ -478,18 +478,18 @@ class CourseOverviewTestCase(ModuleStoreTestCase): ...@@ -478,18 +478,18 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
self.assertEqual( self.assertEqual(
{c.id for c in CourseOverview.get_all_courses()}, {c.id for c in CourseOverview.get_all_courses()},
{c.id for c in org_courses[0] + org_courses[1]}, {c.id for c in org_courses[0] + org_courses[1] + org_courses[2]},
) )
self.assertEqual( self.assertEqual(
{c.id for c in CourseOverview.get_all_courses(org='test_org_1')}, {c.id for c in CourseOverview.get_all_courses(orgs=['test_org_1', 'test_org_2'])},
{c.id for c in org_courses[1]}, {c.id for c in org_courses[1] + org_courses[2]},
) )
# Test case-insensitivity. # Test case-insensitivity.
self.assertEqual( self.assertEqual(
{c.id for c in CourseOverview.get_all_courses(org='TEST_ORG_1')}, {c.id for c in CourseOverview.get_all_courses(orgs=['TEST_ORG_1', 'TEST_ORG_2'])},
{c.id for c in org_courses[1]}, {c.id for c in org_courses[1] + org_courses[2]},
) )
def test_get_all_courses_by_mobile_available(self): def test_get_all_courses_by_mobile_available(self):
......
...@@ -188,6 +188,21 @@ def get_value_for_org(org, val_name, default=None): ...@@ -188,6 +188,21 @@ def get_value_for_org(org, val_name, default=None):
return microsite.get_value_for_org(org, val_name, default) return microsite.get_value_for_org(org, val_name, default)
def get_current_site_orgs():
"""
This returns the orgs configured in site configuration or microsite configuration for the current site.
Returns:
list: A list of organization names.
"""
course_org_filter = get_value('course_org_filter')
# Make sure we have a list
if course_org_filter and not isinstance(course_org_filter, list):
course_org_filter = [course_org_filter]
return course_org_filter
def get_all_orgs(): def get_all_orgs():
""" """
This returns all of the orgs that are considered in site configurations or microsite configuration, This returns all of the orgs that are considered in site configurations or microsite configuration,
......
...@@ -77,8 +77,8 @@ class SiteConfiguration(models.Model): ...@@ -77,8 +77,8 @@ class SiteConfiguration(models.Model):
Configuration value for the given key. Configuration value for the given key.
""" """
for configuration in cls.objects.filter(values__contains=org, enabled=True).all(): for configuration in cls.objects.filter(values__contains=org, enabled=True).all():
org_filter = configuration.get_value('course_org_filter', None) course_org_filter = configuration.get_value('course_org_filter', None)
if org_filter == org: if org in course_org_filter:
return configuration.get_value(name, default) return configuration.get_value(name, default)
return default return default
...@@ -94,9 +94,10 @@ class SiteConfiguration(models.Model): ...@@ -94,9 +94,10 @@ class SiteConfiguration(models.Model):
org_filter_set = set() org_filter_set = set()
for configuration in cls.objects.filter(values__contains='course_org_filter', enabled=True).all(): for configuration in cls.objects.filter(values__contains='course_org_filter', enabled=True).all():
org_filter = configuration.get_value('course_org_filter', None) course_org_filter = configuration.get_value('course_org_filter', [])
if org_filter: if not isinstance(course_org_filter, list):
org_filter_set.add(org_filter) course_org_filter = [course_org_filter]
org_filter_set.update(course_org_filter)
return org_filter_set return org_filter_set
@classmethod @classmethod
......
...@@ -36,6 +36,10 @@ test_config = { # pylint: disable=invalid-name ...@@ -36,6 +36,10 @@ test_config = { # pylint: disable=invalid-name
}, },
} }
test_config_multi_org = { # pylint: disable=invalid-name
"course_org_filter": ["FooOrg", "BarOrg", "FooBarOrg"]
}
class TestHelpers(TestCase): class TestHelpers(TestCase):
""" """
...@@ -189,3 +193,11 @@ class TestHelpers(TestCase): ...@@ -189,3 +193,11 @@ class TestHelpers(TestCase):
list(configuration_helpers.get_all_orgs()), list(configuration_helpers.get_all_orgs()),
test_orgs, test_orgs,
) )
@with_site_configuration(configuration=test_config_multi_org)
def test_get_current_site_orgs(self):
test_orgs = test_config_multi_org['course_org_filter']
self.assertItemsEqual(
list(configuration_helpers.get_current_site_orgs()),
test_orgs
)
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