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 = [
'field_xseries_institutions': [
{
'nid': '637',
'node_id': '637',
'type': 'school',
'title': 'DelftX',
'language': 'und',
......@@ -1480,7 +1480,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [
'field_course_required_weeks': None,
'field_course_required_days': None,
'field_course_required_hours': None,
'nid': '254',
'node_id': '254',
'vid': '8078',
'is_new': False,
'type': 'course',
......@@ -1831,7 +1831,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [
'field_course_required_weeks': '4',
'field_course_required_days': '0',
'field_course_required_hours': '0',
'nid': '354',
'node_id': '354',
'vid': '112156',
'is_new': False,
'type': 'course',
......@@ -2100,7 +2100,7 @@ UNIQUE_MARKETING_SITE_API_COURSE_BODIES = [
'field_course_required_weeks': None,
'field_course_required_days': None,
'field_course_required_hours': None,
'nid': '563',
'node_id': '563',
'vid': '8080',
'is_new': False,
'type': 'course',
......@@ -2371,7 +2371,7 @@ ORIGINAL_MARKETING_SITE_API_COURSE_BODY = {
'field_course_required_weeks': None,
'field_course_required_days': None,
'field_course_required_hours': None,
'nid': '563',
'node_id': '563',
'vid': '8080',
'is_new': False,
'type': 'course',
......@@ -2641,7 +2641,7 @@ UPDATED_MARKETING_SITE_API_COURSE_BODY = {
'field_course_required_weeks': None,
'field_course_required_days': None,
'field_course_required_hours': None,
'nid': '563',
'node_id': '563',
'vid': '8080',
'is_new': False,
'type': 'course',
......@@ -2911,7 +2911,7 @@ NEW_RUN_MARKETING_SITE_API_COURSE_BODY = {
'field_course_required_weeks': None,
'field_course_required_days': None,
'field_course_required_hours': None,
'nid': '563',
'node_id': '563',
'vid': '8080',
'is_new': False,
'type': 'course',
......
......@@ -65,16 +65,16 @@ class ProgramAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProgramAdminForm, self).__init__(*args, **kwargs)
self.fields['type'].required = True
self.fields['marketing_slug'].required = True
self.fields['courses'].required = False
def clean(self):
status = self.cleaned_data.get('status')
marketing_slug = self.cleaned_data.get('marketing_slug')
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(_(
'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
......
# -*- 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):
},
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):
return self.name
......@@ -610,7 +612,7 @@ class Program(TimeStampedModel):
choices=ProgramStatus.choices, validators=[ProgramStatus.validator]
)
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')
order_courses_by_start_date = models.BooleanField(
default=True, verbose_name='Order Courses By Start Date',
......
import json
from bs4 import BeautifulSoup
import requests
from django.utils.functional import cached_property
......@@ -39,7 +41,9 @@ class MarketingSiteAPIClient(object):
}
response = session.post(login_url, data=login_data)
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!')
return session
......@@ -79,15 +83,11 @@ class MarketingSitePublisher(object):
"""
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):
if program_before:
self.data_before = {
'type': program_before.type,
'status': program_before.status,
'title': program_before.title,
}
self.program_before = program_before
def _get_api_client(self, program):
if not program.partner.has_marketing_site:
......@@ -98,12 +98,13 @@ class MarketingSitePublisher(object):
partner=program.partner.short_code)
raise ProgramPublisherException(msg)
if program.type.name != 'MicroMasters':
# Currently, we should not publish any programs that are not MicroMasters types to Marketing Site
if program.type.name not in ['MicroMasters', 'Professional Certificate']:
# We do not publish programs that are not MicroMasters or Professional Certificate to the Marketing Site
return
if self.data_before and \
all(self.data_before[key] == getattr(program, key) for key in ['title', 'status', 'type']):
fields_that_trigger_publish = ['title', 'status', 'type', 'marketing_slug']
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
# nothing we care about has changed. This would save at least 4 network calls
return
......@@ -130,17 +131,13 @@ class MarketingSitePublisher(object):
node_url = '{root}/node.json?field_uuid={uuid}'.format(root=api_client.api_url, uuid=uuid)
response = api_client.api_session.get(node_url)
if response.status_code == 200:
found = response.json()
if found:
list_item = found.get('list')
if list_item:
list_item = response.json().get('list')
return list_item[0]['nid']
def _edit_node(self, api_client, nid, node_data):
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)
def _edit_node(self, api_client, node_id, node_data):
# Drupal does not allow us to update the UUID field on node update
node_data.pop('uuid', None)
node_url = '{root}/node.json/{node_id}'.format(root=api_client.api_url, node_id=node_id)
response = api_client.api_session.put(node_url, data=json.dumps(node_data))
if response.status_code != 200:
raise ProgramPublisherException("Marketing site page edit failed!")
......@@ -153,25 +150,93 @@ class MarketingSitePublisher(object):
else:
raise ProgramPublisherException("Marketing site page creation failed!")
def _delete_node(self, api_client, nid):
node_url = '{root}/node.json/{nid}'.format(root=api_client.api_url, nid=nid)
def _delete_node(self, api_client, node_id):
node_url = '{root}/node.json/{node_id}'.format(root=api_client.api_url, node_id=node_id)
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):
api_client = self._get_api_client(program)
if api_client:
node_data = self._get_node_data(program, api_client.user_id)
nid = self._get_node_id(api_client, program.uuid)
if nid:
node_id = self._get_node_id(api_client, program.uuid)
if node_id:
# 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:
# We should create a new node
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):
api_client = self._get_api_client(program)
if api_client:
nid = self._get_node_id(api_client, program.uuid)
if nid:
self._delete_node(api_client, nid)
node_id = self._get_node_id(api_client, program.uuid)
if node_id:
self._delete_node(api_client, node_id)
import json
import urllib
from django.test import TestCase
from factory.fuzzy import FuzzyText, FuzzyInteger
......@@ -47,6 +48,15 @@ class MarketingSiteAPIClientTestMixin(TestCase):
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):
responses.add(
responses.GET,
......@@ -81,10 +91,11 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
"""
def setUp(self):
super(MarketingSitePublisherTestMixin, self).setUp()
self.nid = FuzzyText().fuzz()
self.node_id = FuzzyText().fuzz()
def mock_api_client(self, status):
self.mock_login_response(status)
self.mock_admin_response(status)
self.mock_csrf_token_response(status)
self.mock_user_id_response(status)
......@@ -94,7 +105,7 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
if exists:
data = {
'list': [{
'nid': self.nid
'nid': self.node_id
}]
}
status = 200
......@@ -108,10 +119,65 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
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):
responses.add(
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({}),
content_type='application/json',
status=status
......@@ -129,7 +195,7 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
def mock_node_delete(self, status):
responses.add(
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='',
content_type='text/html',
status=status
......
......@@ -57,7 +57,7 @@ class AdminTests(TestCase):
self.assertFalse(form.is_valid())
self.assertEqual(
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):
form.save()
......@@ -147,10 +147,8 @@ class AdminTests(TestCase):
@ddt.data(
*itertools.product(
(
(False, False, False),
(True, False, False),
(False, True, False),
(True, True, True)
(False, False),
(True, True)
),
ProgramStatus.labels
)
......@@ -158,13 +156,12 @@ class AdminTests(TestCase):
@ddt.unpack
def test_program_activation_restrictions(self, booleans, label):
"""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)
marketing_slug = '/foo' if has_marketing_slug 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}
if status == ProgramStatus.Active:
......@@ -292,10 +289,12 @@ class ProgramAdminFunctionalTests(LiveServerTestCase):
program = factories.ProgramFactory.build(
partner=Partner.objects.first(),
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_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_type', str(program.type.id))
self._select_option('id_partner', str(program.partner.id))
......@@ -304,6 +303,7 @@ class ProgramAdminFunctionalTests(LiveServerTestCase):
actual = Program.objects.latest()
self.assertEqual(actual.title, program.title)
self.assertEqual(actual.subtitle, program.subtitle)
self.assertEqual(actual.marketing_slug, program.marketing_slug)
self.assertEqual(actual.status, program.status)
self.assertEqual(actual.type, program.type)
self.assertEqual(actual.partner, program.partner)
......
......@@ -19,6 +19,7 @@ from course_discovery.apps.course_metadata.models import (
AbstractMediaModel, AbstractNamedModel, AbstractValueModel,
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.factories import CourseRunFactory, ImageFactory
from course_discovery.apps.course_metadata.tests.mixins import MarketingSitePublisherTestMixin
......@@ -434,8 +435,13 @@ class ProgramTests(MarketingSitePublisherTestMixin):
self.mock_node_edit(200)
toggle_switch('publish_program_to_marketing_site', True)
self.program.title = FuzzyText().fuzz()
self.mock_add_alias()
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(6)
self.assert_responses_call_count(9)
@responses.activate
def test_xseries_program_save(self):
......@@ -470,7 +476,7 @@ class ProgramTests(MarketingSitePublisherTestMixin):
self.mock_node_delete(204)
toggle_switch('publish_program_to_marketing_site', True)
self.program.delete()
self.assert_responses_call_count(5)
self.assert_responses_call_count(6)
@responses.activate
def test_delete_and_no_marketing_site(self):
......
import mock
import responses
from course_discovery.apps.course_metadata.choices import ProgramStatus
......@@ -29,27 +30,31 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin):
@responses.activate
def test_init_session(self):
self.mock_login_response(200)
self.mock_admin_response(200)
session = self.api_client.init_session
self.assert_responses_call_count(2)
self.assert_responses_call_count(3)
self.assertIsNotNone(session)
@responses.activate
def test_init_session_failed(self):
self.mock_login_response(500)
self.mock_admin_response(500)
with self.assertRaises(ProgramPublisherException):
self.api_client.init_session # pylint: disable=pointless-statement
@responses.activate
def test_csrf_token(self):
self.mock_login_response(200)
self.mock_admin_response(200)
self.mock_csrf_token_response(200)
csrf_token = self.api_client.csrf_token
self.assert_responses_call_count(3)
self.assert_responses_call_count(4)
self.assertEqual(self.csrf_token, csrf_token)
@responses.activate
def test_csrf_token_failed(self):
self.mock_login_response(200)
self.mock_admin_response(200)
self.mock_csrf_token_response(500)
with self.assertRaises(ProgramPublisherException):
self.api_client.csrf_token # pylint: disable=pointless-statement
......@@ -57,14 +62,16 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin):
@responses.activate
def test_user_id(self):
self.mock_login_response(200)
self.mock_admin_response(200)
self.mock_user_id_response(200)
user_id = self.api_client.user_id
self.assert_responses_call_count(3)
self.assert_responses_call_count(4)
self.assertEqual(self.user_id, user_id)
@responses.activate
def test_user_id_failed(self):
self.mock_login_response(200)
self.mock_admin_response(200)
self.mock_user_id_response(500)
with self.assertRaises(ProgramPublisherException):
self.api_client.user_id # pylint: disable=pointless-statement
......@@ -72,9 +79,10 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin):
@responses.activate
def test_api_session(self):
self.mock_login_response(200)
self.mock_admin_response(200)
self.mock_csrf_token_response(200)
api_session = self.api_client.api_session
self.assert_responses_call_count(3)
self.assert_responses_call_count(4)
self.assertIsNotNone(api_session)
self.assertEqual(api_session.headers.get('Content-Type'), 'application/json')
self.assertEqual(api_session.headers.get('X-CSRF-Token'), self.csrf_token)
......@@ -82,6 +90,7 @@ class MarketingSiteAPIClientTests(MarketingSiteAPIClientTestMixin):
@responses.activate
def test_api_session_failed(self):
self.mock_login_response(500)
self.mock_admin_response(500)
self.mock_csrf_token_response(500)
with self.assertRaises(ProgramPublisherException):
self.api_client.api_session # pylint: disable=pointless-statement
......@@ -98,6 +107,8 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
self.program.partner.marketing_site_api_username = self.username
self.program.partner.marketing_site_api_password = self.password
self.program.type = ProgramType.objects.get(name='MicroMasters')
with mock.patch.object(MarketingSitePublisher, '_get_headers', return_value={}):
with mock.patch.object(MarketingSitePublisher, '_get_form_build_id_and_form_token', return_value={}):
self.program.save() # pylint: disable=no-member
self.api_client = MarketingSiteAPIClient(
self.username,
......@@ -126,8 +137,8 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
self.mock_node_retrieval(self.program.uuid)
publisher = MarketingSitePublisher()
node_id = publisher._get_node_id(self.api_client, self.program.uuid) # pylint: disable=protected-access
self.assert_responses_call_count(4)
self.assertEqual(node_id, self.nid)
self.assert_responses_call_count(5)
self.assertEqual(node_id, self.node_id)
@responses.activate
def test_get_non_existent_node_id(self):
......@@ -143,8 +154,8 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
self.mock_node_edit(200)
publisher = MarketingSitePublisher()
publish_data = publisher._get_node_data(self.program, self.user_id) # pylint: disable=protected-access
publisher._edit_node(self.api_client, self.nid, publish_data) # pylint: disable=protected-access
self.assert_responses_call_count(4)
publisher._edit_node(self.api_client, self.node_id, publish_data) # pylint: disable=protected-access
self.assert_responses_call_count(5)
@responses.activate
def test_edit_node_failed(self):
......@@ -153,14 +164,14 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
publisher = MarketingSitePublisher()
publish_data = publisher._get_node_data(self.program, self.user_id) # pylint: disable=protected-access
with self.assertRaises(ProgramPublisherException):
publisher._edit_node(self.api_client, self.nid, publish_data) # pylint: disable=protected-access
publisher._edit_node(self.api_client, self.node_id, publish_data) # pylint: disable=protected-access
@responses.activate
def test_create_node(self):
self.mock_api_client(200)
expected = {
'list': [{
'nid': self.nid
'nid': self.node_id
}]
}
self.mock_node_create(expected, 201)
......@@ -183,14 +194,17 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
self.mock_api_client(200)
expected = {
'list': [{
'nid': self.nid
'node_id': self.node_id
}]
}
self.mock_node_retrieval(self.program.uuid, exists=False)
self.mock_node_create(expected, 201)
publisher = MarketingSitePublisher()
self.mock_add_alias()
with mock.patch.object(MarketingSitePublisher, '_get_headers', return_value={}):
with mock.patch.object(MarketingSitePublisher, '_get_form_build_id_and_form_token', return_value={}):
publisher.publish_program(self.program)
self.assert_responses_call_count(6)
self.assert_responses_call_count(8)
@responses.activate
def test_publish_program_edit(self):
......@@ -198,8 +212,11 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
self.mock_node_retrieval(self.program.uuid)
self.mock_node_edit(200)
publisher = MarketingSitePublisher()
self.mock_add_alias()
with mock.patch.object(MarketingSitePublisher, '_get_headers', return_value={}):
with mock.patch.object(MarketingSitePublisher, '_get_form_build_id_and_form_token', return_value={}):
publisher.publish_program(self.program)
self.assert_responses_call_count(6)
self.assert_responses_call_count(8)
@responses.activate
def test_publish_modified_program(self):
......@@ -208,8 +225,76 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
self.mock_node_edit(200)
program_before = ProgramFactory()
publisher = MarketingSitePublisher(program_before)
self.mock_add_alias()
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'):
publisher.publish_program(self.program)
self.assert_responses_call_count(9)
@responses.activate
def test_get_alias_form(self):
self.mock_api_client(200)
self.mock_node_retrieval(self.program.uuid)
self.mock_node_edit(200)
publisher = MarketingSitePublisher()
self.mock_add_alias()
self.mock_get_alias_form()
with mock.patch.object(MarketingSitePublisher, '_get_headers', return_value={}):
publisher.publish_program(self.program)
self.assert_responses_call_count(9)
@responses.activate
def test_get_delete_form(self):
self.mock_api_client(200)
self.mock_node_retrieval(self.program.uuid)
self.mock_node_edit(200)
program_before = ProgramFactory()
publisher = MarketingSitePublisher(program_before)
self.mock_add_alias()
self.mock_get_delete_form(program_before.marketing_slug)
with mock.patch.object(MarketingSitePublisher, '_get_headers', return_value={}):
with mock.patch.object(MarketingSitePublisher, '_get_form_build_id_and_form_token', return_value={}):
publisher.publish_program(self.program)
self.assert_responses_call_count(10)
@responses.activate
def test_get_alias_form_failed(self):
self.mock_api_client(200)
self.mock_node_retrieval(self.program.uuid)
self.mock_node_edit(200)
publisher = MarketingSitePublisher()
self.mock_add_alias()
self.mock_get_alias_form(500)
with mock.patch.object(MarketingSitePublisher, '_get_headers', return_value={}):
with self.assertRaises(ProgramPublisherException):
publisher.publish_program(self.program)
@responses.activate
def test_get_delete_form_failed(self):
self.mock_api_client(200)
self.mock_node_retrieval(self.program.uuid)
self.mock_node_edit(200)
program_before = ProgramFactory()
publisher = MarketingSitePublisher(program_before)
self.mock_add_alias()
self.mock_get_delete_form(program_before.marketing_slug, 500)
with mock.patch.object(MarketingSitePublisher, '_get_headers', return_value={}):
with mock.patch.object(MarketingSitePublisher, '_get_form_build_id_and_form_token', return_value={}):
with self.assertRaises(ProgramPublisherException):
publisher.publish_program(self.program)
@responses.activate
def test_add_alias_failed(self):
self.mock_api_client(200)
self.mock_node_retrieval(self.program.uuid)
self.mock_node_edit(200)
publisher = MarketingSitePublisher()
self.mock_add_alias(500)
with mock.patch.object(MarketingSitePublisher, '_get_form_build_id_and_form_token', return_value={}):
with self.assertRaises(ProgramPublisherException):
publisher.publish_program(self.program)
self.assert_responses_call_count(6)
@responses.activate
def test_publish_unmodified_program(self):
......@@ -241,7 +326,7 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
self.mock_node_delete(204)
publisher = MarketingSitePublisher()
publisher.delete_program(self.program)
self.assert_responses_call_count(5)
self.assert_responses_call_count(6)
@responses.activate
def test_publish_delete_non_existent_program(self):
......@@ -249,4 +334,11 @@ class MarketingSitePublisherTests(MarketingSitePublisherTestMixin):
self.mock_node_retrieval(self.program.uuid, exists=False)
publisher = MarketingSitePublisher()
publisher.delete_program(self.program)
self.assert_responses_call_count(4)
self.assert_responses_call_count(5)
@responses.activate
def test_publish_delete_xseries(self):
self.program = ProgramFactory(type=ProgramType.objects.get(name='XSeries'))
publisher = MarketingSitePublisher()
publisher.delete_program(self.program)
self.assert_responses_call_count(0)
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: apps/api/filters.py
#, python-brace-format
......@@ -240,9 +240,7 @@ msgid "Deleted"
msgstr ""
#: apps/course_metadata/forms.py
msgid ""
"Programs can only be activated if they have a marketing slug and a banner "
"image."
msgid "Programs can only be activated if they have a banner image."
msgstr ""
#: apps/course_metadata/models.py
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: static/js/catalogs-change-form.js
msgid "Preview"
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps/api/filters.py
......@@ -289,12 +289,10 @@ msgid "Deleted"
msgstr "Délétéd Ⱡ'σяєм ιρѕυм #"
#: apps/course_metadata/forms.py
msgid ""
"Programs can only be activated if they have a marketing slug and a banner "
"image."
msgid "Programs can only be activated if they have a banner image."
msgstr ""
"Prögräms çän önlý ßé äçtïvätéd ïf théý hävé ä märkétïng slüg änd ä ßännér "
"ïmägé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
"Prögräms çän önlý ßé äçtïvätéd ïf théý hävé ä ßännér ïmägé. Ⱡ'σяєм ιρѕυм "
"∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: apps/course_metadata/models.py
msgid "Facebook"
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/js/catalogs-change-form.js
......
boto==2.42.0
beautifulsoup4==4.5.1
cryptography==1.5.3
django==1.9.11
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