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
a9c5877c
Commit
a9c5877c
authored
Aug 16, 2016
by
Clinton Blackburn
Committed by
GitHub
Aug 16, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Updated Person model and data loader (#243)
ECOM-5193 and ECOM-5194
parent
d5a47469
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
641 additions
and
101 deletions
+641
-101
course_discovery/apps/api/serializers.py
+1
-2
course_discovery/apps/api/tests/test_serializers.py
+5
-5
course_discovery/apps/course_metadata/admin.py
+14
-9
course_discovery/apps/course_metadata/data_loaders/__init__.py
+1
-1
course_discovery/apps/course_metadata/data_loaders/marketing_site.py
+75
-9
course_discovery/apps/course_metadata/data_loaders/tests/test_api.py
+2
-2
course_discovery/apps/course_metadata/data_loaders/tests/test_marketing_site.py
+57
-20
course_discovery/apps/course_metadata/management/commands/refresh_course_metadata.py
+2
-1
course_discovery/apps/course_metadata/migrations/0018_auto_20160815_2252.py
+177
-0
course_discovery/apps/course_metadata/models.py
+39
-22
course_discovery/apps/course_metadata/tests/factories.py
+22
-19
course_discovery/apps/course_metadata/tests/mock_data.py
+213
-8
course_discovery/apps/course_metadata/tests/test_models.py
+33
-3
No files found.
course_discovery/apps/api/serializers.py
View file @
a9c5877c
...
...
@@ -149,11 +149,10 @@ class SeatSerializer(serializers.ModelSerializer):
class
PersonSerializer
(
serializers
.
ModelSerializer
):
"""Serializer for the ``Person`` model."""
profile_image
=
ImageSerializer
()
class
Meta
(
object
):
model
=
Person
fields
=
(
'
key'
,
'name'
,
'title'
,
'bio'
,
'profile_image
'
,)
fields
=
(
'
uuid'
,
'given_name'
,
'family_name'
,
'bio'
,
'profile_image_url'
,
'slug
'
,)
class
OrganizationSerializer
(
TaggitSerializer
,
serializers
.
ModelSerializer
):
...
...
course_discovery/apps/api/tests/test_serializers.py
View file @
a9c5877c
...
...
@@ -423,15 +423,15 @@ class SeatSerializerTests(TestCase):
class
PersonSerializerTests
(
TestCase
):
def
test_data
(
self
):
person
=
PersonFactory
()
image
=
person
.
profile_image
serializer
=
PersonSerializer
(
person
)
expected
=
{
'
key'
:
person
.
key
,
'
name'
:
person
.
name
,
'
title'
:
person
.
titl
e
,
'
uuid'
:
str
(
person
.
uuid
)
,
'
given_name'
:
person
.
given_
name
,
'
family_name'
:
person
.
family_nam
e
,
'bio'
:
person
.
bio
,
'profile_image'
:
ImageSerializer
(
image
)
.
data
'profile_image_url'
:
person
.
profile_image_url
,
'slug'
:
person
.
slug
,
}
self
.
assertDictEqual
(
serializer
.
data
,
expected
)
...
...
course_discovery/apps/course_metadata/admin.py
View file @
a9c5877c
...
...
@@ -14,6 +14,11 @@ class SeatInline(admin.TabularInline):
extra
=
1
class
PositionInline
(
admin
.
TabularInline
):
model
=
Position
extra
=
0
@admin.register
(
Course
)
class
CourseAdmin
(
admin
.
ModelAdmin
):
inlines
=
(
CourseOrganizationInline
,)
...
...
@@ -82,10 +87,14 @@ class SubjectAdmin(admin.ModelAdmin):
search_fields
=
(
'uuid'
,
'name'
,
'slug'
,)
class
KeyNameAdmin
(
admin
.
ModelAdmin
):
list_display
=
(
'key'
,
'name'
,)
ordering
=
(
'key'
,
'name'
,)
search_fields
=
(
'key'
,
'name'
,)
@admin.register
(
Person
)
class
PersonAdmin
(
admin
.
ModelAdmin
):
inlines
=
(
PositionInline
,)
list_display
=
(
'uuid'
,
'family_name'
,
'given_name'
,
'slug'
,)
list_filter
=
(
'partner'
,)
ordering
=
(
'family_name'
,
'given_name'
,
'uuid'
,)
readonly_fields
=
(
'uuid'
,)
search_fields
=
(
'uuid'
,
'family_name'
,
'given_name'
,
'slug'
,)
class
NamedModelAdmin
(
admin
.
ModelAdmin
):
...
...
@@ -94,12 +103,8 @@ class NamedModelAdmin(admin.ModelAdmin):
search_fields
=
(
'name'
,)
# Register key-name models
for
model
in
(
Person
,):
admin
.
site
.
register
(
model
,
KeyNameAdmin
)
# Register children of AbstractNamedModel
for
model
in
(
LevelType
,
Prerequisite
,
Expertise
,
MajorWork
):
for
model
in
(
LevelType
,
Prerequisite
,):
admin
.
site
.
register
(
model
,
NamedModelAdmin
)
# Register remaining models using basic ModelAdmin classes
...
...
course_discovery/apps/course_metadata/data_loaders/__init__.py
View file @
a9c5877c
...
...
@@ -121,7 +121,7 @@ class AbstractDataLoader(metaclass=abc.ABCMeta):
@classmethod
def
delete_orphans
(
cls
):
""" Remove orphaned objects from the database. """
for
model
in
(
Image
,
Person
,
Video
):
for
model
in
(
Image
,
Video
):
delete_orphans
(
model
)
@classmethod
...
...
course_discovery/apps/course_metadata/data_loaders/marketing_site.py
View file @
a9c5877c
import
abc
import
logging
from
urllib.parse
import
urljoin
,
urlencode
from
uuid
import
UUID
import
requests
from
django.db.models
import
Q
from
django.utils.functional
import
cached_property
from
course_discovery.apps.course_metadata.data_loaders
import
AbstractDataLoader
from
course_discovery.apps.course_metadata.models
import
(
Course
,
CourseOrganization
,
CourseRun
,
Image
,
LanguageTag
,
LevelType
,
Organization
,
Person
,
Subject
,
Program
,
Position
,
)
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -122,15 +125,9 @@ class DrupalApiDataLoader(AbstractDataLoader):
def
set_staff
(
self
,
course_run
,
body
):
"""Update `course_run` with staff from `body`."""
course_run
.
staff
.
clear
()
for
staff_body
in
body
[
'staff'
]:
image
,
__
=
Image
.
objects
.
get_or_create
(
src
=
staff_body
[
'image'
])
defaults
=
{
'name'
:
staff_body
[
'title'
],
'profile_image'
:
image
,
'title'
:
staff_body
[
'display_position'
][
'title'
],
}
person
,
__
=
Person
.
objects
.
update_or_create
(
key
=
staff_body
[
'uuid'
],
defaults
=
defaults
)
course_run
.
staff
.
add
(
person
)
uuids
=
[
staff
[
'uuid'
]
for
staff
in
body
[
'staff'
]]
staff
=
Person
.
objects
.
filter
(
uuid_in
=
uuids
)
course_run
.
staff
.
add
(
*
staff
)
def
get_language_tag
(
self
,
body
):
"""Get a language tag from Drupal data given by `body`."""
...
...
@@ -366,3 +363,72 @@ class SponsorMarketingSiteDataLoader(AbstractMarketingSiteDataLoader):
logger
.
info
(
'Processed sponsor with UUID [
%
s].'
,
uuid
)
return
sponsor
class
PersonMarketingSiteDataLoader
(
AbstractMarketingSiteDataLoader
):
@property
def
node_type
(
self
):
return
'person'
def
process_node
(
self
,
data
):
uuid
=
UUID
(
data
[
'uuid'
])
defaults
=
{
'given_name'
:
data
[
'field_person_first_middle_name'
],
'family_name'
:
data
[
'field_person_last_name'
],
'bio'
:
self
.
clean_html
(
data
[
'field_person_resume'
][
'value'
]),
'profile_image_url'
:
self
.
_get_nested_url
(
data
.
get
(
'field_person_image'
)),
}
person
,
created
=
Person
.
objects
.
update_or_create
(
uuid
=
uuid
,
partner
=
self
.
partner
,
defaults
=
defaults
)
# NOTE (CCB): The AutoSlug field kicks in at creation time. We need to apply overrides in a separate
# operation.
if
created
:
person
.
slug
=
data
[
'url'
]
.
split
(
'/'
)[
-
1
]
person
.
save
()
self
.
set_position
(
person
,
data
)
logger
.
info
(
'Processed person with UUID [
%
s].'
,
uuid
)
return
person
def
set_position
(
self
,
person
,
data
):
uuid
=
data
[
'uuid'
]
try
:
data
=
data
.
get
(
'field_person_positions'
,
[])
if
data
:
data
=
data
[
0
]
# NOTE (CCB): This is not a typo. The field is misspelled on the marketing site.
titles
=
data
[
'field_person_position_tiltes'
]
if
titles
:
title
=
titles
[
0
]
# NOTE (CCB): Not all positions are associated with organizations.
organization
=
None
organization_name
=
(
data
.
get
(
'field_person_position_org_link'
,
{})
or
{})
.
get
(
'title'
)
if
organization_name
:
try
:
# TODO Consider using Elasticsearch as a method of finding better inexact matches.
organization
=
Organization
.
objects
.
get
(
Q
(
name__iexact
=
organization_name
)
|
Q
(
key__iexact
=
organization_name
)
&
Q
(
partner
=
self
.
partner
))
except
Organization
.
DoesNotExist
:
pass
defaults
=
{
'title'
:
title
,
'organization'
:
None
,
'organization_override'
:
None
,
}
if
organization
:
defaults
[
'organization'
]
=
organization
else
:
defaults
[
'organization_override'
]
=
organization_name
Position
.
objects
.
update_or_create
(
person
=
person
,
defaults
=
defaults
)
except
:
# pylint: disable=bare-except
logger
.
exception
(
'Failed to set position for person with UUID [
%
s]!'
,
uuid
)
course_discovery/apps/course_metadata/data_loaders/tests/test_api.py
View file @
a9c5877c
...
...
@@ -18,7 +18,7 @@ from course_discovery.apps.course_metadata.models import (
)
from
course_discovery.apps.course_metadata.tests
import
mock_data
from
course_discovery.apps.course_metadata.tests.factories
import
(
CourseRunFactory
,
SeatFactory
,
ImageFactory
,
PersonFactory
,
VideoFactory
,
OrganizationFactory
,
CourseFactory
,
CourseRunFactory
,
SeatFactory
,
ImageFactory
,
VideoFactory
,
OrganizationFactory
,
CourseFactory
,
)
LOGGER_PATH
=
'course_discovery.apps.course_metadata.data_loaders.api.logger'
...
...
@@ -52,7 +52,7 @@ class AbstractDataLoaderTest(TestCase):
def
test_delete_orphans
(
self
):
""" Verify the delete_orphans method deletes orphaned instances. """
instances
=
(
ImageFactory
(),
PersonFactory
(),
VideoFactory
(),)
instances
=
(
ImageFactory
(),
VideoFactory
(),)
AbstractDataLoader
.
delete_orphans
()
for
instance
in
instances
:
...
...
course_discovery/apps/course_metadata/data_loaders/tests/test_marketing_site.py
View file @
a9c5877c
...
...
@@ -10,15 +10,15 @@ from opaque_keys.edx.keys import CourseKey
from
course_discovery.apps.course_metadata.data_loaders.marketing_site
import
(
DrupalApiDataLoader
,
XSeriesMarketingSiteDataLoader
,
SubjectMarketingSiteDataLoader
,
SchoolMarketingSiteDataLoader
,
SponsorMarketingSiteDataLoader
,
SponsorMarketingSiteDataLoader
,
PersonMarketingSiteDataLoader
,
)
from
course_discovery.apps.course_metadata.data_loaders.tests
import
JSON
from
course_discovery.apps.course_metadata.data_loaders.tests.mixins
import
ApiClientTestMixin
,
DataLoaderTestMixin
from
course_discovery.apps.course_metadata.models
import
(
Course
,
CourseOrganization
,
CourseRun
,
Organization
,
Person
,
Subject
,
Program
,
Video
,
Course
,
CourseOrganization
,
CourseRun
,
Organization
,
Subject
,
Program
,
Video
,
Person
,
)
from
course_discovery.apps.course_metadata.tests
import
mock_data
from
course_discovery.apps.course_metadata.tests.factories
import
ProgramFactory
from
course_discovery.apps.course_metadata.tests.factories
import
ProgramFactory
,
OrganizationFactory
from
course_discovery.apps.ietf_language_tags.models
import
LanguageTag
ENGLISH_LANGUAGE_TAG
=
LanguageTag
(
code
=
'en-us'
,
name
=
'English - United States'
)
...
...
@@ -37,15 +37,13 @@ class DrupalApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCase
super
(
DrupalApiDataLoaderTests
,
self
)
.
setUp
()
for
course_dict
in
mock_data
.
EXISTING_COURSE_AND_RUN_DATA
:
course
=
Course
.
objects
.
create
(
key
=
course_dict
[
'course_key'
],
title
=
course_dict
[
'title'
])
course_run
=
CourseRun
.
objects
.
create
(
CourseRun
.
objects
.
create
(
key
=
course_dict
[
'course_run_key'
],
language
=
self
.
loader
.
get_language_tag
(
course_dict
),
course
=
course
)
# Add some data that doesn't exist in Drupal already
person
=
Person
.
objects
.
create
(
key
=
'orphan_staff_'
+
course_run
.
key
)
course_run
.
staff
.
add
(
person
)
organization
=
Organization
.
objects
.
create
(
key
=
'orphan_org_'
+
course
.
key
)
CourseOrganization
.
objects
.
create
(
organization
=
organization
,
...
...
@@ -54,7 +52,6 @@ class DrupalApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCase
)
Course
.
objects
.
create
(
key
=
mock_data
.
EXISTING_COURSE
[
'course_key'
],
title
=
mock_data
.
EXISTING_COURSE
[
'title'
])
Person
.
objects
.
create
(
key
=
mock_data
.
ORPHAN_STAFF_KEY
)
Organization
.
objects
.
create
(
key
=
mock_data
.
ORPHAN_ORGANIZATION_KEY
)
def
create_mock_subjects
(
self
,
course_runs
):
...
...
@@ -93,23 +90,12 @@ class DrupalApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCase
self
.
assertEqual
(
course_run
.
course
,
course
)
self
.
assert_course_loaded
(
course
,
body
)
self
.
assert_staff_loaded
(
course_run
,
body
)
if
course_run
.
language
:
self
.
assertEqual
(
course_run
.
language
.
code
,
body
[
'current_language'
])
else
:
self
.
assertEqual
(
body
[
'current_language'
],
''
)
def
assert_staff_loaded
(
self
,
course_run
,
body
):
"""Verify that staff have been loaded correctly."""
course_run_staff
=
course_run
.
staff
.
all
()
api_staff
=
body
[
'staff'
]
self
.
assertEqual
(
len
(
course_run_staff
),
len
(
api_staff
))
for
api_staff_member
in
api_staff
:
loaded_staff_member
=
Person
.
objects
.
get
(
key
=
api_staff_member
[
'uuid'
])
self
.
assertIn
(
loaded_staff_member
,
course_run_staff
)
def
assert_course_loaded
(
self
,
course
,
body
):
"""Verify that the course has been loaded correctly."""
self
.
assertEqual
(
course
.
title
,
body
[
'title'
])
...
...
@@ -162,9 +148,7 @@ class DrupalApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestCase
self
.
loader
.
ingest
()
# Verify that orphan data is deleted
self
.
assertFalse
(
Person
.
objects
.
filter
(
key
=
mock_data
.
ORPHAN_STAFF_KEY
)
.
exists
())
self
.
assertFalse
(
Organization
.
objects
.
filter
(
key
=
mock_data
.
ORPHAN_ORGANIZATION_KEY
)
.
exists
())
self
.
assertFalse
(
Person
.
objects
.
filter
(
key__startswith
=
'orphan_staff_'
)
.
exists
())
self
.
assertFalse
(
Organization
.
objects
.
filter
(
key__startswith
=
'orphan_org_'
)
.
exists
())
@responses.activate
...
...
@@ -506,3 +490,56 @@ class SponsorMarketingSiteDataLoaderTests(AbstractMarketingSiteDataLoaderTestMix
for
sponsor
in
sponsors
:
self
.
assert_sponsor_loaded
(
sponsor
)
class
PersonMarketingSiteDataLoaderTests
(
AbstractMarketingSiteDataLoaderTestMixin
,
TestCase
):
loader_class
=
PersonMarketingSiteDataLoader
def
mock_api
(
self
):
bodies
=
mock_data
.
MARKETING_SITE_API_PERSON_BODIES
url
=
self
.
api_url
+
'node.json'
responses
.
add_callback
(
responses
.
GET
,
url
,
callback
=
self
.
mock_api_callback
(
url
,
bodies
),
content_type
=
JSON
)
return
bodies
def
assert_person_loaded
(
self
,
data
):
uuid
=
data
[
'uuid'
]
person
=
Person
.
objects
.
get
(
uuid
=
uuid
,
partner
=
self
.
partner
)
expected_values
=
{
'given_name'
:
data
[
'field_person_first_middle_name'
],
'family_name'
:
data
[
'field_person_last_name'
],
'bio'
:
self
.
loader
.
clean_html
(
data
[
'field_person_resume'
][
'value'
]),
'profile_image_url'
:
data
[
'field_person_image'
][
'url'
],
'slug'
:
data
[
'url'
]
.
split
(
'/'
)[
-
1
],
}
for
field
,
value
in
expected_values
.
items
():
self
.
assertEqual
(
getattr
(
person
,
field
),
value
)
positions
=
data
[
'field_person_positions'
]
if
positions
:
position_data
=
positions
[
0
]
titles
=
position_data
[
'field_person_position_tiltes'
]
if
titles
:
self
.
assertEqual
(
person
.
position
.
title
,
titles
[
0
])
self
.
assertEqual
(
person
.
position
.
organization_name
,
(
position_data
.
get
(
'field_person_position_org_link'
)
or
{})
.
get
(
'title'
))
@responses.activate
def
test_ingest
(
self
):
self
.
mock_login_response
()
people
=
self
.
mock_api
()
OrganizationFactory
(
name
=
'MIT'
)
self
.
loader
.
ingest
()
for
person
in
people
:
self
.
assert_person_loaded
(
person
)
course_discovery/apps/course_metadata/management/commands/refresh_course_metadata.py
View file @
a9c5877c
...
...
@@ -9,7 +9,7 @@ from course_discovery.apps.course_metadata.data_loaders.api import (
)
from
course_discovery.apps.course_metadata.data_loaders.marketing_site
import
(
DrupalApiDataLoader
,
XSeriesMarketingSiteDataLoader
,
SubjectMarketingSiteDataLoader
,
SchoolMarketingSiteDataLoader
,
SponsorMarketingSiteDataLoader
,
SponsorMarketingSiteDataLoader
,
PersonMarketingSiteDataLoader
,
)
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -82,6 +82,7 @@ class Command(BaseCommand):
(
partner
.
marketing_site_url_root
,
SubjectMarketingSiteDataLoader
,),
(
partner
.
marketing_site_url_root
,
SchoolMarketingSiteDataLoader
,),
(
partner
.
marketing_site_url_root
,
SponsorMarketingSiteDataLoader
,),
(
partner
.
marketing_site_url_root
,
PersonMarketingSiteDataLoader
,),
(
partner
.
organizations_api_url
,
OrganizationsApiDataLoader
,),
(
partner
.
courses_api_url
,
CoursesApiDataLoader
,),
(
partner
.
ecommerce_api_url
,
EcommerceApiDataLoader
,),
...
...
course_discovery/apps/course_metadata/migrations/0018_auto_20160815_2252.py
0 → 100644
View file @
a9c5877c
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
import
django_extensions.db.fields
import
uuid
import
django.db.models.deletion
def
delete_people
(
apps
,
schema_editor
):
Person
=
apps
.
get_model
(
'course_metadata'
,
'Person'
)
Person
.
objects
.
all
()
.
delete
()
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0010_auto_20160731_0023'
),
(
'course_metadata'
,
'0017_auto_20160815_2135'
),
]
operations
=
[
migrations
.
RunPython
(
delete_people
,
reverse_code
=
migrations
.
RunPython
.
noop
),
migrations
.
CreateModel
(
name
=
'Position'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
serialize
=
False
,
primary_key
=
True
,
verbose_name
=
'ID'
,
auto_created
=
True
)),
(
'created'
,
django_extensions
.
db
.
fields
.
CreationDateTimeField
(
auto_now_add
=
True
,
verbose_name
=
'created'
)),
(
'modified'
,
django_extensions
.
db
.
fields
.
ModificationDateTimeField
(
auto_now
=
True
,
verbose_name
=
'modified'
)),
(
'title'
,
models
.
CharField
(
max_length
=
255
)),
(
'organization_override'
,
models
.
CharField
(
max_length
=
255
,
blank
=
True
,
null
=
True
)),
(
'organization'
,
models
.
ForeignKey
(
null
=
True
,
to
=
'course_metadata.Organization'
,
blank
=
True
)),
],
options
=
{
'ordering'
:
(
'-modified'
,
'-created'
),
'abstract'
:
False
,
'get_latest_by'
:
'modified'
,
},
),
migrations
.
RemoveField
(
model_name
=
'historicalperson'
,
name
=
'email'
,
),
migrations
.
RemoveField
(
model_name
=
'historicalperson'
,
name
=
'key'
,
),
migrations
.
RemoveField
(
model_name
=
'historicalperson'
,
name
=
'name'
,
),
migrations
.
RemoveField
(
model_name
=
'historicalperson'
,
name
=
'profile_image'
,
),
migrations
.
RemoveField
(
model_name
=
'historicalperson'
,
name
=
'title'
,
),
migrations
.
RemoveField
(
model_name
=
'historicalperson'
,
name
=
'username'
,
),
migrations
.
AddField
(
model_name
=
'historicalperson'
,
name
=
'family_name'
,
field
=
models
.
CharField
(
max_length
=
255
,
blank
=
True
,
null
=
True
),
),
migrations
.
AddField
(
model_name
=
'historicalperson'
,
name
=
'given_name'
,
field
=
models
.
CharField
(
default
=
''
,
max_length
=
255
),
preserve_default
=
False
,
),
migrations
.
AddField
(
model_name
=
'historicalperson'
,
name
=
'partner'
,
field
=
models
.
ForeignKey
(
on_delete
=
django
.
db
.
models
.
deletion
.
DO_NOTHING
,
null
=
True
,
db_constraint
=
False
,
to
=
'core.Partner'
,
related_name
=
'+'
,
blank
=
True
),
),
migrations
.
AddField
(
model_name
=
'historicalperson'
,
name
=
'profile_image_url'
,
field
=
models
.
URLField
(
blank
=
True
,
null
=
True
),
),
migrations
.
AddField
(
model_name
=
'historicalperson'
,
name
=
'slug'
,
field
=
django_extensions
.
db
.
fields
.
AutoSlugField
(
populate_from
=
(
'given_name'
,
'family_name'
),
blank
=
True
,
editable
=
False
),
),
migrations
.
AddField
(
model_name
=
'historicalperson'
,
name
=
'uuid'
,
field
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
verbose_name
=
'UUID'
,
editable
=
False
),
),
migrations
.
AddField
(
model_name
=
'person'
,
name
=
'family_name'
,
field
=
models
.
CharField
(
max_length
=
255
,
blank
=
True
,
null
=
True
),
),
migrations
.
AddField
(
model_name
=
'person'
,
name
=
'given_name'
,
field
=
models
.
CharField
(
default
=
''
,
max_length
=
255
),
preserve_default
=
False
,
),
migrations
.
AddField
(
model_name
=
'person'
,
name
=
'partner'
,
field
=
models
.
ForeignKey
(
null
=
True
,
to
=
'core.Partner'
),
),
migrations
.
AddField
(
model_name
=
'person'
,
name
=
'profile_image_url'
,
field
=
models
.
URLField
(
blank
=
True
,
null
=
True
),
),
migrations
.
AddField
(
model_name
=
'person'
,
name
=
'slug'
,
field
=
django_extensions
.
db
.
fields
.
AutoSlugField
(
populate_from
=
(
'given_name'
,
'family_name'
),
blank
=
True
,
editable
=
False
),
),
migrations
.
AddField
(
model_name
=
'person'
,
name
=
'uuid'
,
field
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
verbose_name
=
'UUID'
,
editable
=
False
),
),
migrations
.
AlterUniqueTogether
(
name
=
'person'
,
unique_together
=
set
([(
'partner'
,
'uuid'
)]),
),
migrations
.
AddField
(
model_name
=
'position'
,
name
=
'person'
,
field
=
models
.
OneToOneField
(
to
=
'course_metadata.Person'
),
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'email'
,
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'expertises'
,
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'key'
,
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'major_works'
,
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'name'
,
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'organizations'
,
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'profile_image'
,
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'title'
,
),
migrations
.
RemoveField
(
model_name
=
'person'
,
name
=
'username'
,
),
migrations
.
DeleteModel
(
name
=
'Expertise'
,
),
migrations
.
DeleteModel
(
name
=
'MajorWork'
,
),
]
course_discovery/apps/course_metadata/models.py
View file @
a9c5877c
...
...
@@ -141,16 +141,6 @@ class SyllabusItem(AbstractValueModel):
parent
=
models
.
ForeignKey
(
'self'
,
blank
=
True
,
null
=
True
,
related_name
=
'children'
)
class
Expertise
(
AbstractNamedModel
):
""" Expertise model. """
pass
class
MajorWork
(
AbstractNamedModel
):
""" MajorWork model. """
pass
class
Organization
(
TimeStampedModel
):
""" Organization model. """
partner
=
models
.
ForeignKey
(
Partner
,
null
=
True
,
blank
=
False
)
...
...
@@ -178,24 +168,51 @@ class Organization(TimeStampedModel):
class
Person
(
TimeStampedModel
):
""" Person model. """
key
=
models
.
CharField
(
max_length
=
255
,
unique
=
True
)
name
=
models
.
CharField
(
max_length
=
255
,
null
=
True
,
blank
=
True
)
title
=
models
.
CharField
(
max_length
=
255
,
null
=
True
,
blank
=
True
)
uuid
=
models
.
UUIDField
(
blank
=
False
,
null
=
False
,
default
=
uuid4
,
editable
=
False
,
verbose_name
=
_
(
'UUID'
))
partner
=
models
.
ForeignKey
(
Partner
,
null
=
True
,
blank
=
False
)
given_name
=
models
.
CharField
(
max_length
=
255
)
family_name
=
models
.
CharField
(
max_length
=
255
,
null
=
True
,
blank
=
True
)
bio
=
models
.
TextField
(
null
=
True
,
blank
=
True
)
profile_image
=
models
.
ForeignKey
(
Image
,
null
=
True
,
blank
=
True
)
organizations
=
models
.
ManyToManyField
(
Organization
,
blank
=
True
)
email
=
models
.
EmailField
(
max_length
=
255
,
null
=
True
,
blank
=
True
)
username
=
models
.
CharField
(
max_length
=
255
,
null
=
True
,
blank
=
True
)
expertises
=
SortedManyToManyField
(
Expertise
,
blank
=
True
,
related_name
=
'person_expertise'
)
major_works
=
SortedManyToManyField
(
MajorWork
,
blank
=
True
,
related_name
=
'person_works'
)
profile_image_url
=
models
.
URLField
(
null
=
True
,
blank
=
True
)
slug
=
AutoSlugField
(
populate_from
=
(
'given_name'
,
'family_name'
),
editable
=
True
)
history
=
HistoricalRecords
()
class
Meta
:
unique_together
=
(
(
'partner'
,
'uuid'
),
)
verbose_name_plural
=
_
(
'People'
)
def
__str__
(
self
):
return
'{key}: {name}'
.
format
(
key
=
self
.
key
,
name
=
self
.
name
)
return
self
.
full_name
class
Meta
(
object
):
verbose_name_plural
=
'People'
@property
def
full_name
(
self
):
return
' '
.
join
((
self
.
given_name
,
self
.
family_name
,))
class
Position
(
TimeStampedModel
):
""" Position model.
This model represent's a `Person`'s role at an organization.
"""
person
=
models
.
OneToOneField
(
Person
)
title
=
models
.
CharField
(
max_length
=
255
)
organization
=
models
.
ForeignKey
(
Organization
,
null
=
True
,
blank
=
True
)
organization_override
=
models
.
CharField
(
max_length
=
255
,
null
=
True
,
blank
=
True
)
def
__str__
(
self
):
return
'{title} at {organization}'
.
format
(
title
=
self
.
title
,
organization
=
self
.
organization_name
)
@property
def
organization_name
(
self
):
name
=
self
.
organization_override
if
self
.
organization
and
not
name
:
name
=
self
.
organization
.
name
return
name
class
Course
(
TimeStampedModel
):
...
...
course_discovery/apps/course_metadata/tests/factories.py
View file @
a9c5877c
from
datetime
import
datetime
from
uuid
import
uuid4
import
factory
from
factory.fuzzy
import
(
FuzzyText
,
FuzzyChoice
,
FuzzyDateTime
,
FuzzyInteger
,
FuzzyDecimal
)
from
factory.fuzzy
import
FuzzyText
,
FuzzyChoice
,
FuzzyDateTime
,
FuzzyInteger
,
FuzzyDecimal
from
pytz
import
UTC
from
course_discovery.apps.core.models
import
Currency
from
course_discovery.apps.core.tests.factories
import
PartnerFactory
from
course_discovery.apps.core.tests.utils
import
FuzzyURL
from
course_discovery.apps.course_metadata.models
import
(
Course
,
CourseRun
,
Organization
,
Person
,
Image
,
Video
,
Subject
,
Seat
,
Prerequisite
,
LevelType
,
Program
,
AbstractSocialNetworkModel
,
CourseRunSocialNetwork
,
PersonSocialNetwork
,
ProgramType
,
SeatType
,
)
from
course_discovery.apps.course_metadata.models
import
*
# pylint: disable=wildcard-import
from
course_discovery.apps.ietf_language_tags.models
import
LanguageTag
...
...
@@ -68,7 +61,7 @@ class SeatFactory(factory.DjangoModelFactory):
type
=
FuzzyChoice
([
name
for
name
,
__
in
Seat
.
SEAT_TYPE_CHOICES
])
price
=
FuzzyDecimal
(
0.0
,
650.0
)
currency
=
factory
.
Iterator
(
Currency
.
objects
.
all
())
upgrade_deadline
=
FuzzyDateTime
(
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
upgrade_deadline
=
FuzzyDateTime
(
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
class
Meta
:
model
=
Seat
...
...
@@ -106,11 +99,11 @@ class CourseRunFactory(factory.DjangoModelFactory):
short_description_override
=
None
full_description_override
=
None
language
=
factory
.
Iterator
(
LanguageTag
.
objects
.
all
())
start
=
FuzzyDateTime
(
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
end
=
FuzzyDateTime
(
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
.
end_dt
enrollment_start
=
FuzzyDateTime
(
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
enrollment_end
=
FuzzyDateTime
(
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
.
end_dt
announcement
=
FuzzyDateTime
(
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
start
=
FuzzyDateTime
(
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
end
=
FuzzyDateTime
(
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
.
end_dt
enrollment_start
=
FuzzyDateTime
(
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
enrollment_end
=
FuzzyDateTime
(
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
.
end_dt
announcement
=
FuzzyDateTime
(
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
image
=
factory
.
SubFactory
(
ImageFactory
)
video
=
factory
.
SubFactory
(
VideoFactory
)
min_effort
=
FuzzyInteger
(
1
,
10
)
...
...
@@ -146,16 +139,26 @@ class OrganizationFactory(factory.DjangoModelFactory):
class
PersonFactory
(
factory
.
DjangoModelFactory
):
key
=
FuzzyText
(
prefix
=
'Person.fake/'
)
name
=
FuzzyText
()
title
=
FuzzyText
()
uuid
=
factory
.
LazyFunction
(
uuid4
)
partner
=
factory
.
SubFactory
(
PartnerFactory
)
given_name
=
factory
.
Faker
(
'first_name'
)
family_name
=
factory
.
Faker
(
'last_name'
)
bio
=
FuzzyText
()
profile_image
=
factory
.
SubFactory
(
ImageFactory
)
profile_image
_url
=
FuzzyURL
(
)
class
Meta
:
model
=
Person
class
PositionFactory
(
factory
.
DjangoModelFactory
):
person
=
factory
.
SubFactory
(
PersonFactory
)
title
=
FuzzyText
()
organization
=
factory
.
SubFactory
(
OrganizationFactory
)
class
Meta
:
model
=
Position
class
ProgramTypeFactory
(
factory
.
django
.
DjangoModelFactory
):
class
Meta
(
object
):
model
=
ProgramType
...
...
course_discovery/apps/course_metadata/tests/mock_data.py
View file @
a9c5877c
This diff is collapsed.
Click to expand it.
course_discovery/apps/course_metadata/tests/test_models.py
View file @
a9c5877c
...
...
@@ -202,10 +202,38 @@ class OrganizationTests(TestCase):
class
PersonTests
(
TestCase
):
""" Tests for the `Person` model. """
def
setUp
(
self
):
super
(
PersonTests
,
self
)
.
setUp
()
self
.
person
=
factories
.
PersonFactory
()
def
test_full_name
(
self
):
""" Verify the property returns the person's full name. """
expected
=
self
.
person
.
given_name
+
' '
+
self
.
person
.
family_name
self
.
assertEqual
(
self
.
person
.
full_name
,
expected
)
def
test_str
(
self
):
""" Verify casting an instance to a string returns a string containing the key and name. """
person
=
factories
.
PersonFactory
()
self
.
assertEqual
(
str
(
person
),
'{key}: {name}'
.
format
(
key
=
person
.
key
,
name
=
person
.
name
))
""" Verify casting an instance to a string returns the person's full name. """
self
.
assertEqual
(
str
(
self
.
person
),
self
.
person
.
full_name
)
class
PositionTests
(
TestCase
):
""" Tests for the `Position` model. """
def
setUp
(
self
):
super
(
PositionTests
,
self
)
.
setUp
()
self
.
position
=
factories
.
PositionFactory
()
def
test_organization_name
(
self
):
""" Verify the property returns the name of the related Organization or the overridden value. """
self
.
assertEqual
(
self
.
position
.
organization_name
,
self
.
position
.
organization
.
name
)
self
.
position
.
organization_override
=
'ACME'
self
.
assertEqual
(
self
.
position
.
organization_name
,
self
.
position
.
organization_override
)
def
test_str
(
self
):
""" Verify casting an instance to a string returns the title and organization. """
expected
=
self
.
position
.
title
+
' at '
+
self
.
position
.
organization_name
self
.
assertEqual
(
str
(
self
.
position
),
expected
)
class
AbstractNamedModelTests
(
TestCase
):
...
...
@@ -378,6 +406,7 @@ class CourseSocialNetworkTests(TestCase):
class
SeatTypeTests
(
TestCase
):
""" Tests of the SeatType model. """
def
test_str
(
self
):
seat_type
=
factories
.
SeatTypeFactory
()
self
.
assertEqual
(
str
(
seat_type
),
seat_type
.
name
)
...
...
@@ -385,6 +414,7 @@ class SeatTypeTests(TestCase):
class
ProgramTypeTests
(
TestCase
):
""" Tests of the ProgramType model. """
def
test_str
(
self
):
program_type
=
factories
.
ProgramTypeFactory
()
self
.
assertEqual
(
str
(
program_type
),
program_type
.
name
)
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