Esempio n. 1
0
def task_transition(task, event):
    """
        update indexes after a transition
    """
    task.reindexObject(['state_group'])

    if event.transition:
        if event.transition.id == 'do_to_assign':
            task.auto_to_do_flag = False
            # Set auto_to_do_flag on task if :
            # assigned_user is set OR
            # level n_plus_1 is not there OR
            # users in level n_plus_1
            if task.assigned_user:
                task.auto_to_do_flag = True
            elif not [dic for dic in get_applied_adaptations()
                      if dic['adaptation'] == 'imio.dms.mail.wfadaptations.TaskServiceValidation']:
                task.auto_to_do_flag = True
            else:
                transitions_levels = get_dms_config(['transitions_levels', 'task'])
                if task.assigned_group and transitions_levels['created'][task.assigned_group][0] != 'do_to_assign':
                    task.auto_to_do_flag = True
        elif event.transition.id == 'back_in_to_assign':
            # Remove auto_to_do_flag on task.
            task.auto_to_do_flag = False
Esempio n. 2
0
def reset_workflows(context):
    """Reset workflows and reapply wf adaptations."""
    if not context.readDataFile("imiodmsmail_singles_marker.txt"):
        return
    site = context.getSite()
    # TODO
    # find what's deleted (with local roles config or collection ?)
    # reset dms_config
    # clean local roles
    # delete collection
    # clean registry with prefix prefix:imio.actionspanel.browser.registry.IImioActionsPanelConfig
    # clean remark config
    # clean function if all removed ?
    # reindex

    site.portal_setup.runImportStepFromProfile('profile-imio.dms.mail:default',
                                               'workflow',
                                               run_dependencies=False)
    applied_adaptations = [
        dic['adaptation'] for dic in get_applied_adaptations()
    ]
    if applied_adaptations:
        success, errors = apply_from_registry(reapply=True)
        if errors:
            logger.error("Problem applying wf adaptations: %d errors" % errors)
    if 'imio.dms.mail.wfadaptations.TaskServiceValidation' not in applied_adaptations:
        update_task_workflow(site)
    site.portal_workflow.updateRoleMappings()
    logger.info("Workflow reset done")
    def test_get_applied_adaptations(self):
        applied_adaptations = get_applied_adaptations()
        self.assertIn(
            {u'workflow': u'workflow1',
             u'adaptation': u'adaptation1',
             u'parameters': {}
            },
            applied_adaptations,
            )

        self.assertIn(
            {u'workflow': u'workflow1',
             u'adaptation': u'adaptation2',
             u'parameters': {}
            },
            applied_adaptations,
            )

        self.assertIn(
            {u'workflow': u'workflow2',
             u'adaptation': u'adaptation2',
             u'parameters': {u"param": u"foobar"}
            },
            applied_adaptations,
            )
Esempio n. 4
0
 def update_assigned_user_check(self, auc_stored):
     if isinstance(auc_stored, str):
         return  # already migrated
     skip = u'imio.dms.mail.wfadaptations.IMSkipProposeToServiceChief' in \
            [dic['adaptation'] for dic in get_applied_adaptations()]
     if auc_stored and skip:
         value = u'mandatory'
     elif auc_stored:
         value = u'n_plus_1'
     else:
         value = u'no_check'
     self.registry[AUC_RECORD] = value
    def test_parameters_form(self):
        self.login(TEST_USER_NAME)
        request = self.portal.REQUEST
        request.form['form.widgets.workflow'] = ['intranet_workflow']
        request.form['form.widgets.adaptation'] = ['dummy_adaptation']
        request.form['form.widgets.param'] = 'foobar'
        self.simulate_form_submit(request)

        # test that the patch has been applied
        self.assertEqual(
            self.dummy_wf_adaptation.patched, 'intranet_workflow;foobar')

        # test that the record has been updated
        self.assertIn(
            {'adaptation': 'dummy_adaptation',
             'workflow': 'intranet_workflow',
             'parameters': {"param": "foobar"}
            },
            get_applied_adaptations(),
            )
Esempio n. 6
0
    def remove_service_chief(self):
        # remove collection
        logger.info('Modifying workflows')
        for folder in (self.imf['mail-searches'], self.omf['mail-searches']):
            if 'searchfor_proposed_to_service_chief' in folder:
                api.content.delete(
                    obj=folder['searchfor_proposed_to_service_chief'])

        # clean dms config
        for ptype in ('dmsincomingmail', 'dmsoutgoingmail', 'task'):  # i_e ok
            config = get_dms_config(['review_levels', ptype])
            if '_validateur' in config:
                del config['_validateur']
                set_dms_config(keys=['review_levels', ptype], value=config)
            config = get_dms_config(['review_states', ptype])
            if 'proposed_to_service_chief' in config:
                del config['proposed_to_service_chief']
                set_dms_config(keys=['review_states', ptype], value=config)

        def remove_localrole_validateur(dic1):
            for state1 in dic1:
                if 'validateur' in dic1[state1]:
                    del dic1[state1]['validateur']

        # clean local roles
        for ptype in ('dmsincomingmail', 'dmsoutgoingmail'):  # i_e ok
            fti = getUtility(IDexterityFTI, name=ptype)
            lr = getattr(fti, 'localroles')
            lrg = lr['static_config']
            if 'proposed_to_service_chief' in lrg:
                del lrg['proposed_to_service_chief']
                remove_localrole_validateur(lrg)
            lrg = lr['treating_groups']
            if 'proposed_to_service_chief' in lrg:
                del lrg['proposed_to_service_chief']
                remove_localrole_validateur(lrg)
            lrg = lr['recipient_groups']
            if 'proposed_to_service_chief' in lrg:
                del lrg['proposed_to_service_chief']
                remove_localrole_validateur(lrg)
            lr._p_changed = True
        # on task
        fti = getUtility(IDexterityFTI, name='task')
        lr = getattr(fti, 'localroles')
        lrg = lr['assigned_group']
        if 'validateur' in lrg['to_do']:
            remove_localrole_validateur(lrg)
        lrg = lr['parents_assigned_groups']
        if 'validateur' in lrg['to_do']:
            remove_localrole_validateur(lrg)
        lr._p_changed = True

        # update registry
        lst = api.portal.get_registry_record(
            'imio.actionspanel.browser.registry.IImioActionsPanelConfig.transitions'
        )
        for entry in ('dmsincomingmail.back_to_service_chief|',
                      'dmsoutgoingmail.back_to_service_chief|'):
            if entry not in lst:
                break
            lst.remove(entry)
        else:
            api.portal.set_registry_record(
                'imio.actionspanel.browser.registry.IImioActionsPanelConfig.transitions',
                lst)

        # update remark states
        for attr in ('imail_remark_states', 'omail_remark_states'):
            lst = (api.portal.get_registry_record(
                'imio.dms.mail.browser.settings.IImioDmsMailConfig.{}'.format(
                    attr)) or [])
            if 'proposed_to_service_chief' in lst:
                lst.remove('proposed_to_service_chief')
                api.portal.set_registry_record(
                    'imio.dms.mail.browser.settings.IImioDmsMailConfig.{}'.
                    format(attr), lst)

        # Manage workflows and wfadaptations
        functions = get_registry_functions()
        if 'validateur' not in [fct['fct_id'] for fct in functions]:
            return  # apply the following only once

        def remove_adaptation_from_registry(name):
            record = api.portal.get_registry_record(RECORD_NAME)
            api.portal.set_registry_record(
                RECORD_NAME, [d for d in record if d['adaptation'] != name])

        # reset workflows
        self.runProfileSteps('imio.dms.mail', steps=['workflow'])
        # self.portal.portal_workflow.updateRoleMappings()  # done later

        # Apply workflow adaptations if necessary
        applied_wfa = [dic['adaptation'] for dic in get_applied_adaptations()]
        n_plus_1_params = {
            'validation_level': 1,
            'state_title': u'À valider par le chef de service',
            'forward_transition_title': u'Proposer au chef de service',
            'backward_transition_title': u'Renvoyer au chef de service',
            'function_title': u'N+1'
        }
        task_adapt = True
        for wkf, acr in (('incomingmail_workflow', 'IM'),
                         ('outgoingmail_workflow', 'OM')):
            if u'imio.dms.mail.wfadaptations.{}SkipProposeToServiceChief'.format(
                    acr) in applied_wfa:
                remove_adaptation_from_registry(
                    u'imio.dms.mail.wfadaptations.{}SkipProposeToServiceChief'.
                    format(acr))
                task_adapt = False
                if acr == 'OM':
                    folder = self.omf['mail-searches']
                    if folder['to_validate'].enabled:
                        folder['to_validate'].enabled = False
                        folder['to_validate'].reindexObject()
            else:
                logger.info(
                    'Applying {}ServiceValidation wf adaptation'.format(acr))
                sva = getattr(wfadaptations,
                              '{}ServiceValidation'.format(acr))()
                sva.reapply = True
                adapt_is_applied = sva.patch_workflow(wkf, **n_plus_1_params)
                if adapt_is_applied:
                    add_applied_adaptation(
                        'imio.dms.mail.wfadaptations.{}ServiceValidation'.
                        format(acr), wkf, True, **n_plus_1_params)

        # update task_workflow
        update_task_workflow(self.portal)
        if task_adapt:
            tsva = TaskServiceValidation()
            adapt_is_applied = tsva.patch_workflow('task_workflow', **{})
            if adapt_is_applied:
                add_applied_adaptation(
                    'imio.dms.mail.wfadaptations.TaskServiceValidation',
                    'task_workflow', False)
        else:
            # update collections
            folder = self.portal['tasks']['task-searches']
            for cid in ('to_assign', 'to_close'):
                if folder[cid].enabled:
                    folder[cid].enabled = False
                    folder[cid].reindexObject()

        invalidate_cachekey_volatile_for(
            'collective.eeafaceted.collectionwidget.cachedcollectionvocabulary'
        )

        # replace EmergencyZoneAdaptation
        im_workflow = self.wtool['incomingmail_workflow']
        if u'imio.dms.mail.wfadaptations.EmergencyZone' in applied_wfa:
            state = im_workflow.states['proposed_to_manager']
            state.title = u'À valider par le CZ'.encode('utf8')
            for tr, tit in (('back_to_manager', u'Renvoyer au CZ'),
                            ('propose_to_manager', u'Proposer au CZ')):
                transition = im_workflow.transitions[tr]
                transition.title = tit.encode('utf8')
            logger.info('Removing EmergencyZone wf adaptation')
            remove_adaptation_from_registry(
                u'imio.dms.mail.wfadaptations.EmergencyZone')

        # redo OMToPrintAdaptation
        if u'imio.dms.mail.wfadaptations.OMToPrint' in applied_wfa:
            logger.info('Applying OMToPrint wf adaptation')
            tpa = OMToPrintAdaptation()
            tpa.patch_workflow('outgoingmail_workflow')

        # redo IMPreManagerValidation
        if u'imio.dms.mail.wfadaptations.IMPreManagerValidation' in applied_wfa:
            logger.info('Applying IMPreManagerValidation wf adaptation')
            params = [
                dic['parameters'] for dic in get_applied_adaptations()
                if dic['adaptation'] ==
                u'imio.dms.mail.wfadaptations.IMPreManagerValidation'
            ][0]
            remove_adaptation_from_registry(
                u'imio.dms.mail.wfadaptations.IMPreManagerValidation')
            del params['collection_title']
            pmva = IMPreManagerValidation()
            adapt_is_applied = pmva.patch_workflow('incomingmail_workflow',
                                                   **params)
            if adapt_is_applied:
                add_applied_adaptation(
                    'imio.dms.mail.wfadaptations.IMPreManagerValidation',
                    'incoming_mail', False, **params)

        # update wf history to replace review_state and correct history
        config = {
            'dmsincomingmail': {
                'wf': 'incomingmail_workflow',  # i_e ok
                'st': {
                    'proposed_to_service_chief': 'proposed_to_n_plus_1'
                },
                'tr': {
                    'propose_to_service_chief': 'propose_to_n_plus_1',
                    'back_to_service_chief': 'back_to_n_plus_1'
                }
            },
            'dmsoutgoingmail': {
                'wf': 'outgoingmail_workflow',
                'st': {
                    'proposed_to_service_chief': 'proposed_to_n_plus_1'
                },
                'tr': {
                    'propose_to_service_chief': 'propose_to_n_plus_1',
                    'back_to_service_chief': 'back_to_n_plus_1'
                }
            }
        }
        for pt in config:
            logger.info('Updating history and indexes of {} type'.format(pt))
            for i, brain in enumerate(self.catalog(portal_type=pt), 1):
                obj = brain.getObject()
                if i % 10000 == 0:
                    logger.info('On brain {}'.format(i))
                # update history
                wfh = []
                wkf = self.wtool[config[pt]['wf']]
                for status in obj.workflow_history.get(config[pt]['wf']):
                    # replace old state by new one
                    if status['review_state'] in config[pt]['st']:
                        status['review_state'] = config[pt]['st'][
                            status['review_state']]
                    # replace old transition by new one
                    if status['action'] in config[pt]['tr']:
                        status['action'] = config[pt]['tr'][status['action']]
                    wfh.append(status)
                obj.workflow_history[config[pt]['wf']] = tuple(wfh)
                # update permissions and roles
                wkf.updateRoleMappingsFor(obj)
                # update state_group (use dms_config), permissions, state
                obj.reindexObject(idxs=[
                    'allowedRolesAndUsers', 'review_state', 'state_group'
                ])
                for child in obj.objectValues():
                    child.reindexObject(idxs=['allowedRolesAndUsers'])

        # migrate plone groups
        # First unregister group deletion handlers
        globalSiteManager.unregisterHandler(pg_group_deleted,
                                            (IGroupDeletedEvent, ))
        globalSiteManager.unregisterHandler(group_deleted,
                                            (IGroupDeletedEvent, ))
        globalSiteManager.unregisterHandler(group_assignment,
                                            (IPrincipalAddedToGroupEvent, ))
        globalSiteManager.unregisterHandler(
            group_unassignment, (IPrincipalRemovedFromGroupEvent, ))
        # move users from _validateur to _n_plus_1
        for group in api.group.get_groups():
            if group.id.endswith('_validateur'):
                org = group.id.split('_')[0]
                np1group = api.group.get('{}_n_plus_1'.format(org))
                if np1group:
                    for user in api.user.get_users(group=group):
                        api.group.add_user(group=np1group, user=user)
                        api.group.remove_user(group=group, user=user)
                api.group.delete(group=group)
        # register again group deletion handlers
        globalSiteManager.registerHandler(pg_group_deleted,
                                          (IGroupDeletedEvent, ))
        globalSiteManager.registerHandler(group_deleted,
                                          (IGroupDeletedEvent, ))
        globalSiteManager.registerHandler(group_assignment,
                                          (IPrincipalAddedToGroupEvent, ))
        globalSiteManager.registerHandler(group_unassignment,
                                          (IPrincipalRemovedFromGroupEvent, ))

        # remove validateur function
        functions = get_registry_functions()
        if 'validateur' in [fct['fct_id'] for fct in functions]:
            set_registry_functions(
                [fct for fct in functions if fct['fct_id'] != 'validateur'])
Esempio n. 7
0
def imiodmsmail_settings_changed(event):
    """ Manage a record change """
    if (IRecordModifiedEvent.providedBy(event) and event.record.interfaceName
            and event.record.interface != IImioDmsMailConfig):
        return
    if event.record.fieldName == 'mail_types':
        invalidate_cachekey_volatile_for(
            'imio.dms.mail.vocabularies.IMMailTypesVocabulary')
        invalidate_cachekey_volatile_for(
            'imio.dms.mail.vocabularies.IMActiveMailTypesVocabulary')
    if event.record.fieldName == 'omail_types':
        invalidate_cachekey_volatile_for(
            'imio.dms.mail.vocabularies.OMMailTypesVocabulary')
        invalidate_cachekey_volatile_for(
            'imio.dms.mail.vocabularies.OMActiveMailTypesVocabulary')
    if event.record.fieldName == 'assigned_user_check':
        update_transitions_auc_config('dmsincomingmail')  # i_e ok
        n_plus_x = 'imio.dms.mail.wfadaptations.IMServiceValidation' in \
                   [adapt['adaptation'] for adapt in get_applied_adaptations()]
        snoi = False
        if event.newValue == u'no_check' or not n_plus_x:
            snoi = True
        portal = api.portal.get()
        folder = portal['incoming-mail']['mail-searches']
        if folder['to_treat_in_my_group'].showNumberOfItems != snoi:
            folder['to_treat_in_my_group'].showNumberOfItems = snoi  # noqa
            folder['to_treat_in_my_group'].reindexObject()
    if event.record.fieldName in ('org_templates_encoder_can_edit',
                                  'org_email_templates_encoder_can_edit'):
        folder_id = ('email' in event.record.fieldName) and 'oem' or 'om'
        portal = api.portal.get()
        main_folder = portal.templates[folder_id]
        s_orgs = get_registry_organizations()
        roles = ['Reader']
        all_roles = ['Reader', 'Contributor', 'Editor']
        if api.portal.get_registry_record(event.record.__name__):
            roles = list(all_roles)
        for uid in s_orgs:
            if uid not in main_folder:
                continue
            folder = main_folder[uid]
            groupname = '{}_encodeur'.format(uid)
            api.group.revoke_roles(groupname=groupname,
                                   roles=all_roles,
                                   obj=folder)
            api.group.grant_roles(groupname=groupname, roles=roles, obj=folder)

    if event.record.fieldName == 'imail_group_encoder':
        if api.portal.get_registry_record(
                'imio.dms.mail.browser.settings.IImioDmsMailConfig.imail_group_encoder'
        ):
            configure_group_encoder(['dmsincomingmail', 'dmsincoming_email'])
    if event.record.fieldName == 'omail_group_encoder':
        if api.portal.get_registry_record(
                'imio.dms.mail.browser.settings.IImioDmsMailConfig.omail_group_encoder'
        ):
            # configure_group_encoder(['dmsoutgoingmail', 'dmsoutgoing_email'])
            configure_group_encoder(['dmsoutgoingmail'])
    if event.record.fieldName == 'contact_group_encoder':
        if api.portal.get_registry_record(
                'imio.dms.mail.browser.settings.IImioDmsMailConfig.contact_group_encoder'
        ):
            configure_group_encoder(
                ['organization', 'person', 'held_position', 'contact_list'],
                contacts_part=True)
            # set permission on contacts directory
            portal = api.portal.get()
            portal['contacts'].manage_permission(
                'imio.dms.mail: Write mail base fields',
                ('Manager', 'Site Administrator', 'Contributor'),
                acquire=1)
    if event.record.fieldName == 'groups_hidden_in_dashboard_filter':
        invalidate_cachekey_volatile_for(
            'imio.dms.mail.vocabularies.TreatingGroupsForFacetedFilterVocabulary'
        )
    if event.record.fieldName == 'users_hidden_in_dashboard_filter':
        invalidate_cachekey_volatile_for(
            'imio.dms.mail.vocabularies.AssignedUsersForFacetedFilterVocabulary'
        )
    if event.record.__name__ == 'imio.dms.mail.imail_folder_period' and event.newValue is not None:
        portal = api.portal.get()
        setattr(portal[MAIN_FOLDERS['dmsincomingmail']], 'folder_period',
                event.newValue)
    if event.record.__name__ == 'imio.dms.mail.omail_folder_period' and event.newValue is not None:
        portal = api.portal.get()
        setattr(portal[MAIN_FOLDERS['dmsoutgoingmail']], 'folder_period',
                event.newValue)
Esempio n. 8
0
def import_principals(self,
                      add_user='',
                      create_file='',
                      ungroup='',
                      dochange=''):
    """
        Import principals from the file 'Extensions/principals.csv' containing
        OrgId;OrgTitle;Userid;Fullname;email;Éditeur;Lecteur;Créateur CS;N+;Tel;Label;ImHandle
        Variable N+ columns following configuration !
    """
    if not check_zope_admin():
        return "You must be a zope manager to run this script"
    exm = self.REQUEST['PUBLISHED']
    path = os.path.dirname(exm.filepath())
    # path = '%s/../../Extensions' % os.environ.get('INSTANCE_HOME')
    portal = api.portal.get()
    out = []
    lf = '\n'
    out.append("Import principals from principals.csv. Possible parameters:")
    out.append("-> add_user=1 : add missing users. Default 0")
    out.append(
        "-> create_file=1 : create principals_gen.csv and exit. Default 0")
    out.append(
        "-> ungroup=1 : remove from group if role column is empty. Default 0")
    out.append("-> dochange=1 : apply changes. Default 0")
    out.append("")
    cf = False
    if create_file == '1':
        cf = True
    doit = False
    if dochange == '1':
        doit = True

    users = get_user_from_criteria(portal, email='@')
    userids = {}
    emails = {}
    # {'description': u'Scanner', 'title': u'Scanner', 'principal_type': 'user', 'userid': 'scanner',
    #  'email': '*****@*****.**', 'pluginid': 'mutable_properties', 'login': '******', 'id': 'scanner'}
    for dic in users:
        userids[dic['userid']] = dic['email']
        uids = emails.setdefault(dic['email'], [])
        uids.append(dic['userid'])
    for eml in emails:
        if len(emails[eml]) > 1:
            out.append("!! same email '{}' for multiples users: {}".format(
                eml, ', '.join(emails[eml])))
    orgas = get_organizations(self, obj=True)  # [(uid, title)]
    val_levels = [('n_plus_{}'.format(dic['parameters']['validation_level']),
                   dic['parameters']['function_title'])
                  for dic in get_applied_adaptations() if dic['adaptation'] ==
                  u'imio.dms.mail.wfadaptations.IMServiceValidation']

    fields = [
        'oi', 'ot', 'ui', 'fn', 'eml', 'ed', 'le', 'cs', 'ph', 'lb', 'im'
    ]
    fieldnames = [
        'OrgId', 'OrgTitle', 'Userid', 'Nom complet', 'Email', 'Éditeur',
        'Lecteur', 'Créateur CS', 'Tél', 'Fonction', 'Autre'
    ]
    for fct, tit in reversed(val_levels):
        fields.insert(5, fct)
        fieldnames.insert(5, tit.encode('utf8'))

    if cf:
        lines = [";".join(fieldnames)]
        # TODO improve creation by using existing user group associations
        for uid, title in orgas:
            lines.append("{};{};{}".format(uid, title.encode('utf8'),
                                           ';' * (len(fields) - 3)))
        if doit:
            gen_file = os.path.join(path, 'principals_gen.csv')
            out.append("Creating file {}".format(gen_file))
            fh = open(gen_file, 'w')
            for line in lines:
                fh.write("%s\n" % line)
            fh.close()
        out.extend(lines)
        return '\n'.join(out)

    filename = os.path.join(path, 'principals.csv')
    out.append("Reading file {}".format(filename))
    msg, rows = system.read_dictcsv(filename,
                                    fieldnames=fields,
                                    strip_chars=' ',
                                    skip_empty=True,
                                    skip_lines=1,
                                    **{'delimiter': ';'})
    if msg:  # stop if csv error
        return msg
    regtool = self.portal_registration
    cu = False
    if add_user == '1':
        cu = True
    for dic in rows:
        ln = dic.pop('_ln')
        try:
            validate_email_address(dic['eml'])
        except Exception:
            out.append("Line %d: incorrect email value '%s'" %
                       (ln, dic['eml']))
            continue
        if dic['ph']:
            dic['ph'] = filter(type(dic['ph']).isdigit, dic['ph'])
            try:
                validate_phone(dic['ph'])
            except Exception:
                out.append("Line %d: incorrect phone value '%s'" %
                           (ln, dic['ph']))
                continue
        # check userid
        if not dic['ui']:
            out.append("Line %d: userid empty. Skipping line" % ln)
            continue
        if not re.match(r'[a-zA-Z1-9\-]+$', dic['ui']):
            out.append("Line %d: userid '%s' is not well formed" %
                       (ln, dic['ui']))
            continue
        # check user
        if dic['ui'] not in userids:
            emlfound = dic['eml'] in emails
            if not cu:
                out.append("Line %d: userid '%s' not found" % (ln, dic['ui']))
                if emlfound:
                    out.append("Line %d: but email '%s' found on users '%s'" %
                               (ln, dic['eml'], ', '.join(emails[dic['eml']])))
                continue
            else:
                try:
                    out.append("=> Create user '%s': '%s', '%s'" %
                               (dic['ui'], dic['fn'], dic['eml']))
                    if emlfound:
                        out.append(
                            "Line %d: but email '%s' found on users '%s'" %
                            (ln, dic['eml'], ', '.join(emails[dic['eml']])))
                    userids[dic['ui']] = dic['eml']
                    uids = emails.setdefault(dic['eml'], [])
                    uids.append(dic['ui'])
                    if doit:
                        user = api.user.create(
                            username=dic['ui'],
                            email=dic['eml'],
                            password=regtool.generatePassword(),
                            properties={'fullname': dic['fn']})
                except Exception, ex:
                    out.append("Line %d, cannot create user: %s" %
                               (ln, safe_encode(ex.message)))
                    continue
        user = api.user.get(userid=dic['ui'])
        # groups
        if user is not None:
            try:
                groups = [
                    g.id for g in api.group.get_groups(username=dic['ui'])
                ]
            except Exception, ex:
                out.append("Line %d, cannot get groups of userid '%s': %s" %
                           (ln, dic['ui'], safe_encode(ex.message)))
                groups = []