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
c7179fce
Commit
c7179fce
authored
Jul 26, 2016
by
Clinton Blackburn
Committed by
GitHub
Jul 26, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #180 from edx/clintonb/api-updates
API Updates
parents
bdbdb5b9
30cc8db5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
71 additions
and
12 deletions
+71
-12
course_discovery/apps/api/serializers.py
+18
-6
course_discovery/apps/api/tests/test_serializers.py
+5
-1
course_discovery/apps/course_metadata/models.py
+18
-0
course_discovery/apps/course_metadata/search_indexes.py
+1
-4
course_discovery/apps/course_metadata/tests/test_models.py
+29
-1
No files found.
course_discovery/apps/api/serializers.py
View file @
c7179fce
...
@@ -39,11 +39,15 @@ COURSE_RUN_FACET_FIELD_QUERIES = {
...
@@ -39,11 +39,15 @@ COURSE_RUN_FACET_FIELD_QUERIES = {
'availability_archived'
:
{
'query'
:
'end:<=now'
},
'availability_archived'
:
{
'query'
:
'end:<=now'
},
}
}
COURSE_RUN_SEARCH_FIELDS
=
(
COURSE_RUN_SEARCH_FIELDS
=
(
'
key'
,
'title'
,
'short_description'
,
'full_description'
,
'start'
,
'end'
,
'enrollment_start'
,
'enrollment_end
'
,
'
text'
,
'key'
,
'title'
,
'short_description'
,
'full_description'
,
'start'
,
'end'
,
'enrollment_start
'
,
'
pacing_type'
,
'language'
,
'transcript_languages'
,
'marketing_url'
,
'content_type'
,
'org'
,
'number'
,
'seat_types
'
,
'
enrollment_end'
,
'pacing_type'
,
'language'
,
'transcript_languages'
,
'marketing_url'
,
'content_type'
,
'org
'
,
'
image_url'
,
'type'
,
'text
'
,
'
number'
,
'seat_types'
,
'image_url'
,
'type'
,
'level_type'
,
'availability
'
,
)
)
PROGRAM_FACET_FIELD_OPTIONS
=
{
'category'
:
{},
}
PROGRAM_SEARCH_FIELDS
=
(
PROGRAM_SEARCH_FIELDS
=
(
'uuid'
,
'title'
,
'subtitle'
,
'category'
,
'marketing_url'
,
'organizations'
,
'content_type'
,
'image_url'
,
'text'
,
'uuid'
,
'title'
,
'subtitle'
,
'category'
,
'marketing_url'
,
'organizations'
,
'content_type'
,
'image_url'
,
'text'
,
)
)
...
@@ -194,6 +198,7 @@ class CourseRunSerializer(TimestampModelSerializer):
...
@@ -194,6 +198,7 @@ class CourseRunSerializer(TimestampModelSerializer):
instructors
=
PersonSerializer
(
many
=
True
)
instructors
=
PersonSerializer
(
many
=
True
)
staff
=
PersonSerializer
(
many
=
True
)
staff
=
PersonSerializer
(
many
=
True
)
marketing_url
=
serializers
.
SerializerMethodField
()
marketing_url
=
serializers
.
SerializerMethodField
()
level_type
=
serializers
.
SlugRelatedField
(
read_only
=
True
,
slug_field
=
'name'
)
class
Meta
(
object
):
class
Meta
(
object
):
model
=
CourseRun
model
=
CourseRun
...
@@ -201,7 +206,7 @@ class CourseRunSerializer(TimestampModelSerializer):
...
@@ -201,7 +206,7 @@ class CourseRunSerializer(TimestampModelSerializer):
'course'
,
'key'
,
'title'
,
'short_description'
,
'full_description'
,
'start'
,
'end'
,
'course'
,
'key'
,
'title'
,
'short_description'
,
'full_description'
,
'start'
,
'end'
,
'enrollment_start'
,
'enrollment_end'
,
'announcement'
,
'image'
,
'video'
,
'seats'
,
'enrollment_start'
,
'enrollment_end'
,
'announcement'
,
'image'
,
'video'
,
'seats'
,
'content_language'
,
'transcript_languages'
,
'instructors'
,
'staff'
,
'content_language'
,
'transcript_languages'
,
'instructors'
,
'staff'
,
'pacing_type'
,
'min_effort'
,
'max_effort'
,
'modified'
,
'marketing_url'
,
'pacing_type'
,
'min_effort'
,
'max_effort'
,
'modified'
,
'marketing_url'
,
'level_type'
,
'availability'
,
)
)
def
get_marketing_url
(
self
,
obj
):
def
get_marketing_url
(
self
,
obj
):
...
@@ -479,6 +484,11 @@ class CourseFacetSerializer(BaseHaystackFacetSerializer):
...
@@ -479,6 +484,11 @@ class CourseFacetSerializer(BaseHaystackFacetSerializer):
class
CourseRunSearchSerializer
(
HaystackSerializer
):
class
CourseRunSearchSerializer
(
HaystackSerializer
):
availability
=
serializers
.
SerializerMethodField
()
def
get_availability
(
self
,
result
):
return
result
.
object
.
availability
class
Meta
:
class
Meta
:
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
fields
=
COURSE_RUN_SEARCH_FIELDS
fields
=
COURSE_RUN_SEARCH_FIELDS
...
@@ -499,6 +509,7 @@ class CourseRunFacetSerializer(BaseHaystackFacetSerializer):
...
@@ -499,6 +509,7 @@ class CourseRunFacetSerializer(BaseHaystackFacetSerializer):
class
ProgramSearchSerializer
(
HaystackSerializer
):
class
ProgramSearchSerializer
(
HaystackSerializer
):
class
Meta
:
class
Meta
:
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
field_options
=
PROGRAM_FACET_FIELD_OPTIONS
fields
=
PROGRAM_SEARCH_FIELDS
fields
=
PROGRAM_SEARCH_FIELDS
ignore_fields
=
COMMON_IGNORED_FIELDS
ignore_fields
=
COMMON_IGNORED_FIELDS
index_classes
=
[
ProgramIndex
]
index_classes
=
[
ProgramIndex
]
...
@@ -509,6 +520,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer):
...
@@ -509,6 +520,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer):
class
Meta
:
class
Meta
:
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
field_options
=
PROGRAM_FACET_FIELD_OPTIONS
fields
=
PROGRAM_SEARCH_FIELDS
fields
=
PROGRAM_SEARCH_FIELDS
ignore_fields
=
COMMON_IGNORED_FIELDS
ignore_fields
=
COMMON_IGNORED_FIELDS
index_classes
=
[
ProgramIndex
]
index_classes
=
[
ProgramIndex
]
...
@@ -517,7 +529,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer):
...
@@ -517,7 +529,7 @@ class ProgramFacetSerializer(BaseHaystackFacetSerializer):
class
AggregateSearchSerializer
(
HaystackSerializer
):
class
AggregateSearchSerializer
(
HaystackSerializer
):
class
Meta
:
class
Meta
:
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
fields
=
COURSE_RUN_SEARCH_FIELDS
fields
=
COURSE_RUN_SEARCH_FIELDS
+
PROGRAM_SEARCH_FIELDS
ignore_fields
=
COMMON_IGNORED_FIELDS
ignore_fields
=
COMMON_IGNORED_FIELDS
serializers
=
{
serializers
=
{
CourseRunIndex
:
CourseRunSearchSerializer
,
CourseRunIndex
:
CourseRunSearchSerializer
,
...
@@ -531,7 +543,7 @@ class AggregateFacetSearchSerializer(BaseHaystackFacetSerializer):
...
@@ -531,7 +543,7 @@ class AggregateFacetSearchSerializer(BaseHaystackFacetSerializer):
class
Meta
:
class
Meta
:
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
field_aliases
=
COMMON_SEARCH_FIELD_ALIASES
field_options
=
COURSE_RUN_FACET_FIELD_OPTIONS
field_options
=
{
**
COURSE_RUN_FACET_FIELD_OPTIONS
,
**
PROGRAM_FACET_FIELD_OPTIONS
}
field_queries
=
COURSE_RUN_FACET_FIELD_QUERIES
field_queries
=
COURSE_RUN_FACET_FIELD_QUERIES
ignore_fields
=
COMMON_IGNORED_FIELDS
ignore_fields
=
COMMON_IGNORED_FIELDS
serializers
=
{
serializers
=
{
...
...
course_discovery/apps/api/tests/test_serializers.py
View file @
c7179fce
...
@@ -152,6 +152,8 @@ class CourseRunSerializerTests(TestCase):
...
@@ -152,6 +152,8 @@ class CourseRunSerializerTests(TestCase):
'utm_medium'
:
request
.
user
.
referral_tracking_id
,
'utm_medium'
:
request
.
user
.
referral_tracking_id
,
})
})
),
),
'level_type'
:
course_run
.
level_type
.
name
,
'availability'
:
course_run
.
availability
,
}
}
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
...
@@ -375,6 +377,8 @@ class CourseRunSearchSerializerTests(TestCase):
...
@@ -375,6 +377,8 @@ class CourseRunSearchSerializerTests(TestCase):
'seat_types'
:
course_run
.
seat_types
,
'seat_types'
:
course_run
.
seat_types
,
'image_url'
:
course_run
.
image_url
,
'image_url'
:
course_run
.
image_url
,
'type'
:
course_run
.
type
,
'type'
:
course_run
.
type
,
'level_type'
:
course_run
.
level_type
.
name
,
'availability'
:
course_run
.
availability
,
}
}
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
...
@@ -398,7 +402,7 @@ class ProgramSearchSerializerTests(TestCase):
...
@@ -398,7 +402,7 @@ class ProgramSearchSerializerTests(TestCase):
'category'
:
program
.
category
,
'category'
:
program
.
category
,
'marketing_url'
:
program
.
marketing_url
,
'marketing_url'
:
program
.
marketing_url
,
'organizations'
:
[
OrganizationsMixin
.
format_organization
(
organization
)],
'organizations'
:
[
OrganizationsMixin
.
format_organization
(
organization
)],
'content_type'
:
'program
_{category}'
.
format
(
category
=
program
.
category
)
,
'content_type'
:
'program
'
,
'image_url'
:
program
.
image_url
,
'image_url'
:
program
.
image_url
,
}
}
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
course_discovery/apps/course_metadata/models.py
View file @
c7179fce
...
@@ -356,6 +356,24 @@ class CourseRun(TimeStampedModel):
...
@@ -356,6 +356,24 @@ class CourseRun(TimeStampedModel):
return
None
return
None
@property
def
level_type
(
self
):
return
self
.
course
.
level_type
@property
def
availability
(
self
):
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
upcoming_cutoff
=
now
+
datetime
.
timedelta
(
days
=
60
)
if
self
.
end
and
self
.
end
<=
now
:
return
_
(
'Archived'
)
elif
self
.
start
and
self
.
end
and
(
self
.
start
<=
now
<
self
.
end
):
return
_
(
'Current'
)
elif
self
.
start
and
(
now
<
self
.
start
<
upcoming_cutoff
):
return
_
(
'Starting Soon'
)
else
:
return
_
(
'Upcoming'
)
@classmethod
@classmethod
def
search
(
cls
,
query
):
def
search
(
cls
,
query
):
""" Queries the search index.
""" Queries the search index.
...
...
course_discovery/apps/course_metadata/search_indexes.py
View file @
c7179fce
...
@@ -39,6 +39,7 @@ class BaseCourseIndex(OrganizationsMixin, BaseIndex):
...
@@ -39,6 +39,7 @@ class BaseCourseIndex(OrganizationsMixin, BaseIndex):
full_description
=
indexes
.
CharField
(
model_attr
=
'full_description'
,
null
=
True
)
full_description
=
indexes
.
CharField
(
model_attr
=
'full_description'
,
null
=
True
)
subjects
=
indexes
.
MultiValueField
(
faceted
=
True
)
subjects
=
indexes
.
MultiValueField
(
faceted
=
True
)
organizations
=
indexes
.
MultiValueField
(
faceted
=
True
)
organizations
=
indexes
.
MultiValueField
(
faceted
=
True
)
level_type
=
indexes
.
CharField
(
model_attr
=
'level_type__name'
,
null
=
True
,
faceted
=
True
)
def
prepare_subjects
(
self
,
obj
):
def
prepare_subjects
(
self
,
obj
):
return
[
subject
.
name
for
subject
in
obj
.
subjects
.
all
()]
return
[
subject
.
name
for
subject
in
obj
.
subjects
.
all
()]
...
@@ -47,7 +48,6 @@ class BaseCourseIndex(OrganizationsMixin, BaseIndex):
...
@@ -47,7 +48,6 @@ class BaseCourseIndex(OrganizationsMixin, BaseIndex):
class
CourseIndex
(
BaseCourseIndex
,
indexes
.
Indexable
):
class
CourseIndex
(
BaseCourseIndex
,
indexes
.
Indexable
):
model
=
Course
model
=
Course
level_type
=
indexes
.
CharField
(
model_attr
=
'level_type__name'
,
null
=
True
,
faceted
=
True
)
course_runs
=
indexes
.
MultiValueField
()
course_runs
=
indexes
.
MultiValueField
()
expected_learning_items
=
indexes
.
MultiValueField
()
expected_learning_items
=
indexes
.
MultiValueField
()
...
@@ -114,6 +114,3 @@ class ProgramIndex(OrganizationsMixin, BaseIndex, indexes.Indexable):
...
@@ -114,6 +114,3 @@ class ProgramIndex(OrganizationsMixin, BaseIndex, indexes.Indexable):
marketing_url
=
indexes
.
CharField
(
model_attr
=
'marketing_url'
,
null
=
True
)
marketing_url
=
indexes
.
CharField
(
model_attr
=
'marketing_url'
,
null
=
True
)
organizations
=
indexes
.
MultiValueField
(
faceted
=
True
)
organizations
=
indexes
.
MultiValueField
(
faceted
=
True
)
image_url
=
indexes
.
CharField
(
model_attr
=
'image_url'
,
null
=
True
)
image_url
=
indexes
.
CharField
(
model_attr
=
'image_url'
,
null
=
True
)
def
prepare_content_type
(
self
,
obj
):
return
'program_{category}'
.
format
(
category
=
obj
.
category
)
course_discovery/apps/course_metadata/tests/test_models.py
View file @
c7179fce
...
@@ -3,9 +3,11 @@ import datetime
...
@@ -3,9 +3,11 @@ import datetime
import
ddt
import
ddt
import
mock
import
mock
import
pytz
import
pytz
from
d
jango.db
import
IntegrityError
from
d
ateutil.parser
import
parse
from
django.conf
import
settings
from
django.conf
import
settings
from
django.db
import
IntegrityError
from
django.test
import
TestCase
from
django.test
import
TestCase
from
freezegun
import
freeze_time
from
course_discovery.apps.core.utils
import
SearchQuerySetWrapper
from
course_discovery.apps.core.utils
import
SearchQuerySetWrapper
from
course_discovery.apps.course_metadata.models
import
(
from
course_discovery.apps.course_metadata.models
import
(
...
@@ -178,6 +180,30 @@ class CourseRunTests(TestCase):
...
@@ -178,6 +180,30 @@ class CourseRunTests(TestCase):
factories
.
SeatFactory
(
course_run
=
self
.
course_run
,
type
=
seat_type
)
factories
.
SeatFactory
(
course_run
=
self
.
course_run
,
type
=
seat_type
)
self
.
assert_course_run_has_no_type
(
self
.
course_run
,
set
([
seat_type
]))
self
.
assert_course_run_has_no_type
(
self
.
course_run
,
set
([
seat_type
]))
def
test_level_type
(
self
):
""" Verify the property returns the associated Course's level type. """
self
.
assertEqual
(
self
.
course_run
.
level_type
,
self
.
course_run
.
course
.
level_type
)
@freeze_time
(
'2016-06-21 00:00:00Z'
)
@ddt.data
(
(
None
,
None
,
'Upcoming'
),
(
'2030-01-01 00:00:00Z'
,
None
,
'Upcoming'
),
(
None
,
'2016-01-01 00:00:00Z'
,
'Archived'
),
(
'2015-01-01 00:00:00Z'
,
'2016-01-01 00:00:00Z'
,
'Archived'
),
(
'2016-01-01 00:00:00Z'
,
'2017-01-01 00:00:00Z'
,
'Current'
),
(
'2016-07-21 00:00:00Z'
,
'2017-01-01 00:00:00Z'
,
'Starting Soon'
),
)
@ddt.unpack
def
test_availability
(
self
,
start
,
end
,
expected_availability
):
""" Verify the property returns the appropriate availability string based on the start/end dates. """
if
start
:
start
=
parse
(
start
)
if
end
:
end
=
parse
(
end
)
course_run
=
factories
.
CourseRunFactory
(
start
=
start
,
end
=
end
)
self
.
assertEqual
(
course_run
.
availability
,
expected_availability
)
class
OrganizationTests
(
TestCase
):
class
OrganizationTests
(
TestCase
):
""" Tests for the `Organization` model. """
""" Tests for the `Organization` model. """
...
@@ -272,6 +298,7 @@ class ProgramTests(TestCase):
...
@@ -272,6 +298,7 @@ class ProgramTests(TestCase):
class
PersonSocialNetworkTests
(
TestCase
):
class
PersonSocialNetworkTests
(
TestCase
):
"""Tests of the PersonSocialNetwork model."""
"""Tests of the PersonSocialNetwork model."""
def
setUp
(
self
):
def
setUp
(
self
):
super
(
PersonSocialNetworkTests
,
self
)
.
setUp
()
super
(
PersonSocialNetworkTests
,
self
)
.
setUp
()
self
.
network
=
factories
.
PersonSocialNetworkFactory
()
self
.
network
=
factories
.
PersonSocialNetworkFactory
()
...
@@ -294,6 +321,7 @@ class PersonSocialNetworkTests(TestCase):
...
@@ -294,6 +321,7 @@ class PersonSocialNetworkTests(TestCase):
class
CourseSocialNetworkTests
(
TestCase
):
class
CourseSocialNetworkTests
(
TestCase
):
"""Tests of the CourseSocialNetwork model."""
"""Tests of the CourseSocialNetwork model."""
def
setUp
(
self
):
def
setUp
(
self
):
super
(
CourseSocialNetworkTests
,
self
)
.
setUp
()
super
(
CourseSocialNetworkTests
,
self
)
.
setUp
()
self
.
network
=
factories
.
CourseRunSocialNetworkFactory
()
self
.
network
=
factories
.
CourseRunSocialNetworkFactory
()
...
...
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