From 82fc798e1e5b00417f6309cb02b9c7941352600c Mon Sep 17 00:00:00 2001 From: Mark Pittaway Date: Fri, 2 Feb 2024 17:40:37 +1100 Subject: [PATCH 1/2] [SDESK-7166] fix(ingest): Assignment ID lost when updating from PlanningML --- server/planning/common.py | 15 +++- .../feed_parsers/superdesk_planning_xml.py | 90 +++++++++++++------ 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/server/planning/common.py b/server/planning/common.py index 819a9f3df..69e49dfe1 100644 --- a/server/planning/common.py +++ b/server/planning/common.py @@ -8,7 +8,7 @@ # AUTHORS and LICENSE files distributed with this source code, or # at https://www.sourcefabric.org/superdesk/license -from typing import NamedTuple, Dict, Any, Set, Tuple +from typing import NamedTuple, Dict, Any, Set, Optional import re import time @@ -30,6 +30,8 @@ import json from bson import ObjectId +from planning.types import Planning, Coverage + ITEM_STATE = "state" ITEM_EXPIRY = "expiry" @@ -838,3 +840,14 @@ def update_ingest_on_patch(updates: Dict[str, Any], original: Dict[str, Any]): # The local version has been published # and no change to ``pubstatus`` on ingested item updates.pop("state") + + +def get_coverage_from_planning(planning_item: Planning, coverage_id: str) -> Optional[Coverage]: + return next( + ( + coverage + for coverage in planning_item.get("coverages") or [] + if coverage.get("coverage_id") == coverage_id + ), + None, + ) diff --git a/server/planning/feed_parsers/superdesk_planning_xml.py b/server/planning/feed_parsers/superdesk_planning_xml.py index ed41daa53..3a11944d1 100644 --- a/server/planning/feed_parsers/superdesk_planning_xml.py +++ b/server/planning/feed_parsers/superdesk_planning_xml.py @@ -1,7 +1,9 @@ +from typing import Optional import logging from eve.utils import config from flask import current_app as app +from superdesk import get_resource_service from superdesk.io.feed_parsers import NewsMLTwoFeedParser import pytz from xml.etree.ElementTree import Element @@ -14,8 +16,10 @@ from superdesk.utc import utcnow, utc_to_local from superdesk.errors import ParserError +from planning.types import Planning from planning.common import ( get_coverage_status_from_cv, + get_coverage_from_planning, get_default_coverage_status_qcode_on_ingest, WORKFLOW_STATE, POST_STATE, @@ -78,30 +82,35 @@ def get_item_id(self, tree: Element) -> str: def parse(self, tree: Element, provider=None): self.root = tree self.set_missing_voc_policy() + planning_service = get_resource_service("planning") try: guid = self.get_item_id(tree) + original: Optional[Planning] = planning_service.find_one(req=None, _id=guid) + item = self.parse_item(tree, original) + return [item] if item is not None else [] + except Exception as ex: + raise ParserError.parseMessageError(ex, provider) - item = { - GUID_FIELD: guid, - ITEM_TYPE: CONTENT_TYPE.PLANNING, - "state": CONTENT_STATE.INGESTED, - "_id": guid, - } - - self.parse_item_meta(tree, item) - self.parse_content_meta(tree, item) - self.parse_news_coverage_set(tree, item) - self.parse_news_coverage_status(tree, item) + def parse_item(self, tree: Element, original: Optional[Planning]) -> Optional[Planning]: + guid = (original or {}).get("_id") or self.get_item_id(tree) + item = { + GUID_FIELD: guid, + ITEM_TYPE: CONTENT_TYPE.PLANNING, + "state": CONTENT_STATE.INGESTED, + "_id": guid, + } - upgrade_rich_text_fields(item, "planning") - for coverage in item.get("coverages") or []: - upgrade_rich_text_fields(coverage.get("planning") or {}, "coverage") + self.parse_item_meta(tree, item) + self.parse_content_meta(tree, item) + self.parse_news_coverage_set(tree, item, original) + self.parse_news_coverage_status(tree, item) - return [item] + upgrade_rich_text_fields(item, "planning") + for coverage in item.get("coverages") or []: + upgrade_rich_text_fields(coverage.get("planning") or {}, "coverage") - except Exception as ex: - raise ParserError.parseMessageError(ex, provider) + return item def parse_item_meta(self, tree, item): """Parse itemMeta tag @@ -175,17 +184,26 @@ def parse_news_coverage_status(self, tree, item): # Get the ``news_coverage_status.qcode`` to use assert_el = tree.find(self.qname("assert")) - status_qcode = get_default_coverage_status_qcode_on_ingest() + ingest_status_qcode = None if assert_el is not None: news_coverage_status = assert_el.find(self.qname("newsCoverageStatus")).get("qcode") if news_coverage_status: - status_qcode = news_coverage_status + ingest_status_qcode = news_coverage_status # Now assign the ``news_coverage_status`` to all coverages of this Planning item - coverage_status = get_coverage_status_from_cv(status_qcode) + default_status_qcode = get_default_coverage_status_qcode_on_ingest() + default_coverage_status = get_coverage_status_from_cv(default_status_qcode) + default_coverage_status.pop("is_active", None) + + coverage_status = get_coverage_status_from_cv(ingest_status_qcode or default_status_qcode) coverage_status.pop("is_active", None) for coverage in item["coverages"]: - coverage["news_coverage_status"] = coverage_status + if ingest_status_qcode is not None: + # Status was provided by ingest, use that + coverage["news_coverage_status"] = coverage_status + elif coverage.get("news_coverage_status") is None: + # Status wasn't provided in ingest, and coverage has no current value, use default instead + coverage["news_coverage_status"] = default_coverage_status def parse_genre(self, planning_elt, planning): """Parse Genre tag @@ -225,7 +243,7 @@ def parse_coverage_planning(self, news_coverage_elt, item): return None - def get_coverage_details(self, news_coverage_elt, item): + def get_coverage_details(self, news_coverage_elt: Element, item: Planning, original: Optional[Planning]): """Process the Coverage element and optionally return the coverage details If ``None`` is returned, this coverage is not added to the Planning item @@ -239,7 +257,7 @@ def get_coverage_details(self, news_coverage_elt, item): return None modified = news_coverage_elt.get("modified") - return { + coverage_details = { "coverage_id": coverage_id, "workflow_status": WORKFLOW_STATE.DRAFT, "firstcreated": item["firstcreated"], @@ -247,16 +265,30 @@ def get_coverage_details(self, news_coverage_elt, item): "planning": planning, } - def parse_news_coverage_set(self, tree, item): - """Parse newsCoverageSet tag + original_coverage = get_coverage_from_planning(original, coverage_id) if original else None + if original_coverage is not None: + direct_copy_fields = { + "workflow_status", + "news_coverage_status", + "previous_status", + "assigned_to", + "flags", + "original_creator", + "version_creator", + } + for field in direct_copy_fields: + if field in original_coverage: + coverage_details[field] = original_coverage[field] + + return coverage_details + + def parse_news_coverage_set(self, tree: Element, item: Planning, original: Optional[Planning]): + """Parse newsCoverageSet tag""" - :param tree: tree - :param item: planning item - """ item["coverages"] = [] news_coverage_set = tree.find(self.qname("newsCoverageSet")) if news_coverage_set is not None: for news_coverage_elt in news_coverage_set.findall(self.qname("newsCoverage")): - coverage = self.get_coverage_details(news_coverage_elt, item) + coverage = self.get_coverage_details(news_coverage_elt, item, original) if coverage is not None: item["coverages"].append(coverage) From 33ab2d4de519013433aacac90e33d6cf928adbaa Mon Sep 17 00:00:00 2001 From: Mark Pittaway Date: Fri, 2 Feb 2024 17:49:26 +1100 Subject: [PATCH 2/2] fix lint --- server/planning/common.py | 6 +----- server/planning/feed_parsers/superdesk_planning_xml.py | 5 ++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/server/planning/common.py b/server/planning/common.py index 69e49dfe1..4cb81b839 100644 --- a/server/planning/common.py +++ b/server/planning/common.py @@ -844,10 +844,6 @@ def update_ingest_on_patch(updates: Dict[str, Any], original: Dict[str, Any]): def get_coverage_from_planning(planning_item: Planning, coverage_id: str) -> Optional[Coverage]: return next( - ( - coverage - for coverage in planning_item.get("coverages") or [] - if coverage.get("coverage_id") == coverage_id - ), + (coverage for coverage in planning_item.get("coverages") or [] if coverage.get("coverage_id") == coverage_id), None, ) diff --git a/server/planning/feed_parsers/superdesk_planning_xml.py b/server/planning/feed_parsers/superdesk_planning_xml.py index 3a11944d1..54041f797 100644 --- a/server/planning/feed_parsers/superdesk_planning_xml.py +++ b/server/planning/feed_parsers/superdesk_planning_xml.py @@ -250,8 +250,11 @@ def get_coverage_details(self, news_coverage_elt: Element, item: Planning, origi """ coverage_id = news_coverage_elt.get("id") - planning = self.parse_coverage_planning(news_coverage_elt, item) + if coverage_id is None: + logger.warning("Unable to process coverage details, no coverage id found in ingest source") + return None + planning = self.parse_coverage_planning(news_coverage_elt, item) if planning is None: logger.warning(f"Failed to process coverage '{coverage_id}', planning details not found") return None