Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
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
ecommerce
Commits
52238620
Commit
52238620
authored
Aug 15, 2016
by
Malik Shahzad
Committed by
GitHub
Aug 15, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #874 from edx/malikshahzad228/SOL-1926
SOL-1926: Add organization filter on courses.
parents
dd39bf49
9db2ddc4
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
222 additions
and
8 deletions
+222
-8
ecommerce/core/management/commands/create_or_update_site.py
+12
-5
ecommerce/core/tests/test_commands.py
+65
-1
ecommerce/courses/management/commands/add_course_organization.py
+18
-0
ecommerce/courses/migrations/0005_course_org.py
+26
-0
ecommerce/courses/models.py
+1
-0
ecommerce/courses/tests/test_add_course_organization.py
+31
-0
ecommerce/extensions/api/serializers.py
+12
-0
ecommerce/extensions/api/v2/tests/views/test_courses.py
+11
-0
ecommerce/extensions/api/v2/tests/views/test_publication.py
+15
-0
ecommerce/extensions/api/v2/urls.py
+1
-1
ecommerce/extensions/api/v2/views/courses.py
+10
-1
ecommerce/extensions/partner/migrations/0009_partner_organization_list.py
+19
-0
ecommerce/extensions/partner/models.py
+1
-0
No files found.
ecommerce/core/management/commands/create_or_update_site.py
View file @
52238620
...
...
@@ -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_nam
e
partner
.
short_code
=
partner_code
partner
.
save
()
logger
.
info
(
'Partner created with code
%
s
'
,
partner_code
)
partner
.
name
=
partner_name
partner
.
short_code
=
partner_cod
e
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
,
...
...
ecommerce/core/tests/test_commands.py
View file @
52238620
...
...
@@ -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'
],
...
...
ecommerce/courses/management/commands/add_course_organization.py
0 → 100644
View file @
52238620
""" 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'
])
ecommerce/courses/migrations/0005_course_org.py
0 → 100644
View file @
52238620
# -*- 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
,
),
]
ecommerce/courses/models.py
View file @
52238620
...
...
@@ -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
)
...
...
ecommerce/courses/tests/test_add_course_organization.py
0 → 100644
View file @
52238620
# 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'
)
ecommerce/extensions/api/serializers.py
View file @
52238620
...
...
@@ -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
:
...
...
ecommerce/extensions/api/v2/tests/views/test_courses.py
View file @
52238620
...
...
@@ -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
()
...
...
ecommerce/extensions/api/v2/tests/views/test_publication.py
View file @
52238620
...
...
@@ -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'
...
...
ecommerce/extensions/api/v2/urls.py
View file @
52238620
...
...
@@ -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
)
\
...
...
ecommerce/extensions/api/v2/views/courses.py
View file @
52238620
...
...
@@ -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.
...
...
ecommerce/extensions/partner/migrations/0009_partner_organization_list.py
0 → 100644
View file @
52238620
# -*- 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
),
),
]
ecommerce/extensions/partner/models.py
View file @
52238620
...
...
@@ -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
...
...
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