Commit 52238620 by Malik Shahzad Committed by GitHub

Merge pull request #874 from edx/malikshahzad228/SOL-1926

SOL-1926: Add organization filter on courses.
parents dd39bf49 9db2ddc4
......@@ -46,6 +46,12 @@ class Command(BaseCommand):
type=str,
default='',
help='Partner name to select/create and associate with site.')
parser.add_argument('--partner-orgs',
action='store',
dest='partner_orgs',
type=str,
default='',
help='Organization filter for partner course listings.')
parser.add_argument('--lms-url-root',
action='store',
dest='lms_url_root',
......@@ -101,6 +107,7 @@ class Command(BaseCommand):
site_name = options.get('site_name')
partner_code = options.get('partner_code')
partner_name = options.get('partner_name')
partner_orgs = options.get('partner_orgs')
lms_url_root = options.get('lms_url_root')
client_id = options.get('client_id')
client_secret = options.get('client_secret')
......@@ -121,11 +128,11 @@ class Command(BaseCommand):
site.save()
partner, partner_created = Partner.objects.get_or_create(code=partner_code)
if partner_created:
partner.name = partner_name
partner.short_code = partner_code
partner.save()
logger.info('Partner created with code %s', partner_code)
partner.name = partner_name
partner.short_code = partner_code
partner.organization_list = partner_orgs
partner.save()
logger.info('Partner %s with code %s', 'created' if partner_created else 'updated', partner_code)
SiteConfiguration.objects.update_or_create(
site=site,
......
......@@ -26,6 +26,7 @@ class CreateOrUpdateSiteCommandTests(TestCase):
self.client_secret = 'ecommerce-secret'
self.segment_key = 'test-segment-key'
self.from_email = 'site_from_email@example.com'
self.partner_orgs = 'test-orgs'
def _check_site_configuration(self, site, partner):
site_configuration = site.siteconfiguration
......@@ -41,7 +42,7 @@ class CreateOrUpdateSiteCommandTests(TestCase):
def _call_command(self, site_domain, partner_code, lms_url_root, client_id, client_secret, from_email,
site_id=None, site_name=None, partner_name=None, theme_scss_path=None,
payment_processors=None, segment_key=None, enable_enrollment_codes=False):
payment_processors=None, segment_key=None, enable_enrollment_codes=False, partner_orgs=None):
"""
Internal helper method for interacting with the create_or_update_site management command
"""
......@@ -74,6 +75,8 @@ class CreateOrUpdateSiteCommandTests(TestCase):
command_args.append('--enable-enrollment-codes={enable_enrollment_codes}'.format(
enable_enrollment_codes=enable_enrollment_codes
))
if partner_orgs:
command_args.append('--partner-orgs={partner_orgs}'.format(partner_orgs=partner_orgs))
call_command(self.command_name, *command_args)
def test_create_site(self):
......@@ -128,6 +131,67 @@ class CreateOrUpdateSiteCommandTests(TestCase):
self._check_site_configuration(site, partner)
self.assertTrue(site.siteconfiguration.enable_enrollment_codes)
def test_create_partner(self):
""" Verify the command creates Site, Partner, and SiteConfiguration. """
site_domain = 'ecommerce-fake1.server'
new_partner = 'fake2'
partner_orgs = 'test-org'
self.assertFalse(Partner.objects.filter(code=new_partner))
self._call_command(
site_domain=site_domain,
partner_code=new_partner,
lms_url_root=self.lms_url_root,
theme_scss_path=self.theme_scss_path,
payment_processors=self.payment_processors,
client_id=self.client_id,
client_secret=self.client_secret,
segment_key=self.segment_key,
from_email=self.from_email,
partner_orgs=partner_orgs
)
partner = Partner.objects.get(code=new_partner)
self.assertEqual(partner.short_code, new_partner)
self.assertEqual(partner.organization_list, partner_orgs)
def test_update_partner_org(self):
""" Verify the command updates partner organization """
site_domain = 'ecommerce-fake2.server'
partner_org1 = 'test-org1'
partner_org2 = 'test-org2'
self._call_command(
site_domain=site_domain,
partner_code=self.partner,
lms_url_root=self.lms_url_root,
theme_scss_path=self.theme_scss_path,
payment_processors=self.payment_processors,
client_id=self.client_id,
client_secret=self.client_secret,
segment_key=self.segment_key,
from_email=self.from_email,
partner_orgs=partner_org1
)
self.assertEqual(Partner.objects.get(code=self.partner).organization_list, partner_org1)
self._call_command(
site_domain=site_domain,
partner_code=self.partner,
lms_url_root=self.lms_url_root,
theme_scss_path=self.theme_scss_path,
payment_processors=self.payment_processors,
client_id=self.client_id,
client_secret=self.client_secret,
segment_key=self.segment_key,
from_email=self.from_email,
partner_orgs=partner_org2
)
self.assertEqual(Partner.objects.get(code=self.partner).organization_list, partner_org2)
@data(
['--site-id=1'],
['--site-id=1', '--site-name=fake.server'],
......
""" This command populate organization in existing courses."""
from __future__ import unicode_literals
from django.core.management import BaseCommand
from opaque_keys.edx.keys import CourseKey
from ecommerce.courses.models import Course
class Command(BaseCommand):
"""Populate organization in courses."""
help = 'Populate organization in courses'
def handle(self, *args, **options):
for course in Course.objects.all().iterator():
course.organization = CourseKey.from_string(course.id).org
course.save(update_fields=['organization'])
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('courses', '0004_auto_20150803_1406'),
]
operations = [
migrations.AddField(
model_name='course',
name='organization',
field=models.CharField(default=None, max_length=100),
preserve_default=False,
),
migrations.AddField(
model_name='historicalcourse',
name='organization',
field=models.CharField(default=None, max_length=100),
preserve_default=False,
),
]
......@@ -36,6 +36,7 @@ class Course(models.Model):
)
history = HistoricalRecords()
thumbnail_url = models.URLField(null=True, blank=True)
organization = models.CharField(max_length=100, null=False, blank=False)
def __unicode__(self):
return unicode(self.id)
......
# encoding: utf-8
"""Contains the tests for populate organization in existing courses command."""
from __future__ import unicode_literals
import ddt
from django.core.management import call_command
from django.test import TestCase
from ecommerce.courses.models import Course
from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
@ddt.ddt
class AddCourseOrganizationTests(CourseCatalogTestMixin, TestCase):
"""Tests the add course organization."""
def setUp(self):
super(AddCourseOrganizationTests, self).setUp()
self.course = CourseFactory()
self.course_org = 'test-org'
def test_add_course_organization(self):
""" Verify add course organization. """
course = CourseFactory.create()
self.assertEqual(course.organization, '')
call_command('add_course_organization')
course = Course.objects.filter(id=self.course.id).first()
self.assertEqual(course.organization, 'test-org')
......@@ -9,6 +9,7 @@ from django.db import transaction
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model
from opaque_keys.edx.keys import CourseKey
from oscar.core.loading import get_model, get_class
from rest_framework import serializers
from rest_framework.reverse import reverse
......@@ -277,6 +278,8 @@ class AtomicPublicationSerializer(serializers.Serializer): # pylint: disable=ab
course_verification_deadline = self.validated_data.get('verification_deadline')
products = self.validated_data['products']
partner = self.get_partner()
organization_list = partner.organization_list
course_org = CourseKey.from_string(course_id).org
try:
if not waffle.switch_is_active('publish_course_modes_to_lms'):
......@@ -287,12 +290,21 @@ class AtomicPublicationSerializer(serializers.Serializer): # pylint: disable=ab
).format(course_id=course_id)
raise Exception(message)
elif organization_list and organization_list.find(course_org) == -1:
# if partner has organizations list, and course is not from organizations
message = _(
u'Course [{course_id}] is not saved. '
u'Courses only from organization {partner_org_list} will be saved.'
).format(course_id=course_id, partner_org_list=organization_list)
raise Exception(message)
# Explicitly delimit operations which will be rolled back if an exception is raised.
with transaction.atomic():
course, created = Course.objects.get_or_create(id=course_id)
course.name = course_name
course.verification_deadline = course_verification_deadline
course.organization = course_org
course.save()
for product in products:
......
......@@ -17,6 +17,7 @@ from ecommerce.extensions.api.v2.tests.views import JSON_CONTENT_TYPE, ProductSe
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.tests.testcases import TestCase
Partner = get_model('partner', 'Partner')
Product = get_model('catalogue', 'Product')
ProductClass = get_model('catalogue', 'ProductClass')
Selector = get_class('partner.strategy', 'Selector')
......@@ -104,6 +105,16 @@ class CourseViewSetTests(ProductSerializerMixin, CourseCatalogTestMixin, TestCas
response = self.client.get(self.list_path)
self.assertDictEqual(json.loads(response.content), {'count': 0, 'next': None, 'previous': None, 'results': []})
def test_list_with_organization(self):
""" Verify the view returns a list of Courses. """
# If courses organization is not same as partner.
partner = Partner.objects.first()
partner.organization_list = 'test-org-which-is-not-in-course'
partner.save()
response = self.client.get(self.list_path)
self.assertDictEqual(json.loads(response.content), {'count': 0, 'next': None, 'previous': None, 'results': []})
def test_create(self):
""" Verify the view can create a new Course."""
Course.objects.all().delete()
......
......@@ -290,6 +290,21 @@ class AtomicPublicationTests(CourseCatalogTestMixin, TestCase):
self.assertEqual(response.status_code, 400)
self.assert_course_does_not_exist(self.course_id)
def test_invalid_course_org(self):
"""Verify that attempting to save a course with a bad ORG yields a 500."""
partner = self.partner
dummy_org_name = 'bad-org'
partner.organization_list = dummy_org_name
partner.save()
error_msg = 'Course [{course_id}] is not saved. ' \
'Courses only from organization {partner_org}' \
' will be saved.'.format(course_id=self.course_id, partner_org=dummy_org_name)
response = self.client.post(self.create_path, json.dumps(self.data), JSON_CONTENT_TYPE)
self.assertEqual(response.status_code, 500)
self.assertEqual(response.data['error'], error_msg)
self.assert_course_does_not_exist(self.course_id)
def test_invalid_product_class(self):
"""Verify that attempting to save a product with a product class other than 'Seat' yields a 400."""
product_class = 'Not a Seat'
......
......@@ -75,7 +75,7 @@ urlpatterns = [
]
router = ExtendedSimpleRouter()
router.register(r'courses', course_views.CourseViewSet) \
router.register(r'courses', course_views.CourseViewSet, base_name='course') \
.register(r'products', product_views.ProductViewSet,
base_name='course-product', parents_query_lookups=['course_id'])
router.register(r'partners', partner_views.PartnerViewSet) \
......
......@@ -13,10 +13,19 @@ from ecommerce.extensions.api import serializers
class CourseViewSet(NonDestroyableModelViewSet):
lookup_value_regex = COURSE_ID_REGEX
queryset = Course.objects.all()
serializer_class = serializers.CourseSerializer
permission_classes = (IsAuthenticated, IsAdminUser,)
def get_queryset(self):
"""Returns Courses that belongs to the are related to site partner"""
qs = Course.objects.all()
organizations = self.request.site.siteconfiguration.partner.organization_list
if organizations:
qs = qs.filter(organization__in=organizations.split(','))
return qs
def list(self, request, *args, **kwargs):
"""
List all courses.
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('partner', '0008_auto_20150914_1057'),
]
operations = [
migrations.AddField(
model_name='partner',
name='organization_list',
field=models.CharField(max_length=100, null=True, blank=True),
),
]
......@@ -12,6 +12,7 @@ class StockRecord(AbstractStockRecord):
class Partner(AbstractPartner):
# short_code is the unique identifier for the 'Partner'
short_code = models.CharField(max_length=8, unique=True, null=False, blank=False)
organization_list = models.CharField(max_length=100, null=True, blank=True)
class Meta(object):
# Model name that will appear in the admin panel
......
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