Beispiel #1
0
 def configure_autocomplete_widget(self, folder):
     """ Configure and add autocomplete widget """
     # ajouter ++resource++select2/select2_locale_fr.js dans portal_javascript
     reimport_faceted_config(folder,
                             xml='im-mail-searches.xml',
                             default_UID=folder['all_mails'].UID())
     # we reindex organizations
     for brain in self.catalog(portal_type='organization'):
         brain.getObject().reindexObject(idxs=['sortable_title'])
Beispiel #2
0
    def update_collections(self):
        # update incomingmail collections
        for brain in api.content.find(context=self.imf['mail-searches'],
                                      portal_type='DashboardCollection'):
            obj = brain.getObject()
            if 'CreationDate' in obj.customViewFields:
                buf = list(obj.customViewFields)
                buf[buf.index('CreationDate')] = u'reception_date'
                obj.customViewFields = tuple(buf)

        collections = [
            {},
            {},
            {},
            {},
            {},
            {},
            {},
            {
                'id':
                'in_copy_unread',
                'tit':
                _('im_in_copy_unread'),
                'subj': (u'todo', ),
                'query': [
                    {
                        'i': 'portal_type',
                        'o': 'plone.app.querystring.operation.selection.is',
                        'v': ['dmsincomingmail']
                    },  # i_e ok
                    {
                        'i': 'CompoundCriterion',
                        'o': 'plone.app.querystring.operation.compound.is',
                        'v': 'dmsincomingmail-in-copy-group-unread'
                    }
                ],
                'cond':
                u"",
                'bypass': [],
                'flds':
                (u'select_row', u'pretty_link', u'review_state',
                 u'treating_groups', u'assigned_user', u'due_date',
                 u'mail_type', u'sender', u'reception_date', u'actions'),
                'sort':
                u'organization_type',
                'rev':
                True,
                'count':
                True
            },
            {},
            {
                'id':
                'followed',
                'tit':
                _('im_followed'),
                'subj': (u'search', ),
                'query': [
                    {
                        'i': 'portal_type',
                        'o': 'plone.app.querystring.operation.selection.is',
                        'v': ['dmsincomingmail']
                    },  # i_e ok
                    {
                        'i': 'CompoundCriterion',
                        'o': 'plone.app.querystring.operation.compound.is',
                        'v': 'dmsincomingmail-followed'
                    }
                ],
                'cond':
                u"",
                'bypass': [],
                'flds':
                (u'select_row', u'pretty_link', u'review_state',
                 u'treating_groups', u'assigned_user', u'due_date',
                 u'mail_type', u'sender', u'reception_date', u'actions'),
                'sort':
                u'organization_type',
                'rev':
                True,
                'count':
                False
            }
        ]
        if 'in_copy_unread' not in self.imf['mail-searches']:
            createDashboardCollections(self.imf['mail-searches'], collections)

        # ICollectionCategories
        alsoProvides(self.imf['mail-searches'], ICollectionCategories)
        alsoProvides(self.omf['mail-searches'], ICollectionCategories)
        alsoProvides(self.portal['tasks']['task-searches'],
                     ICollectionCategories)
        # I...BatchActions
        noLongerProvides(self.imf['mail-searches'], IIMDashboard)
        alsoProvides(self.imf['mail-searches'], IIMDashboardBatchActions)
        noLongerProvides(self.omf['mail-searches'], IOMDashboard)
        alsoProvides(self.omf['mail-searches'], IOMDashboardBatchActions)
        noLongerProvides(self.portal['tasks']['task-searches'], ITaskDashboard)
        alsoProvides(self.portal['tasks']['task-searches'],
                     ITaskDashboardBatchActions)
        # Rename category label
        self.imf['mail-searches'].setRights('Courrier entrant')
        self.omf['mail-searches'].setRights('Courrier sortant')
        # Rename collection title
        self.imf['mail-searches']['all_mails'].setTitle(u'Tout')
        self.imf['mail-searches']['all_mails'].reindexObject()
        self.imf['mail-searches']['have_treated'].setTitle(u"Que j'ai traité")
        self.imf['mail-searches']['have_treated'].reindexObject()
        self.omf['mail-searches']['all_mails'].setTitle(u'Tout')
        self.omf['mail-searches']['all_mails'].reindexObject()
        self.omf['mail-searches']['have_treated'].setTitle(u"Que j'ai traité")
        self.omf['mail-searches']['have_treated'].reindexObject()
        self.portal['tasks']['task-searches']['to_treat'].setTitle(
            u'Qui me sont assignées')
        self.portal['tasks']['task-searches']['to_treat'].reindexObject()

        # reimport faceted
        reimport_faceted_config(
            self.imf['mail-searches'],
            xml='im-mail-searches.xml',
            default_UID=self.imf['mail-searches']['all_mails'].UID())
        reimport_faceted_config(
            self.omf['mail-searches'],
            xml='om-mail-searches.xml',
            default_UID=self.omf['mail-searches']['all_mails'].UID())
Beispiel #3
0
    def run(self):
        logger.info('Migrating to imio.dms.mail 1.0...')
        self.cleanRegistries()
        self.upgradeProfile('collective.dms.mailcontent:default')
        # We have to reapply type info before doing other subproducts migration
        self.runProfileSteps('imio.dms.mail', steps=['typeinfo'])
        # We have to update type schema because plone.dexterity doesn't detect schema_policy modification. BUG #44
        for portal_type in ['dmsincomingmail', 'dmsoutgoingmail']:  # i_e ok
            schemaName = dxutils.portalTypeToSchemaName(portal_type)
            schema = getattr(plone.dexterity.schema.generated, schemaName)
            fti = getUtility(IDexterityFTI, name=portal_type)
            model = fti.lookupModel()
            syncSchema(model.schema, schema, overwrite=True, sync_bases=True)
            notify(plone.dexterity.schema.SchemaInvalidatedEvent(portal_type))

        self.upgradeProfile('collective.task:default')
        self.upgradeProfile('dexterity.localroles:default')
        self.upgradeProfile('dexterity.localrolesfield:default')
        self.upgradeProfile('collective.contact.plonegroup:default')
        self.runProfileSteps('imio.dms.mail',
                             steps=[
                                 'actions', 'componentregistry',
                                 'controlpanel', 'plone.app.registry',
                                 'portlets', 'repositorytool', 'rolemap',
                                 'sharing', 'workflow'
                             ])
        self.portal.portal_workflow.updateRoleMappings()
        self.runProfileSteps('collective.dms.mailcontent',
                             steps=['controlpanel'])
        self.runProfileSteps('collective.contact.plonegroup',
                             steps=['controlpanel'])
        self.reinstall([
            'collective.messagesviewlet:messages',
            'collective.querynextprev:default',
            'imio.dashboard:default',
        ])

        # set jqueryui autocomplete to False. If not, contact autocomplete doesn't work
        self.registry[
            'collective.js.jqueryui.controlpanel.IJQueryUIPlugins.ui_autocomplete'] = False

        # delete old dmsmail portlet
        self.delete_portlet(self.portal, 'portlet_maindmsmail')

        # remove deprecated interfaces
        self.remove_contact_interfaces()

        # moved notes content to task_description
        catalog = api.portal.get_tool('portal_catalog')
        brains = catalog.searchResults(portal_type='dmsincomingmail')  # i_e ok
        for brain in brains:
            obj = brain.getObject()
            if not base_hasattr(obj, 'notes') or not obj.notes:
                continue
            text = u'<p>%s</p>\r\n' % obj.notes.replace('\r\n', '<br />\r\n')
            obj.task_description = richtextval(text)
            delattr(obj, 'notes')
        #    obj.reindexObject()

        # replace collections by Dashboard collections
        im_folder = self.portal['incoming-mail']
        alsoProvides(im_folder, INextPrevNotNavigable)
        alsoProvides(im_folder, IIMDashboard)
        self.replaceCollections(im_folder)

        # apply contact faceted config
        reimport_faceted_config(self.portal['contacts'],
                                'contacts-faceted.xml')

        # add new indexes for dashboard
        addOrUpdateIndexes(self.portal,
                           indexInfos={
                               'mail_type': ('FieldIndex', {}),
                               'mail_date': ('DateIndex', {}),
                               'in_out_date': ('DateIndex', {}),
                           })

        # set dashboard on incoming mail
        configure_faceted_folder(
            im_folder,
            xml='default_dashboard_widgets.xml',
            default_UID=im_folder['mail-searches']['all_mails'].UID())

        # set task local roles configuration
        configure_task_rolefields(self.portal)

        # update dexterity local roles configuration
        self.update_local_roles()

        # add task actionspanel config
        if not self.registry[
                'imio.actionspanel.browser.registry.IImioActionsPanelConfig.transitions']:
            self.registry['imio.actionspanel.browser.registry.IImioActionsPanelConfig.transitions'] = []
        self.registry['imio.actionspanel.browser.registry.IImioActionsPanelConfig.transitions'] += \
            ['task.back_in_created|', 'task.back_in_to_assign|', 'task.back_in_to_do|',
             'task.back_in_progress|', 'task.back_in_realized|']

        # activate ckeditor
        configure_ckeditor(self.portal, custom='ged')

        # Set markup allowed types
        adapter = MarkupControlPanelAdapter(self.portal)
        adapter.set_allowed_types(['text/html'])

        # update searchabletext
        self.update_dmsmainfile()

        self.upgradeAll()
        for prod in [
                'plone.formwidget.autocomplete', 'collective.documentviewer',
                'plone.formwidget.masterselect', 'collective.contact.core',
                'collective.contact.duplicated', 'collective.dms.basecontent',
                'collective.dms.scanbehavior', 'collective.externaleditor',
                'plone.app.collection', 'plone.app.intid',
                'collective.contact.facetednav', 'plonetheme.imioapps',
                'PasswordStrength', 'imio.dms.mail'
        ]:
            mark_last_version(self.portal, product=prod)

        self.portal.manage_permission(
            'CMFEditions: Revert to previous versions',
            ('Manager', 'Site Administrator'),
            acquire=0)

        #self.refreshDatabase()
        self.finish()
Beispiel #4
0
    def update_site(self):
        # documentgenerator config
        set_oo_port()
        set_uno_path()

        # add templates configuration
        add_templates(self.portal)

        # set som objects as not next/prev navigable
        for obj in (self.portal['front-page'], self.portal['contacts'], self.portal['templates']):
            if not INextPrevNotNavigable.providedBy(obj):
                alsoProvides(obj, INextPrevNotNavigable)

        # publish outgoing-mail folder
        if api.content.get_state(self.omf) != 'internally_published':
            transitions(self.omf, ["show_internally"])
        # add group
        if api.group.get('expedition') is None:
            api.group.create('expedition', '1 Expédition courrier sortant')
            self.portal['outgoing-mail'].manage_addLocalRoles('expedition', ['Contributor'])
            self.portal['contacts'].manage_addLocalRoles('expedition', ['Contributor', 'Editor', 'Reader'])
            api.group.add_user(groupname='expedition', username='******')
        # dir_general can add outgoing mails
        self.portal['outgoing-mail'].manage_addLocalRoles('dir_general', ['Contributor'])

        # rename group title
        encodeurs = api.group.get('encodeurs')
        if encodeurs.getProperty('title') != '1 Encodeurs courrier entrant':
            self.portal.portal_groups.editGroup('encodeurs', title='1 Encodeurs courrier entrant')
        # update im mail-searches
        # alsoProvides(self.imf['mail-searches'], ICollectionCategories)
        # add new collection to_treat_in_my_group
        createIMailCollections(self.imf['mail-searches'])
        reimport_faceted_config(self.imf['mail-searches'], xml='im-mail-searches.xml',
                                default_UID=self.imf['mail-searches']['all_mails'].UID())
        # update permissions
        for perm in ("imio.dms.mail: Write mail base fields", "imio.dms.mail: Write treating group field",
                     "imio.dms.mail: Write userid field"):
            if 'Site Administrator' not in [dic['name'] for dic in self.portal.rolesOfPermission(perm)
                                            if dic['selected'] == 'SELECTED']:
                self.portal.manage_permission(perm, ('Manager', 'Site Administrator'), acquire=0)

        # configure external edition
        self.portal.portal_memberdata.manage_changeProperties(ext_editor=True)
        self.registry['externaleditor.ext_editor'] = True
        if 'Image' in self.registry['externaleditor.externaleditor_enabled_types']:
            self.registry['externaleditor.externaleditor_enabled_types'] = ['PODTemplate', 'ConfigurablePODTemplate',
                                                                            'DashboardPODTemplate', 'SubTemplate',
                                                                            'StyleTemplate', 'dmsommainfile']
        change_user_properties(self.portal, kw='ext_editor:True', dochange='1')

        # searched types
        changeSearchedTypes(self.portal)

        # add documentation message
        add_message('doc2-0', 'Documentation 2.0', u'<p>Vous pouvez consulter la <a href="http://www.imio.be/'
                    u'support/documentation/topic/cp_app_ged" target="_blank">documentation en ligne de la '
                    u'version 2.0</a>, ainsi que d\'autres documentations liées.</p>', msg_type='significant',
                    can_hide=True, req_roles=['Authenticated'], activate=True)

        val = api.portal.get_registry_record('imio.actionspanel.browser.registry.IImioActionsPanelConfig.transitions')
        if 'dmsoutgoingmail.back_to_agent|' not in val:
            val += ['dmsoutgoingmail.back_to_agent|', 'dmsoutgoingmail.back_to_creation|',
                    'dmsoutgoingmail.back_to_service_chief|', 'dmsoutgoingmail.back_to_print|',
                    'dmsoutgoingmail.back_to_be_signed|', 'dmsoutgoingmail.back_to_scanned|']
            api.portal.set_registry_record('imio.actionspanel.browser.registry.IImioActionsPanelConfig.transitions',
                                           val)
        # update front-page
        frontpage = self.portal['front-page']
        if frontpage.Title() == 'Gestion du courrier 1.1':
            frontpage.setTitle(_("front_page_title"))
            frontpage.setDescription(_("front_page_descr"))
            frontpage.setText(_("front_page_text"), mimetype='text/html')
Beispiel #5
0
    def run(self):
        logger.info('Migrating to imio.dms.mail 1.1...')
        self.cleanRegistries()
        self.runProfileSteps(
            'imio.dms.mail',
            steps=['actions', 'cssregistry', 'jsregistry', 'workflow'])
        self.runProfileSteps('collective.messagesviewlet',
                             steps=['collective-messagesviewlet-messages'],
                             profile='messages')
        self.upgradeProfile('collective.dms.mailcontent:default')
        self.upgradeProfile('collective.task:default')
        self.upgradeProfile('eea.facetednavigation:default')
        self.upgradeProfile('collective.querynextprev:default')
        im_folder = self.portal['incoming-mail']

        # set mail-searches folder as not next/prev navigable
        if not INextPrevNotNavigable.providedBy(im_folder['task-searches']):
            alsoProvides(im_folder['task-searches'], INextPrevNotNavigable)

        # activate field on DashboardCollection
        self.add_view_field('mail_type',
                            im_folder['mail-searches'],
                            before='CreationDate')
        self.add_view_field('sender',
                            im_folder['mail-searches'],
                            before='CreationDate')
        self.add_view_field('task_parent',
                            im_folder['task-searches'],
                            before='review_state')

        # set showNumberOfItems on some collections
        self.update_count(im_folder['mail-searches'],
                          ids=[
                              'to_validate', 'to_treat', 'im_treating',
                              'searchfor_created'
                          ])
        self.update_count(im_folder['task-searches'],
                          ids=['to_validate', 'to_treat', 'im_treating'])

        # update criterion on validation collections
        self.update_validation_collections()

        # Activate browser message
        msg = self.portal['messages-config']['browser-warning']
        api.content.transition(obj=msg, to_state='activated')

        # update searchabletext
        self.update_dmsmainfile()
        self.update_dmsincomingmail()

        # add new indexes
        addOrUpdateIndexes(self.portal,
                           indexInfos={'state_group': ('FieldIndex', {})})

        # add metadata in portal_catalog
        addOrUpdateColumns(self.portal, columns=('mail_type', ))

        # block parent portlets on contacts
        blacklistPortletCategory(self.portal['contacts'])

        # add local roles
        self.portal['contacts'].manage_addLocalRoles(
            'dir_general', ['Contributor', 'Editor', 'Reader'])

        # configure autocomplete widget
        self.configure_autocomplete_widget(im_folder['mail-searches'])

        # configure task batch actions
        alsoProvides(im_folder['task-searches'], ITaskDashboard)

        # reimport contact faceted config
        reimport_faceted_config(self.portal['contacts'],
                                xml='contacts-faceted.xml')

        # remove tinymce resources
        configure_ckeditor(self.portal,
                           default=0,
                           allusers=0,
                           forceTextPaste=0,
                           scayt=0)

        self.upgradeAll()

        self.runProfileSteps('imio.dms.mail',
                             steps=['cssregistry', 'jsregistry'])

        # set jqueryui autocomplete to False. If not, contact autocomplete doesn't work
        self.registry[
            'collective.js.jqueryui.controlpanel.IJQueryUIPlugins.ui_autocomplete'] = False

        for prod in [
                'plone.formwidget.autocomplete', 'collective.plonefinder',
                'plone.formwidget.contenttree', 'plone.app.dexterity',
                'plone.formwidget.masterselect',
                'collective.behavior.talcondition',
                'collective.contact.facetednav',
                'collective.contact.plonegroup', 'collective.contact.widget',
                'collective.dms.batchimport', 'collective.dms.scanbehavior',
                'collective.documentgenerator',
                'collective.eeafaceted.collectionwidget',
                'collective.eeafaceted.z3ctable', 'collective.messagesviewlet',
                'collective.querynextprev', 'dexterity.localroles',
                'dexterity.localrolesfield', 'imio.actionspanel',
                'imio.dashboard', 'imio.dms.mail', 'plone.formwidget.datetime',
                'plonetheme.imioapps'
        ]:
            mark_last_version(self.portal, product=prod)

        #self.refreshDatabase()
        self.finish()
Beispiel #6
0
def configure_group_encoder(portal_types, contacts_part=False):
    """
        Used to configure a creating function and group for some internal organizations.
        Update portal_type to add behavior, configure localroles field
    """
    # function
    functions = get_registry_functions()
    if CREATING_GROUP_SUFFIX not in [fct['fct_id'] for fct in functions]:
        functions.append({
            'fct_title': u'Indicateur du service',
            'fct_id': CREATING_GROUP_SUFFIX,
            'fct_orgs': [],
            'fct_management': False,
            'enabled': True
        })
        set_registry_functions(functions)
    if contacts_part and CONTACTS_PART_SUFFIX not in [
            fct['fct_id'] for fct in functions
    ]:
        functions.append({
            'fct_title': u'Contacts par défaut',
            'fct_id': CONTACTS_PART_SUFFIX,
            'fct_orgs': [],
            'fct_management': False,
            'enabled': True
        })
        set_registry_functions(functions)
    # role and permission
    portal = api.portal.get()
    # existing_roles = list(portal.valid_roles())
    # if CREATING_FIELD_ROLE not in existing_roles:
    #     existing_roles.append(CREATING_FIELD_ROLE)
    #     portal.__ac_roles__ = tuple(existing_roles)
    #     portal.manage_permission('imio.dms.mail: Write creating group field',
    #                              ('Manager', 'Site Administrator', CREATING_FIELD_ROLE), acquire=0)

    # local roles config
    config = {
        'dmsincomingmail': {  # i_e ok
            'created': {
                CREATING_GROUP_SUFFIX: {
                    'roles': [
                        'Contributor', 'Editor', 'DmsFile Contributor',
                        'Base Field Writer', 'Treating Group Writer'
                    ]
                }
            },
            #                                                          CREATING_FIELD_ROLE]}},
            'proposed_to_manager': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Base Field Writer', 'Reader']
                }
            },
            'proposed_to_agent': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Reader']
                }
            },
            'in_treatment': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Reader']
                }
            },
            'closed': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Reader']
                }
            }
        },
        'dmsincoming_email': {
            'created': {
                CREATING_GROUP_SUFFIX: {
                    'roles': [
                        'Contributor', 'Editor', 'DmsFile Contributor',
                        'Base Field Writer', 'Treating Group Writer'
                    ]
                }
            },
            #                                                          CREATING_FIELD_ROLE]}},
            'proposed_to_manager': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Base Field Writer', 'Reader']
                }
            },
            'proposed_to_agent': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Reader']
                }
            },
            'in_treatment': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Reader']
                }
            },
            'closed': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Reader']
                }
            }
        },
        'dmsoutgoingmail': {
            'to_be_signed': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Editor', 'Reviewer']
                }
            },
            'sent': {
                CREATING_GROUP_SUFFIX: {
                    'roles': ['Reader', 'Reviewer']
                }
            },
            'scanned': {
                CREATING_GROUP_SUFFIX: {
                    'roles': [
                        'Contributor', 'Editor', 'Reviewer',
                        'DmsFile Contributor', 'Base Field Writer',
                        'Treating Group Writer'
                    ]
                }
            },
        },
        # 'dmsoutgoing_email': {
        #     'to_be_signed': {CREATING_GROUP_SUFFIX: {'roles': ['Editor', 'Reviewer']}},
        #     'sent': {CREATING_GROUP_SUFFIX: {'roles': ['Reader', 'Reviewer']}},
        #     'scanned': {CREATING_GROUP_SUFFIX: {'roles': ['Contributor', 'Editor', 'Reviewer', 'DmsFile Contributor',
        #                                                   'Base Field Writer', 'Treating Group Writer']}},
        # },
    }

    # add localroles for possible proposed_to_n_plus_ states
    # only incoming mails
    if 'dmsincomingmail' in portal_types:  # i_e ok
        for typ in ('dmsincomingmail', 'dmsincoming_email'):
            states = list_wf_states(portal, typ)
            for st_id, st_tit in states:
                if st_id.startswith('proposed_to_n_plus_'):
                    config[typ][st_id] = {
                        CREATING_GROUP_SUFFIX: {
                            'roles': ['Reader']
                        }
                    }

    # criterias config
    criterias = {
        'dmsincomingmail':
        ('incoming-mail', 'mail-searches', 'all_mails'),  # i_e ok
        'dmsoutgoingmail': ('outgoing-mail', 'mail-searches', 'all_mails'),
        'organization': ('contacts', 'orgs-searches', 'all_orgs'),
        'person': ('contacts', 'persons-searches', 'all_persons'),
        'held_position': ('contacts', 'hps-searches', 'all_hps'),
        'contact_list': ('contacts', 'cls-searches', 'all_cls'),
    }

    for portal_type in portal_types:
        # behaviors
        fti = getUtility(IDexterityFTI, name=portal_type)
        # try:
        #     fti = getUtility(IDexterityFTI, name=portal_type)
        # except ComponentLookupError:
        #     continue
        if 'imio.dms.mail.content.behaviors.IDmsMailCreatingGroup' not in fti.behaviors:
            old_bav = tuple(fti.behaviors)
            fti.behaviors = tuple(
                list(fti.behaviors) +
                ['imio.dms.mail.content.behaviors.IDmsMailCreatingGroup'])
            ftiModified(
                fti,
                ObjectModifiedEvent(
                    fti,
                    DexterityFTIModificationDescription('behaviors', old_bav)))

        # local roles
        if config.get(portal_type):
            msg = add_fti_configuration(portal_type,
                                        config[portal_type],
                                        keyname='creating_group')
            if msg:
                logger.warn(msg)

        # criterias
        folder_id, category_id, default_id = criterias.get(
            portal_type, ('', '', ''))
        if folder_id:
            reimport_faceted_config(
                portal[folder_id][category_id],
                xml='mail-searches-group-encoder.xml',
                default_UID=portal[folder_id][category_id][default_id].UID())

    # display added field for im and om
    if not contacts_part:
        config = {'dmsincomingmail': 'imail', 'dmsoutgoingmail': 'omail'}
        key = 'imio.dms.mail.browser.settings.IImioDmsMailConfig.{}_fields'.format(
            config[portal_types[0]])
        fields = api.portal.get_registry_record(key)
        if 'IDmsMailCreatingGroup.creating_group' not in [
                f['field_name'] for f in fields
        ]:
            fields.append({
                'field_name': 'IDmsMailCreatingGroup.creating_group',
                'read_tal_condition': u'',
                'write_tal_condition': u''
            })
            api.portal.set_registry_record(key, fields)