Commit f6efcbf9 by Ahsan Ulhaq

Program marketing slug to be URL of published Drupal page

ECOM-7310
parent c218120c
...@@ -2,7 +2,7 @@ import json ...@@ -2,7 +2,7 @@ import json
from urllib.parse import urljoin from urllib.parse import urljoin
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from django.utils.functional import cached_property from django.utils.text import slugify
from course_discovery.apps.course_metadata.choices import CourseRunStatus from course_discovery.apps.course_metadata.choices import CourseRunStatus
from course_discovery.apps.course_metadata.exceptions import ( from course_discovery.apps.course_metadata.exceptions import (
...@@ -255,6 +255,7 @@ class ProgramMarketingSitePublisher(BaseMarketingSitePublisher): ...@@ -255,6 +255,7 @@ class ProgramMarketingSitePublisher(BaseMarketingSitePublisher):
self.edit_node(node_id, node_data) self.edit_node(node_id, node_data)
if node_id: if node_id:
self.get_and_delete_alias(slugify(obj.title))
self.update_node_alias(obj, node_id, previous_obj) self.update_node_alias(obj, node_id, previous_obj)
def serialize_obj(self, obj): def serialize_obj(self, obj):
...@@ -292,65 +293,77 @@ class ProgramMarketingSitePublisher(BaseMarketingSitePublisher): ...@@ -292,65 +293,77 @@ class ProgramMarketingSitePublisher(BaseMarketingSitePublisher):
""" """
new_alias = self.alias(obj) new_alias = self.alias(obj)
previous_alias = self.alias(previous_obj) if previous_obj else None previous_alias = self.alias(previous_obj) if previous_obj else None
new_alias_delete_path = self.alias_delete_path(self.get_alias_list_url(obj.marketing_slug))
if new_alias != previous_alias: if new_alias != previous_alias or not new_alias_delete_path:
alias_add_url = '{}/add'.format(self.alias_api_base) # Delete old alias before saving the new one.
if previous_obj and previous_obj.marketing_slug != obj.marketing_slug:
self.get_and_delete_alias(previous_obj.marketing_slug)
headers = { headers = {
'content-type': 'application/x-www-form-urlencoded' 'content-type': 'application/x-www-form-urlencoded'
} }
data = { data = {
**self.alias_form_inputs, **self.alias_form_inputs(self.alias_add_url),
'alias': new_alias, 'alias': new_alias,
'form_id': 'path_admin_form', 'form_id': 'path_admin_form',
'op': 'Save', 'op': 'Save',
'source': 'node/{}'.format(node_id), 'source': 'node/{}'.format(node_id),
} }
response = self.client.api_session.post(alias_add_url, headers=headers, data=data) response = self.client.api_session.post(self.alias_add_url, headers=headers, data=data)
if response.status_code != 200: if response.status_code != 200:
raise AliasCreateError raise AliasCreateError
# Delete old alias after saving the new one. def get_and_delete_alias(self, slug):
if previous_obj: """
alias_list_url = '{base}/list/{slug}'.format( Get the URL alias for the provided slug and delete it if exists.
base=self.alias_api_base,
slug=previous_obj.marketing_slug
)
alias_delete_path = self.alias_delete_path(alias_list_url)
if alias_delete_path: Arguments:
alias_delete_url = '{base}/{path}'.format( slug (str): slug for which URL alias has to be fetched.
base=self.client.api_url, """
path=alias_delete_path.strip('/') alias_list_url = self.get_alias_list_url(slug)
) alias_delete_path = self.alias_delete_path(alias_list_url)
if alias_delete_path:
self.delete_alias(alias_delete_path)
def delete_alias(self, alias_delete_path):
"""
Delete the url alias for provided path
"""
headers = {
'content-type': 'application/x-www-form-urlencoded'
}
alias_delete_url = '{base}/{path}'.format(
base=self.client.api_url,
path=alias_delete_path.strip('/')
)
data = { data = {
**self.alias_form_inputs, **self.alias_form_inputs(alias_delete_url),
'confirm': 1, 'confirm': 1,
'form_id': 'path_admin_delete_confirm', 'form_id': 'path_admin_delete_confirm',
'op': 'Confirm', 'op': 'Confirm'
} }
response = self.client.api_session.post(alias_delete_url, headers=headers, data=data) response = self.client.api_session.post(alias_delete_url, headers=headers, data=data)
if response.status_code != 200: if response.status_code != 200:
raise AliasDeleteError raise AliasDeleteError
def alias(self, obj): def alias(self, obj):
return '{type}/{slug}'.format(type=obj.type.slug, slug=obj.marketing_slug) return '{type}/{slug}'.format(type=obj.type.slug, slug=obj.marketing_slug)
@cached_property def alias_form_inputs(self, url):
def alias_form_inputs(self):
""" """
Scrape input values from the form used to modify Drupal aliases. Scrape input values from the form used to modify Drupal aliases.
Raises: Raises:
FormRetrievalError: If there's a problem getting the form from Drupal. FormRetrievalError: If there's a problem getting the form from Drupal.
""" """
response = self.client.api_session.get(self.alias_add_url) response = self.client.api_session.get(url)
if response.status_code != 200: if response.status_code != 200:
raise FormRetrievalError raise FormRetrievalError
...@@ -379,3 +392,9 @@ class ProgramMarketingSitePublisher(BaseMarketingSitePublisher): ...@@ -379,3 +392,9 @@ class ProgramMarketingSitePublisher(BaseMarketingSitePublisher):
delete_element = html.select('.delete.last a') delete_element = html.select('.delete.last a')
return delete_element[0].get('href') if delete_element else None return delete_element[0].get('href') if delete_element else None
def get_alias_list_url(self, slug):
return '{base}/list/{slug}'.format(
base=self.alias_api_base,
slug=slug
)
...@@ -148,6 +148,15 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin): ...@@ -148,6 +148,15 @@ class MarketingSitePublisherTestMixin(MarketingSiteAPIClientTestMixin):
'</input><input name="form_token" value="2"></input></form></html>' '</input><input name="form_token" value="2"></input></form></html>'
) )
def mock_delete_alias_form(self, status=200):
responses.add(
responses.GET,
'{root}/admin/config/search/path/delete/foo'.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): def mock_get_delete_form(self, alias, status=200):
responses.add( responses.add(
responses.GET, responses.GET,
......
...@@ -263,7 +263,10 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin): ...@@ -263,7 +263,10 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
@mock.patch.object(ProgramMarketingSitePublisher, 'create_node', return_value='node_id') @mock.patch.object(ProgramMarketingSitePublisher, 'create_node', return_value='node_id')
@mock.patch.object(ProgramMarketingSitePublisher, 'edit_node', return_value=None) @mock.patch.object(ProgramMarketingSitePublisher, 'edit_node', return_value=None)
@mock.patch.object(ProgramMarketingSitePublisher, 'update_node_alias', return_value=None) @mock.patch.object(ProgramMarketingSitePublisher, 'update_node_alias', return_value=None)
def test_publish_obj(self, mock_update_node_alias, mock_edit_node, mock_create_node, *args): # pylint: disable=unused-argument @mock.patch.object(ProgramMarketingSitePublisher, 'get_and_delete_alias', return_value=None)
def test_publish_obj(
self, mock_get_and_delete_alias, mock_update_node_alias, mock_edit_node, mock_create_node, *args
): # pylint: disable=unused-argument
""" """
Verify that the publisher only attempts to publish programs of certain types, Verify that the publisher only attempts to publish programs of certain types,
only attempts an edit when any one of a set of trigger fields is changed, only attempts an edit when any one of a set of trigger fields is changed,
...@@ -287,6 +290,7 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin): ...@@ -287,6 +290,7 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
assert mock_create_node.called assert mock_create_node.called
assert not mock_edit_node.called assert not mock_edit_node.called
assert mock_get_and_delete_alias.called
assert mock_update_node_alias.called assert mock_update_node_alias.called
for mocked_method in mocked_methods: for mocked_method in mocked_methods:
...@@ -305,6 +309,7 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin): ...@@ -305,6 +309,7 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
assert not mock_create_node.called assert not mock_create_node.called
assert mock_edit_node.called assert mock_edit_node.called
assert mock_get_and_delete_alias.called
assert mock_update_node_alias.called assert mock_update_node_alias.called
@responses.activate @responses.activate
...@@ -340,10 +345,14 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin): ...@@ -340,10 +345,14 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
and deletes an old alias, if one existed, and that appropriate exceptions and deletes an old alias, if one existed, and that appropriate exceptions
are raised for non-200 status codes. are raised for non-200 status codes.
""" """
# No previous object is provided. Alias creation should occur, but alias # No previous object is provided. Create a new node and make sure
# deletion should not. # title alias created, by default, based on the title is deleted
# and a new alias based on marketing slug is created.
self.mock_api_client() self.mock_api_client()
self.mock_get_alias_form() self.mock_get_alias_form()
self.mock_get_delete_form(self.obj.title)
self.mock_delete_alias()
self.mock_get_delete_form(self.obj.marketing_slug)
self.mock_add_alias() self.mock_add_alias()
self.publisher.update_node_alias(self.obj, self.node_id, None) self.publisher.update_node_alias(self.obj, self.node_id, None)
...@@ -356,6 +365,9 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin): ...@@ -356,6 +365,9 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
# alias creation. An exception should be raised. # alias creation. An exception should be raised.
self.mock_api_client() self.mock_api_client()
self.mock_get_alias_form() self.mock_get_alias_form()
self.mock_get_delete_form(self.obj.title)
self.mock_delete_alias()
self.mock_get_delete_form(self.obj.marketing_slug)
self.mock_add_alias(status=500) self.mock_add_alias(status=500)
with pytest.raises(AliasCreateError): with pytest.raises(AliasCreateError):
...@@ -366,6 +378,7 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin): ...@@ -366,6 +378,7 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
# A previous object is provided, but the marketing slug hasn't changed. # A previous object is provided, but the marketing slug hasn't changed.
# Neither alias creation nor alias deletion should occur. # Neither alias creation nor alias deletion should occur.
self.mock_api_client() self.mock_api_client()
self.mock_get_delete_form(self.obj.marketing_slug)
self.publisher.update_node_alias(self.obj, self.node_id, self.obj) self.publisher.update_node_alias(self.obj, self.node_id, self.obj)
...@@ -376,10 +389,13 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin): ...@@ -376,10 +389,13 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
previous_obj = ProgramFactory() previous_obj = ProgramFactory()
self.mock_api_client() self.mock_api_client()
self.mock_get_alias_form() self.mock_get_delete_form(self.obj.marketing_slug)
self.mock_add_alias()
self.mock_get_delete_form(previous_obj.marketing_slug) self.mock_get_delete_form(previous_obj.marketing_slug)
self.mock_delete_alias_form()
self.mock_delete_alias() self.mock_delete_alias()
self.mock_get_alias_form()
self.mock_get_delete_form(self.obj.marketing_slug)
self.mock_add_alias()
self.publisher.update_node_alias(self.obj, self.node_id, previous_obj) self.publisher.update_node_alias(self.obj, self.node_id, previous_obj)
...@@ -391,9 +407,9 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin): ...@@ -391,9 +407,9 @@ class ProgramMarketingSitePublisherTests(MarketingSitePublisherTestMixin):
# Same scenario, but this time a non-200 status code is returned during # Same scenario, but this time a non-200 status code is returned during
# alias deletion. An exception should be raised. # alias deletion. An exception should be raised.
self.mock_api_client() self.mock_api_client()
self.mock_get_alias_form() self.mock_get_delete_form(self.obj.marketing_slug)
self.mock_add_alias()
self.mock_get_delete_form(previous_obj.marketing_slug) self.mock_get_delete_form(previous_obj.marketing_slug)
self.mock_delete_alias_form()
self.mock_delete_alias(status=500) self.mock_delete_alias(status=500)
with pytest.raises(AliasDeleteError): with pytest.raises(AliasDeleteError):
......
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