Commit b98c25dc by Clinton Blackburn

Updated Partner model and admin

- Added verbose name and help text to multiple fields
- Renamed OIDC and marketing site fields
- Made API and marketing site fields optional
- Grouped related fields in admin

ECOM-5094
parent c27186f6
...@@ -38,6 +38,24 @@ class CurrencyAdmin(admin.ModelAdmin): ...@@ -38,6 +38,24 @@ class CurrencyAdmin(admin.ModelAdmin):
@admin.register(Partner) @admin.register(Partner)
class PartnerAdmin(admin.ModelAdmin): 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',) list_display = ('name', 'short_code',)
ordering = ('name', 'short_code',) ordering = ('name', 'short_code',)
search_fields = ('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): ...@@ -58,19 +58,24 @@ class Currency(models.Model):
class Partner(TimeStampedModel): class Partner(TimeStampedModel):
name = models.CharField(max_length=128, unique=True, null=False, blank=False) 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) short_code = models.CharField(
courses_api_url = models.URLField(max_length=255, null=True) max_length=8, unique=True, null=False, blank=False, verbose_name=_('Short Code'),
ecommerce_api_url = models.URLField(max_length=255, null=True) help_text=_('Convenient code/slug used to identify this Partner (e.g. for management commands.)'))
organizations_api_url = models.URLField(max_length=255, null=True) courses_api_url = models.URLField(max_length=255, null=True, blank=True, verbose_name=_('Courses API URL'))
programs_api_url = models.URLField(max_length=255, null=True) ecommerce_api_url = models.URLField(max_length=255, null=True, blank=True, verbose_name=_('E-Commerce API URL'))
marketing_api_url = models.URLField(max_length=255, null=True) organizations_api_url = models.URLField(max_length=255, null=True, blank=True,
marketing_url_root = models.URLField(max_length=255, null=True) verbose_name=_('Organizations API URL'))
social_auth_edx_oidc_url_root = models.CharField(max_length=255, null=True) programs_api_url = models.URLField(max_length=255, null=True, blank=True, verbose_name=_('Programs API URL'))
social_auth_edx_oidc_key = models.CharField(max_length=255, null=True) marketing_site_api_url = models.URLField(max_length=255, null=True, blank=True,
social_auth_edx_oidc_secret = models.CharField(max_length=255, null=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): def __str__(self):
return '{name} ({code})'.format(name=self.name, code=self.short_code) return self.name
class Meta: class Meta:
verbose_name = _('Partner') verbose_name = _('Partner')
......
...@@ -25,11 +25,11 @@ class PartnerFactory(factory.DjangoModelFactory): ...@@ -25,11 +25,11 @@ class PartnerFactory(factory.DjangoModelFactory):
ecommerce_api_url = '{root}/api/courses/v1/'.format(root=FuzzyUrlRoot().fuzz()) ecommerce_api_url = '{root}/api/courses/v1/'.format(root=FuzzyUrlRoot().fuzz())
organizations_api_url = '{root}/api/organizations/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()) programs_api_url = '{root}/api/programs/v1/'.format(root=FuzzyUrlRoot().fuzz())
marketing_api_url = '{root}/api/courses/v1/'.format(root=FuzzyUrlRoot().fuzz()) marketing_site_api_url = '{root}/api/courses/v1/'.format(root=FuzzyUrlRoot().fuzz())
marketing_url_root = '{root}/'.format(root=FuzzyUrlRoot().fuzz()) marketing_site_url_root = '{root}/'.format(root=FuzzyUrlRoot().fuzz())
social_auth_edx_oidc_url_root = '{root}'.format(root=FuzzyUrlRoot().fuzz()) oidc_url_root = '{root}'.format(root=FuzzyUrlRoot().fuzz())
social_auth_edx_oidc_key = FuzzyText().fuzz() oidc_key = FuzzyText().fuzz()
social_auth_edx_oidc_secret = FuzzyText().fuzz() oidc_secret = FuzzyText().fuzz()
class Meta(object): class Meta(object):
model = Partner model = Partner
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
from django.test import TestCase from django.test import TestCase
from social.apps.django_app.default.models import UserSocialAuth from social.apps.django_app.default.models import UserSocialAuth
from course_discovery.apps.core.models import Currency, Partner from course_discovery.apps.core.models import Currency
from course_discovery.apps.core.tests.factories import UserFactory from course_discovery.apps.core.tests.factories import UserFactory, PartnerFactory
class UserTests(TestCase): class UserTests(TestCase):
...@@ -60,11 +60,7 @@ class PartnerTests(TestCase): ...@@ -60,11 +60,7 @@ class PartnerTests(TestCase):
""" Tests for the Partner class. """ """ Tests for the Partner class. """
def test_str(self): def test_str(self):
""" """ Verify the method returns the name of the Partner. """
Verify casting an instance to a string returns a string containing the name and short code of the partner.
""" partner = PartnerFactory()
self.assertEqual(str(partner), partner.name)
code = 'test'
name = 'Test Partner'
instance = Partner(name=name, short_code=code)
self.assertEqual(str(instance), '{name} ({code})'.format(name=name, code=code))
...@@ -275,7 +275,7 @@ class DrupalApiDataLoader(AbstractDataLoader): ...@@ -275,7 +275,7 @@ class DrupalApiDataLoader(AbstractDataLoader):
"""Loads course runs from the Drupal API.""" """Loads course runs from the Drupal API."""
def ingest(self): 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) client = self.get_api_client(api_url)
logger.info('Refreshing Courses and CourseRuns from %s...', api_url) logger.info('Refreshing Courses and CourseRuns from %s...', api_url)
response = client.courses.get() response = client.courses.get()
...@@ -347,7 +347,7 @@ class DrupalApiDataLoader(AbstractDataLoader): ...@@ -347,7 +347,7 @@ class DrupalApiDataLoader(AbstractDataLoader):
defaults = { defaults = {
'name': sponsor_body['title'], 'name': sponsor_body['title'],
'logo_image': image, '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) organization, __ = Organization.objects.update_or_create(key=sponsor_body['uuid'], defaults=defaults)
CourseOrganization.objects.create( CourseOrganization.objects.create(
...@@ -369,7 +369,7 @@ class DrupalApiDataLoader(AbstractDataLoader): ...@@ -369,7 +369,7 @@ class DrupalApiDataLoader(AbstractDataLoader):
course_run.language = self.get_language_tag(body) course_run.language = self.get_language_tag(body)
course_run.course = course 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.start = self.parse_date(body['start'])
course_run.end = self.parse_date(body['end']) course_run.end = self.parse_date(body['end'])
......
...@@ -3,10 +3,10 @@ import logging ...@@ -3,10 +3,10 @@ import logging
from django.core.management import BaseCommand, CommandError from django.core.management import BaseCommand, CommandError
from edx_rest_api_client.client import EdxRestApiClient 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 ( from course_discovery.apps.course_metadata.data_loaders import (
CoursesApiDataLoader, DrupalApiDataLoader, OrganizationsApiDataLoader, EcommerceApiDataLoader, ProgramsApiDataLoader CoursesApiDataLoader, DrupalApiDataLoader, OrganizationsApiDataLoader, EcommerceApiDataLoader, ProgramsApiDataLoader
) )
from course_discovery.apps.core.models import Partner
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -65,9 +65,9 @@ class Command(BaseCommand): ...@@ -65,9 +65,9 @@ class Command(BaseCommand):
try: try:
access_token, __ = EdxRestApiClient.get_oauth_access_token( access_token, __ = EdxRestApiClient.get_oauth_access_token(
'{root}/access_token'.format(root=partner.social_auth_edx_oidc_url_root.strip('/')), '{root}/access_token'.format(root=partner.oidc_url_root.strip('/')),
partner.social_auth_edx_oidc_key, partner.oidc_key,
partner.social_auth_edx_oidc_secret, partner.oidc_secret,
token_type=token_type token_type=token_type
) )
except Exception: except Exception:
...@@ -82,7 +82,7 @@ class Command(BaseCommand): ...@@ -82,7 +82,7 @@ class Command(BaseCommand):
loaders.append(CoursesApiDataLoader) loaders.append(CoursesApiDataLoader)
if partner.ecommerce_api_url: if partner.ecommerce_api_url:
loaders.append(EcommerceApiDataLoader) loaders.append(EcommerceApiDataLoader)
if partner.marketing_api_url: if partner.marketing_site_api_url:
loaders.append(DrupalApiDataLoader) loaders.append(DrupalApiDataLoader)
if partner.programs_api_url: if partner.programs_api_url:
loaders.append(ProgramsApiDataLoader) loaders.append(ProgramsApiDataLoader)
...@@ -90,10 +90,6 @@ class Command(BaseCommand): ...@@ -90,10 +90,6 @@ class Command(BaseCommand):
if loaders: if loaders:
for loader_class in loaders: for loader_class in loaders:
try: try:
loader_class( loader_class(partner, access_token, token_type).ingest()
partner,
access_token,
token_type,
).ingest()
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
logger.exception('%s failed!', loader_class.__name__) logger.exception('%s failed!', loader_class.__name__)
...@@ -31,7 +31,7 @@ class RefreshCourseMetadataCommandTests(TestCase): ...@@ -31,7 +31,7 @@ class RefreshCourseMetadataCommandTests(TestCase):
'expires_in': 30 '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.add_callback(
responses.POST, responses.POST,
url, url,
...@@ -80,7 +80,7 @@ class RefreshCourseMetadataCommandTests(TestCase): ...@@ -80,7 +80,7 @@ class RefreshCourseMetadataCommandTests(TestCase):
body = mock_data.MARKETING_API_BODY body = mock_data.MARKETING_API_BODY
responses.add( responses.add(
responses.GET, responses.GET,
self.partner.marketing_api_url + 'courses/', self.partner.marketing_site_api_url + 'courses/',
body=json.dumps(body), body=json.dumps(body),
status=200, status=200,
content_type='application/json' content_type='application/json'
...@@ -104,6 +104,9 @@ class RefreshCourseMetadataCommandTests(TestCase): ...@@ -104,6 +104,9 @@ class RefreshCourseMetadataCommandTests(TestCase):
call_command('refresh_course_metadata') call_command('refresh_course_metadata')
organizations = Organization.objects.all() organizations = Organization.objects.all()
for organization in organizations:
print(organization.key)
self.assertEqual(organizations.count(), 3) self.assertEqual(organizations.count(), 3)
for organization in organizations: for organization in organizations:
......
...@@ -506,7 +506,7 @@ class Program(TimeStampedModel): ...@@ -506,7 +506,7 @@ class Program(TimeStampedModel):
def marketing_url(self): def marketing_url(self):
if self.marketing_slug: if self.marketing_slug:
path = '{category}/{slug}'.format(category=self.category, slug=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 return None
......
...@@ -472,6 +472,8 @@ PROGRAMS_API_BODIES = [ ...@@ -472,6 +472,8 @@ PROGRAMS_API_BODIES = [
], ],
'banner_image_urls': {}, 'banner_image_urls': {},
}, },
# This item is invalid (due to a null marketing_slug) and will not be loaded.
{ {
'uuid': '01bc3a40-fa9d-4076-8885-660b2f7a594e', 'uuid': '01bc3a40-fa9d-4076-8885-660b2f7a594e',
'id': 3, 'id': 3,
......
...@@ -328,7 +328,7 @@ class DrupalApiDataLoaderTests(DataLoaderTestMixin, TestCase): ...@@ -328,7 +328,7 @@ class DrupalApiDataLoaderTests(DataLoaderTestMixin, TestCase):
body = mock_data.MARKETING_API_BODY body = mock_data.MARKETING_API_BODY
responses.add( responses.add(
responses.GET, responses.GET,
self.partner.marketing_api_url + 'courses/', self.partner.marketing_site_api_url + 'courses/',
body=json.dumps(body), body=json.dumps(body),
status=200, status=200,
content_type='application/json' content_type='application/json'
...@@ -440,7 +440,7 @@ class DrupalApiDataLoaderTests(DataLoaderTestMixin, TestCase): ...@@ -440,7 +440,7 @@ class DrupalApiDataLoaderTests(DataLoaderTestMixin, TestCase):
# TODO: Change the -2 to -1 after ECOM-4493 is in production. # TODO: Change the -2 to -1 after ECOM-4493 is in production.
msg = 'An error occurred while updating {0} from {1}'.format( msg = 'An error occurred while updating {0} from {1}'.format(
api_data[-2]['course_id'], api_data[-2]['course_id'],
self.partner.marketing_api_url self.partner.marketing_site_api_url
) )
mock_logger.exception.assert_called_with(msg) mock_logger.exception.assert_called_with(msg)
......
...@@ -278,7 +278,7 @@ class ProgramTests(TestCase): ...@@ -278,7 +278,7 @@ class ProgramTests(TestCase):
def test_marketing_url(self): def test_marketing_url(self):
""" Verify the property creates a complete marketing URL. """ """ 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) category=self.program.category, slug=self.program.marketing_slug)
self.assertEqual(self.program.marketing_url, expected) 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