From: claytron <svn...@pl...> - 2006-07-20 13:12:39
|
Author: claytron Date: Thu Jul 20 13:12:30 2006 New Revision: 26290 Added: remember/trunk/skins/remember/prefs_user_manage.cpy (contents, props changed) remember/trunk/skins/remember/prefs_user_manage.cpy.metadata (contents, props changed) remember/trunk/skins/remember/prefs_users_overview.cpt (contents, props changed) remember/trunk/skins/remember/prefs_users_overview.cpt.metadata (contents, props changed) Modified: remember/trunk/content/member.py remember/trunk/tools/memberdata.py remember/trunk/tools/membership.py Log: * set up prefs_users_overview to work with remember * added addMember and deleteMembers code to membership tool * added search code to membership and memberdata tools (searching seems to be a bit strict at the moment) Modified: remember/trunk/content/member.py ============================================================================== --- remember/trunk/content/member.py (original) +++ remember/trunk/content/member.py Thu Jul 20 13:12:30 2006 @@ -24,6 +24,9 @@ from member_schema import content_schema metadata_schema = atapi.ExtensibleMetadata.schema.copy() +import logging +logger = logging.getLogger('remember') + _marker = [] class BaseMember(object): Added: remember/trunk/skins/remember/prefs_user_manage.cpy ============================================================================== --- (empty file) +++ remember/trunk/skins/remember/prefs_user_manage.cpy Thu Jul 20 13:12:30 2006 @@ -0,0 +1,43 @@ +## Script (Python) "prefs_user_manage" +##bind container=container +##bind context=context +##bind namespace= +##bind script=script +##bind subpath=traverse_subpath +##parameters=users=[], resetpassword=[], delete=[] +##title=Edit users +## + +acl_users = context.acl_users +mtool = context.portal_membership +getMemberById = mtool.getMemberById +mailPassword = context.portal_registration.mailPassword +setMemberProperties = context.plone_utils.setMemberProperties +generatePassword = context.portal_registration.generatePassword + +for user in users: + # Don't bother if the user will be deleted anyway + if user.id in delete: + continue + + member = getMemberById(user.id) + # If email address was changed, set the new one + if user.email != member.getProperty('email'): + setMemberProperties(member, email=user.email) + + # If reset password has been checked email user a new password + if hasattr(user, 'resetpassword'): + pw = generatePassword() + else: + pw = None + + member.update(password=pw, roles=user.get('roles',[])) + if pw: + mailPassword(user.id, context.REQUEST) + +if delete: + # BBB We should eventually have a global switch to determine member area + # deletion + mtool.deleteMembers(delete, delete_memberareas=0, delete_localroles=1) + +return state.set(portal_status_message=context.translate('Changes applied.')) Added: remember/trunk/skins/remember/prefs_user_manage.cpy.metadata ============================================================================== --- (empty file) +++ remember/trunk/skins/remember/prefs_user_manage.cpy.metadata Thu Jul 20 13:12:30 2006 @@ -0,0 +1,7 @@ +[validators] +validators = + +[actions] +action.success = redirect_to:string:prefs_users_overview +action.failure = traverse_to:string:prefs_users_overview + Added: remember/trunk/skins/remember/prefs_users_overview.cpt ============================================================================== --- (empty file) +++ remember/trunk/skins/remember/prefs_users_overview.cpt Thu Jul 20 13:12:30 2006 @@ -0,0 +1,263 @@ +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" + lang="en" + metal:use-macro="here/prefs_main_template/macros/master" + i18n:domain="plone"> + +<metal:block metal:fill-slot="top_slot" + tal:define="dummy python:request.set('disable_border',1)" /> + +<body> + +<div metal:fill-slot="prefs_configlet_content" + tal:define="Batch python:modules['Products.CMFPlone'].Batch; + b_start request/b_start | python:0; + b_size request/b_size | python:20; + portal_roles here/getGlobalPortalRoles; + searchstring request/searchstring | nothing;"> + + <div id="content" class="documentEditable" + tal:condition="python:checkPermission('Manage users', here)"> + + <h5 class="hiddenStructure">Views</h5> + + <ul class="contentViews"> + <li class="selected"> + <a href="" + tal:attributes="href template_id" + i18n:translate="label_users">Users</a> + </li> + <li> + <a href="prefs_groups_overview" + i18n:translate="label_groups">Groups</a> + </li> + </ul> + + <div class="contentActions"> + + </div> + + <div class="documentContent" id="region-content"> + <a name="documentContent"></a> + + <div metal:use-macro="here/global_statusmessage/macros/portal_message"> + Portal status message + </div> + + <div class="configlet"> + <h1 i18n:translate="heading_users_overview">Users Overview</h1> + + <a href="" + class="link-parent" + tal:attributes="href string: $portal_url/plone_control_panel" + i18n:translate="label_up_to_plone_setup"> + Up to Site Setup + </a> + + <p i18n:translate="description_user_management"> + Click the user's name to see and change the details of a + specific user. Click the envelope icon to send a mail to + the user. You can also edit the e-mail addresses, and + add/remove users. + </p> + <p i18n:translate="user_role_note"> + Note that roles listed here apply directly to a user. + They do not reflect additional roles users may have due + to group memberships. + </p> + <p i18n:translate="description_pas_users_listing" + tal:condition="nothing | python:not searchstring and not context.acl_users.canListAllUsers()"> + Note: Some or all of your PAS user source + plugins do not allow listing of users, so you may not see + the users defined by those plugins unless doing a specific + search. + </p> + + <form action="" + name="users_add" + method="post" + tal:attributes="action template_id"> + <select name="type_name" + tabindex="" + tal:define="member_types + here/membrane_tool/listMembraneTypes" + tal:condition="python: len(member_types) > 1" + tal:attributes="value string:foo; + tabindex tabindex/next;"> + <option tal:repeat="type_name member_types" + tal:attributes="value type_name" + tal:content="type_name"> + </option> + </select> + + <input class="standalone add" + tabindex="" + type="submit" + name="form.button.AddUser" + value="Add New User" + i18n:attributes="value label_add_new_user;" + tal:attributes="tabindex tabindex/next;" + /> + <input type="hidden" name="form.submitted" value="1" /> + </form> + + <form action="" + name="users_search" + method="post" + tal:attributes="action template/getId" + tal:define="findAll python:'form.button.FindAll' in request.keys(); + searchstring request/searchstring | nothing; + portal_users python:(searchstring or findAll) and mtool.searchForMembers(name=searchstring) or []; + batch python:Batch(portal_users, b_size, int(b_start), orphan=1)"> + <input type="hidden" name="form.submitted" value="1" /> + + <table class="listing" summary="User Listing"> + <tr> + <th colspan="6" tal:attributes="colspan python:len(portal_roles)+4"> + <span tal:omit-tag="" i18n:translate="label_user_search">User Search</span>: + <input tabindex="" + class="quickSearch" + type="text" + name="searchstring" + value="" + tal:attributes="value searchstring; + tabindex tabindex/next;" + /> + + <input type="submit" + class="searchButton" + name="form.button.Search" + value="Search" + tabindex="" + i18n:attributes="value label_search;" + tal:attributes="tabindex tabindex/next;" /> + + <input type="submit" + class="searchButton" + name="form.button.FindAll" + value="Show all" + tabindex="" + i18n:attributes="value label_showall;" + tal:condition="not:site_properties/many_users" + tal:attributes="tabindex tabindex/next;" /> + </th> + </tr> + <tal:block tal:condition="portal_users" > + <tr> + <th rowspan="2" i18n:translate="listingheader_user_name">User name</th> + <th rowspan="2" i18n:translate="listingheader_email_address">E-mail Address</th> + <th colspan="3" tal:attributes="colspan python:len(portal_roles)" + i18n:translate="listingheader_roles">Roles</th> + <th rowspan="2" i18n:translate="listingheader_reset_password">Reset Password</th> + <th rowspan="2" i18n:translate="listingheader_remove_user">Remove user</th> + </tr> + <tr> + <th tal:repeat="portal_role portal_roles" tal:content="portal_role" i18n:translate="">Role</th> + </tr> + </tal:block> + + <tal:block repeat="this_user batch"> + <tr tal:define="oddrow repeat/this_user/odd; + userid this_user/getId; + fullname python: this_user.getProperty('fullname')" + tal:attributes="class python:test(oddrow,'odd','even')"> + + <td> + <a href="prefs_user_details" + tal:attributes="href this_user/absolute_url"> + <tal:block replace="structure portal/user.gif"/> <span tal:replace="this_user/getId">username</span> + (<span tal:replace="fullname">Full Name</span>) + </a> + <input type="hidden" name="users.id:records" tal:attributes="value userid" /> + </td> + + <td tal:define="email python:this_user.getProperty('email')"> + <a href="#" + class="link-plain" + tal:attributes="href string:mailto:${email}" + title="Send a mail to this user" + i18n:attributes="title title_send_mail_to_user;" + ><tal:block replace="structure here/mail_icon.gif"/></a> + <input style="margin:2px;" + type="text" + size="15" + name="users.email:records" + value="" + tal:attributes="value email;" /> + </td> + + <td class="listingCheckbox" + tal:define="user_roles python:this_user.getProperty('roles') or []" + tal:repeat="portal_role portal_roles"> + <input type="checkbox" + class="noborder" + name="users.roles:list:records" + value="Manager" + tal:attributes="value portal_role; + checked python:test(portal_role in user_roles, 'checked', nothing);" /> + </td> + + <td class="listingCheckbox"> + <input type="checkbox" + class="noborder" + name="users.resetpassword:records" + value="" + tal:attributes="value userid;" /> + </td> + + <td class="listingCheckbox"> + <input type="checkbox" + class="noborder notify" + name="delete:list" + value="" + tal:attributes="value userid;" /> + </td> + </tr> + </tal:block> + <tr tal:condition="not:batch"> + <td tal:condition="searchstring" + i18n:translate="text_nomatches" + style="text-align:center;">No matches</td> + <tal:block tal:condition="not:searchstring"> + <td tal:condition="site_properties/many_users" + class="discreet" + i18n:translate="text_no_user_searchstring" + style="text-align:center; font-size: 100%;"> + Enter a username to search for + </td> + <td tal:condition="not:site_properties/many_users" + class="discreet" + i18n:translate="text_no_user_searchstring_largesite" + style="text-align:center; font-size: 100%;"> + Enter a username to search for, or click 'Show All' + </td> + </tal:block> + </tr> + </table> + + <div metal:use-macro="here/batch_macros/macros/navigation" /> + + <input class="context" + tabindex="" + type="submit" + name="form.button.Modify" + value="Apply Changes" + i18n:attributes="value label_apply_changes;" + tal:attributes="tabindex tabindex/next;" + tal:condition="batch" + /> + + </form> + + </div> + </div> + </div> + + <div id="content" class="documentEditable" + tal:condition="python:not checkPermission('Manage users', here)"> + <tal:block replace="here/raiseUnauthorized" /> + </div> + </div> + + </body> +</html> + Added: remember/trunk/skins/remember/prefs_users_overview.cpt.metadata ============================================================================== --- (empty file) +++ remember/trunk/skins/remember/prefs_users_overview.cpt.metadata Thu Jul 20 13:12:30 2006 @@ -0,0 +1,16 @@ +[default] +title=User Management + +[security] +View = 0:Authenticated + +[validators] +validators = + +[actions] +action.success..AddUser = redirect_to:string:join_form?came_from_prefs=1 +action.success..Search = traverse_to:string:prefs_users_overview +action.success..Modify = traverse_to:string:prefs_user_manage +action.success = traverse_to:string:prefs_users_overview + + Modified: remember/trunk/tools/memberdata.py ============================================================================== --- remember/trunk/tools/memberdata.py (original) +++ remember/trunk/tools/memberdata.py Thu Jul 20 13:12:30 2006 @@ -14,6 +14,8 @@ from Products.Archetypes import public as atapi from Products.remember.config import DEFAULT_MEMBER_TYPE +search_catalog = 'membrane_tool' + class MemberDataContainer(atapi.BaseBTreeFolder, BaseTool): """ Default container for remember Member objects. Members don't @@ -105,6 +107,92 @@ Delete member data of the specified member. """ pass + + def searchForMembers( self, REQUEST=None, **kw ): + """ + Do a catalog search on a sites members. If a 'brains' argument is set + to a True value, search will return only member_catalog metadata. + Otherwise, memberdata objects returned. + + If 'brains' is a False value and a 'portal_only' parameter is passed + in with a True value then only members from the portal's acl_users + folder will be returned. + """ + + if REQUEST: + search_dict = getattr(REQUEST, 'form', REQUEST) + else: + REQUEST = {} + search_dict = kw + + results=[] + catalog=getToolByName(self, search_catalog) + + # no reason to iterate over all those indexes + try: + from sets import Set + indexes=Set(catalog.indexes()) + indexes = indexes & Set(search_dict.keys()) + except: + # Unless we are on 2.3 + catalog.indexes() + + query={} + + def dateindex_query(field_value, field_usage): + usage, val = field_usage.split(':') + return { 'query': field_value, usage:val } + + def zctextindex_query(field_value): + # Auto Globbing + if not field_value.endswith('*') and field_value.find(' ') == -1: + field_value += '*' + return field_value + + special_query = dict(( + ( 'DateIndex', dateindex_query ), + ( 'ZCTextIndex', zctextindex_query ) + )) + + if search_dict: + # Make a indexname: fxToApply dict + idx_fx = dict(\ + [(x.id, special_query[x.meta_type])\ + for x in catalog.Indexes.objectValues()\ + if (x.meta_type in special_query.keys() and x.id in indexes)]\ + ) + + for i in indexes: + val=search_dict.get(i, None) + usage_val = search_dict.get('%s_usage' %i) + if type(val) == type([]): + val = filter(None, val) + + if (i in idx_fx.keys() and val): + if usage_val: + val = idx_fx[i](val, usage_val) + else: + val = idx_fx[i](val) + + if val: + query.update({i:val}) + + results=catalog(query) + + if results and not (search_dict.get('brains', False) or \ + REQUEST.get('brains', False)): + if search_dict.get('portal_only', False) or \ + REQUEST.get('portal_only', False): + res = [] + for r in results: + mem = r.getObject() + if mem._isPortalUser(): + res.append(mem) + results = res + else: + results = [r.getObject() for r in results] + + return filter(None, results) atapi.registerType(MemberDataContainer) InitializeClass(MemberDataContainer) Modified: remember/trunk/tools/membership.py ============================================================================== --- remember/trunk/tools/membership.py (original) +++ remember/trunk/tools/membership.py Thu Jul 20 13:12:30 2006 @@ -1,7 +1,84 @@ +import logging +from AccessControl import ClassSecurityInfo +from Products.CMFCore.utils import getToolByName from Products.PlonePAS.tools.membership import MembershipTool as BaseTool +logger = logging.getLogger('remember') + class MembershipTool(BaseTool): """ remember customization of MembershipTool. """ meta_type = "remember Membership Tool" + + security = ClassSecurityInfo() + + def _getMemberDataContainer(self): + md_path = getattr(self, 'memberdata_container_path', None) + if md_path is None: + mdc = getToolByName(self, 'portal_memberdata') + md_path = mdc.getPhysicalPath() + return self.unrestrictedTraverse(md_path) + + security.declarePrivate('addMember') + def addMember(self, id, password, roles, domains, properties=None): + '''Adds a new member to the user folder. Security checks will have + already been performed. Called by portal_registration. + ''' + + member_type = 'Member' + typeName = None #str(self._getMemberDataContainer().getTypeName()) + if typeName: + member_type = typeName + self._getMemberDataContainer().invokeFactory(member_type,id) + member=getattr(self._getMemberDataContainer().aq_explicit,id) + logger.info('\n\n the props be:\n\n' + str(properties)) + member.edit(password=password,roles=roles,domains=domains,**(properties or {})) + + #security.declareProtected(ManageUsers, 'deleteMembers') + def deleteMembers(self, members, delete_memberareas=1, + delete_localroles=1): + """Delete members specified by member_ids. + """ + # Delete the member objects from the member data container + self._getMemberDataContainer().manage_delObjects(members) + + # Delete members' home folders including all content items. + if delete_memberareas: + for member_id in members: + self.deleteMemberArea(member_id) + + # Delete members' local roles. + if delete_localroles: + utool = getToolByName(self, 'portal_url', None) + self.deleteLocalRoles( utool.getPortalObject(), members, + reindex=1, recursive=1 ) + + def searchForMembers( self, REQUEST=None, **kw ): + """ + here for backwards compatibility; member searching is better + accomplished using the member_catalog, which this ultimately + delegates to + """ + if type(REQUEST) == type({}): + param = REQUEST # folder_localroles_form passes a dict here as REQUEST + REQUEST = None + elif REQUEST: + param = REQUEST.form + else: + param = kw + + # mapping from older lookup names to the indexes that exist + # in the member_catalog + key_map = {'name': 'getId', + 'email': 'getEmail', + 'roles': 'getFilteredRoles', + 'groupname': 'getGroups', # XXX this is a case sensitive search, but is case insensitive in standard plone 2.1 + 'last_login_time': 'getLastLoginTime', + } + for key in key_map.keys(): + if param.has_key(key): + # swap old parameter for what the catalog expects + param[key_map[key]] = param.pop(key) + + return self._getMemberDataContainer().searchForMembers(REQUEST, **param) |