Commit 5d48d785 by Clinton Blackburn Committed by GitHub

Merge pull request #191 from edx/clintonb/partner-admin-cleanup

Updated Partner model and admin
parents c138c0a2 b98c25dc
......@@ -38,6 +38,24 @@ class CurrencyAdmin(admin.ModelAdmin):
@admin.register(Partner)
class PartnerAdmin(admin.ModelAdmin):
fieldsets = (
(None, {
'fields': ('name', 'short_code',)
}),
(_('OpenID Connect'), {
'description': _(
'OpenID Connect is used for front-end authentication as well as getting access to the APIs.'),
'fields': ('oidc_url_root', 'oidc_key', 'oidc_secret',)
}),
(_('API Configuration'), {
'description': _('Configure the APIs that will be used to retrieve data.'),
'fields': ('courses_api_url', 'ecommerce_api_url', 'organizations_api_url', 'programs_api_url',)
}),
(_('Marketing Site Configuration'), {
'description': _('Configure the marketing site URLs that will be used to retrieve data and create URLs.'),
'fields': ('marketing_site_url_root', 'marketing_site_api_url',)
}),
)
list_display = ('name', 'short_code',)
ordering = ('name', 'short_code',)
search_fields = ('name', 'short_code',)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_partner'),
]
operations = [
migrations.AlterField(
model_name='partner',
name='courses_api_url',
field=models.URLField(null=True, verbose_name='Courses API URL', max_length=255, blank=True),
),
migrations.AlterField(
model_name='partner',
name='ecommerce_api_url',
field=models.URLField(null=True, verbose_name='E-Commerce API URL', max_length=255, blank=True),
),
migrations.AlterField(
model_name='partner',
name='marketing_api_url',
field=models.URLField(blank=True, null=True, max_length=255, verbose_name='Marketing Site API URL'),
),
migrations.AlterField(
model_name='partner',
name='marketing_url_root',
field=models.URLField(blank=True, null=True, max_length=255, verbose_name='Marketing Site URL'),
),
migrations.AlterField(
model_name='partner',
name='organizations_api_url',
field=models.URLField(null=True, verbose_name='Organizations API URL', max_length=255, blank=True),
),
migrations.AlterField(
model_name='partner',
name='programs_api_url',
field=models.URLField(blank=True, null=True, max_length=255, verbose_name='Programs API URL'),
),
migrations.AlterField(
model_name='partner',
name='short_code',
field=models.CharField(unique=True, help_text='Convenient code/slug used to identify this Partner (e.g. for management commands.)', max_length=8, verbose_name='Short Code'),
),
migrations.AlterField(
model_name='partner',
name='social_auth_edx_oidc_key',
field=models.CharField(null=True, max_length=255, verbose_name='OpenID Connect Key'),
),
migrations.AlterField(
model_name='partner',
name='social_auth_edx_oidc_secret',
field=models.CharField(null=True, max_length=255, verbose_name='OpenID Connect Secret'),
),
migrations.AlterField(
model_name='partner',
name='social_auth_edx_oidc_url_root',
field=models.CharField(null=True, max_length=255, verbose_name='OpenID Connect URL'),
),
migrations.RenameField(
model_name='partner',
old_name='social_auth_edx_oidc_key',
new_name='oidc_key',
),
migrations.RenameField(
model_name='partner',
old_name='social_auth_edx_oidc_secret',
new_name='oidc_secret',
),
migrations.RenameField(
model_name='partner',
old_name='social_auth_edx_oidc_url_root',
new_name='oidc_url_root',
),
migrations.RenameField(
model_name='partner',
old_name='marketing_api_url',
new_name='marketing_site_api_url',
),
migrations.RenameField(
model_name='partner',
old_name='marketing_url_root',
new_name='marketing_site_url_root',
),
]
......@@ -58,19 +58,24 @@ class Currency(models.Model):
class Partner(TimeStampedModel):
name = models.CharField(max_length=128, unique=True, null=False, blank=False)
short_code = models.CharField(max_length=8, unique=True, null=False, blank=False)
courses_api_url = models.URLField(max_length=255, null=True)
ecommerce_api_url = models.URLField(max_length=255, null=True)
organizations_api_url = models.URLField(max_length=255, null=True)
programs_api_url = models.URLField(max_length=255, null=True)
marketing_api_url = models.URLField(max_length=255, null=True)
marketing_url_root = models.URLField(max_length=255, null=True)
social_auth_edx_oidc_url_root = models.CharField(max_length=255, null=True)
social_auth_edx_oidc_key = models.CharField(max_length=255, null=True)
social_auth_edx_oidc_secret = models.CharField(max_length=255, null=True)
short_code = models.CharField(
max_length=8, unique=True, null=False, blank=False, verbose_name=_('Short Code'),
help_text=_('Convenient code/slug used to identify this Partner (e.g. for management commands.)'))
courses_api_url = models.URLField(max_length=255, null=True, blank=True, verbose_name=_('Courses API URL'))
ecommerce_api_url = models.URLField(max_length=255, null=True, blank=True, verbose_name=_('E-Commerce API URL'))
organizations_api_url = models.URLField(max_length=255, null=True, blank=True,
verbose_name=_('Organizations API URL'))
programs_api_url = models.URLField(max_length=255, null=True, blank=True, verbose_name=_('Programs API URL'))
marketing_site_api_url = models.URLField(max_length=255, null=True, blank=True,
verbose_name=_('Marketing Site API URL'))
marketing_site_url_root = models.URLField(max_length=255, null=True, blank=True,
verbose_name=_('Marketing Site URL'))
oidc_url_root = models.CharField(max_length=255, null=True, verbose_name=_('OpenID Connect URL'))
oidc_key = models.CharField(max_length=255, null=True, verbose_name=_('OpenID Connect Key'))
oidc_secret = models.CharField(max_length=255, null=True, verbose_name=_('OpenID Connect Secret'))
def __str__(self):
return '{name} ({code})'.format(name=self.name, code=self.short_code)
return self.name
class Meta:
verbose_name = _('Partner')
......
......@@ -25,11 +25,11 @@ class PartnerFactory(factory.DjangoModelFactory):
ecommerce_api_url = '{root}/api/courses/v1/'.format(root=FuzzyUrlRoot().fuzz())
organizations_api_url = '{root}/api/organizations/v1/'.format(root=FuzzyUrlRoot().fuzz())
programs_api_url = '{root}/api/programs/v1/'.format(root=FuzzyUrlRoot().fuzz())
marketing_api_url = '{root}/api/courses/v1/'.format(root=FuzzyUrlRoot().fuzz())
marketing_url_root = '{root}/'.format(root=FuzzyUrlRoot().fuzz())
social_auth_edx_oidc_url_root = '{root}'.format(root=FuzzyUrlRoot().fuzz())
social_auth_edx_oidc_key = FuzzyText().fuzz()
social_auth_edx_oidc_secret = FuzzyText().fuzz()
marketing_site_api_url = '{root}/api/courses/v1/'.format(root=FuzzyUrlRoot().fuzz())
marketing_site_url_root = '{root}/'.format(root=FuzzyUrlRoot().fuzz())
oidc_url_root = '{root}'.format(root=FuzzyUrlRoot().fuzz())
oidc_key = FuzzyText().fuzz()
oidc_secret = FuzzyText().fuzz()
class Meta(object):
model = Partner
......@@ -3,8 +3,8 @@
from django.test import TestCase
from social.apps.django_app.default.models import UserSocialAuth
from course_discovery.apps.core.models import Currency, Partner
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.core.models import Currency
from course_discovery.apps.core.tests.factories import UserFactory, PartnerFactory
class UserTests(TestCase):
......@@ -60,11 +60,7 @@ class PartnerTests(TestCase):
""" Tests for the Partner class. """
def test_str(self):
"""
Verify casting an instance to a string returns a string containing the name and short code of the partner.
"""
code = 'test'
name = 'Test Partner'
instance = Partner(name=name, short_code=code)
self.assertEqual(str(instance), '{name} ({code})'.format(name=name, code=code))
""" Verify the method returns the name of the Partner. """
partner = PartnerFactory()
self.assertEqual(str(partner), partner.name)
......@@ -275,7 +275,7 @@ class DrupalApiDataLoader(AbstractDataLoader):
"""Loads course runs from the Drupal API."""
def ingest(self):
api_url = self.partner.marketing_api_url
api_url = self.partner.marketing_site_api_url
client = self.get_api_client(api_url)
logger.info('Refreshing Courses and CourseRuns from %s...', api_url)
response = client.courses.get()
......@@ -347,7 +347,7 @@ class DrupalApiDataLoader(AbstractDataLoader):
defaults = {
'name': sponsor_body['title'],
'logo_image': image,
'homepage_url': urljoin(self.partner.marketing_url_root, sponsor_body['uri']),
'homepage_url': urljoin(self.partner.marketing_site_url_root, sponsor_body['uri']),
}
organization, __ = Organization.objects.update_or_create(key=sponsor_body['uuid'], defaults=defaults)
CourseOrganization.objects.create(
......@@ -369,7 +369,7 @@ class DrupalApiDataLoader(AbstractDataLoader):
course_run.language = self.get_language_tag(body)
course_run.course = course
course_run.marketing_url = urljoin(self.partner.marketing_url_root, body['course_about_uri'])
course_run.marketing_url = urljoin(self.partner.marketing_site_url_root, body['course_about_uri'])
course_run.start = self.parse_date(body['start'])
course_run.end = self.parse_date(body['end'])
......
......@@ -3,10 +3,10 @@ import logging
from django.core.management import BaseCommand, CommandError
from edx_rest_api_client.client import EdxRestApiClient
from course_discovery.apps.core.models import Partner
from course_discovery.apps.course_metadata.data_loaders import (
CoursesApiDataLoader, DrupalApiDataLoader, OrganizationsApiDataLoader, EcommerceApiDataLoader, ProgramsApiDataLoader
)
from course_discovery.apps.core.models import Partner
logger = logging.getLogger(__name__)
......@@ -65,9 +65,9 @@ class Command(BaseCommand):
try:
access_token, __ = EdxRestApiClient.get_oauth_access_token(
'{root}/access_token'.format(root=partner.social_auth_edx_oidc_url_root.strip('/')),
partner.social_auth_edx_oidc_key,
partner.social_auth_edx_oidc_secret,
'{root}/access_token'.format(root=partner.oidc_url_root.strip('/')),
partner.oidc_key,
partner.oidc_secret,
token_type=token_type
)
except Exception:
......@@ -82,7 +82,7 @@ class Command(BaseCommand):
loaders.append(CoursesApiDataLoader)
if partner.ecommerce_api_url:
loaders.append(EcommerceApiDataLoader)
if partner.marketing_api_url:
if partner.marketing_site_api_url:
loaders.append(DrupalApiDataLoader)
if partner.programs_api_url:
loaders.append(ProgramsApiDataLoader)
......@@ -90,10 +90,6 @@ class Command(BaseCommand):
if loaders:
for loader_class in loaders:
try:
loader_class(
partner,
access_token,
token_type,
).ingest()
loader_class(partner, access_token, token_type).ingest()
except Exception: # pylint: disable=broad-except
logger.exception('%s failed!', loader_class.__name__)
......@@ -6,28 +6,32 @@ from django.test import TestCase
from course_discovery.apps.core.tests.factories import PartnerFactory
from course_discovery.apps.core.tests.utils import mock_api_callback
from course_discovery.apps.course_metadata.models import Course, CourseRun, Organization, Partner, Program
from course_discovery.apps.course_metadata.models import Course, CourseRun, Organization, Program
from course_discovery.apps.course_metadata.tests import mock_data
ACCESS_TOKEN = 'secret'
ACCESS_TOKEN_TYPE = 'Bearer'
JSON = 'application/json'
LOGGER_NAME = 'course_metadata.management.commands.refresh_course_metadata'
class RefreshCourseMetadataCommandTests(TestCase):
def setUp(self):
super(RefreshCourseMetadataCommandTests, self).setUp()
self.partner = PartnerFactory()
self.mock_access_token_api()
self.mock_organizations_api()
self.mock_lms_courses_api()
self.mock_ecommerce_courses_api()
self.mock_marketing_courses_api()
self.mock_programs_api()
def mock_access_token_api(self):
body = {
'access_token': ACCESS_TOKEN,
'expires_in': 30
}
url = self.partner.social_auth_edx_oidc_url_root.strip('/') + '/access_token'
url = self.partner.oidc_url_root.strip('/') + '/access_token'
responses.add_callback(
responses.POST,
url,
......@@ -76,7 +80,7 @@ class RefreshCourseMetadataCommandTests(TestCase):
body = mock_data.MARKETING_API_BODY
responses.add(
responses.GET,
self.partner.marketing_api_url + 'courses/',
self.partner.marketing_site_api_url + 'courses/',
body=json.dumps(body),
status=200,
content_type='application/json'
......@@ -96,50 +100,47 @@ class RefreshCourseMetadataCommandTests(TestCase):
@responses.activate
def test_refresh_course_metadata(self):
""" Verify """
self.mock_access_token_api()
self.mock_organizations_api()
self.mock_lms_courses_api()
self.mock_ecommerce_courses_api()
self.mock_marketing_courses_api()
self.mock_programs_api()
""" Verify the refresh_course_metadata management command creates new objects. """
call_command('refresh_course_metadata')
partners = Partner.objects.all()
self.assertEqual(len(partners), 1)
organizations = Organization.objects.all()
self.assertEqual(len(organizations), 3)
for organization in organizations:
print(organization.key)
self.assertEqual(organizations.count(), 3)
for organization in organizations:
self.assertEqual(organization.partner.short_code, self.partner.short_code)
courses = Course.objects.all()
self.assertEqual(len(courses), 2)
self.assertEqual(courses.count(), 2)
for course in courses:
self.assertEqual(course.partner.short_code, self.partner.short_code)
course_runs = CourseRun.objects.all()
self.assertEqual(len(course_runs), 3)
self.assertEqual(course_runs.count(), 3)
for course_run in course_runs:
self.assertEqual(course_run.course.partner.short_code, self.partner.short_code)
programs = Program.objects.all()
self.assertEqual(len(programs), 2)
self.assertEqual(programs.count(), 2)
for program in programs:
self.assertEqual(program.partner.short_code, self.partner.short_code)
# Refresh only a specific partner
command_args = ['--partner_code={0}'.format(partners[0].short_code)]
command_args = ['--partner_code={0}'.format(self.partner.short_code)]
call_command('refresh_course_metadata', *command_args)
# Invalid partner code
@responses.activate
def test_refresh_course_metadata_with_invalid_partner_code(self):
""" Verify an error is raised if an invalid partner code is passed on the command line. """
with self.assertRaises(CommandError):
command_args = ['--partner_code=invalid']
call_command('refresh_course_metadata', *command_args)
# Access token but no token type
@responses.activate
def test_refresh_course_metadata_with_no_token_type(self):
""" Verify an error is raised if an access token is passed in without a token type. """
with self.assertRaises(CommandError):
command_args = ['--access_token=test-access-token']
call_command('refresh_course_metadata', *command_args)
......@@ -506,7 +506,7 @@ class Program(TimeStampedModel):
def marketing_url(self):
if self.marketing_slug:
path = '{category}/{slug}'.format(category=self.category, slug=self.marketing_slug)
return urljoin(self.partner.marketing_url_root, path)
return urljoin(self.partner.marketing_site_url_root, path)
return None
......
......@@ -472,6 +472,8 @@ PROGRAMS_API_BODIES = [
],
'banner_image_urls': {},
},
# This item is invalid (due to a null marketing_slug) and will not be loaded.
{
'uuid': '01bc3a40-fa9d-4076-8885-660b2f7a594e',
'id': 3,
......
......@@ -328,7 +328,7 @@ class DrupalApiDataLoaderTests(DataLoaderTestMixin, TestCase):
body = mock_data.MARKETING_API_BODY
responses.add(
responses.GET,
self.partner.marketing_api_url + 'courses/',
self.partner.marketing_site_api_url + 'courses/',
body=json.dumps(body),
status=200,
content_type='application/json'
......@@ -440,7 +440,7 @@ class DrupalApiDataLoaderTests(DataLoaderTestMixin, TestCase):
# TODO: Change the -2 to -1 after ECOM-4493 is in production.
msg = 'An error occurred while updating {0} from {1}'.format(
api_data[-2]['course_id'],
self.partner.marketing_api_url
self.partner.marketing_site_api_url
)
mock_logger.exception.assert_called_with(msg)
......
......@@ -278,7 +278,7 @@ class ProgramTests(TestCase):
def test_marketing_url(self):
""" Verify the property creates a complete marketing URL. """
expected = '{root}/{category}/{slug}'.format(root=self.program.partner.marketing_url_root.strip('/'),
expected = '{root}/{category}/{slug}'.format(root=self.program.partner.marketing_site_url_root.strip('/'),
category=self.program.category, slug=self.program.marketing_slug)
self.assertEqual(self.program.marketing_url, expected)
......
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