Commit a9c5877c by Clinton Blackburn Committed by GitHub

Updated Person model and data loader (#243)

ECOM-5193 and ECOM-5194
parent d5a47469
......@@ -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):
......
......@@ -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.title,
'uuid': str(person.uuid),
'given_name': person.given_name,
'family_name': person.family_name,
'bio': person.bio,
'profile_image': ImageSerializer(image).data
'profile_image_url': person.profile_image_url,
'slug': person.slug,
}
self.assertDictEqual(serializer.data, expected)
......
......@@ -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
......
......@@ -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
......
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)
......@@ -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:
......
......@@ -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)
......@@ -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,),
......
# -*- 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',
),
]
......@@ -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):
......
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
......
......@@ -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)
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