Skip to content

Commit a810deb

Browse files
authored
Merge pull request #187 from collective/better_topic_migration
2 parents 6e802f7 + 5760d0a commit a810deb

2 files changed

Lines changed: 126 additions & 26 deletions

File tree

CHANGES.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ Changelog
3131
- Add example for importing collective.jsonify data to documentation.
3232
[pbauer]
3333

34+
- Better serialization of Topics:
35+
- Use newer criteria added in Plone 5
36+
- Add fallback for some criteria
37+
- Export sort_on and sort_reversed
38+
- Export customView as tabular_view
39+
[pbauer]
40+
3441
- Always import discussions independent if discussion support is enabled or not
3542
on a particular content object (#182)
3643
[ajung]

src/collective/exportimport/serializer.py

Lines changed: 119 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -408,49 +408,142 @@ class SerializeTopicToJson(SerializeToJson):
408408
"""This uses the topic migration from p.a.contenttypes to turn Criteria into a Querystring."""
409409

410410
def __call__(self, version=None, include_items=False):
411-
topic_metadata = super(SerializeTopicToJson, self).__call__(version=version)
411+
# 1. Get the default serialisation for AT content
412+
item = super(SerializeTopicToJson, self).__call__(version=version)
412413

413-
# migrate criteria
414-
formquery = []
414+
# 2. Get querystring-registry
415+
query = []
415416
reg = getUtility(IRegistry)
416417
reader = IQuerystringRegistryReader(reg)
417-
self.registry = reader.parseRegistry()
418-
418+
registry = reader.parseRegistry()
419+
420+
# Inject new selection-operators that were added in Plone 5
421+
selection = registry["plone"]["app"]["querystring"]["operation"]["selection"]
422+
new_operators = ["all", "any", "none"]
423+
for operator in new_operators:
424+
if operator not in selection:
425+
# just a dummy method to pass validation
426+
selection[operator] = {"operation": "collective.exportimport"}
427+
428+
# Inject any operator for some fields
429+
any_operator = "plone.app.querystring.operation.selection.any"
430+
fields_with_any_operator = ['Creator', 'Subject', 'portal_type', 'review_state']
431+
for field in fields_with_any_operator:
432+
operations = registry["plone"]["app"]["querystring"]["field"][field]["operations"]
433+
if any_operator not in operations:
434+
registry["plone"]["app"]["querystring"]["field"][field]["operations"].append(any_operator)
435+
436+
# Inject all operator for Subject
437+
all_operator= "plone.app.querystring.operation.selection.all"
438+
fields_with_any_operator = ["Subject"]
439+
for field in fields_with_any_operator:
440+
operations = registry["plone"]["app"]["querystring"]["field"][field]["operations"]
441+
if all_operator not in operations:
442+
registry["plone"]["app"]["querystring"]["field"][field]["operations"].append(all_operator)
443+
444+
# 3. Migrate criteria using the converters from p.a.contenttypes
419445
criteria = self.context.listCriteria()
420446
for criterion in criteria:
421447
type_ = criterion.__class__.__name__
422448
if type_ == "ATSortCriterion":
423-
# Sort order and direction are now stored in the Collection.
424-
self._collection_sort_reversed = criterion.getReversed()
425-
self._collection_sort_on = criterion.Field()
426-
logger.debug(
427-
"Sort on %r, reverse: %s.",
428-
self._collection_sort_on,
429-
self._collection_sort_reversed,
430-
)
449+
# Migrate sorting
450+
item["sort_reversed"] = criterion.getReversed()
451+
item["sort_on"] = criterion.Field()
431452
continue
432453

433454
converter = CONVERTERS.get(type_)
434455
if converter is None:
435-
msg = "Unsupported criterion {0}".format(type_)
456+
msg = u"Unsupported criterion {0}".format(type_)
436457
logger.error(msg)
437458
raise ValueError(msg)
459+
before = len(query)
438460
try:
439-
converter(formquery, criterion, self.registry)
440-
except Exception as e:
441-
logger.info(e)
442-
443-
topic_metadata["query"] = json_compatible(formquery)
444-
445-
# migrate batch size
461+
converter(query, criterion, registry)
462+
except Exception:
463+
logger.info(u"Error converting criterion %s", criterion.__dict__, exc_info=True)
464+
pass
465+
466+
# Try to manually convert when no criterion was added
467+
# this happens with invalid criteria (e.g. path without a path)
468+
if len(query) == before:
469+
fixed = self.fix_criteria(criterion)
470+
if fixed:
471+
query.append(fixed)
472+
else:
473+
logger.info(u"Check maybe broken collection %s", self.context.absolute_url())
474+
475+
# 4. So some manual fixes in the migrated query
476+
indexes_to_fix = [
477+
u"portal_type",
478+
u"review_state",
479+
u"Creator",
480+
u"Subject",
481+
]
482+
operator_mapping = {
483+
# old -> new
484+
u"plone.app.querystring.operation.selection.is":
485+
u"plone.app.querystring.operation.selection.any",
486+
u"plone.app.querystring.operation.string.is":
487+
u"plone.app.querystring.operation.selection.any",
488+
}
489+
fixed_query = []
490+
for crit in query:
491+
if crit["o"].endswith("relativePath") and crit["v"] == "..":
492+
# relativePath no longer accepts ..
493+
crit["v"] = "..::1"
494+
if crit["i"] in indexes_to_fix:
495+
for old_operator, new_operator in operator_mapping.items():
496+
if crit["o"] == old_operator:
497+
crit["o"] = new_operator
498+
if crit["o"] == u"plone.app.querystring.operation.string.currentUser":
499+
crit["v"] = ""
500+
fixed_query.append(crit)
501+
query = fixed_query
502+
503+
# 5. Migrate batch size
446504
if self.context.itemCount:
447-
topic_metadata["b_size"] = self.context.itemCount
505+
item["item_count"] = self.context.itemCount
506+
507+
# 6. Migrate customView
508+
if item.pop("customView", False):
509+
item["layout"] = "tabular_view"
510+
511+
item["query"] = json_compatible(query)
512+
return item
513+
514+
def fix_criteria(self, criterion):
515+
"""Try to fix some invalid criteria"""
516+
FIXES = {
517+
# real operators
518+
"or": "plone.app.querystring.operation.selection.any",
519+
# fake operators
520+
"contains": "plone.app.querystring.operation.string.contains",
521+
"any": "plone.app.querystring.operation.selection.any",
522+
}
448523

449-
if hasattr(self, "_collection_sort_on"):
450-
topic_metadata["sort_on"] = self._collection_sort_on
451-
topic_metadata["sort_reversed"] = self._collection_sort_reversed
524+
type_ = criterion.__class__.__name__
525+
field = criterion.field
526+
value = getattr(criterion, "value", None)
527+
operator = getattr(criterion, "operator", None)
528+
529+
if type_ == "ATSimpleStringCriterion":
530+
operator = "contains"
531+
if type_ == "ATSelectionCriterion":
532+
operator = "any"
533+
if type_ == "ATListCriterion":
534+
operator = "any"
535+
if type_ in ["ATPathCriterion", "ATDateCriteria"] and not value:
536+
return
537+
if field == "commentators":
538+
# no index
539+
return
452540

453-
return topic_metadata
541+
query = {
542+
"i": field,
543+
"o": FIXES.get(operator, operator),
544+
"v": value,
545+
}
546+
return query
454547

455548

456549
def get_dx_blob_path(obj):

0 commit comments

Comments
 (0)