Source code for ploneintranet.api.userprofile

# -*- coding: utf-8 -*-
from dexterity.membrane.behavior.password import IProvidePasswordsSchema
from itertools import imap
from plone import api as plone_api
from plone.api.exc import InvalidParameterError
from import decode
from import INetworkTool
from ploneintranet.userprofile.content.userprofile import IUserProfile
from ploneintranet.userprofile.interfaces import IMemberGroup
from Products.CMFPlone.utils import safe_unicode
from z3c.form.interfaces import IValidator
from zope.component import getMultiAdapter
from zope.component import queryUtility

import random
import string

[docs]def get_users( context=None, full_objects=True, **kwargs ): """ List users from catalog, avoiding expensive LDAP lookups. :param context: Any content object that will be used to find the UserResolver context :type context: Content object :param full_objects: A switch to indicate if full objects or brains should be returned :type full_objects: boolean :returns: user brains or user objects :rtype: iterator """ try: mtool = plone_api.portal.get_tool('membrane_tool') except InvalidParameterError: return [] if context: acl_users = plone_api.portal.get_tool('acl_users') try: # adapters provided by pi.userprofile and pi.workspace members = set([x for x in IMemberGroup(context).members]) # In case of groups, resolve the group members for id in list(members): group = acl_users.getGroupById(id) if group: members.remove(id) # these are not membrane profiles but acl members members = members.union(set( [user.getId() for user in group.getGroupMembers()])) # both context and query: calculate intersection if 'exact_getUserId' in kwargs: _combi = list( members.intersection( set(kwargs['exact_getUserId']))) kwargs['exact_getUserId'] = _combi else: kwargs['exact_getUserId'] = list(members) except TypeError: # could not adapt to IMemberGroup pass portal_type = 'ploneintranet.userprofile.userprofile', search_results = mtool.searchResults(portal_type=portal_type, **kwargs) if full_objects: return (x.getObject() for x in search_results) else: return search_results
[docs]def get_userids(): ''' For the moment it just returns all the ids of the userprofiles we have in the site. :returns: the userprofile ids :rtype: iterator ''' portal = plone_api.portal.get() profiles = portal.get('profiles', {}) return profiles.keys()
[docs]def get_user_suggestions( context=None, full_objects=True, min_matches=5, **kwargs ): """ This is a wrapper around get_users with the intent of providing staggered suggestion of users for a user picker: 1. Users from the current context (workspace) If not enough users, add: 2. Users followed by the current logged-in user If not enough combined users from 1+2, fallback to: 3. All users in the portal. List users from catalog, avoiding expensive LDAP lookups. :param context: Any content object that will be used to find the UserResolver context :type context: Content object :param full_objects: A switch to indicate if full objects or brains should be returned :type full_objects: boolean :param min_matches: Keeps expanding search until this treshold is reached :type min_matches: int :returns: user brains or user objects :rtype: iterator """ def expand(search_results, full_objects, **kwargs): """Helper function to delay full object expansion""" # Filter results by chosen review state if 'review_state' in kwargs: search_results = filter( lambda x: getattr(x, 'review_state', '') == kwargs['review_state'], # noqa search_results) if full_objects: return (x.getObject() for x in search_results) else: return search_results # By default, only return users that are enabled if 'review_state' not in kwargs: kwargs['review_state'] = 'enabled' # stage 1 context users if context: context_users = [x for x in get_users(context, False, **kwargs)] if len(context_users) >= min_matches: return expand(context_users, full_objects, **kwargs) # prepare stage 2 and 3 all_users = [x for x in get_users(None, False, **kwargs)] # skip stage 2 if not enough users if len(all_users) < min_matches: return expand(all_users, full_objects, **kwargs) # prepare stage 2 filter - unicode! graph = queryUtility(INetworkTool) following_ids = [x for x in graph.get_following( 'user', plone_api.user.get_current().id)] following_users = [x for x in all_users if decode(x.getUserId) in following_ids] # apply stage 2 filter if context: filtered_users = set(context_users).union(set(following_users)) else: filtered_users = following_users if len(filtered_users) >= min_matches: return expand(filtered_users, full_objects, **kwargs) # fallback to stage 3 all users return expand(all_users, full_objects, **kwargs)
[docs]def get_users_from_userids_and_groupids(ids=None): """ Given a list of userids and groupids return the set of users FIXME this has to be folded into get_users """ acl_users = plone_api.portal.get_tool('acl_users') userids = set([]) portal = plone_api.portal.get() groups_container = portal.get('groups', {}) # BBB userprofile and workprofile should be in the same module # to avoid circular imports if groups_container: mapping = { group.getGroupId(): key for key, group in groups_container.objectItems() } else: mapping = {} for principalid in ids: if principalid in mapping: group = groups_container[mapping[principalid]] else: group = acl_users.getGroupById(principalid) if group: userids.update(group.getGroupMembers()) else: userids.add(principalid) return [user for user in imap(get, userids) if user]
[docs]def get(userid): """Get a Plone Intranet user profile by userid. userid == username, but username != getUsername(), see #1043. :param userid: Usernid of the user profile to be found :type userid: string :returns: User profile matching the given userid :rtype: `ploneintranet.userprofile.content.userprofile.UserProfile` object """ # try first of all to get the user from the profiles folder portal = plone_api.portal.get() user = portal.unrestrictedTraverse( 'profiles/{}'.format(userid), None ) if user is not None: return user # If we can't find the user there let's ask the membrane catalog # and return the first result for profile in get_users(exact_getUserId=userid): return profile # If we cannot find any match we will give up and return None return None
[docs]def get_current(): """Get the Plone Intranet user profile for the current logged-in user :returns: User profile matching the current logged-in user :rtype: `ploneintranet.userprofile.content.userprofile.UserProfile` object """ if plone_api.user.is_anonymous(): return None current_member = plone_api.user.get_current() # non-membrane users (e.g. admin) have getUserName() but not getUserId() userid = current_member.getId() return get(userid)
[docs]def create( username, email=None, password=None, approve=False, properties=None ): """Create a Plone Intranet user profile. :param username: [required] The userid for the new user. WTF? see #1043. :type username: string :param email: [required] Email for the new user. :type email: string :param password: Password for the new user. If it's not set we generate a random 12-char alpha-numeric one. :type password: string :param approve: If True, the user profile will be automatically approved and be able to log in. :type approve: boolean :param properties: User properties to assign to the new user. :type properties: dict :returns: Newly created user :rtype: `ploneintranet.userprofile.content.userprofile.UserProfile` object """ portal = plone_api.portal.get() # We have to manually validate the username validator = getMultiAdapter( (portal, None, None, IUserProfile['username'], None), IValidator) validator.validate(safe_unicode(username)) # Generate a random password if not password: chars = string.ascii_letters + string.digits password = ''.join(random.choice(chars) for x in range(12)) profile_container = portal.contentValues( {'portal_type': "ploneintranet.userprofile.userprofilecontainer"} )[0] if properties is None: # Avoids using dict as default for a keyword argument. properties = {} if 'fullname' in properties: # Translate from plone-style 'fullname' # to first and last names fullname = properties.pop('fullname') if ' ' in fullname: firstname, lastname = fullname.split(' ', 1) else: firstname = '' lastname = fullname properties['first_name'] = firstname properties['last_name'] = lastname profile = plone_api.content.create( container=profile_container, type='ploneintranet.userprofile.userprofile', id=username, username=username, email=email, **properties) # We need to manually set the password via the behaviour IProvidePasswordsSchema(profile).password = password if approve: plone_api.content.transition(profile, 'approve') profile.reindexObject() return profile
[docs]def avatar_url(username=None): """Get the avatar image url for a user profile :param username: Username for which to get the avatar url :type username: string :returns: absolute url for the avatar image :rtype: string """ portal = plone_api.portal.get() return '{0}/@@avatars/{1}'.format( portal.absolute_url(), username, )
[docs]def avatar_tag(username=None, link_to=None): """Get the tag that renders the user avatar wrapped in a link :param username: Username for which to get the avatar url :type username: string :returns: HTML for the avatar tag :rtype: string """ profile = get(username) if not profile: return '' target_url = '' profile_url = profile.absolute_url() link_class = ['pat-avatar', 'avatar'] outer_tag = 'a' if link_to == 'image': if profile.portrait: target_url = profile_url + '/@@avatar_profile.jpg' link_class.extend(['pat-gallery', 'user-info-avatar']) else: target_url = '' link_class.append('user-info-avatar') elif link_to == 'profile': target_url = profile_url elif link_to is None: outer_tag = 'span' img_class = [] if not profile.portrait: img_class.append('default-user') if target_url: target_url = 'href="' + target_url + '"' avatar_data = { 'outer_tag': outer_tag, 'fullname': profile.fullname, 'profile_url': profile_url, 'target_url': target_url, 'initials': profile.initials, 'title': profile.fullname or profile.getId() or username, 'link_class': ' '.join(link_class), 'img_class': ' '.join(img_class), } tag = u""" <{outer_tag} {target_url} class="{link_class}" data-initials="{initials}" title="{title}" > <img src="{profile_url}/@@avatar_profile.jpg" alt="Image of {fullname}" class="{img_class}" i18n:attributes="alt"> </{outer_tag}>""".format(**avatar_data) return tag