def all_workspace_users(site): """return a set of all workspace users""" r = set() all_workspaces = get_workspaces(site) for workspace in all_workspaces: roster = IWorkspaceRoster(workspace) r.update(roster.keys()) return r
def other_users(project): """ Given project, get de-duped list of users who are neither form users nor project managers. """ form_users = set(merged_form_users(project)) managers = set(IWorkspaceRoster(project).groups['managers'].keys()) all_users = set(IWorkspaceRoster(project).keys()) return ((all_users - managers) - form_users)
def add_project(self, id, title=None): project = self.add_check(PROJECT_TYPE, id, IProject, Project) members = ISiteMembers(self.portal) if self.TEST_MEMBER not in members: members.register(self.TEST_MEMBER, send=False) assert self.TEST_MEMBER in members roster = IWorkspaceRoster(project) roster.add(self.TEST_MEMBER) assert self.TEST_MEMBER in roster return project
def update(self, *args, **kwargs): self.roster = IWorkspaceRoster(self.context) if 'confirm_purge' in self.request.form: username = self.request.form.get('purgeuser').strip() if username not in self.roster: raise ValueError('User name for purge not found %s' % username) if not self.roster.can_purge(username): raise ValueError('User name %s locked from purging.' % username) self.roster.purge_user(username)
def user_workspaces(username, context=None, finder=get_workspaces): """ Get workspaces for username, matching only workspaces for which the given user is a member; may be given context or use the site root as default context. A workspace enumerator/finder other than get_workspaces() may be passed (e.g. collective.teamwork.utils.get_projects). """ suffix = '-viewers' site = getSite() context = context or site # get all PAS groups for workspaces contained within context: all_workspaces = finder(context) if not all_workspaces: # context contains no workspaces, even if context itself is workspace return [] _pasgroup = lambda g: g.pas_group() _wgroups = lambda w: map(_pasgroup, IWorkspaceRoster(w).groups.values()) local_groups = set( zip(*itertools.chain(*map(_wgroups, all_workspaces)))[0]) if not local_groups: return [] # get all '-viewers' groups user belongs to, intersect with local: user = ISiteMembers(site).get(username) usergroups = [name for name in user.getGroups() if name.endswith(suffix)] considered = [name for name in local_groups.intersection(usergroups)] # each considered group (by suffix convention) is always 1:1 with # workspaces, no dupes, so we can map those workspaces: return map(group_workspace, set(considered))
def test_unassign_contained_secondary(self): """Unassign from workgroup, removed from secondary contained""" project, roster = self._base_fixtures() team = project['team1'] team_roster = IWorkspaceRoster(team) username = '******' self.site_members.register(username, send=False) roster.add(username) team_roster.add(username) team_roster.groups['managers'].add(username) self.assertIn(username, roster) self.assertIn(username, team_roster) self.assertIn(username, team_roster.groups['managers']) roster.unassign(username) self.assertNotIn(username, roster) self.assertNotIn(username, team_roster) self.assertNotIn(username, team_roster.groups['managers'])
def _base_fixtures(self): """ Simple membership, workspace, and roster fixture, for DRY reasons. """ if not getattr(self, '_workspace', None): self._workspace = self.portal['project1'] if not getattr(self, '_roster', None): self._roster = IWorkspaceRoster(self._workspace) return (self._workspace, self._roster)
def update(self, *args, **kwargs): self.roster = IWorkspaceRoster(self.context) if "confirm_purge" in self.request.form: username = self.request.form.get("purgeuser").strip() if username not in self.roster: raise ValueError("User name for purge not found %s" % username) if not self.roster.can_purge(username): raise ValueError("User name %s locked from purging." % username) self.roster.purge_user(username)
def __init__(self, context, request): if not IWorkspaceContext.providedBy(context): raise ValueError('Context not a workspace') self.context = context self.request = request self.portal = getSite() self._members = ISiteMembers(self.portal) self._roster = IWorkspaceRoster(self.context) self._mtool = getToolByName(self.portal, 'portal_membership') self._secmgr = None
def merged_form_users(project): """ Given a project, get de-duped set of form users from all contained workspaces, not including managers at root of the project. """ workspaces = all_workspaces(project) _form_users = [form_users(w) for w in workspaces] merged = set(reduce(lambda a, b: set(a) | set(b), _form_users)) exclude = set(IWorkspaceRoster(project).groups['managers'].keys()) return list(merged - exclude)
def test_can_purge(self): """Testing IWorkspaceRoster.can_purge()""" project1, roster1 = self._base_fixtures() project2 = self.portal['project2'] roster2 = IWorkspaceRoster(project2) user_oneproject = '*****@*****.**' user_twoprojects = '*****@*****.**' self.site_members.register(user_oneproject, send=False) self.site_members.register(user_twoprojects, send=False) roster1.add(user_oneproject) roster1.add(user_twoprojects) roster2.add(user_twoprojects) self.assertTrue(roster1.can_purge(user_oneproject)) self.assertFalse(roster1.can_purge(user_twoprojects)) # disallowed self.assertFalse(roster2.can_purge(user_twoprojects)) # here too. self.assertFalse(roster2.can_purge(user_oneproject)) # not in here # cannot purge from a team, even with otherwise purgeable user: self.assertFalse( IWorkspaceRoster(project1['team1']).can_purge(user_oneproject) )
def fix_current_user(context): """ When a project is added, the current user context may not yet have Manager role added to the generated user object, even if the user is technically a member of the group. This is only an issue when we attempt to do something requiring a group membership added in the same transation. This is a workaround. """ current = ISiteMembers(getSite()).current() roster = IWorkspaceRoster(context) manager_group = roster.groups.get('managers').pas_group()[0] current._addGroups((manager_group,))
class PurgeUserView(WorkspaceViewBase): """View to purge a single user from project""" def __init__(self, context, request): if not IProjectContext.providedBy(context): raise ValueError("Can only purge from top-level projects") super(PurgeUserView, self).__init__(context, request) def update(self, *args, **kwargs): self.roster = IWorkspaceRoster(self.context) if "confirm_purge" in self.request.form: username = self.request.form.get("purgeuser").strip() if username not in self.roster: raise ValueError("User name for purge not found %s" % username) if not self.roster.can_purge(username): raise ValueError("User name %s locked from purging." % username) self.roster.purge_user(username) def __call__(self, *args, **kwargs): self.update(*args, **kwargs) return self.index(*args, **kwargs) # form template
def test_purge_exceptions(self): """Test for expected failure on disallowed purge of user""" project1, roster1 = self._base_fixtures() project2 = self.portal['project2'] roster2 = IWorkspaceRoster(project2) username = '******' self.site_members.register(username, send=False) roster1.add(username) roster2.add(username) # user cannot be purged because of membership elsewhere: self.assertRaises( RuntimeError, roster2.purge_user, username, ) # user not in roster self.assertRaises( RuntimeError, roster2.purge_user, '*****@*****.**', )
class PurgeUserView(WorkspaceViewBase): """View to purge a single user from project""" def __init__(self, context, request): if not IProjectContext.providedBy(context): raise ValueError('Can only purge from top-level projects') super(PurgeUserView, self).__init__(context, request) def update(self, *args, **kwargs): self.roster = IWorkspaceRoster(self.context) if 'confirm_purge' in self.request.form: username = self.request.form.get('purgeuser').strip() if username not in self.roster: raise ValueError('User name for purge not found %s' % username) if not self.roster.can_purge(username): raise ValueError('User name %s locked from purging.' % username) self.roster.purge_user(username) def __call__(self, *args, **kwargs): self.update(*args, **kwargs) return self.index(*args, **kwargs) # form template
class RosterView(object): def __init__(self, context, request): self.context = context self.request = request self.roster = None # to be set in self.update() def update(self): self.roster = IWorkspaceRoster(self.context) self.members = self.roster.values() # list of IPropertiedUser objects def __call__(self, *args, **kwargs): self.update(*args, **kwargs) return self.index(*args, **kwargs)
def test_assign_unassign_recursive(self): """Unassigning user from project removes from contained workspaces""" workspace, roster = self._base_fixtures() username = '******' self.site_members.register(username, send=False) project, project_roster = self._base_fixtures() team = project['team1'] team_roster = IWorkspaceRoster(team) subteam = team['subteam'] subteam_roster = IWorkspaceRoster(subteam) self.assertNotIn(username, project_roster) self.assertNotIn(username, team_roster) self.assertNotIn(username, subteam_roster) # add recursively adds to parent workspaces, walking upward: subteam_roster.add(username) self.assertIn(username, subteam_roster) self.assertIn(username, team_roster) self.assertIn(username, project_roster) # remove recursively removes from contained workspaces: project_roster.unassign(username) self.assertNotIn(username, project_roster) self.assertNotIn(username, team_roster) self.assertNotIn(username, subteam_roster)
def update(self, *args, **kwargs): # get list of IPropertiedUser objects for all members members = IWorkspaceRoster(self.context).values() self._update_schema(members) self.info = map(self._info, members) self.output = StringIO() self.output.write(u'\ufeff'.encode('utf8')) # UTF-8 BOM for MSExcel self.writer = csv.DictWriter( self.output, self.schemakeys, extrasaction='ignore', ) # write heading row: self.writer.writerow(dict([(n, n) for n in self.schemakeys])) for record in self.info: self.writer.writerow(record) self.output.seek(0)
def test_can_purge(self): """Testing IWorkspaceRoster.can_purge()""" project1, roster1 = self._base_fixtures() project2 = self.portal['project2'] roster2 = IWorkspaceRoster(project2) user_oneproject = '*****@*****.**' user_twoprojects = '*****@*****.**' self.site_members.register(user_oneproject, send=False) self.site_members.register(user_twoprojects, send=False) roster1.add(user_oneproject) roster1.add(user_twoprojects) roster2.add(user_twoprojects) self.assertTrue(roster1.can_purge(user_oneproject)) self.assertFalse(roster1.can_purge(user_twoprojects)) # disallowed self.assertFalse(roster2.can_purge(user_twoprojects)) # here too. self.assertFalse(roster2.can_purge(user_oneproject)) # not in here # cannot purge from a team, even with otherwise purgeable user: self.assertFalse( IWorkspaceRoster(project1['team1']).can_purge(user_oneproject))
def report_main(site, datestamp, perproject=False): """ Given site and datestamp for snapshot, append report result to file named project_users.csv with column format: month, date, project_name, #users, #managers, #teams, #forms. """ if DETAIL_DEBUG: print '###### SITE REPORT DETAIL: %s ######' % site.getId() catalog = getToolByName(site, 'portal_catalog') r = catalog.unrestrictedSearchResults( {'object_provides': IProjectContext.__identifier__}) projects = [brain._unrestrictedGetObject() for brain in r] if not os.path.isdir(DIRNAME): os.mkdir(DIRNAME) columns = ( 'month', 'date', 'all_users', 'managers', 'form_users', 'other_users', 'project_count', 'team_count', ) sitesnap = ProjectSnapshot( name='site-%s' % site.getId(), title=_u(site.Title()), date=datestamp, month=MONTHS.get(datestamp.month), ) sitesnap.project_count = 0 sitesnap.team_count = 0 site_filename = os.path.join(DIRNAME, '%s.csv' % sitesnap.name) if os.path.exists(site_filename): out = open(site_filename, 'r') data = out.readlines() out.close() if any([(str(datestamp) in line) for line in data]): return # already have site report for date, done with this site site_out = open(site_filename, 'a') # append to EOF else: site_out = open(site_filename, 'w') # will create site_out.write('%s\n' % ','.join(columns)) # new file, ergo headings for project in projects: sitesnap.project_count += 1 roster = IWorkspaceRoster(project) snapshot = ProjectSnapshot( name=project.getId(), title=_u(project.Title()), ) snapshot.project_count = 1 snapshot.date = datestamp snapshot.month = MONTHS.get(datestamp.month) snapshot.all_users = set(filtered_users(roster)) sitesnap.all_users = sitesnap.all_users.union(snapshot.all_users) snapshot.other_users = set(filtered_users(other_users(project))) snapshot.managers = set( filtered_users(roster.groups['managers'].keys())) sitesnap.managers = sitesnap.managers.union(snapshot.managers) snapshot.form_users = set(filtered_users(merged_form_users(project))) sitesnap.form_users = sitesnap.form_users.union(snapshot.form_users) snapshot.team_count = len(all_workspaces(project)) - 1 sitesnap.team_count += snapshot.team_count if DETAIL_DEBUG: print '== PROJECT: %s ==' % project.getId() print ' Team workspace count: %s' % snapshot.team_count print ' Total users (all categories): %s' % len( snapshot.all_users) print ' Managers of project: %s' % len(snapshot.managers) for email in snapshot.managers: print '\t - %s' % email print ' Form entry users, incl. team managers who are not project managers: %s' % len( snapshot.form_users) for email in snapshot.form_users: print '\t - %s' % email print ' Other users (not managers, not form entry): %s' % len( snapshot.other_users) for email in snapshot.other_users: print '\t - %s' % email if perproject: proj_filename = os.path.join( DIRNAME, '%s-%s.csv' % ( site.getId(), project.getId(), )) if os.path.exists(proj_filename): out = open(proj_filename, 'r') data = out.readlines() # existing data in file out.close() if any([(str(datestamp) in line) for line in data]): continue # don't duplicate entry for date already in file out = open(proj_filename, 'a') # append to EOF else: out = open(proj_filename, 'w') # will create out.write('%s\n' % ','.join(columns)) # new file -> headings writer = csv.DictWriter(out, columns, extrasaction='ignore') # write row to CSV from snapshot, convert u'' to utf-8 as needed writer.writerow( dict([(k, output_value(k, v)) for k, v in snapshot.__dict__.items()])) out.close() # now normalize site-wide users for greatest role... if a user is manager # in project A, do not include them in form users just because they have # form user role in project B: sitesnap.form_users = sitesnap.form_users - sitesnap.managers sitesnap.other_users = (sitesnap.all_users - sitesnap.managers) - (sitesnap.form_users) # final reconciliation, do not include any users in specific lists # if not in all_users group: sitesnap.form_users = sitesnap.form_users.intersection(sitesnap.all_users) sitesnap.managers = sitesnap.managers.intersection(sitesnap.all_users) if DETAIL_DEBUG: print '== SITE: %s ==' % site.getId() print ' Total UNIQUE users (all categories): %s' % len( sitesnap.all_users) print ' UNIQUE managers of project: %s' % len(sitesnap.managers) for email in sitesnap.managers: print '\t - %s' % email print ' UNIQUE form entry users, incl. team managers who are not project managers: %s' % len( sitesnap.form_users) for email in sitesnap.form_users: print '\t - %s' % email print ' UNIQUE other users (not managers, not form entry): %s' % len( sitesnap.other_users) for email in sitesnap.other_users: print '\t - %s' % email site_writer = csv.DictWriter(site_out, columns, extrasaction='ignore') site_writer.writerow( dict([(k, output_value(k, v)) for k, v in sitesnap.__dict__.items()])) site_out.close()
def update(self): self.roster = IWorkspaceRoster(self.context) self.members = self.roster.values() # list of IPropertiedUser objects
def form_users(workspace): """Given a workspace, get the ids of users with forms role""" roster = IWorkspaceRoster(workspace) _formusers = set(roster.groups['forms'].keys()) _leads = set(roster.groups['managers'].keys()) return list(_formusers | _leads)