Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
course-discovery
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
course-discovery
Commits
5107ad29
Commit
5107ad29
authored
Aug 17, 2016
by
Mike Dikan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update to course API to include (condensed) program info
parent
125bf70c
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
78 additions
and
15 deletions
+78
-15
course_discovery/apps/api/serializers.py
+32
-1
course_discovery/apps/api/tests/test_serializers.py
+26
-5
course_discovery/apps/api/v1/tests/test_views/mixins.py
+3
-2
course_discovery/apps/api/v1/tests/test_views/test_course_runs.py
+2
-2
course_discovery/apps/api/v1/views.py
+2
-2
course_discovery/apps/course_metadata/models.py
+6
-2
course_discovery/apps/course_metadata/tests/test_models.py
+7
-1
No files found.
course_discovery/apps/api/serializers.py
View file @
5107ad29
...
@@ -189,6 +189,19 @@ class CatalogSerializer(serializers.ModelSerializer):
...
@@ -189,6 +189,19 @@ class CatalogSerializer(serializers.ModelSerializer):
fields
=
(
'id'
,
'name'
,
'query'
,
'courses_count'
,
'viewers'
)
fields
=
(
'id'
,
'name'
,
'query'
,
'courses_count'
,
'viewers'
)
class
NestedProgramSerializer
(
serializers
.
ModelSerializer
):
"""
Serializer used when nesting a Program inside another entity (e.g. a Course). The resulting data includes only
the basic details of the Program and none of the details about its related entities (e.g. courses).
"""
type
=
serializers
.
SlugRelatedField
(
slug_field
=
'name'
,
queryset
=
ProgramType
.
objects
.
all
())
class
Meta
:
model
=
Program
fields
=
(
'uuid'
,
'title'
,
'type'
,
'marketing_slug'
,
'marketing_url'
,)
read_only_fields
=
(
'uuid'
,
'marketing_url'
,)
class
CourseRunSerializer
(
TimestampModelSerializer
):
class
CourseRunSerializer
(
TimestampModelSerializer
):
"""Serializer for the ``CourseRun`` model."""
"""Serializer for the ``CourseRun`` model."""
course
=
serializers
.
SlugRelatedField
(
read_only
=
True
,
slug_field
=
'key'
)
course
=
serializers
.
SlugRelatedField
(
read_only
=
True
,
slug_field
=
'key'
)
...
@@ -218,6 +231,15 @@ class CourseRunSerializer(TimestampModelSerializer):
...
@@ -218,6 +231,15 @@ class CourseRunSerializer(TimestampModelSerializer):
return
get_marketing_url_for_user
(
self
.
context
[
'request'
]
.
user
,
obj
.
marketing_url
)
return
get_marketing_url_for_user
(
self
.
context
[
'request'
]
.
user
,
obj
.
marketing_url
)
class
CourseRunWithProgramsSerializer
(
CourseRunSerializer
):
"""A ``CourseRunSerializer`` which includes programs derived from parent course."""
programs
=
NestedProgramSerializer
(
many
=
True
)
class
Meta
(
CourseRunSerializer
.
Meta
):
model
=
CourseRun
fields
=
CourseRunSerializer
.
Meta
.
fields
+
(
'programs'
,
)
class
ContainedCourseRunsSerializer
(
serializers
.
Serializer
):
class
ContainedCourseRunsSerializer
(
serializers
.
Serializer
):
"""Serializer used to represent course runs contained by a catalog."""
"""Serializer used to represent course runs contained by a catalog."""
course_runs
=
serializers
.
DictField
(
course_runs
=
serializers
.
DictField
(
...
@@ -244,13 +266,22 @@ class CourseSerializer(TimestampModelSerializer):
...
@@ -244,13 +266,22 @@ class CourseSerializer(TimestampModelSerializer):
fields
=
(
fields
=
(
'key'
,
'title'
,
'short_description'
,
'full_description'
,
'level_type'
,
'subjects'
,
'prerequisites'
,
'key'
,
'title'
,
'short_description'
,
'full_description'
,
'level_type'
,
'subjects'
,
'prerequisites'
,
'expected_learning_items'
,
'image'
,
'video'
,
'owners'
,
'sponsors'
,
'modified'
,
'course_runs'
,
'expected_learning_items'
,
'image'
,
'video'
,
'owners'
,
'sponsors'
,
'modified'
,
'course_runs'
,
'marketing_url'
'marketing_url'
,
)
)
def
get_marketing_url
(
self
,
obj
):
def
get_marketing_url
(
self
,
obj
):
return
get_marketing_url_for_user
(
self
.
context
[
'request'
]
.
user
,
obj
.
marketing_url
)
return
get_marketing_url_for_user
(
self
.
context
[
'request'
]
.
user
,
obj
.
marketing_url
)
class
CourseWithProgramsSerializer
(
CourseSerializer
):
"""A ``CourseSerializer`` which includes programs."""
programs
=
NestedProgramSerializer
(
many
=
True
)
class
Meta
(
CourseSerializer
.
Meta
):
model
=
Course
fields
=
CourseSerializer
.
Meta
.
fields
+
(
'programs'
,
)
class
CourseSerializerExcludingClosedRuns
(
CourseSerializer
):
class
CourseSerializerExcludingClosedRuns
(
CourseSerializer
):
"""A ``CourseSerializer`` which only includes active course runs, as determined by ``CourseQuerySet``."""
"""A ``CourseSerializer`` which only includes active course runs, as determined by ``CourseQuerySet``."""
course_runs
=
CourseRunSerializer
(
many
=
True
,
source
=
'active_course_runs'
)
course_runs
=
CourseRunSerializer
(
many
=
True
,
source
=
'active_course_runs'
)
...
...
course_discovery/apps/api/tests/test_serializers.py
View file @
5107ad29
...
@@ -11,7 +11,8 @@ from course_discovery.apps.api.serializers import (
...
@@ -11,7 +11,8 @@ from course_discovery.apps.api.serializers import (
CatalogSerializer
,
CourseSerializer
,
CourseRunSerializer
,
ContainedCoursesSerializer
,
ImageSerializer
,
CatalogSerializer
,
CourseSerializer
,
CourseRunSerializer
,
ContainedCoursesSerializer
,
ImageSerializer
,
SubjectSerializer
,
PrerequisiteSerializer
,
VideoSerializer
,
OrganizationSerializer
,
SeatSerializer
,
SubjectSerializer
,
PrerequisiteSerializer
,
VideoSerializer
,
OrganizationSerializer
,
SeatSerializer
,
PersonSerializer
,
AffiliateWindowSerializer
,
ContainedCourseRunsSerializer
,
CourseRunSearchSerializer
,
PersonSerializer
,
AffiliateWindowSerializer
,
ContainedCourseRunsSerializer
,
CourseRunSearchSerializer
,
ProgramSerializer
,
ProgramSearchSerializer
,
ProgramCourseSerializer
ProgramSerializer
,
ProgramSearchSerializer
,
ProgramCourseSerializer
,
NestedProgramSerializer
,
CourseRunWithProgramsSerializer
,
CourseWithProgramsSerializer
)
)
from
course_discovery.apps.catalogs.tests.factories
import
CatalogFactory
from
course_discovery.apps.catalogs.tests.factories
import
CatalogFactory
from
course_discovery.apps.core.models
import
User
from
course_discovery.apps.core.models
import
User
...
@@ -76,7 +77,7 @@ class CourseSerializerTests(TestCase):
...
@@ -76,7 +77,7 @@ class CourseSerializerTests(TestCase):
request
=
make_request
()
request
=
make_request
()
CourseRunFactory
.
create_batch
(
3
,
course
=
course
)
CourseRunFactory
.
create_batch
(
3
,
course
=
course
)
serializer
=
CourseSerializer
(
course
,
context
=
{
'request'
:
request
})
serializer
=
Course
WithPrograms
Serializer
(
course
,
context
=
{
'request'
:
request
})
expected
=
{
expected
=
{
'key'
:
course
.
key
,
'key'
:
course
.
key
,
...
@@ -99,7 +100,8 @@ class CourseSerializerTests(TestCase):
...
@@ -99,7 +100,8 @@ class CourseSerializerTests(TestCase):
'utm_source'
:
request
.
user
.
username
,
'utm_source'
:
request
.
user
.
username
,
'utm_medium'
:
request
.
user
.
referral_tracking_id
,
'utm_medium'
:
request
.
user
.
referral_tracking_id
,
})
})
)
),
'programs'
:
NestedProgramSerializer
(
course
.
programs
,
many
=
True
,
context
=
{
'request'
:
request
})
.
data
,
}
}
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
...
@@ -119,9 +121,11 @@ class CourseRunSerializerTests(TestCase):
...
@@ -119,9 +121,11 @@ class CourseRunSerializerTests(TestCase):
def
test_data
(
self
):
def
test_data
(
self
):
request
=
make_request
()
request
=
make_request
()
course_run
=
CourseRunFactory
()
course_run
=
CourseRunFactory
()
course
=
course_run
.
course
image
=
course_run
.
image
image
=
course_run
.
image
video
=
course_run
.
video
video
=
course_run
.
video
serializer
=
CourseRunSerializer
(
course_run
,
context
=
{
'request'
:
request
})
serializer
=
CourseRunWithProgramsSerializer
(
course_run
,
context
=
{
'request'
:
request
})
ProgramFactory
(
courses
=
[
course
])
expected
=
{
expected
=
{
'course'
:
course_run
.
course
.
key
,
'course'
:
course_run
.
course
.
key
,
...
@@ -154,6 +158,7 @@ class CourseRunSerializerTests(TestCase):
...
@@ -154,6 +158,7 @@ class CourseRunSerializerTests(TestCase):
),
),
'level_type'
:
course_run
.
level_type
.
name
,
'level_type'
:
course_run
.
level_type
.
name
,
'availability'
:
course_run
.
availability
,
'availability'
:
course_run
.
availability
,
'programs'
:
NestedProgramSerializer
(
course
.
programs
,
many
=
True
,
context
=
{
'request'
:
request
})
.
data
,
}
}
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
...
@@ -239,7 +244,7 @@ class ProgramCourseSerializerTests(TestCase):
...
@@ -239,7 +244,7 @@ class ProgramCourseSerializerTests(TestCase):
'utm_source'
:
self
.
request
.
user
.
username
,
'utm_source'
:
self
.
request
.
user
.
username
,
'utm_medium'
:
self
.
request
.
user
.
referral_tracking_id
,
'utm_medium'
:
self
.
request
.
user
.
referral_tracking_id
,
})
})
)
)
,
}
}
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
...
@@ -384,6 +389,22 @@ class ImageSerializerTests(TestCase):
...
@@ -384,6 +389,22 @@ class ImageSerializerTests(TestCase):
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
class
NestedProgramSerializerTests
(
TestCase
):
def
test_data
(
self
):
program
=
ProgramFactory
()
serializer
=
NestedProgramSerializer
(
program
)
expected
=
{
'uuid'
:
str
(
program
.
uuid
),
'marketing_slug'
:
program
.
marketing_slug
,
'marketing_url'
:
program
.
marketing_url
,
# pylint: disable=no-member
'type'
:
program
.
type
.
name
,
'title'
:
program
.
title
,
}
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
class
VideoSerializerTests
(
TestCase
):
class
VideoSerializerTests
(
TestCase
):
def
test_data
(
self
):
def
test_data
(
self
):
video
=
VideoFactory
()
video
=
VideoFactory
()
...
...
course_discovery/apps/api/v1/tests/test_views/mixins.py
View file @
5107ad29
...
@@ -7,7 +7,8 @@ from django.conf import settings
...
@@ -7,7 +7,8 @@ from django.conf import settings
from
rest_framework.test
import
APIRequestFactory
from
rest_framework.test
import
APIRequestFactory
from
course_discovery.apps.api.serializers
import
(
from
course_discovery.apps.api.serializers
import
(
CatalogSerializer
,
CourseSerializer
,
CourseSerializerExcludingClosedRuns
,
FlattenedCourseRunWithCourseSerializer
CatalogSerializer
,
CourseWithProgramsSerializer
,
CourseSerializerExcludingClosedRuns
,
FlattenedCourseRunWithCourseSerializer
)
)
...
@@ -27,7 +28,7 @@ class SerializationMixin(object):
...
@@ -27,7 +28,7 @@ class SerializationMixin(object):
return
self
.
_serialize_object
(
CatalogSerializer
,
catalog
,
many
,
format
)
return
self
.
_serialize_object
(
CatalogSerializer
,
catalog
,
many
,
format
)
def
serialize_course
(
self
,
course
,
many
=
False
,
format
=
None
):
def
serialize_course
(
self
,
course
,
many
=
False
,
format
=
None
):
return
self
.
_serialize_object
(
CourseSerializer
,
course
,
many
,
format
)
return
self
.
_serialize_object
(
Course
WithPrograms
Serializer
,
course
,
many
,
format
)
def
serialize_catalog_course
(
self
,
course
,
many
=
False
,
format
=
None
):
def
serialize_catalog_course
(
self
,
course
,
many
=
False
,
format
=
None
):
return
self
.
_serialize_object
(
CourseSerializerExcludingClosedRuns
,
course
,
many
,
format
)
return
self
.
_serialize_object
(
CourseSerializerExcludingClosedRuns
,
course
,
many
,
format
)
...
...
course_discovery/apps/api/v1/tests/test_views/test_course_runs.py
View file @
5107ad29
...
@@ -6,7 +6,7 @@ from django.db.models.functions import Lower
...
@@ -6,7 +6,7 @@ from django.db.models.functions import Lower
from
rest_framework.reverse
import
reverse
from
rest_framework.reverse
import
reverse
from
rest_framework.test
import
APITestCase
,
APIRequestFactory
from
rest_framework.test
import
APITestCase
,
APIRequestFactory
from
course_discovery.apps.api.serializers
import
CourseRunSerializer
from
course_discovery.apps.api.serializers
import
CourseRun
WithPrograms
Serializer
from
course_discovery.apps.core.tests.factories
import
UserFactory
from
course_discovery.apps.core.tests.factories
import
UserFactory
from
course_discovery.apps.core.tests.mixins
import
ElasticsearchTestMixin
from
course_discovery.apps.core.tests.mixins
import
ElasticsearchTestMixin
from
course_discovery.apps.course_metadata.models
import
CourseRun
from
course_discovery.apps.course_metadata.models
import
CourseRun
...
@@ -27,7 +27,7 @@ class CourseRunViewSetTests(ElasticsearchTestMixin, APITestCase):
...
@@ -27,7 +27,7 @@ class CourseRunViewSetTests(ElasticsearchTestMixin, APITestCase):
self
.
request
.
user
=
self
.
user
self
.
request
.
user
=
self
.
user
def
serialize_course_run
(
self
,
course_run
,
**
kwargs
):
def
serialize_course_run
(
self
,
course_run
,
**
kwargs
):
return
CourseRunSerializer
(
course_run
,
context
=
{
'request'
:
self
.
request
},
**
kwargs
)
.
data
return
CourseRun
WithPrograms
Serializer
(
course_run
,
context
=
{
'request'
:
self
.
request
},
**
kwargs
)
.
data
def
test_get
(
self
):
def
test_get
(
self
):
""" Verify the endpoint returns the details for a single course. """
""" Verify the endpoint returns the details for a single course. """
...
...
course_discovery/apps/api/v1/views.py
View file @
5107ad29
...
@@ -181,7 +181,7 @@ class CourseViewSet(viewsets.ReadOnlyModelViewSet):
...
@@ -181,7 +181,7 @@ class CourseViewSet(viewsets.ReadOnlyModelViewSet):
lookup_value_regex
=
COURSE_ID_REGEX
lookup_value_regex
=
COURSE_ID_REGEX
queryset
=
Course
.
objects
.
all
()
queryset
=
Course
.
objects
.
all
()
permission_classes
=
(
IsAuthenticated
,)
permission_classes
=
(
IsAuthenticated
,)
serializer_class
=
serializers
.
CourseSerializer
serializer_class
=
serializers
.
Course
WithPrograms
Serializer
def
get_queryset
(
self
):
def
get_queryset
(
self
):
q
=
self
.
request
.
query_params
.
get
(
'q'
,
None
)
q
=
self
.
request
.
query_params
.
get
(
'q'
,
None
)
...
@@ -225,7 +225,7 @@ class CourseRunViewSet(viewsets.ReadOnlyModelViewSet):
...
@@ -225,7 +225,7 @@ class CourseRunViewSet(viewsets.ReadOnlyModelViewSet):
lookup_value_regex
=
COURSE_RUN_ID_REGEX
lookup_value_regex
=
COURSE_RUN_ID_REGEX
queryset
=
CourseRun
.
objects
.
all
()
.
order_by
(
Lower
(
'key'
))
queryset
=
CourseRun
.
objects
.
all
()
.
order_by
(
Lower
(
'key'
))
permission_classes
=
(
IsAuthenticated
,)
permission_classes
=
(
IsAuthenticated
,)
serializer_class
=
serializers
.
CourseRunSerializer
serializer_class
=
serializers
.
CourseRun
WithPrograms
Serializer
def
_get_partner
(
self
):
def
_get_partner
(
self
):
""" Return the partner for the code passed in or the default partner """
""" Return the partner for the code passed in or the default partner """
...
...
course_discovery/apps/course_metadata/models.py
View file @
5107ad29
...
@@ -389,6 +389,10 @@ class CourseRun(TimeStampedModel):
...
@@ -389,6 +389,10 @@ class CourseRun(TimeStampedModel):
return
self
.
course
.
prerequisites
return
self
.
course
.
prerequisites
@property
@property
def
programs
(
self
):
return
self
.
course
.
programs
@property
def
seat_types
(
self
):
def
seat_types
(
self
):
return
list
(
self
.
seats
.
values_list
(
'type'
,
flat
=
True
))
return
list
(
self
.
seats
.
values_list
(
'type'
,
flat
=
True
))
...
@@ -576,7 +580,7 @@ class Program(TimeStampedModel):
...
@@ -576,7 +580,7 @@ class Program(TimeStampedModel):
)
)
marketing_slug
=
models
.
CharField
(
marketing_slug
=
models
.
CharField
(
help_text
=
_
(
'Slug used to generate links to the marketing site'
),
blank
=
True
,
max_length
=
255
,
db_index
=
True
)
help_text
=
_
(
'Slug used to generate links to the marketing site'
),
blank
=
True
,
max_length
=
255
,
db_index
=
True
)
courses
=
models
.
ManyToManyField
(
Course
)
courses
=
models
.
ManyToManyField
(
Course
,
related_name
=
'programs'
)
# NOTE (CCB): Editors of this field should validate the values to ensure only CourseRuns associated
# NOTE (CCB): Editors of this field should validate the values to ensure only CourseRuns associated
# with related Courses are stored.
# with related Courses are stored.
excluded_course_runs
=
models
.
ManyToManyField
(
CourseRun
,
blank
=
True
)
excluded_course_runs
=
models
.
ManyToManyField
(
CourseRun
,
blank
=
True
)
...
@@ -623,7 +627,7 @@ class Program(TimeStampedModel):
...
@@ -623,7 +627,7 @@ class Program(TimeStampedModel):
@property
@property
def
course_runs
(
self
):
def
course_runs
(
self
):
excluded_course_run_ids
=
[
course_run
.
id
for
course_run
in
self
.
excluded_course_runs
.
all
()]
excluded_course_run_ids
=
[
course_run
.
id
for
course_run
in
self
.
excluded_course_runs
.
all
()]
return
CourseRun
.
objects
.
filter
(
course__program
=
self
)
.
exclude
(
id__in
=
excluded_course_run_ids
)
return
CourseRun
.
objects
.
filter
(
course__program
s
=
self
)
.
exclude
(
id__in
=
excluded_course_run_ids
)
@property
@property
def
languages
(
self
):
def
languages
(
self
):
...
...
course_discovery/apps/course_metadata/tests/test_models.py
View file @
5107ad29
...
@@ -309,7 +309,13 @@ class ProgramTests(TestCase):
...
@@ -309,7 +309,13 @@ class ProgramTests(TestCase):
self
.
assertIsNone
(
self
.
program
.
marketing_url
)
self
.
assertIsNone
(
self
.
program
.
marketing_url
)
def
test_course_runs
(
self
):
def
test_course_runs
(
self
):
""" Verify the property returns the set of associated CourseRuns minus those that are explicitly excluded. """
"""
Verify that we only fetch course runs for the program, and not other course runs for other programs and that the
property returns the set of associated CourseRuns minus those that are explicitly excluded.
"""
course_run
=
factories
.
CourseRunFactory
()
factories
.
ProgramFactory
(
courses
=
[
course_run
.
course
])
# Verify that course run is not returned in set
self
.
assertEqual
(
set
(
self
.
program
.
course_runs
),
set
(
self
.
course_runs
))
self
.
assertEqual
(
set
(
self
.
program
.
course_runs
),
set
(
self
.
course_runs
))
def
test_languages
(
self
):
def
test_languages
(
self
):
...
...
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