Commit 5f58a698 by Matthew Piatetsky

Update program alias in drupal when saving program

ECOM-5562
parent 50197ac6
...@@ -752,7 +752,7 @@ MARKETING_SITE_API_XSERIES_BODIES = [ ...@@ -752,7 +752,7 @@ MARKETING_SITE_API_XSERIES_BODIES = [
'field_xseries_institutions': [ 'field_xseries_institutions': [
{ {
'nid': '637', 'node_id': '637',
'type': 'school', 'type': 'school',
'title': 'DelftX', 'title': 'DelftX',
'language': 'und', 'language': 'und',
...@@ -1480,7 +1480,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [ ...@@ -1480,7 +1480,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [
'field_course_required_weeks': None, 'field_course_required_weeks': None,
'field_course_required_days': None, 'field_course_required_days': None,
'field_course_required_hours': None, 'field_course_required_hours': None,
'nid': '254', 'node_id': '254',
'vid': '8078', 'vid': '8078',
'is_new': False, 'is_new': False,
'type': 'course', 'type': 'course',
...@@ -1831,7 +1831,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [ ...@@ -1831,7 +1831,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [
'field_course_required_weeks': '4', 'field_course_required_weeks': '4',
'field_course_required_days': '0', 'field_course_required_days': '0',
'field_course_required_hours': '0', 'field_course_required_hours': '0',
'nid': '354', 'node_id': '354',
'vid': '112156', 'vid': '112156',
'is_new': False, 'is_new': False,
'type': 'course', 'type': 'course',
...@@ -2100,7 +2100,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [ ...@@ -2100,7 +2100,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [
'field_course_required_weeks': None, 'field_course_required_weeks': None,
'field_course_required_days': None, 'field_course_required_days': None,
'field_course_required_hours': None, 'field_course_required_hours': None,
'nid': '563', 'node_id': '563',
'vid': '8080', 'vid': '8080',
'is_new': False, 'is_new': False,
'type': 'course', 'type': 'course',
...@@ -2371,7 +2371,7 @@ ORIGINAL_MARKETING_SITE_API_COURSE_BODY = { ...@@ -2371,7 +2371,7 @@ ORIGINAL_MARKETING_SITE_API_COURSE_BODY = {
'field_course_required_weeks': None, 'field_course_required_weeks': None,
'field_course_required_days': None, 'field_course_required_days': None,
'field_course_required_hours': None, 'field_course_required_hours': None,
'nid': '563', 'node_id': '563',
'vid': '8080', 'vid': '8080',
'is_new': False, 'is_new': False,
'type': 'course', 'type': 'course',
...@@ -2641,7 +2641,7 @@ UPDATED_MARKETING_SITE_API_COURSE_BODY = { ...@@ -2641,7 +2641,7 @@ UPDATED_MARKETING_SITE_API_COURSE_BODY = {
'field_course_required_weeks': None, 'field_course_required_weeks': None,
'field_course_required_days': None, 'field_course_required_days': None,
'field_course_required_hours': None, 'field_course_required_hours': None,
'nid': '563', 'node_id': '563',
'vid': '8080', 'vid': '8080',
'is_new': False, 'is_new': False,
'type': 'course', 'type': 'course',
...@@ -2911,7 +2911,7 @@ NEW_RUN_MARKETING_SITE_API_COURSE_BODY = { ...@@ -2911,7 +2911,7 @@ NEW_RUN_MARKETING_SITE_API_COURSE_BODY = {
'field_course_required_weeks': None, 'field_course_required_weeks': None,
'field_course_required_days': None, 'field_course_required_days': None,
'field_course_required_hours': None, 'field_course_required_hours': None,
'nid': '563', 'node_id': '563',
'vid': '8080', 'vid': '8080',
'is_new': False, 'is_new': False,
'type': 'course', 'type': 'course',
......
...@@ -65,16 +65,16 @@ class ProgramAdminForm(forms.ModelForm): ...@@ -65,16 +65,16 @@ class ProgramAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ProgramAdminForm, self).__init__(*args, **kwargs) super(ProgramAdminForm, self).__init__(*args, **kwargs)
self.fields['type'].required = True self.fields['type'].required = True
self.fields['marketing_slug'].required = True
self.fields['courses'].required = False self.fields['courses'].required = False
def clean(self): def clean(self):
status = self.cleaned_data.get('status') status = self.cleaned_data.get('status')
marketing_slug = self.cleaned_data.get('marketing_slug')
banner_image = self.cleaned_data.get('banner_image') banner_image = self.cleaned_data.get('banner_image')
if status == ProgramStatus.Active and not (marketing_slug and banner_image): if status == ProgramStatus.Active and not banner_image:
raise ValidationError(_( raise ValidationError(_(
'Programs can only be activated if they have a marketing slug and a banner image.' 'Programs can only be activated if they have a banner image.'
)) ))
return self.cleaned_data return self.cleaned_data
......
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2016-12-20 16:44
from __future__ import unicode_literals
from django.db import migrations, models
import django_extensions.db.fields
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0039_programtype_logo_image'),
]
operations = [
migrations.AddField(
model_name='programtype',
name='slug',
field=django_extensions.db.fields.AutoSlugField(blank=True, editable=False, help_text='Leave this field blank to have the value generated automatically.', populate_from='name'),
),
migrations.AlterField(
model_name='program',
name='marketing_slug',
field=models.CharField(db_index=True, help_text='Slug used to generate links to the marketing site', max_length=255, unique=True),
),
]
...@@ -593,6 +593,8 @@ class ProgramType(TimeStampedModel): ...@@ -593,6 +593,8 @@ class ProgramType(TimeStampedModel):
}, },
help_text=_('Please provide an image file with transparent background'), help_text=_('Please provide an image file with transparent background'),
) )
slug = AutoSlugField(populate_from='name', editable=True, blank=True,
help_text=_('Leave this field blank to have the value generated automatically.'))
def __str__(self): def __str__(self):
return self.name return self.name
...@@ -610,7 +612,7 @@ class Program(TimeStampedModel): ...@@ -610,7 +612,7 @@ class Program(TimeStampedModel):
choices=ProgramStatus.choices, validators=[ProgramStatus.validator] choices=ProgramStatus.choices, validators=[ProgramStatus.validator]
) )
marketing_slug = models.CharField( marketing_slug = models.CharField(
help_text=_('Slug used to generate links to the marketing site'), blank=True, max_length=255, db_index=True) help_text=_('Slug used to generate links to the marketing site'), unique=True, max_length=255, db_index=True)
courses = SortedManyToManyField(Course, related_name='programs') courses = SortedManyToManyField(Course, related_name='programs')
order_courses_by_start_date = models.BooleanField( order_courses_by_start_date = models.BooleanField(
default=True, verbose_name='Order Courses By Start Date', default=True, verbose_name='Order Courses By Start Date',
......
import json import json
from bs4 import BeautifulSoup
import requests import requests
from django.utils.functional import cached_property from django.utils.functional import cached_property
...@@ -39,7 +41,9 @@ class MarketingSiteAPIClient(object): ...@@ -39,7 +41,9 @@ class MarketingSiteAPIClient(object):
} }
response = session.post(login_url, data=login_data) response = session.post(login_url, data=login_data)
expected_url = '{root}/users/{username}'.format(root=self.api_url, username=self.username) expected_url = '{root}/users/{username}'.format(root=self.api_url, username=self.username)
if not (response.status_code == 200 and response.url == expected_url): admin_url = '{root}/admin'.format(root=self.api_url)
can_access_admin = session.get(admin_url)
if not (can_access_admin.status_code == 200 and response.url == expected_url):
raise ProgramPublisherException('Marketing Site Login failed!') raise ProgramPublisherException('Marketing Site Login failed!')
return session return session
...@@ -79,15 +83,11 @@ class MarketingSitePublisher(object): ...@@ -79,15 +83,11 @@ class MarketingSitePublisher(object):
""" """
This is the publisher that would publish the object data to marketing site This is the publisher that would publish the object data to marketing site
""" """
data_before = None program_before = None
def __init__(self, program_before=None): def __init__(self, program_before=None):
if program_before: if program_before:
self.data_before = { self.program_before = program_before
'type': program_before.type,
'status': program_before.status,
'title': program_before.title,
}
def _get_api_client(self, program): def _get_api_client(self, program):
if not program.partner.has_marketing_site: if not program.partner.has_marketing_site:
...@@ -98,12 +98,13 @@ class MarketingSitePublisher(object): ...@@ -98,12 +98,13 @@ class MarketingSitePublisher(object):
partner=program.partner.short_code) partner=program.partner.short_code)
raise ProgramPublisherException(msg) raise ProgramPublisherException(msg)
if program.type.name != 'MicroMasters': if program.type.name not in ['MicroMasters', 'Professional Certificate']:
# Currently, we should not publish any programs that are not MicroMasters types to Marketing Site # We do not publish programs that are not MicroMasters or Professional Certificate to the Marketing Site
return return
if self.data_before and \ fields_that_trigger_publish = ['title', 'status', 'type', 'marketing_slug']
all(self.data_before[key] == getattr(program, key) for key in ['title', 'status', 'type']): if self.program_before and \
all(getattr(self.program_before, key) == getattr(program, key) for key in fields_that_trigger_publish):
# We don't need to publish to marketing site because # We don't need to publish to marketing site because
# nothing we care about has changed. This would save at least 4 network calls # nothing we care about has changed. This would save at least 4 network calls
return return
...@@ -130,17 +131,13 @@ class MarketingSitePublisher(object): ...@@ -130,17 +131,13 @@ class MarketingSitePublisher(object):
node_url = '{root}/node.json?field_uuid={uuid}'.format(root=api_client.api_url, uuid=uuid) node_url = '{root}/node.json?field_uuid={uuid}'.format(root=api_client.api_url, uuid=uuid)
response = api_client.api_session.get(node_url) response = api_client.api_session.get(node_url)
if response.status_code == 200: if response.status_code == 200:
found = response.json() list_item = response.json().get('list')
if found: return list_item[0]['nid']
list_item = found.get('list')
if list_item: def _edit_node(self, api_client, node_id, node_data):
return list_item[0]['nid'] # Drupal does not allow us to update the UUID field on node update
node_data.pop('uuid', None)
def _edit_node(self, api_client, nid, node_data): node_url = '{root}/node.json/{node_id}'.format(root=api_client.api_url, node_id=node_id)
if node_data.get('uuid'):
# Drupal do not allow us to update the UUID field on node update
del node_data['uuid']
node_url = '{root}/node.json/{nid}'.format(root=api_client.api_url, nid=nid)
response = api_client.api_session.put(node_url, data=json.dumps(node_data)) response = api_client.api_session.put(node_url, data=json.dumps(node_data))
if response.status_code != 200: if response.status_code != 200:
raise ProgramPublisherException("Marketing site page edit failed!") raise ProgramPublisherException("Marketing site page edit failed!")
...@@ -153,25 +150,93 @@ class MarketingSitePublisher(object): ...@@ -153,25 +150,93 @@ class MarketingSitePublisher(object):
else: else:
raise ProgramPublisherException("Marketing site page creation failed!") raise ProgramPublisherException("Marketing site page creation failed!")
def _delete_node(self, api_client, nid): def _delete_node(self, api_client, node_id):
node_url = '{root}/node.json/{nid}'.format(root=api_client.api_url, nid=nid) node_url = '{root}/node.json/{node_id}'.format(root=api_client.api_url, node_id=node_id)
api_client.api_session.delete(node_url) api_client.api_session.delete(node_url)
def _get_form_build_id_and_form_token(self, api_client, url):
form_attributes = {}
response = api_client.api_session.get(url)
if response.status_code != 200:
raise ProgramPublisherException('Marketing site alias form retrieval failed!')
form = BeautifulSoup(response.text, 'html.parser')
for field in ('form_build_id', 'form_token'):
form_attributes[field] = form.find('input', {'name': field}).get('value')
return form_attributes
def _get_delete_alias_url(self, api_client, url):
response = api_client.api_session.get(url)
if response.status_code != 200:
raise ProgramPublisherException('Marketing site alias form retrieval failed!')
form = BeautifulSoup(response.text, 'html.parser')
delete_element = form.select('.delete.last a')
return delete_element[0].get('href') if delete_element else None
def _get_headers(self):
headers = {
'content-type': 'application/x-www-form-urlencoded'
}
return headers
def _make_alias(self, program):
alias = '{program_type_slug}/{slug}'.format(program_type_slug=program.type.slug, slug=program.marketing_slug)
return alias
def _add_alias(self, api_client, node_id, alias, before_slug):
base_aliases_url = '{root}/admin/config/search/path'.format(root=api_client.api_url)
add_aliases_url = '{url}/add'.format(url=base_aliases_url)
node_url = 'node/{node_id}'.format(node_id=node_id)
data = {
'source': node_url,
'alias': alias,
'form_id': 'path_admin_form',
'op': 'Save'
}
form_attributes = self._get_form_build_id_and_form_token(api_client, add_aliases_url)
data.update(form_attributes)
headers = self._get_headers()
response = api_client.api_session.post(add_aliases_url, headers=headers, data=data)
if response.status_code != 200:
raise ProgramPublisherException('Marketing site alias creation failed!')
# Delete old alias after saving new one
if before_slug:
list_aliases_url = '{url}/list/{slug}'.format(url=base_aliases_url, slug=before_slug)
delete_alias_url = self._get_delete_alias_url(api_client, list_aliases_url)
if delete_alias_url:
delete_alias_url = '{root}{url}'.format(root=api_client.api_url, url=delete_alias_url)
data = {
"confirm": 1,
"form_id": "path_admin_delete_confirm",
"op": "Confirm"
}
form_attributes = self._get_form_build_id_and_form_token(api_client, delete_alias_url)
data.update(form_attributes)
response = api_client.api_session.post(delete_alias_url, headers=headers, data=data)
if response.status_code != 200:
raise ProgramPublisherException('Marketing site alias deletion failed!')
def publish_program(self, program): def publish_program(self, program):
api_client = self._get_api_client(program) api_client = self._get_api_client(program)
if api_client: if api_client:
node_data = self._get_node_data(program, api_client.user_id) node_data = self._get_node_data(program, api_client.user_id)
nid = self._get_node_id(api_client, program.uuid) node_id = self._get_node_id(api_client, program.uuid)
if nid: if node_id:
# We would like to edit the existing node # We would like to edit the existing node
self._edit_node(api_client, nid, node_data) self._edit_node(api_client, node_id, node_data)
else: else:
# We should create a new node # We should create a new node
self._create_node(api_client, node_data) self._create_node(api_client, node_data)
before_alias = self._make_alias(self.program_before) if self.program_before else None
new_alias = self._make_alias(program)
before_slug = self.program_before.marketing_slug if self.program_before else None
if not self.program_before or (before_alias != new_alias):
self._add_alias(api_client, node_id, new_alias, before_slug)
def delete_program(self, program): def delete_program(self, program):
api_client = self._get_api_client(program) api_client = self._get_api_client(program)
if api_client: if api_client:
nid = self._get_node_id(api_client, program.uuid) node_id = self._get_node_id(api_client, program.uuid)
if nid: if node_id:
self._delete_node(api_client, nid) self._delete_node(api_client, node_id)
import json import json
import urllib
from django.test import TestCase from django.test import TestCase
from factory.fuzzy import FuzzyText, FuzzyInteger from factory.fuzzy import FuzzyText, FuzzyInteger
...@@ -47,6 +48,15 @@ class MarketingSiteAPIClientTestMixin(TestCase): ...@@ -47,6 +48,15 @@ class MarketingSiteAPIClientTestMixin(TestCase):
status=status status=status
) )
def mock_admin_response(self, status):
""" Test that we can access the admin """
response_url = '{root}/admin'.format(root=self.api_root)
responses.add(
responses.GET,
response_url,
status=status
)
def mock_csrf_token_response(self, status): def mock_csrf_token_response(self, status):
responses.add( responses.add(
responses.GET, responses.GET,
...@@ -81,10 +91,11 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin): ...@@ -81,10 +91,11 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
""" """
def setUp(self): def setUp(self):
super(MarketingSitePublisherTestMixin, self).setUp() super(MarketingSitePublisherTestMixin, self).setUp()
self.nid = FuzzyText().fuzz() self.node_id = FuzzyText().fuzz()
def mock_api_client(self, status): def mock_api_client(self, status):
self.mock_login_response(status) self.mock_login_response(status)
self.mock_admin_response(status)
self.mock_csrf_token_response(status) self.mock_csrf_token_response(status)
self.mock_user_id_response(status) self.mock_user_id_response(status)
...@@ -94,7 +105,7 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin): ...@@ -94,7 +105,7 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
if exists: if exists:
data = { data = {
'list': [{ 'list': [{
'nid': self.nid 'nid': self.node_id
}] }]
} }
status = 200 status = 200
...@@ -108,10 +119,65 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin): ...@@ -108,10 +119,65 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
match_querystring=True match_querystring=True
) )
def mock_add_alias(self, status=200):
node_url = 'node/{node_id}'.format(node_id=self.node_id)
alias = '{program_type}/{slug}'.format(
program_type=self.program.type.name.lower(),
slug=self.program.marketing_slug
)
data = {
'source': node_url,
'alias': alias,
'form_id': 'path_admin_form',
'op': 'Save'
}
responses.add(
responses.POST,
'{root}/admin/config/search/path/add'.format(root=self.api_root),
body=urllib.parse.urlencode(data),
status=status
)
def mock_delete_alias(self, status=200):
data = {
"confirm": 1,
"form_id": "path_admin_delete_confirm",
"op": "Confirm"
}
responses.add(
responses.POST,
'{root}/foo'.format(root=self.api_root),
body=urllib.parse.urlencode(data),
status=status
)
def mock_get_alias_form(self, status=200):
responses.add(
responses.GET,
'{root}/admin/config/search/path/add'.format(root=self.api_root),
status=status,
body='<html><form><input name="form_build_id" value="1">'
'</input><input name="form_token" value="2"></input></form></html>'
)
def mock_get_delete_form(self, alias, status=200):
responses.add(
responses.GET,
'{root}/admin/config/search/path/list/{alias}'.format(root=self.api_root, alias=alias),
status=status,
body='<li class="delete last"><a href="/admin/config/search/path/delete/bar"></a></li>'
)
responses.add(
responses.POST,
'{root}/admin/config/search/path/delete/bar'.format(root=self.api_root),
status=status
)
def mock_node_edit(self, status): def mock_node_edit(self, status):
responses.add( responses.add(
responses.PUT, responses.PUT,
'{root}/node.json/{nid}'.format(root=self.api_root, nid=self.nid), '{root}/node.json/{node_id}'.format(root=self.api_root, node_id=self.node_id),
body=json.dumps({}), body=json.dumps({}),
content_type='application/json', content_type='application/json',
status=status status=status
...@@ -129,7 +195,7 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin): ...@@ -129,7 +195,7 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
def mock_node_delete(self, status): def mock_node_delete(self, status):
responses.add( responses.add(
responses.DELETE, responses.DELETE,
'{root}/node.json/{nid}'.format(root=self.api_root, nid=self.nid), '{root}/node.json/{node_id}'.format(root=self.api_root, node_id=self.node_id),
body='', body='',
content_type='text/html', content_type='text/html',
status=status status=status
......
...@@ -57,7 +57,7 @@ class AdminTests(TestCase): ...@@ -57,7 +57,7 @@ class AdminTests(TestCase):
self.assertFalse(form.is_valid()) self.assertFalse(form.is_valid())
self.assertEqual( self.assertEqual(
form.errors['__all__'], form.errors['__all__'],
['Programs can only be activated if they have a marketing slug and a banner image.'] ['Programs can only be activated if they have a banner image.']
) )
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
form.save() form.save()
...@@ -147,10 +147,8 @@ class AdminTests(TestCase): ...@@ -147,10 +147,8 @@ class AdminTests(TestCase):
@ddt.data( @ddt.data(
*itertools.product( *itertools.product(
( (
(False, False, False), (False, False),
(True, False, False), (True, True)
(False, True, False),
(True, True, True)
), ),
ProgramStatus.labels ProgramStatus.labels
) )
...@@ -158,13 +156,12 @@ class AdminTests(TestCase): ...@@ -158,13 +156,12 @@ class AdminTests(TestCase):
@ddt.unpack @ddt.unpack
def test_program_activation_restrictions(self, booleans, label): def test_program_activation_restrictions(self, booleans, label):
"""Verify that program activation requires both a marketing slug and a banner image.""" """Verify that program activation requires both a marketing slug and a banner image."""
has_marketing_slug, has_banner_image, can_be_activated = booleans has_banner_image, can_be_activated = booleans
status = getattr(ProgramStatus, label) status = getattr(ProgramStatus, label)
marketing_slug = '/foo' if has_marketing_slug else ''
banner_image = make_image_file('test_banner.jpg') if has_banner_image else '' banner_image = make_image_file('test_banner.jpg') if has_banner_image else ''
data = self._post_data(status=status, marketing_slug=marketing_slug) data = self._post_data(status=status, marketing_slug='/foo')
files = {'banner_image': banner_image} files = {'banner_image': banner_image}
if status == ProgramStatus.Active: if status == ProgramStatus.Active:
...@@ -292,10 +289,12 @@ class ProgramAdminFunctionalTests(LiveServerTestCase): ...@@ -292,10 +289,12 @@ class ProgramAdminFunctionalTests(LiveServerTestCase):
program = factories.ProgramFactory.build( program = factories.ProgramFactory.build(
partner=Partner.objects.first(), partner=Partner.objects.first(),
status=ProgramStatus.Unpublished, status=ProgramStatus.Unpublished,
type=ProgramType.objects.first() type=ProgramType.objects.first(),
marketing_slug='foo'
) )
self.browser.find_element_by_id('id_title').send_keys(program.title) self.browser.find_element_by_id('id_title').send_keys(program.title)
self.browser.find_element_by_id('id_subtitle').send_keys(program.subtitle) self.browser.find_element_by_id('id_subtitle').send_keys(program.subtitle)
self.browser.find_element_by_id('id_marketing_slug').send_keys(program.marketing_slug)
self._select_option('id_status', program.status) self._select_option('id_status', program.status)
self._select_option('id_type', str(program.type.id)) self._select_option('id_type', str(program.type.id))
self._select_option('id_partner', str(program.partner.id)) self._select_option('id_partner', str(program.partner.id))
...@@ -304,6 +303,7 @@ class ProgramAdminFunctionalTests(LiveServerTestCase): ...@@ -304,6 +303,7 @@ class ProgramAdminFunctionalTests(LiveServerTestCase):
actual = Program.objects.latest() actual = Program.objects.latest()
self.assertEqual(actual.title, program.title) self.assertEqual(actual.title, program.title)
self.assertEqual(actual.subtitle, program.subtitle) self.assertEqual(actual.subtitle, program.subtitle)
self.assertEqual(actual.marketing_slug, program.marketing_slug)
self.assertEqual(actual.status, program.status) self.assertEqual(actual.status, program.status)
self.assertEqual(actual.type, program.type) self.assertEqual(actual.type, program.type)
self.assertEqual(actual.partner, program.partner) self.assertEqual(actual.partner, program.partner)
......
...@@ -19,6 +19,7 @@ from course_discovery.apps.course_metadata.models import ( ...@@ -19,6 +19,7 @@ from course_discovery.apps.course_metadata.models import (
AbstractMediaModel, AbstractNamedModel, AbstractValueModel, AbstractMediaModel, AbstractNamedModel, AbstractValueModel,
CorporateEndorsement, Course, CourseRun, Endorsement, FAQ, SeatType, ProgramType, CorporateEndorsement, Course, CourseRun, Endorsement, FAQ, SeatType, ProgramType,
) )
from course_discovery.apps.course_metadata.publishers import MarketingSitePublisher
from course_discovery.apps.course_metadata.tests import factories, toggle_switch from course_discovery.apps.course_metadata.tests import factories, toggle_switch
from course_discovery.apps.course_metadata.tests.factories import CourseRunFactory, ImageFactory from course_discovery.apps.course_metadata.tests.factories import CourseRunFactory, ImageFactory
from course_discovery.apps.course_metadata.tests.mixins import MarketingSitePublisherTestMixin from course_discovery.apps.course_metadata.tests.mixins import MarketingSitePublisherTestMixin
...@@ -434,8 +435,13 @@ class ProgramTests(MarketingSitePublisherTestMixin): ...@@ -434,8 +435,13 @@ class ProgramTests(MarketingSitePublisherTestMixin):
self.mock_node_edit(200) self.mock_node_edit(200)
toggle_switch('publish_program_to_marketing_site', True) toggle_switch('publish_program_to_marketing_site', True)
self.program.title = FuzzyText().fuzz() self.program.title = FuzzyText().fuzz()
self.program.save() self.mock_add_alias()
self.assert_responses_call_count(6) self.mock_delete_alias()
with mock.patch.object(MarketingSitePublisher, '_get_headers', return_value={}):
with mock.patch.object(MarketingSitePublisher, '_get_form_build_id_and_form_token', return_value={}):
with mock.patch.object(MarketingSitePublisher, '_get_delete_alias_url', return_value='/foo'):
self.program.save()
self.assert_responses_call_count(9)
@responses.activate @responses.activate
def test_xseries_program_save(self): def test_xseries_program_save(self):
...@@ -470,7 +476,7 @@ class ProgramTests(MarketingSitePublisherTestMixin): ...@@ -470,7 +476,7 @@ class ProgramTests(MarketingSitePublisherTestMixin):
self.mock_node_delete(204) self.mock_node_delete(204)
toggle_switch('publish_program_to_marketing_site', True) toggle_switch('publish_program_to_marketing_site', True)
self.program.delete() self.program.delete()
self.assert_responses_call_count(5) self.assert_responses_call_count(6)
@responses.activate @responses.activate
def test_delete_and_no_marketing_site(self): def test_delete_and_no_marketing_site(self):
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-30 14:38+0500\n" "POT-Creation-Date: 2017-01-03 10:50-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: apps/api/filters.py #: apps/api/filters.py
#, python-brace-format #, python-brace-format
...@@ -240,9 +240,7 @@ msgid "Deleted" ...@@ -240,9 +240,7 @@ msgid "Deleted"
msgstr "" msgstr ""
#: apps/course_metadata/forms.py #: apps/course_metadata/forms.py
msgid "" msgid "Programs can only be activated if they have a banner image."
"Programs can only be activated if they have a marketing slug and a banner "
"image."
msgstr "" msgstr ""
#: apps/course_metadata/models.py #: apps/course_metadata/models.py
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-30 14:38+0500\n" "POT-Creation-Date: 2017-01-03 10:50-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: static/js/catalogs-change-form.js #: static/js/catalogs-change-form.js
msgid "Preview" msgid "Preview"
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-30 14:38+0500\n" "POT-Creation-Date: 2017-01-03 10:50-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps/api/filters.py #: apps/api/filters.py
...@@ -289,12 +289,10 @@ msgid "Deleted" ...@@ -289,12 +289,10 @@ msgid "Deleted"
msgstr "Délétéd Ⱡ'σяєм ιρѕυм #" msgstr "Délétéd Ⱡ'σяєм ιρѕυм #"
#: apps/course_metadata/forms.py #: apps/course_metadata/forms.py
msgid "" msgid "Programs can only be activated if they have a banner image."
"Programs can only be activated if they have a marketing slug and a banner "
"image."
msgstr "" msgstr ""
"Prögräms çän önlý ßé äçtïvätéd ïf théý hävé ä märkétïng slüg änd ä ßännér " "Prögräms çän önlý ßé äçtïvätéd ïf théý hävé ä ßännér ïmägé. Ⱡ'σяєм ιρѕυм "
"ïmägé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#" "∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: apps/course_metadata/models.py #: apps/course_metadata/models.py
msgid "Facebook" msgid "Facebook"
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-30 14:38+0500\n" "POT-Creation-Date: 2017-01-03 10:50-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/js/catalogs-change-form.js #: static/js/catalogs-change-form.js
......
boto==2.42.0 boto==2.42.0
beautifulsoup4==4.5.1
cryptography==1.5.3 cryptography==1.5.3
django==1.9.11 django==1.9.11
django-autocomplete-light==3.1.8 django-autocomplete-light==3.1.8
......
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