From 37077e608168470412e617044afc8a12f4dc9c7e Mon Sep 17 00:00:00 2001 From: oli <23073839+odelaere@users.noreply.github.com> Date: Tue, 24 May 2022 16:37:28 +0200 Subject: [PATCH 1/2] adjust export import for new demo profile --- .../PloneMeeting/exportimport/content.py | 94 ++++++++++++++++--- .../PloneMeeting/profiles/__init__.py | 94 ++++++++++++++----- 2 files changed, 150 insertions(+), 38 deletions(-) diff --git a/src/Products/PloneMeeting/exportimport/content.py b/src/Products/PloneMeeting/exportimport/content.py index 2fa9ffdc5..9a0486318 100644 --- a/src/Products/PloneMeeting/exportimport/content.py +++ b/src/Products/PloneMeeting/exportimport/content.py @@ -17,6 +17,7 @@ from imio.helpers.security import generate_password from imio.helpers.security import is_develop_environment from plone import api +from plone.app.textfield import RichTextValue from plone.namedfile.file import NamedBlobFile from plone.namedfile.file import NamedBlobImage from plone.namedfile.file import NamedImage @@ -37,7 +38,7 @@ from Products.PloneMeeting.config import TOOL_FOLDER_RECURRING_ITEMS from Products.PloneMeeting.Extensions.imports import import_contacts from Products.PloneMeeting.profiles import DEFAULT_USER_PASSWORD -from Products.PloneMeeting.utils import org_id_to_uid +from Products.PloneMeeting.utils import org_id_to_uid, cleanMemoize from Products.PloneMeeting.utils import updateCollectionCriterion from z3c.relationfield.relation import RelationValue from zope.component import getUtility @@ -48,12 +49,11 @@ import os import transaction - # PloneMeeting-Error related constants ----------------------------------------- MEETING_CONFIG_ERROR = 'A validation error occurred while instantiating ' \ 'meeting configuration "%s" with id "%s". %s' MEETINGCONFIG_BADREQUEST_ERROR = 'There was an error during creation of ' \ - 'MeetingConfig with id "%s". Original error : "%s"' + 'MeetingConfig with id "%s". Original error : "%s"' def update_labels_jar(jar, values): @@ -80,9 +80,14 @@ def __init__(self, context, productname): self.tool = self.portal.portal_plonemeeting # set correct title self.tool.setTitle(translate('pm_configuration', - domain='PloneMeeting', - context=self.request)) + domain='PloneMeeting', + context=self.request)) self.profileData = self.getProfileData() + + self.wfTool = api.portal.get_tool('portal_workflow') + self.pTool = api.portal.get_tool('plone_utils') + self.mTool = api.portal.get_tool('portal_membership') + # Initialize the tool if we have data if not self.profileData: return @@ -218,15 +223,78 @@ def run(self): # adapt userDescr.ploneGroups to turn cfg_num into cfg_id self.addUsersOutsideGroups(self.data.usersOutsideGroups) + # manage meeting and item creation + for cfg in self.data.meetingConfigs: + cleanMemoize(self.portal) + self._add_meeting_and_items(cfg) + # commit before continuing so elements like scales on annex types are correctly saved transaction.commit() return self.successMessage + def _add_meeting_and_items(self, meeting_cfg): + cfg = getattr(self.tool, meeting_cfg.id) + if meeting_cfg.meetings: + for meeting in meeting_cfg.meetings: + self._create_meeting(meeting, cfg) + + if meeting_cfg.items: + for item in meeting_cfg.items: + self._create_item(item, cfg) + + def _create_item(self, item, cfg): + user_folder = self.tool.getPloneMeetingFolder(cfg.getId(), item.creator) + with api.env.adopt_user(username=item.creator): + self.tool.invalidateAllCache() + template = getattr(self.tool.getMeetingConfig(user_folder).itemtemplates, item.itemTemplate) + item_obj = template.clone(newOwnerId=item.creator, + destFolder=user_folder, + newPortalType=cfg.getItemTypeName()) + item_obj.setTitle(item.title) + item_obj.setBudgetRelated(item.budget_related) + if item.to_state == 'proposed': + self.wfTool.doActionFor(item_obj, 'propose') + + # todo handle asking advice + # todo handle annexes + if item.to_state in ('validated', 'presented'): + self.wfTool.doActionFor(item_obj, 'validate') + if item.to_state == 'presented': + self.wfTool.doActionFor(item_obj, 'present') + else: + for transaction in cfg.getTransitionsForPresentingAnItem(): + if item_obj.query_state() == item.to_state: + break + self.wfTool.doActionFor(item_obj, transaction) + + return item_obj + + def _create_meeting(self, meeting, cfg): + user_folder = self.tool.getPloneMeetingFolder(cfg.getId(), meeting.creator) + + meeting_id = user_folder.invokeFactory( + cfg.getMeetingTypeName(), + id=meeting.date.strftime('%Y%m%d'), + date=meeting.date, + start_date=meeting.start_date, + end_date=meeting.end_date, + ) + meeting_obj = getattr(user_folder, meeting_id) + if meeting.observations: + meeting_obj.observations = RichTextValue(meeting.observations) + + self.pTool.changeOwnershipOf(meeting_obj, meeting.creator) + self.portal.REQUEST["PUBLISHED"] = meeting_obj + for item in meeting.items: + item.to_state = 'presented' + self._create_item(item, cfg) + + def _correct_advice_states(self, advice_states): """ """ return ['{0}__state__{1}'.format( - self.cfg_num_to_id(v.split('__state__')[0]), - v.split('__state__')[1]) for v in advice_states] + self.cfg_num_to_id(v.split('__state__')[0]), + v.split('__state__')[1]) for v in advice_states] def _finishConfigFor(self, cfg, data): """When the MeetingConfig has been created, some parameters still need to be applied @@ -269,7 +337,7 @@ def _finishConfigFor(self, cfg, data): cfg.setOrderedContacts(selectableOrderedContacts) # turn contact path to uid - for org_storing_field in ('orderedContacts', ): + for org_storing_field in ('orderedContacts',): org_storing_data = getattr(data, org_storing_field, []) if org_storing_data: contact_uids = [] @@ -462,8 +530,8 @@ def addItemToConfig(self, cfg, descr, isRecurring=True): folder = getattr(cfg, TOOL_FOLDER_ITEM_TEMPLATES) data = descr.__dict__ itemType = isRecurring and \ - cfg.getItemTypeName(configType='MeetingItemRecurring') or \ - cfg.getItemTypeName(configType='MeetingItemTemplate') + cfg.getItemTypeName(configType='MeetingItemRecurring') or \ + cfg.getItemTypeName(configType='MeetingItemTemplate') folder.invokeFactory(itemType, **data) item = getattr(folder, descr.id) # adapt org related values as we have org id on descriptor and we need to set org UID @@ -478,7 +546,7 @@ def addItemToConfig(self, cfg, descr, isRecurring=True): proposing_group_uid = org_id_to_uid(proposing_group_id) group_in_charge_uid = org_id_to_uid(group_in_charge_id) item.setProposingGroup(proposing_group_uid) - item.setGroupsInCharge((group_in_charge_uid, )) + item.setGroupsInCharge((group_in_charge_uid,)) item.proposingGroupWithGroupInCharge = '{0}__groupincharge__{1}'.format( proposing_group_uid, group_in_charge_uid) if item.associatedGroups: @@ -593,13 +661,13 @@ def addPodTemplate(self, container, pt, source): if not pod_template_to_use_cfg: logger.warning( 'Cfg with id {0} not found when adding Pod template {1}, template was not added'.format( - pt.pod_template_to_use['cfg_id'], pt.pod_template_to_use['template_id'])) + pt.pod_template_to_use['cfg_id'], pt.pod_template_to_use['template_id'])) return pod_template = pod_template_to_use_cfg.podtemplates.get(pt.pod_template_to_use['template_id']) if not pod_template: logger.warning( 'Pod template with id {0} not found in cfg with id {1}, template was not added'.format( - pt.pod_template_to_use['template_id'], pt.pod_template_to_use['cfg_id'])) + pt.pod_template_to_use['template_id'], pt.pod_template_to_use['cfg_id'])) return pod_template_to_use = pod_template.UID() else: diff --git a/src/Products/PloneMeeting/profiles/__init__.py b/src/Products/PloneMeeting/profiles/__init__.py index 103974823..f453baf3c 100644 --- a/src/Products/PloneMeeting/profiles/__init__.py +++ b/src/Products/PloneMeeting/profiles/__init__.py @@ -5,7 +5,7 @@ from collective.contact.plonegroup.config import PLONEGROUP_ORG from plone.api.validation import at_least_one_of -from Products.PloneMeeting.config import DEFAULT_LIST_TYPES +from Products.PloneMeeting.config import DEFAULT_LIST_TYPES, ITEM_DEFAULT_TEMPLATE_ID from Products.PloneMeeting.config import DEFAULT_USER_PASSWORD from Products.PloneMeeting.config import MEETING_GROUP_SUFFIXES @@ -51,48 +51,87 @@ def getData(self, **kw): return res -class RecurringItemDescriptor(Descriptor): +class ItemAdviceDescriptor(Descriptor): + + def __init__(self, adviser_group, adviser, delay=None, delay_started_on=None, delay_ended_on=None, + advice_type=None): + self.adviser_group = adviser_group + self.delay = delay + self.delay_started_on = delay_started_on + self.delay_ended_on = delay_ended_on + self.adviser = adviser + self.advice_type = advice_type + + +class ItemDescriptor(Descriptor): excludedFields = ['title'] - def __init__(self, id, title, proposingGroup='', groupsInCharge=(), proposingGroupWithGroupInCharge='', - description='', category='', associatedGroups=(), decision='', - itemKeywords='', itemTags=(), meetingTransitionInsertingMe='_init_', privacy='public'): - self.id = id + @at_least_one_of('proposingGroup', 'proposingGroupWithGroupInCharge') + def __init__(self, title, creator, proposingGroup='', groupsInCharge=(), proposingGroupWithGroupInCharge='', + itemTemplate=ITEM_DEFAULT_TEMPLATE_ID, description='', category='', classifier='', associatedGroups=(), motivation='', + decision='', itemKeywords='', itemTags=(), privacy='public', listType='normal', to_state='validated', + advices=[], annexes=[], budget_related=False): self.title = title + self.creator = creator + # the proposingGroup can be empty ('') for itemtemplate self.proposingGroup = proposingGroup self.groupsInCharge = groupsInCharge self.proposingGroupWithGroupInCharge = proposingGroupWithGroupInCharge + self.itemTemplate = itemTemplate self.description = description self.category = category + self.classifier = classifier self.associatedGroups = associatedGroups + self.motivation = motivation self.decision = decision self.itemKeywords = itemKeywords self.itemTags = itemTags - self.meetingTransitionInsertingMe = meetingTransitionInsertingMe self.privacy = privacy + self.listType = listType + self.to_state = to_state + self.budget_related = budget_related + self.advices = advices + self.annexes = annexes -class ItemTemplateDescriptor(Descriptor): +class RecurringItemDescriptor(ItemDescriptor): excludedFields = ['title'] @at_least_one_of('proposingGroup', 'proposingGroupWithGroupInCharge') - def __init__(self, id, title, proposingGroup='', groupsInCharge=(), proposingGroupWithGroupInCharge='', - description='', category='', associatedGroups=(), decision='', - itemKeywords='', itemTags=(), templateUsingGroups=[], privacy='public'): + def __init__(self, id, title, creator='dgen', proposingGroup='', groupsInCharge=(), proposingGroupWithGroupInCharge='', + description='', category='', classifier='', associatedGroups=(), motivation='', decision='', + itemKeywords='', itemTags=(), meetingTransitionInsertingMe='_init_', privacy='public'): + super(RecurringItemDescriptor, self).__init__(title, creator, proposingGroup, groupsInCharge, + proposingGroupWithGroupInCharge, description, category, + classifier, associatedGroups, motivation, decision, itemKeywords, + itemTags, privacy) + self.id = id + self.meetingTransitionInsertingMe = meetingTransitionInsertingMe + + +class ItemTemplateDescriptor(ItemDescriptor): + excludedFields = ['title'] + + @at_least_one_of('proposingGroup', 'proposingGroupWithGroupInCharge') + def __init__(self, id, title, creator='dgen', proposingGroup='', groupsInCharge=(), proposingGroupWithGroupInCharge='', + description='', category='', associatedGroups=(), decision='', itemKeywords='', itemTags=(), + templateUsingGroups=[], privacy='public'): + super(ItemTemplateDescriptor, self).__init__(title, creator, proposingGroup, groupsInCharge, + proposingGroupWithGroupInCharge, description, category, + associatedGroups, decision, itemKeywords, itemTags, privacy) self.id = id - self.title = title - # the proposingGroup can be empty ('') for itemtemplate - self.proposingGroup = proposingGroup - self.groupsInCharge = groupsInCharge - self.proposingGroupWithGroupInCharge = proposingGroupWithGroupInCharge - self.description = description - self.category = category - self.associatedGroups = associatedGroups - self.decision = decision - self.itemKeywords = itemKeywords - self.itemTags = itemTags self.templateUsingGroups = templateUsingGroups - self.privacy = privacy + + +class MeetingDescriptor(Descriptor): + def __init__(self, date, creator='dgen', start_date=None, end_date=None, observations=None, items=[], to_state='closed'): + self.date = date + self.creator = creator + self.start_date = start_date + self.end_date = end_date + self.observations = observations + self.items = items + self.to_state = to_state class CategoryDescriptor(Descriptor): @@ -359,6 +398,7 @@ def get(klass): if not klass.instance: klass.instance = OrgDescriptor(None, None, None) return klass.instance + get = classmethod(get) def __init__(self, id, title, acronym, description=u'', @@ -419,9 +459,10 @@ def get(klass): if not klass.instance: klass.instance = MeetingConfigDescriptor(None, None, None) return klass.instance + get = classmethod(get) - def __init__(self, id, title, folderTitle, isDefault=False, active=True): + def __init__(self, id, title, folderTitle, isDefault=False, active=True, meetings=[], items=[]): self.id = id # Identifier of the meeting config. self.title = title self.active = active @@ -820,6 +861,8 @@ def __init__(self, id, title, folderTitle, isDefault=False, active=True): # content_category_groups parameters ----------------------------------- self.category_group_activated_attrs = {} + self.meetings = meetings + self.items = items class PloneMeetingConfiguration(Descriptor): @@ -836,6 +879,7 @@ def get(klass): if not klass.instance: klass.instance = PloneMeetingConfiguration('My meetings', [], []) return klass.instance + get = classmethod(get) def __init__(self, meetingFolderTitle, meetingConfigs, orgs): @@ -886,4 +930,4 @@ def __init__(self, meetingFolderTitle, meetingConfigs, orgs): self.directory_position_types = [] self.contactsTemplates = [] # ~[PodTemplateDescriptor]~ -# ------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ \ No newline at end of file From fd394fba810a1bc67cfba0cfd88970f141ff4f5b Mon Sep 17 00:00:00 2001 From: oli <23073839+odelaere@users.noreply.github.com> Date: Wed, 25 May 2022 15:19:25 +0200 Subject: [PATCH 2/2] meeting are created with a proper assembly --- .../PloneMeeting/exportimport/content.py | 34 +++++++++++++++++-- .../PloneMeeting/profiles/__init__.py | 6 +++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Products/PloneMeeting/exportimport/content.py b/src/Products/PloneMeeting/exportimport/content.py index 9a0486318..d374f84e9 100644 --- a/src/Products/PloneMeeting/exportimport/content.py +++ b/src/Products/PloneMeeting/exportimport/content.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from collections import OrderedDict from collective.contact.plonegroup.browser.settings import invalidate_soev_cache from collective.contact.plonegroup.browser.settings import invalidate_ssoev_cache @@ -223,13 +224,14 @@ def run(self): # adapt userDescr.ploneGroups to turn cfg_num into cfg_id self.addUsersOutsideGroups(self.data.usersOutsideGroups) + # commit before continuing so elements like scales on annex types are correctly saved + transaction.commit() + # manage meeting and item creation for cfg in self.data.meetingConfigs: cleanMemoize(self.portal) self._add_meeting_and_items(cfg) - # commit before continuing so elements like scales on annex types are correctly saved - transaction.commit() return self.successMessage def _add_meeting_and_items(self, meeting_cfg): @@ -280,6 +282,24 @@ def _create_meeting(self, meeting, cfg): end_date=meeting.end_date, ) meeting_obj = getattr(user_folder, meeting_id) + + attendees = OrderedDict({}) + if meeting.attendees: + for attendee_id in meeting.attendees: + attendees[org_id_to_uid(attendee_id)] = meeting.attendees[attendee_id] + else: + for attendee_uid in cfg.getOrderedContacts(): + attendees[attendee_uid] = 'attendee' + + signatories = {} + # todo need a tool like held_position_id_to_uid or force static uids + for attendee_uid in attendees: + held_position = api.content.uuidToObject(attendee_uid) + if held_position.signature_number: + signatories[attendee_uid] = held_position.signature_number + + meeting_obj._do_update_contacts(attendees=attendees, signatories=signatories) + if meeting.observations: meeting_obj.observations = RichTextValue(meeting.observations) @@ -289,6 +309,16 @@ def _create_meeting(self, meeting, cfg): item.to_state = 'presented' self._create_item(item, cfg) + if meeting.to_state != 'created': + state_transactions = { + 'frozen': 'freeze', + 'decided': 'decide', + 'closed': 'close', + } + for state in state_transactions: + if meeting.to_state == meeting_obj.query_state(): + break + self.wfTool.doActionFor(meeting_obj, state_transactions[state]) def _correct_advice_states(self, advice_states): """ """ diff --git a/src/Products/PloneMeeting/profiles/__init__.py b/src/Products/PloneMeeting/profiles/__init__.py index f453baf3c..df967814e 100644 --- a/src/Products/PloneMeeting/profiles/__init__.py +++ b/src/Products/PloneMeeting/profiles/__init__.py @@ -2,6 +2,7 @@ # # GNU General Public License (GPL) # +from collections import OrderedDict from collective.contact.plonegroup.config import PLONEGROUP_ORG from plone.api.validation import at_least_one_of @@ -124,13 +125,16 @@ def __init__(self, id, title, creator='dgen', proposingGroup='', groupsInCharge class MeetingDescriptor(Descriptor): - def __init__(self, date, creator='dgen', start_date=None, end_date=None, observations=None, items=[], to_state='closed'): + def __init__(self, date, creator='dgen', start_date=None, end_date=None, observations=None, items=[], + attendees=OrderedDict({}), signatories={}, to_state='closed'): self.date = date self.creator = creator self.start_date = start_date self.end_date = end_date self.observations = observations self.items = items + self.attendees = attendees + self.signatories = signatories self.to_state = to_state