def save(self, *args, **kwargs): suppress_publication = kwargs.pop('suppress_publication', False) is_publishable = ( self.course.partner.has_marketing_site and waffle.switch_is_active('publish_course_runs_to_marketing_site') and # Pop to clean the kwargs for the base class save call below not suppress_publication) if is_publishable: publisher = CourseRunMarketingSitePublisher(self.course.partner) previous_obj = CourseRun.objects.get( id=self.id) if self.id else None if not self.slug: # If we are publishing this object to marketing site, # let's make sure slug is defined self.slug = slugify(self.title) with transaction.atomic(): super(CourseRun, self).save(*args, **kwargs) publisher.publish_obj(self, previous_obj=previous_obj) else: logger.info('Course run [%s] is not publishable.', self.key) super(CourseRun, self).save(*args, **kwargs)
def setUp(self): super().setUp() self.partner = PartnerFactory() self.publisher = CourseRunMarketingSitePublisher(self.partner) self.api_root = self.publisher.client.api_url self.username = self.publisher.client.username self.obj = CourseRunFactory()
def published(self, site): # Grab some variables, bailing if anything doesn't make sense publisher_run = self.course_run discovery_run = publisher_run and publisher_run.discovery_course_run discovery_course = discovery_run and discovery_run.course if not discovery_course: return now = datetime.datetime.now(pytz.UTC) # Publish new course discovery_run.announcement = now discovery_run.status = CourseRunStatus.Published discovery_run.save() # Now, find old course runs that are no longer active but still published. # These will be unpublished in favor of the new course. for run in discovery_course.course_runs.all(): if run != discovery_run and run.status == CourseRunStatus.Published and run.end and run.end < now: CourseRunMarketingSitePublisher(site.partner).add_url_redirect( discovery_run, run) run.status = CourseRunStatus.Unpublished run.save() # Notify course team if waffle.switch_is_active('enable_publisher_email_notifications'): emails.send_course_run_published_email(self.course_run, site)
def handle(self, *args, **options): config = DrupalPublishUuidConfig.get_solo() # Publish course runs if config.course_run_ids: course_run_ids = config.course_run_ids.split(',') course_runs = CourseRun.objects.filter(key__in=course_run_ids) for course_run in course_runs: publisher = CourseRunMarketingSitePublisher( course_run.course.partner) publisher.publish_obj(course_run, include_uuid=True) # Publish people if config.push_people: publisher = MarketingSitePeople() for person in Person.objects.all(): logger.info('Updating person node %s [%s].', person.slug, person.uuid) try: publisher.update_person(person) except (PersonToMarketingException, MarketingSiteAPIClientException): logger.exception( 'An error occurred while updating person %s on the marketing site.', person.full_name, )
def save(self, *args, **kwargs): suppress_publication = kwargs.pop('suppress_publication', False) is_publishable = ( self.course.partner.has_marketing_site and waffle.switch_is_active('publish_course_runs_to_marketing_site') and # Pop to clean the kwargs for the base class save call below not suppress_publication ) if is_publishable: publisher = CourseRunMarketingSitePublisher(self.course.partner) previous_obj = CourseRun.objects.get(id=self.id) if self.id else None with transaction.atomic(): super(CourseRun, self).save(*args, **kwargs) publisher.publish_obj(self, previous_obj=previous_obj) else: super(CourseRun, self).save(*args, **kwargs)
def setUp(self): super(CourseRunStateTests, self).setUp() language_tag = LanguageTag(code='te-st', name='Test Language') language_tag.save() self.site = SiteFactory() self.partner = PartnerFactory(site=self.site) self.course_run.transcript_languages.add(language_tag) self.course_run.language = language_tag self.course_run.is_micromasters = True self.course_run.micromasters_name = 'test' self.course_run.lms_course_id = 'course-v1:edX+DemoX+Demo_Course' self.course_run.save() self.course.course_state.name = CourseStateChoices.Approved self.course.save() self.course_run.staff.add(PersonFactory()) self.course_run_state.preview_accepted = False self.course_run_state.save() self.assertTrue(self.course_run_state.can_send_for_review()) self.publisher = CourseRunMarketingSitePublisher(self.partner) self.api_root = self.publisher.client.api_url self.username = self.publisher.client.username
class CourseRunMarketingSitePublisherTests(MarketingSitePublisherTestMixin): """ Tests covering course run-specific publishing logic. """ def setUp(self): super().setUp() self.partner = PartnerFactory() self.publisher = CourseRunMarketingSitePublisher(self.partner) self.api_root = self.publisher.client.api_url self.username = self.publisher.client.username self.obj = CourseRunFactory() @mock.patch.object(CourseRunMarketingSitePublisher, 'node_id', return_value='node_id') @mock.patch.object(CourseRunMarketingSitePublisher, 'serialize_obj', return_value='data') @mock.patch.object(CourseRunMarketingSitePublisher, 'edit_node', return_value=None) def test_publish_obj(self, mock_edit_node, *args): # pylint: disable=unused-argument """ Verify that the publisher attempts to publish when course run status changes. """ # No previous object. No editing should occur. self.publisher.publish_obj(self.obj) assert not mock_edit_node.called # A previous object is provided, but the status hasn't changed. # No editing should occur. self.publisher.publish_obj(self.obj, previous_obj=self.obj) assert not mock_edit_node.called # A previous object is provided, and the status has changed. # Editing should occur. previous_obj = CourseRunFactory(status=CourseRunStatus.Unpublished) self.publisher.publish_obj(self.obj, previous_obj=previous_obj) mock_edit_node.assert_called_with('node_id', 'data') @responses.activate def test_serialize_obj(self): """ Verify that the publisher serializes data required to publish course runs. """ self.mock_api_client() actual = self.publisher.serialize_obj(self.obj) expected = { 'field_course_id': self.obj.key, 'author': { 'id': self.user_id }, 'status': 1, } assert actual == expected self.obj.status = CourseRunStatus.Unpublished actual = self.publisher.serialize_obj(self.obj) expected['status'] = 0 assert actual == expected
class CourseRunMarketingSitePublisherTests(MarketingSitePublisherTestMixin): """ Tests covering course run-specific publishing logic. """ def setUp(self): super().setUp() self.partner = PartnerFactory() self.publisher = CourseRunMarketingSitePublisher(self.partner) self.api_root = self.publisher.client.api_url self.username = self.publisher.client.username self.obj = CourseRunFactory() @mock.patch.object(CourseRunMarketingSitePublisher, 'node_id', return_value=None) @mock.patch.object(CourseRunMarketingSitePublisher, 'create_node') def test_publish_obj_create_disabled(self, mock_create_node, mock_node_id): self.publisher.publish_obj(self.obj) mock_node_id.assert_called_with(self.obj) assert not mock_create_node.called @mock.patch.object(CourseRunMarketingSitePublisher, 'serialize_obj', return_value={'data': 'test'}) @mock.patch.object(CourseRunMarketingSitePublisher, 'node_id', return_value=None) @mock.patch.object(CourseRunMarketingSitePublisher, 'create_node', return_value='node_id') @mock.patch.object(CourseRunMarketingSitePublisher, 'update_node_alias') @override_switch('auto_course_about_page_creation', True) def test_publish_obj_create_successful(self, mock_update_node_alias, mock_create_node, *args): # pylint: disable=unused-argument self.publisher.publish_obj(self.obj) mock_create_node.assert_called_with({ 'data': 'test', 'field_course_uuid': str(self.obj.uuid) }) mock_update_node_alias.assert_called_with(self.obj, 'node_id', None) @mock.patch.object(CourseRunMarketingSitePublisher, 'node_id', return_value=None) @mock.patch.object(CourseRunMarketingSitePublisher, 'serialize_obj', return_value={'data': 'test'}) @mock.patch.object(CourseRunMarketingSitePublisher, 'create_node', return_value='node1') @mock.patch.object(CourseRunMarketingSitePublisher, 'update_node_alias') @override_switch('auto_course_about_page_creation', True) def test_publish_obj_create_if_exists_on_discovery(self, mock_update_node_alias, mock_create_node, mock_serialize_obj, mock_node_id, *args): # pylint: disable=unused-argument self.publisher.publish_obj(self.obj) mock_node_id.assert_called_with(self.obj) mock_serialize_obj.assert_called_with(self.obj) mock_create_node.assert_called_with({ 'data': 'test', 'field_course_uuid': str(self.obj.uuid) }) mock_update_node_alias.assert_called_with(self.obj, 'node1', None) @mock.patch.object(CourseRunMarketingSitePublisher, 'node_id', return_value='node_id') @mock.patch.object(CourseRunMarketingSitePublisher, 'serialize_obj', return_value='data') @mock.patch.object(CourseRunMarketingSitePublisher, 'edit_node', return_value=None) @mock.patch.object(CourseRunMarketingSitePublisher, 'update_node_alias') def test_publish_obj_edit(self, mock_node_alias, mock_edit_node, *args): # pylint: disable=unused-argument """ Verify that the publisher attempts to publish when course run status changes. """ # A previous object is provided, but the status hasn't changed. # No editing should occur. self.publisher.publish_obj(self.obj, previous_obj=self.obj) assert not mock_edit_node.called # A previous object is provided, and the status has changed. # Editing should occur. previous_obj = CourseRunFactory(status=CourseRunStatus.Unpublished) self.publisher.publish_obj(self.obj, previous_obj=previous_obj) mock_edit_node.assert_called_with('node_id', 'data') mock_node_alias.assert_called_with(self.obj, 'node_id', None) @responses.activate def test_serialize_obj(self): """ Verify that the publisher serializes data required to publish course runs. """ self.mock_api_client() actual = self.publisher.serialize_obj(self.obj) expected = { 'field_course_id': self.obj.key, 'title': self.obj.title, 'author': { 'id': self.user_id }, 'status': 1, 'type': 'course', } assert actual == expected self.obj.status = CourseRunStatus.Unpublished actual = self.publisher.serialize_obj(self.obj) expected['status'] = 0 assert actual == expected @responses.activate def test_update_node_alias(self): """ Verify that the publisher attempts to create a new alias associated with the new course_run, and that appropriate exceptions are raised for non-200 status codes. """ # No previous object is provided. Create a new node and make sure # 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_get_alias_form() self.mock_get_delete_form(self.obj.slug) self.mock_delete_alias() self.mock_get_delete_form(self.obj.slug) self.mock_add_alias() self.publisher.update_node_alias(self.obj, self.node_id, None) assert responses.calls[-1].request.url == '{}/add'.format( self.publisher.alias_api_base) responses.reset() # Same scenario, but this time a non-200 status code is returned during # alias creation. An exception should be raised. self.mock_api_client() self.mock_get_alias_form() self.mock_get_delete_form(self.obj.slug) self.mock_delete_alias() self.mock_get_delete_form(self.obj.slug) self.mock_add_alias(status=500) with pytest.raises(AliasCreateError): self.publisher.update_node_alias(self.obj, self.node_id, None) responses.reset() # A previous object is provided, but the marketing slug hasn't changed. # Neither alias creation nor alias deletion should occur. self.mock_api_client() self.mock_get_delete_form(self.obj.slug) self.publisher.update_node_alias(self.obj, self.node_id, self.obj) responses.reset() # In this case, similate the fact that alias form retrival returned error # FormRetrievalError should be raised self.mock_api_client() self.mock_get_delete_form(self.obj.slug) self.mock_get_alias_form(status=500) with pytest.raises(FormRetrievalError): self.publisher.update_node_alias(self.obj, self.node_id, None) def test_alias(self): """ Verify that aliases are constructed correctly. """ actual = self.publisher.alias(self.obj) expected = 'course/{slug}'.format(slug=self.obj.slug) assert actual == expected