Commits

Leho Kraav committed 60a9190

Comments (0)

Files changed (38)

UserManagerPlugin-r9528/0.11/setup.py

+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+#
+# Copyright 2007-2008 Optaros, Inc
+#
+
+from setuptools import setup
+
+setup(name="TracUserManagerPlugin",
+      version="0.4",
+      packages=['tracusermanager', 
+                'tracusermanager.account', 
+                'tracusermanager.permissions',
+                'tracusermanager.profile'],
+      author="Catalin Balan", 
+      author_email="cbalan@optaros.com", 
+      url="http://code.optaros.com/trac/oforge",
+      description="Trac User Manager",
+      license="BSD",
+      entry_points={'trac.plugins': [
+            'tracusermanager.api = tracusermanager.api',
+            'tracusermanager.admin = tracusermanager.admin',
+            'tracusermanager.account = tracusermanager.account',
+            'tracusermanager.permissions = tracusermanager.permissions',
+            'tracusermanager.profile = tracusermanager.profile'
+            ]},
+      package_data={'tracusermanager' : ['htdocs/js/*.js', 
+                                         'htdocs/css/*.css', 
+                                         'templates/*.html',
+                                         'htdocs/img/*.png']},
+      
+      test_suite = 'tracusermanager.tests'
+      ) 

UserManagerPlugin-r9528/0.11/tracusermanager/__init__.py

+ 

UserManagerPlugin-r9528/0.11/tracusermanager/account/__init__.py

+from admin_um import *

UserManagerPlugin-r9528/0.11/tracusermanager/account/admin_um.py

+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Optaros, Inc.
+#
+
+from random import Random
+
+from trac.core import *
+from trac.util.translation import _
+from trac.perm import PermissionSystem
+from trac.web.chrome import add_stylesheet
+from trac.util.translation import _
+
+from acct_mgr.api import AccountManager
+
+from tracusermanager.admin import IUserManagerPanelProvider
+
+class AccountUserManagerPanel(Component):
+    
+    implements(IUserManagerPanelProvider)
+    
+    def get_usermanager_admin_panels(self, req):
+       return [('account', _('Authentication'))]
+    
+    def render_usermanager_admin_panel(self, req, panel, user, path_info):
+        
+        data={'TYPES':['trac-managed', 'server-managed'],
+              'set_password_enabled': AccountManager(self.env).supports('set_password'),
+              'delete_enabled': AccountManager(self.env).supports('delete_user')}
+        messages=[]
+        errors=[]
+        
+        if req.method=='POST':
+            if req.args.has_key('um_account_update_type'):
+                if req.args.get('um_account_type')=='trac-managed' and not AccountManager(self.env).has_user(user.username):
+                    AccountManager(self.env).set_password(user.username, ''.join([Random().choice('pleaseChangeThisPassword') for x in range(10)]) )
+                    messages.append(_("Successfully changed %s's authentication method")%(user.username))
+                elif req.args.get('um_account_type')=='server-managed':
+                    AccountManager(self.env).delete_user(user.username)
+                    messages.append(_("Successfully changed %s's authentication method")%(user.username))
+                else:
+                    raise TracError("Unknow account type")
+            elif req.args.has_key('um_account_change_password'):
+                if req.args['um_account_confirm_password'] == req.args['um_account_new_password']:
+                    AccountManager(self.env).set_password(user.username, req.args['um_account_new_password'] )
+                    messages.append(_("Successfully changed %s's password")%(user.username))
+                else:
+                    errors.append(_('Passwords don\'t match'))
+            else:
+                raise TracError("Unknow action")
+        
+        # Adding type
+        data.update(type=AccountManager(self.env).has_user(user.username) and 'trac-managed' or 'server-managed')
+        
+        return 'admin_um_account.html',{'um_account':data, 'messages':messages, 'errors':errors}

UserManagerPlugin-r9528/0.11/tracusermanager/admin.py

+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Optaros, Inc.
+#
+
+import os
+from StringIO import StringIO
+
+from trac.core import *
+from trac.config import *
+from trac.admin.api import IAdminPanelProvider
+from trac.web.chrome import add_stylesheet, add_script, add_notice, add_warning
+from trac.util import get_reporter_id
+from trac.util.html import html
+from trac.util.datefmt import format_datetime
+from trac.util.translation import _
+
+from tracusermanager.api import UserManager, User
+
+__all__=['IUserManagerPanelProvider']
+
+class IUserManagerPanelProvider(Interface):
+    
+    def get_usermanager_admin_panels():
+        """Returns a list with provided admin panels.
+        Format: [('panel_name', 'panel_label', order)]
+        
+        @return: list
+        """
+    
+    def render_usermanager_admin_panel(req, panel, user, path_info):
+        """Render's user admin panel.
+        This method should return a tuplet of a form:
+            (template, data)
+        If data has key 'errors' or 'messages', 
+        those items will be added to user_manager['errors'], ['messages'].
+                
+        @param req: trac.web.api.Request
+        @param panel: str
+        @return: tuple
+        """
+        
+class IUserListCellContributor(Interface):
+    
+    def get_userlist_cells(self):
+        """Should return a list of provided cells in form of
+        [ ('cell_name', _('Cell Label')) ]
+        """
+        
+    def render_userlist_cell(self, cell_name, user):
+        """Should render user cell"""
+        
+
+class UserManagementAdminPage(Component):
+    
+    implements(IAdminPanelProvider)
+
+    panel_providers = ExtensionPoint(IUserManagerPanelProvider)
+    cells_providers = ExtensionPoint(IUserListCellContributor)
+    
+    default_panel = Option('user_manager', 'admin_default_panel', 'profile',
+        """Default user admin panel.""")
+
+    # IAdminPageProvider methods
+    def get_admin_panels(self, req):
+        if req.perm.has_permission('TRAC_ADMIN'):
+            yield ('accounts', _('Accounts'), 'users', 'Users')
+        
+    def render_admin_panel( self, req, cat, page, path_info):
+        username = None
+        panel = None
+        panel_path_info = None
+
+        data = {}
+        um_data = dict(default_panel = self.default_panel,
+                       messages=[], errors=[])
+        
+        # collecting username, current panel and eventual path_info
+        if path_info is not None:
+            path_info_list = path_info.split('/')
+            username = path_info_list[0]
+            if len(path_info_list)>1:
+                panel = path_info_list[1]
+            if len(path_info_list)>2:
+                panel_path_info = path_info_list[2:]
+        
+        # action handling
+        if req.args.has_key('um_session_management') and panel is None:
+            return self._do_session_management(req, cat, page, path_info)
+        
+        if req.method=="POST" and panel is None:
+            try:
+                if req.args.has_key("um_newuser_create"):
+                    um_data['messages'].append( self._do_create_user(req) )                    
+                elif req.args.has_key("um_user_delete"):
+                    um_data['messages'].append( self._do_delete_user(req) )
+                elif req.args.has_key('um_import_current_users'):
+                    um_data['messages'].append( self._do_import_current_users(req) )
+            except Exception, e:
+                um_data['errors'].append(e)
+     
+        if username:
+            user = UserManager(self.env).get_user(username)
+            panels, providers = self._get_panels(req)
+            um_data.update(user = user, panels = panels)
+            if panel:
+                um_data['default_panel'] = panel
+                if providers.has_key(panel):
+                    um_data.update(panel = panel)
+                    provider = providers.get(panel)
+                    try:
+                        panel_template, data = provider.render_usermanager_admin_panel( req, panel, user, panel_path_info )
+                        um_data.update(template = panel_template)
+                    except Exception, e:
+                        um_data['errors'].append(e)
+                        
+                    # Moving messages from data to um_data
+                    if data.has_key('errors'):
+                        um_data['errors'].extend(data.pop('errors'))
+                    if data.has_key('messages'):
+                        um_data['messages'].extend(data.pop('messages'))
+        
+        # adding user list
+        um_data.update(users = UserManager(self.env).get_active_users())
+        
+        # additional cells
+        um_data.update(cells=list(self._get_cells(um_data['users'])))
+        
+        # adding usernamager's data to the data dict
+        data.update(user_manager = um_data)
+        
+        # checking for external users
+        trac_managed_users_out = self._do_import_current_users(req, dry_run=True)
+        if len(trac_managed_users_out)>0:
+            um_data['errors'].append(html.form(html.b(_("WARNING: ")),_(" [%s] users are not added to the team.")%(', '.join(trac_managed_users_out)),html.input(type="submit", name="um_import_current_users", value=_("Add Users")), action=req.href.admin('accounts/users'), method="post") )
+        
+        try:
+            from acct_mgr.api import AccountManager
+            data.update(account_manager = AccountManager(self.env))
+        except Exception, e:
+            data.update(account_manager = {'has_user':lambda x: False})
+            self.log.error('Account manager not loaded')
+        
+        # adding stylesheets
+        add_stylesheet(req, 'tracusermanager/css/admin_um.css')
+        add_script(req, 'tracusermanager/js/admin_um.js')
+        
+        return 'admin_um.html', data    
+    
+    # Internal methods
+    def _do_create_user(self, req):
+        """ """
+        if not req.args.get('um_newuser_username') or not req.args.get('um_newuser_username').strip():
+            raise TracError(_("Username field is mandatory"))
+        
+        is_trac_managed = req.args.get('um_newuser_type')=='trac-managed'
+        if is_trac_managed and not req.args.get('um_newuser_password'):
+            raise TracError(_('Password field it\'s mandatory'))
+        
+        user = User(req.args.get('um_newuser_username').strip())
+        for field in ['name', 'email', 'role']+(is_trac_managed and ['password'] or []):
+            if field=='password':
+                if req.args.get('um_newuser_password')==req.args.get('um_newuser_confirm_password'):
+                    try:
+                        from acct_mgr.api import AccountManager
+                        AccountManager(self.env).set_password(user.username, req.args.get('um_newuser_password'))
+                    except Exception, e:
+                        self.log.error(e)
+                        raise TracError(_('Unable to set %s\'s password. Please check out log messages.'%(user.username)))
+                else:
+                    raise TracError(_('Passwords don\'t match'))
+                continue
+            if req.args.get('um_newuser_%s'%(field)):
+                user[field]=req.args.get('um_newuser_%s'%(field))
+    
+        if UserManager(self.env).create_user(user):
+            return _("Successfully created user [%s].")%(user.username)
+        
+    def _do_delete_user(self, req):
+        """ """
+        if UserManager(self.env).delete_user(req.args.get('um_deleteuser_username')):
+            return _("Successfully removed user [%s].")%(req.args.get('um_deleteuser_username'))
+    
+    def _do_import_current_users(self, req, dry_run = False):
+        """ """
+        active_users = [user.username for user in UserManager(self.env).get_active_users()]
+        try:
+            from acct_mgr.api import AccountManager
+            known_users = list( AccountManager(self.env).get_users() )
+        except:
+            return []
+                
+        imported_users=[]
+        for username in known_users:
+            if not username in active_users:
+                imported_users.append(username)
+                if not dry_run:
+                    UserManager(self.env).create_user(User(username))
+        if dry_run:
+            return imported_users
+           
+        if len(imported_users)>0:
+            return _("Successfully imported the following users [%s].")%(','.join(imported_users))
+        else:
+            return _("No users imported.")
+    
+    def _do_session_management(self, req, cat, panel, path_info):
+        if req.method=="POST" and req.args.has_key("um_session_delete_selected"):
+            sel = req.args.getlist('sel')
+            if len(sel)>0:
+                db = self.env.get_db_cnx()
+                cursor = db.cursor()
+                try:
+                    cursor.executemany("DELETE FROM session_attribute WHERE sid=%s", [(sid,) for sid in sel])
+                    cursor.executemany("DELETE FROM session WHERE sid=%s", [(sid,) for sid in sel])
+                    db.commit()
+                except Exception, e:
+                    db.rollback()
+                    self.log(e)
+                    raise TracError("Unable to delete selected users. [%s]"%(e))
+                add_notice(req, "Succesfully removed [%s] sessions."%(",".join(sel)))
+            else:
+                add_warning(req, "No session ids selected")
+                
+                
+        team = [user.username for user in UserManager(self.env).get_active_users()]
+        sessions={}
+        for username, name, email in self.env.__class__.get_known_users(self.env):
+            sessions[username]=dict(username = username,
+                                    name=name,
+                                    email=email, in_team=username in team)
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        cursor.execute("SELECT sid,last_visit "
+                       "FROM session "
+                       "WHERE authenticated=1")
+        
+        for username, last_visit in cursor:
+            if sessions.has_key(username) and last_visit:
+                sessions[username]['last_visit'] = format_datetime(last_visit)
+        
+        data = dict(sessions=sorted(sessions.itervalues(), 
+                                    key=lambda session: session['username']))
+        
+        return "admin_session_management.html", data
+        
+    def _get_panels(self, req):
+        """Return a list of available admin panels."""
+        panels = []
+        providers = {}
+
+        for provider in self.panel_providers:
+            p = list(provider.get_usermanager_admin_panels(req))
+            for panel in p:
+                providers[panel[0]] = provider
+            panels += p
+
+        return panels, providers
+    
+    def _get_cells(self, user_list):
+        for provider in self.cells_providers:
+            for cell, label, order in provider.get_userlist_cells():
+                yield dict(name=cell, label=label, order=order, render_method = provider.render_userlist_cell )

UserManagerPlugin-r9528/0.11/tracusermanager/api.py

+# -*- coding: iso-8859-1 -*-
+#
+# Copyright 2008 Optaros, Inc.
+#
+try:
+    import threading
+except ImportError:
+    import dummy_threading as threading
+import time
+
+import traceback 
+import time
+from StringIO import StringIO
+
+from trac.core import *
+from trac.config import *
+from trac.env import IEnvironmentSetupParticipant
+from trac.util.translation import _
+from trac.web.chrome import ITemplateProvider
+
+class IUserAttributeChangeListener(Interface):
+
+    def user_attribute_changed(username, attribute, old_value, new_value): 
+        """
+        Called when a user attribute changes.
+        """
+
+class User(object):
+    """Object representing a user"""
+    
+    def __init__(self, username=None, user_manager=None, **attr):
+        self.username = username
+        self.user_manager = user_manager
+        self.default_attributes = attr
+        self.changes = {}
+        self.deleted = {}
+        
+    def exists(self):
+        """Return true if user exists."""
+        if self.store:
+            return len(self.user_manager.search_users(self.username))>0
+        return False
+
+    def __getitem__(self, attribute):
+        """Gets user attribute.
+        
+        @param name: str
+        """
+        if self.changes.has_key(attribute):
+            return self.changes[attribute]
+        if self.user_manager:
+            value = self.user_manager.get_user_attribute(self.username, attribute)
+            if value:
+                return value
+        if self.default_attributes.has_key(attribute):
+            return self.default_attributes[attribute]      
+            
+        return None
+        
+    def __setitem__(self, attribute, value):
+        """Sets user attribute.
+        
+        @param name: str
+        @param value: str
+        """
+        self.changes[attribute] = value
+    
+    def __delitem__(self, attribute):
+        """Removes user attribute.
+        
+        @param name: str
+        """
+        self.deleted[attribute] = 1
+    
+    def save(self):
+        return self.user_manager.save_user(self)
+        
+class IUserStore(Interface):
+    
+    def get_supported_user_operations(username):
+        """Returns supported operations 
+        in form of [operation, ].
+        
+        @return: list"""
+    
+    def execute_user_operation(operation, user, operation_arguments):
+        """Executes user operation.
+        
+        @param operation: str
+        @param user: tracusermanager.api.User
+        @param operation_arguments: dict"""
+    
+    def create_user(username):
+        """Creates an user.
+        Returns True if succeeded.
+        
+        @param user: str
+        @return: bool"""
+        
+    def search_users(user_pattern):
+        """Returns a list of user names that matches user_pattern.
+        
+        @param user_pattern: str
+        @return: list"""
+    
+    def delete_user(username):
+        """Deletes an user.
+        Returns True if the delete operation succeded.
+        
+        @param user: str
+        @return: bool"""
+
+class IAttributeProvider(Interface):
+    
+    def get_user_attribute(username, attribute):
+        """Returns user's attributes.
+        
+        @param username: str
+        @param attribute: str"""
+    
+    def set_user_attribute(username, attribute, value):
+        """Sets user's attribute value.
+                
+        @param username: str 
+        @param attribute: str
+        @param value: str
+        @return: bool
+        """
+    
+    def delete_user_attribute(username, attribute):
+        """Removes user attribute
+        
+        @param username: str
+        @param attribute: str
+        @return: bool 
+        """
+    
+    def get_usernames_with_attributes(attributes_dict):
+        """Returns a list of usernames 
+        that have "user[attributes_dict.keys] like attributes_dict.values".
+        
+        @param attributes_dict: str
+        @return: list"""
+
+class UserManager(Component):
+    
+    implements(ITemplateProvider)
+    
+    user_store = ExtensionOption('user_manager', 'user_store', IUserStore,
+                            'SessionUserStore',
+        """Name of the component implementing `IUserStore`, which is used
+        for storing project's team""")
+
+    attribute_provider = ExtensionOption('user_manager', 'attribute_provider', IAttributeProvider,
+                            'SessionAttributeProvider',
+        """Name of the component implementing `IAttributeProvider`, which is used
+        for storing user attributes""")
+
+    change_listeners = ExtensionPoint(IUserAttributeChangeListener)
+    
+    # Public methods
+    def get_user(self, username):
+        return User(username, self)
+
+    def get_active_users(self):
+        """Returns a list with the current users(team)
+        in form of [tracusermanager.api.User, ]
+        
+        @return: list"""
+        return self.search_users()
+    
+    def save_user(self, user):  
+        for attribute, value in user.changes.items():
+            self.set_user_attribute(user.username, attribute, value)
+        for attribute in user.deleted.keys():
+            self.delete_user_attribute(user.username, attribute)
+        return True
+        
+    # Public methods : IUserStore 
+    def get_supported_user_operations(self, username):
+        return self.user_store.get_supported_user_operations(username)
+    
+    def execute_user_operation(operation, user, operation_arguments):
+        return self.user_store.execute_user_operation(operation, user, operation_arguments)
+    
+    def create_user(self, user):
+        if user.username is None:
+            raise TracError(_("Username must be specified in order to create it"))
+        if self.user_store.create_user(user.username):
+            user_attributes = user.default_attributes
+            user_attributes.update(user.changes)
+            for attribute, value in user_attributes.items():
+                self.set_user_attribute(user.username, attribute, value)
+            return True
+        return False
+        
+    def search_users(self, user_templates=[]):
+        """Returns a list of users matching 
+        user_templates."""
+        search_result = {}
+        templates=[]
+        
+        if isinstance(user_templates, str):
+            templates = [User(user_templates)]
+        elif not isinstance(user_templates, list):
+            templates = [user_templates]
+        else:
+            templates = user_templates
+        
+        if len(templates)==0:
+            # no filters are passed so we'll return all users
+            return [ self.get_user(username)
+                        for username in self.user_store.search_users()]
+        
+        # search 
+        search_candidates = []
+        for user_template in templates:
+            # by username
+            if user_template.username is not None:
+                search_result.update([ (username,self.get_user(username)) 
+                                          for username in self.user_store.search_users(user_template.username)])
+            else:
+                search_attrs = user_template.default_attributes.copy()
+                search_attrs.update(user_template.changes.copy())
+                search_attrs.update(enabled='1')            
+                search_result.update([ (username, self.get_user(username))
+                                        for username in self.attribute_provider.get_usernames_with_attributes(search_attrs)])
+                
+        return search_result.values()
+        
+    def delete_user(self, username):
+        try:
+            from acct_mgr.api import AccountManager
+            if AccountManager(self.env).has_user(username):
+                AccountManager(self.env).delete_user(username)
+        except Exception, e:
+            self.log.error("Unable to delete user's authentication details")
+        return self.user_store.delete_user(username)
+
+    # Public methods : IAttributeStore 
+    def get_user_attribute(self, username, attribute):
+        return self.attribute_provider.get_user_attribute(username, attribute)
+
+    def set_user_attribute(self, username, attribute, value):
+        oldval = self.attribute_provider.get_user_attribute(username, attribute)
+        retval = self.attribute_provider.set_user_attribute(username, attribute, value)
+        for listener in self.change_listeners:
+            listener.user_attribute_changed(username, attribute, oldval, value)
+        return retval
+
+    def delete_user_attribute(self, username, attribute):
+        oldval = self.attribute_provider.get_user_attribute(username, attribute)
+        retval = self.attribute_provider.delete_user_attribute(username, attribute)
+        for listener in self.change_listeners:
+            listener.user_attribute_changed(username, attribute, oldval, None)
+        return self.attribute_provider.delete_user_attribute(username, attribute)
+    
+    def get_usernames_with_attributes(self, attribute_dict):
+        return self.attribute_provider.get_usernames_with_attributes(attribute_dict)
+    
+    # ITemplateProvider methods
+    def get_templates_dirs(self):
+        from pkg_resources import resource_filename
+        return [resource_filename('tracusermanager', 'templates')]
+
+    def get_htdocs_dirs(self):
+        from pkg_resources import resource_filename
+        return [('tracusermanager', resource_filename(__name__, 'htdocs'))] 
+
+
+
+class SessionUserStore(Component):
+    
+    implements(IUserStore)
+    
+    def get_supported_user_operations(self, username):
+        return []
+        
+    def execute_user_operation(self, operation, user, operation_arguments):
+        return True
+    
+    def create_user(self, username):
+        db = self.env.get_db_cnx()
+       
+        cursor = db.cursor()
+        try:
+            cursor.execute("INSERT INTO session (sid, last_visit, authenticated)"
+                           " VALUES(%s,%s,1)", [username, int(time.time())])
+            db.commit()
+        except Exception, e:
+            db.rollback()
+            self.log.debug("Session for %s exists, no need to re-create it."%(username))
+            
+        cursor = db.cursor()
+        try:
+            # clean up 
+            cursor.execute("DELETE "
+                               "FROM session_attribute "
+                               "WHERE sid=%s and authenticated=1 and name='enabled'", [username])
+            
+            # register active user 
+            cursor.execute("INSERT "
+                               "INTO session_attribute "
+                               "(sid,authenticated,name,value) "
+                               "VALUES(%s,1,'enabled','1')", [username])
+            # and .. commit
+            db.commit()
+            return True
+        
+        except Exception, e:
+            db.rollback()
+            out = StringIO()
+            traceback.print_exc(file=out)
+            self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
+            raise TracError(_("Unable to create user [%s].")%(username))
+            return False
+        
+    def search_users(self, username_pattern=None):
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        search_result = []
+        
+        try:
+            if username_pattern is None:
+                cursor.execute("SELECT sid FROM session_attribute "
+                                   "WHERE name='enabled' and value='1'")
+            else:
+                cursor.execute("SELECT sid FROM session_attribute "
+                               "WHERE sid like %s "
+                                   "and name='enabled' "
+                                   "and value='1'", (username_pattern,))
+            for username, in cursor:
+                search_result.append(username)
+            
+            
+        except Exception, e:
+            out = StringIO()
+            traceback.print_exc(file=out)
+            self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
+            raise TracError(_("Unable to find username from pattern [%s].")%(username_pattern))
+        
+        return search_result
+            
+    def delete_user(self, username):
+        
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        
+        try:
+            cursor.execute("DELETE "
+                               "FROM session_attribute "
+                               "WHERE sid=%s and name='enabled'", (username,))
+            db.commit()
+            return True
+        
+        except Exception, e:
+            out = StringIO()
+            traceback.print_exc(file=out)
+            self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
+            raise TracError(_("Unable to delete user [%s].")%(username))
+            return False
+        
+class SessionAttributeProvider(Component):
+    implements(IAttributeProvider)
+    
+    def get_user_attribute(self, username, attribute):
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        try:
+            cursor.execute("SELECT value "
+                           "FROM session_attribute "
+                           "WHERE sid=%s and name=%s ", (username, attribute))
+            
+            _result = list(cursor)
+            if len(_result)>0:
+                return _result[0][0]
+        except Exception, e:
+            out = StringIO()
+            traceback.print_exc(file=out)
+            self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
+            raise TracError(_("Unable to load attribute %s for user [%s].")%(attribute, username))    
+               
+        return None
+    
+    def set_user_attribute(self, username, attribute, value):
+        """Sets user's attribute value.
+                
+        @param username: str 
+        @param attribute: str
+        @param value: str
+        @return: bool
+        """
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        
+        try:
+            
+            cursor.execute("DELETE FROM session_attribute "
+                               "WHERE sid=%s and name=%s", (username, attribute))
+            
+            cursor.execute("INSERT INTO session_attribute "
+                               "(sid, authenticated, name, value) VALUES (%s, 1, %s, %s)",
+                               (username, attribute, value))
+            db.commit()
+            
+            return True
+        except Exception, e:
+            out = StringIO()
+            traceback.print_exc(file=out)
+            self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
+            raise TracError("Unable to set attribute %s for user [%s]."%(attribute, username))
+    
+        return False
+    
+    def delete_user_attribute(self, username, attribute):
+        """Removes user attribute.
+        
+        @param username: str
+        @param attribute: str
+        @return: bool 
+        """
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        
+        try:
+            
+            cursor.execute("DELETE FROM session_attribute "
+                               "WHERE sid=%s and name=%s", (username, attribute))            
+            db.commit()
+            
+            return True
+        except Exception, e:
+            out = StringIO()
+            traceback.print_exc(file=out)
+            self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
+            raise TracError("Unable to delete attribute %s for user [%s]."%(attribute, username))
+        
+        return False
+
+    def get_usernames_with_attributes(self, attributes_dict=None):
+        """ Returns all usernames matching attributes_dict.
+        
+        Example: self.get_usernames_with_attributes(dict(name='John%', email='%'))
+        
+        @param attributes_dict: dict
+        @return: list
+        """
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        
+        try:                     
+            if attributes_dict is None:
+                cursor.execute("SELECT sid FROM session_attribute WHERE name='enabled'")
+            else:
+                """ The following line executes a query that should look like this:
+                
+                    #for attributes_dict = dict(name='John%', email='%@exemple.com')):
+                        SELECT  sid
+                        FROM session_attribute 
+                        WHERE name='name' AND value like 'John%' 
+                           OR name='email' AND value like '%@exemple.com' 
+                        GROUP BY sid 
+                        HAVING count(*)=2           
+                """
+                
+                # dict to list attr_dict = { k1:v1, k2:v2, ... } -> [k1,v1,k2,v2..., len(attr_dict)]
+                attributes_list=[]
+                for k, v in attributes_dict.items():
+                    attributes_list.append(k.startswith('NOT_') and k[4:] or k)
+                    attributes_list.append(v)
+                    
+                attributes_list.append(len(attributes_dict))
+                
+                def _get_condition(k,v):
+                    return "name=%s AND value " + (k.startswith('NOT_') and 'NOT' or '') + " LIKE %s"
+                    
+                cursor.execute("SELECT sid"
+                               " FROM session_attribute"
+                               " WHERE " + " OR ".join([ _get_condition(k,v) for k,v in attributes_dict.items()]) +
+                               " GROUP BY sid"
+                               " HAVING count(*)=%s", attributes_list)
+            return [id for id, in cursor]
+        except Exception, e:
+            out = StringIO()
+            traceback.print_exc(file=out)
+            self.log.error('%s: %s\n%s' % (self.__class__.__name__, str(e), out.getvalue()))
+            return []
+
+class CachedSessionAttributeProvider(SessionAttributeProvider):
+    CACHE_UPDATE_INTERVAL = 50
+    
+    def __init__(self):
+        self._attribute_cache = {}
+        self._attribute_cache_last_update = {}
+        self._attribute_cache_lock = threading.RLock()
+
+    def _update_cache(self, username, force=False):
+        self._attribute_cache_lock.acquire()
+        try:
+            now = time.time()
+            if now > self._attribute_cache_last_update.get(username,0) + CachedSessionAttributeProvider.CACHE_UPDATE_INTERVAL \
+                    or not self._attribute_cache.has_key(username) \
+                    or force:
+                db = self.env.get_db_cnx()
+                cursor = db.cursor()
+                cursor.execute("SELECT name, value FROM session_attribute WHERE sid=%s",(username,))
+                self._attribute_cache[username] = {}
+                for name,value in cursor:
+                    self._attribute_cache[username][name] = value
+                self._attribute_cache_last_update[username] = now
+                self.log.debug("Updating SessionAttributeProvider attribute cache for user <%s>"%(username,))
+        finally:
+            self._attribute_cache_lock.release()
+    
+    def get_user_attribute(self, username, attribute):
+        self._update_cache(username)
+        if username in self._attribute_cache:
+            return self._attribute_cache[username].get(attribute)
+        return None
+    
+    def set_user_attribute(self, username, attribute, value):
+        return_value = super(CachedSessionAttributeProvider, self).set_user_attribute(username, attribute, value)
+        self._update_cache(username, force=True)
+        return return_value
+        
+    def delete_user_attribute(self, username, attribute):
+        return_value = super(CachedSessionAttributeProvider, self).delete_user_attribute(username, attribute)
+        self._update_cache(username, force=True)
+        return return_value
+
+class EnvironmentFixKnownUsers(Component):
+    implements(IEnvironmentSetupParticipant)
+    
+    # IEnvironmentSetupParticipant methods
+    def environment_created(self):
+        pass
+    
+    def environment_needs_upgrade(self, db):
+        def inline_overwrite_get_known_users(environment = None, cnx=None):
+            users = UserManager(self.env).get_active_users()
+            if len(users)>0:
+                for user in users:
+                    yield (user.username, user['name'], user['email'])
+            else:
+                # No users defined, so we're returning the original list
+                for user, name, email in  self.env.__class__.get_known_users(self.env):
+                    yield (user, name, email)
+        
+        self.env.get_known_users = inline_overwrite_get_known_users
+        
+        return False    
+
+    def upgrade_environment(self, db):
+        pass

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/css/admin_um.css

+/**
+ * Main Layout
+ */
+div#um_panel{
+}
+
+div#um_active_users{
+}
+
+
+/**
+ *	Messages
+ */
+div.um_errors{
+	padding:3px;
+	text-align:center;
+}
+
+div.um_messages{
+	padding:3px;
+	border: 2px solid green;
+	background-color:#66CC99;
+	text-align:center;
+}
+
+
+/**
+ *  Create New User
+ */
+#um_create .field LABEL{
+	display:block;
+}
+
+
+
+/**
+ *	Active User 
+ */
+div#um_active_users TABLE{
+	width:99%;
+	border: 1px solid #DDDDDD;
+	
+}
+div#um_active_users TABLE TD{
+	padding:0px;
+	border-width:0px 1px;
+}
+
+div#um_active_users TABLE TH, div#um_active_users TABLE TR{
+	border-width:0px 1px;
+}
+
+div#um_active_users TABLE TFOOT TH input[type="submit"]{
+	margin-left:135px;
+}
+
+div#um_active_users TABLE TFOOT TH{
+	padding:5px;
+	background-color:#F7F7F0;
+	/*text-align:right;*/
+	font-size:11px;
+	font-weight:bold;
+	padding:2px 0.5em;
+	vertical-align:bottom;
+}
+
+div#um_active_users TABLE TFOOT FIELDSET {
+	background-color:#ffffff;	
+}
+
+div#um_active_users TABLE TFOOT FIELDSET.close{
+	border-width:0pt;
+	background-color:transparent;
+	margin: 5px 0px 0px 0px;
+}
+
+
+div#um_active_users TABLE TFOOT FIELDSET LEGEND{
+	cursor:pointer;
+	_cursor:hand;
+	font-size:12px;
+	color:#bb0000;
+		padding-left:12px;
+}
+
+div#um_active_users TABLE TFOOT TH DIV.field LABEL{
+	display:block;
+	min-width:120px;
+	float:left;
+	margin-right:5px;
+	padding:4px;
+	text-align:right;
+	color:#999999;
+}
+
+div#um_active_users TABLE THEAD TR:first-child th{
+	border-bottom:1px solid #DDDDDD;	
+}
+
+div#um_active_users TABLE TH input[type="submit"]{
+	padding:0px;
+	margin:0px;
+	font-size:8pt;
+}
+div#um_active_users .expander{
+	background:url(../img/expander_normal.png) no-repeat 0% 50% ;
+	padding-left:16px;
+	text-decoration:none;
+	border-width:0px;
+}
+
+div#um_active_users .expander_open{
+	background:url(../img/expander_open.png) no-repeat 0% 50% ;
+	padding-left:16px;
+	text-decoration:none;
+	border-width:0px;
+}
+
+div#um_active_users .um_user_panels{
+	border:1px solid #999999;
+	background:#e7e7e7 none repeat scroll 0%;
+	padding:10px;
+	text-align:left;
+}
+
+div#um_active_users .um_user_panels div.um_messages{
+	margin-bottom:5px;	
+}
+
+div#um_active_users .um_user_panels div.um_errors{
+	margin-bottom:5px;	
+}
+
+div#um_active_users .um_user_panels .um_user_panels_tabs {
+	list-style-image:none;
+	list-style-position:outside;
+	list-style-type:none;
+	margin:0pt;
+	padding:0pt 0pt 1px 1px;
+	border:0px solid ;
+	padding-right:10px;
+}
+
+div#um_active_users .um_user_panels .um_user_panels_tabs:after {
+	clear:both;
+	content:" ";
+	display:block;
+}
+
+div#um_active_users .um_user_panels .um_user_panels_tabs li {
+	float:left;
+	margin:0pt 3px 0pt 0px;
+}
+div#um_active_users .um_user_panels .um_user_panels_tabs a {
+	color:#000000;
+	display:block;
+	padding:2px 9px 1px;
+	position:relative;
+	text-decoration:none;
+	top:1px;
+	z-index:2;
+	background-color:white;
+}
+
+div#um_active_users .um_user_panels .um_user_panels_tabs .on a{
+	border-color:#f7f7f7;
+	border-style:solid ;
+	border-width:1px 1px 0pt 0px;
+	font-weight:bold;
+	background:#F7F7F7 none repeat scroll 0%;
+}
+
+.um_active_users_count{
+	text-align:right;
+	font-weight:bold;
+	font-size:11px;
+	margin-top:10px;	
+}
+
+.um_user_panels_tabs .on a:link, .um_user_panels_tabs .on a:visited {
+	cursor:text;
+}
+
+.um_user_panels_tabs a:hover, .um_user_panels_tabs a:focus, .um_user_panels_tabs a:active {
+	cursor:pointer;
+}
+
+.um_user_panel{
+	padding:5px;
+	background-color:#f7f7f7;
+	clear:both;
+}
+
+.um_user_panel FIELDSET{
+	margin:0px;
+}
+form{
+	margin:0px;
+}

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/css/admin_um_permissions.css

+.um_permissions_group{
+	min-width:170px;
+	min-height:170px;
+	margin:3px;
+	padding:3px;
+	float:left;
+	background-color:#f5f5f5;
+}
+
+.um_permissions_group .um_permissions_group_name{
+	font-weight:bold;
+}
+
+.um_permissions_group_groups{
+	clear:both;
+	width:100%;
+	height:auto;
+}
+
+.um_permissions_group label{
+	margin-right:3px;
+}
+
+.um_permissions_actions{
+	text-align:right;
+	padding:5px;	
+	clear:both;
+	border-top:1px solid #C7C7C7
+}

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/css/admin_um_profile.css

+
+.um_profile{
+	padding:3px;
+}
+.um_profile_picture{
+	float:left;
+	width:100px;
+	min-height:100px;
+	margin:0px 10px 10px 0px;
+	border:1px solid #D7D7D7;
+}
+
+.um_profile_picture INPUT{
+	font-size:10px;
+}
+
+.um_profile_picture IMG{
+	width:100px;
+	height:auto;
+}
+
+.um_profile_fields{
+	
+}
+
+.um_profile_fields .field LABEL{
+	margin-right:5px;
+	min-width:70px;
+	text-align:right;
+	display:block;
+	float:left;
+	font-weight:bold;
+}
+
+.um_profile_actions{
+	border-top:1px solid #c7c7c7;
+	text-align:right;
+	padding:5px;
+	
+}

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/css/macros_um_profile.css

+
+div.um_profile_macro table{
+	border-collapse:separate;
+}
+
+div.um_profile_macro table td, div.um_profile_macro table tr{
+	padding:0;
+	border-width:0 1px;
+}
+
+div.um_profile_macro table tfoot th{
+	background-color:#F7F7F0;
+	text-align:right;
+	font-size:11px;
+	font-weight:bold;
+	padding:2px 0.5em;
+}
+
+div.um_profile_macro span.expander{
+	cursor:pointer;
+	background:url(../img/expander_normal.png) no-repeat 0% 50% ;
+	padding-left:16px;
+}
+
+div.um_profile_macro span.expander_open{
+	background:url(../img/expander_open.png) no-repeat 0% 50% ;
+}
+
+div.um_profile_macro .um_profile{
+	background:#F7F7F7 none repeat scroll 0%;
+	padding:10px;
+	text-align:left;	
+}
+
+div.um_profile_macro .um_profile .um_profile_picture{
+	float:left;
+	width:100px;
+	min-height:100px;
+	margin:0px 10px 10px 0px;
+	border:1px solid #D7D7D7;
+}
+
+div.um_profile_macro .um_profile .um_profile_picture img{
+	width:100px;
+	height:auto;
+}
+
+div.um_profile_macro .um_profile .um_profile_field{
+	list-style-type:none;
+	margin-bottom:2px;
+	text-align:justify;
+}
+
+div.um_profile_macro .um_profile .um_profile_field label {
+	margin:0px 5px;
+	font-weight:bold;	
+	float:left;
+}

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/css/prefs_um_profile.css

+
+.um_profile{
+	padding:3px;
+}
+.um_profile_picture{
+	float:right;
+	width:100px;
+	min-height:100px;
+	margin:0px 10px 10px 0px;
+	border:1px solid #D7D7D7;
+}
+
+.um_profile_picture INPUT{
+	font-size:10px;
+}
+
+.um_profile_picture IMG{
+	width:100px;
+	height:auto;
+}
+
+.um_profile_fields{
+	
+}
+
+.um_profile_fields .field LABEL{
+	margin-right:5px;
+	min-width:70px;
+	text-align:right;
+	display:block;
+	float:left;
+	font-weight:bold;
+}
+
+.um_profile_actions{
+	border-top:1px solid #c7c7c7;
+	/*text-align:right;*/
+	padding:5px;
+	
+}

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/img/expander_normal.png

Added
New image

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/img/expander_open.png

Added
New image

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/img/no_picture.png

Added
New image

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/img/um.alert.png

Added
New image

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/img/user.png

Added
New image

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/js/admin_um.js

+jQuery(document).ready(function($){
+	$('div#um_active_users TABLE TFOOT FIELDSET LEGEND').click(function(){
+		$(this).toggleClass('expander_open').parent().toggleClass('close').find('>.field-wrapper').toggle();
+	});
+});

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/js/admin_um_permissions.js

+
+jQuery(document).ready(function($){
+    // Hide permissions 
+	permissions = $('.um_permissions_group').not('.um_permissions_group_groups');
+	permissions.hide();
+	
+	groups = jQuery('.um_permissions_group_groups')[0];  
+	
+	toggle_link = document.createElement('A');
+    toggle_link.innerHTML="[ Show all permissions ]";
+    toggle_link.href="#";
+    $(toggle_link).css('margin-bottom','5px');
+    
+	groups.parentNode.insertBefore(toggle_link, permissions[0]);
+
+    jQuery(toggle_link).click(function(){
+		permissions.toggle(); 
+		if($(permissions[0]).is(':visible'))
+			toggle_link.innerHTML="[ Hide all permissions ]";
+		else
+		    toggle_link.innerHTML="[ Show all permissions ]";
+		return false;
+		});
+});

UserManagerPlugin-r9528/0.11/tracusermanager/htdocs/js/macros_um_profile.js

+jQuery(document).ready(function($){
+	$(".um_profile_macro .expander").click(function(){
+		$(this).toggleClass('expander_open').parents('tr:first').next().find('.um_profile:first').toggle();
+    })		
+});

UserManagerPlugin-r9528/0.11/tracusermanager/permissions/__init__.py

+from tracusermanager.permissions.admin_um import *

UserManagerPlugin-r9528/0.11/tracusermanager/permissions/admin_um.py

+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Optaros, Inc.
+#
+
+from trac.core import *
+from trac.util.translation import _
+from trac.perm import PermissionSystem
+from trac.web.chrome import add_stylesheet, add_script
+
+from tracusermanager.admin import IUserManagerPanelProvider
+
+class PermissionUserManagerPanel(Component):
+    
+    implements(IUserManagerPanelProvider)
+    
+    def get_usermanager_admin_panels(self, req):
+       return [('permissions', _('Permissions'))]
+    
+    def render_usermanager_admin_panel(self, req, panel, user, path_info):
+        user_actions = self._get_user_permissions(user)
+        all_user_actions = PermissionSystem(self.env).get_user_permissions(user.username)
+        actions = PermissionSystem(self.env).get_actions()+list(set([group for group, permissions in PermissionSystem(self.env).get_all_permissions()]))
+        data = dict(actions=actions, 
+                    all_user_actions=all_user_actions, 
+                    user_actions=user_actions,
+                    permsys = PermissionSystem(self.env), 
+                    messages=[], errors=[])
+ 
+        if req.method=="POST":
+            updated_user_permissions = req.args.getlist('um_permission')
+            
+            for action in actions:
+                if action in updated_user_permissions:
+                    if not all_user_actions.has_key(action):
+                        try: 
+                            PermissionSystem(self.env).grant_permission(user.username, action)
+                            data['messages'].append(_("Granted permission [%s] for user [%s].")%(action, user.username))
+                        except Exception, e:
+                            data['errors'].append(e)
+                else:
+                    if user_actions.has_key(action):
+                        try:
+                            PermissionSystem(self.env).revoke_permission(user.username, action)
+                            data['messages'].append(_("Revoked permission [%s] for user [%s].")%(action, user.username))
+                        except Exception, e:
+                            data['errors'].append(e)
+            if len(data['errors'])==0:
+                data['messages'].append(_('Successfully updated user permissions for user [%s]')%(user.username))
+                
+            # Updating data
+            data['user_actions'] = self._get_user_permissions(user)
+            data['all_user_actions'] = PermissionSystem(self.env).get_user_permissions(user.username)
+
+        add_stylesheet(req, 'tracusermanager/css/admin_um_permissions.css')
+        add_script(req, 'tracusermanager/js/admin_um_permissions.js')
+        
+        return 'admin_um_permissions.html', data
+        
+    def _get_user_permissions(self, user):
+        #return PermissionSystem(self.env).get_user_permissions(user.username)
+        return dict([(action, True) for username, action in PermissionSystem(self.env).get_all_permissions() 
+                     if username == user.username ])

UserManagerPlugin-r9528/0.11/tracusermanager/profile/__init__.py

+from tracusermanager.profile.admin_um import *
+from tracusermanager.profile.admin import *
+from tracusermanager.profile.api import *
+from tracusermanager.profile.macros import *
+from tracusermanager.profile.prefs import *

UserManagerPlugin-r9528/0.11/tracusermanager/profile/admin.py

+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Optaros, Inc.
+#
+
+from trac.admin.api import IAdminPanelProvider
+from trac.core import *
+from trac.web.chrome import add_stylesheet, add_script
+from trac.util.text import to_unicode
+from trac.util.translation import _
+
+from tracusermanager.profile.api import UserProfileManager
+
+
+class UserProfileFieldsAdminPage(Component):
+    
+    implements(IAdminPanelProvider)
+
+    # IAdminPanelProvider methods
+    def get_admin_panels(self, req):
+        if req.perm.has_permission('TRAC_ADMIN'):
+            yield ('accounts', _('Accounts'), 'xum_profile_fields', _('User Profile Fields'))
+
+    def render_admin_panel(self, req, cat, page, path_info):
+        #assert req.perm.has_permission('TRAC_ADMIN')
+        #req.perm.assert_permission('TRAC_ADMIN')
+        
+        def _field_from_req(self, req):
+            cfdict = {'name': to_unicode(req.args.get('name')),
+                      'label': to_unicode(req.args.get('label')),
+                      'type': to_unicode(req.args.get('type')),
+                      'value': to_unicode(req.args.get('value')),
+                      'options': [x.strip() for x in to_unicode(req.args.get('options')).split("\n")],
+                      'cols': to_unicode(req.args.get('cols')),
+                      'rows': to_unicode(req.args.get('rows')),
+                      'order': req.args.get('order', 0),
+                      'internal': req.args.get('internal', 0)
+                      }
+            return cfdict
+        
+        cfadmin = {} # Return values for template rendering
+        cfadmin['SUPPORTED_TYPES'] = UserProfileManager(self.env).SUPPORTED_FIELD_TYPES
+        
+        # Detail view?
+        if path_info:
+            
+            currentcf = UserProfileManager(self.env).get_user_profile_fields(False).get(path_info, False).copy()
+            if not currentcf:
+                raise TracError("Profile field %s does not exist." % path_info)
+            
+            if req.method == 'POST':
+                if req.args.get('save'):
+                    cfdict = _field_from_req(self, req) 
+                    UserProfileManager(self.env).update_user_profile_field(cfdict)
+                    req.redirect(req.href.admin(cat, page))
+                elif req.args.get('cancel'):
+                    req.redirect(req.href.admin(cat, page))
+            
+            if currentcf.has_key('options'):
+                optional_line = ''
+                if '' in currentcf['options']:
+                    optional_line = '\n'
+                currentcf['options'] = optional_line + "\n".join(currentcf['options'])
+            
+            cfadmin['customfield'] = currentcf
+            cfadmin['display'] = 'detail'
+            
+        else:
+            if req.method == 'POST':
+                # Add Custom Field
+                if req.args.get('add') and req.args.get('name'):
+                    cfdict = _field_from_req(self, req)
+                    UserProfileManager(self.env).update_user_profile_field(cfdict, create=True)
+                    req.redirect(req.href.admin(cat, page))
+                    
+                         
+                # Remove Custom Field
+                elif req.args.get('remove') and req.args.get('sel'):
+                    sel = req.args.get('sel')
+                    sel = isinstance(sel, list) and sel or [sel]
+                    if not sel:
+                        raise TracError, 'No custom field selected'
+                    for name in sel:
+                        UserProfileManager(self.env).delete_user_profile_field({'name': name})
+                    
+                    req.redirect(req.href.admin(cat, page))
+
+                elif req.args.get('apply'):
+                    # Change order
+                    
+                    order = dict([(key[6:], req.args.get(key)) for key
+                                  in req.args.keys()
+                                  if key.startswith('order_')])
+                    values = dict([(val, True) for val in order.values()])
+                    if len(order) != len(values):
+                        raise TracError, 'Order numbers must be unique.'
+                    cf = UserProfileManager(self.env).get_user_profile_fields(False)
+                    for cur_cf_name, cur_cf in cf.items():
+                        cur_cf['order'] = order[cur_cf_name]
+                        UserProfileManager(self.env).update_user_profile_field(cur_cf)
+                    req.redirect(req.href.admin(cat, page))
+
+            cf_list = []
+            for item, attributes in UserProfileManager(self.env).get_user_profile_fields(False).items():               
+                attributes['href'] = req.href.admin(cat, page, attributes['name'])
+                cf_list.append(attributes)
+            
+            cfadmin['customfields'] = sorted(cf_list, lambda x,y:x['order']-y['order'])
+            cfadmin['display'] = 'list'
+            
+        return ('admin_um_profile_fields.html', {'cfadmin': cfadmin})

UserManagerPlugin-r9528/0.11/tracusermanager/profile/admin_um.py

+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Optaros, Inc.
+#
+
+from trac.core import *
+from trac.util.translation import _
+from trac.web.chrome import add_stylesheet
+
+from tracusermanager.admin import IUserManagerPanelProvider, IUserListCellContributor
+from tracusermanager.profile.api import UserProfileManager
+
+class UserProfileUserManagerPanel(Component):
+    
+    implements(IUserManagerPanelProvider, IUserListCellContributor)
+    
+    # IUserManagerPanelProvider
+    def get_usermanager_admin_panels(self, req):
+        return [('profile', _('Profile'))]
+    
+    def render_usermanager_admin_panel(self, req, panel, user, path_info):
+        data = dict(messages=[], errors=[],
+                    um_profile_fields=UserProfileManager(self.env).get_user_profile_fields())
+        
+        if req.method=="POST":
+            if req.args.has_key("um_profile_picture_remove"):
+                if UserProfileManager(self.env).remove_user_file(user["picture_href"]):
+                    del user["picture_href"]
+                    if user.save():
+                        data['messages'].append(_("Successfully removed %s's picture.")%(user.username))
+            if req.args.has_key("um_profile_update"):
+                for field in data['um_profile_fields'].keys():
+                    if req.args.has_key("um_profile_%s"%(field)):
+                        if data['um_profile_fields'][field]['type']=='file':
+                            user_file_new = UserProfileManager(self.env).get_uploaded_file_href(req, user, field, "um_profile_%s"%(field))
+                            user_file_old = user[field]
+                            if user_file_new!=user_file_old:
+                                user[field] = user_file_new
+                                if user_file_old:
+                                    try:
+                                        UserProfileManager(self.env).remove_user_file(user_file_old)
+                                    except Exception, e:
+                                        self.log.error(e)
+                                        data['errors'].append(_("Unable to remove previous %s=[%s]")%(field, user_file_old))
+                        elif data['um_profile_fields'][field]['type']=='multichecks':
+                            user[field] = '|'.join(req.args.getlist("um_profile_%s"%(field)))
+                        else:
+                            user[field] = req.args.get("um_profile_%s"%(field))
+                    elif data['um_profile_fields'][field]['type']=='multichecks':
+                        # cleanup if none selected
+                        user[field]=''
+
+                if user.save():
+                    data['messages'].append(_("Successfully updated profile for user [%s].")%(user.username))
+        
+        add_stylesheet(req, 'tracusermanager/css/admin_um_profile.css')
+        
+        return 'admin_um_profile.html', data    
+
+    # IUserListCellContributor methods
+    def get_userlist_cells(self):
+        yield ('name', _('Name'),0)
+        yield ('email', _('Email'),1)
+        yield ('role', _('Role'),2)
+        
+    def render_userlist_cell(self, cell_name, user):
+        """Should render user cell"""
+        #if cell_name in ('name', 'email', 'role'):
+        return user[cell_name]
+        #return None

UserManagerPlugin-r9528/0.11/tracusermanager/profile/api.py

+# -*- coding: utf-8 -*-
+#
+# Copyright 2008 Optaros, Inc.
+#
+
+import traceback 
+import os
+import re
+from StringIO import StringIO
+
+from trac.attachment import Attachment
+from trac.core import *
+from trac.config import Option
+from trac.util.translation import _
+from trac.util.text import unicode_unquote
+from trac.util import get_reporter_id
+from trac.wiki import WikiPage
+
+class IUserProfilesListMacroCellContributor(Interface):
+    
+    def get_userlistmacro_cells(self):
+        """Should return a list of provided cells in form of
+        [ ('cell_name', _('Cell Label')) ]
+        """
+        
+    def render_userlistmacro_cell(self, cell_name, user):
+        """Should render user cell"""
+
+
+def parse_custom_fields_config(rawfields):
+    """ """
+    fields = {}
+    for option, value in rawfields:
+        parts = option.split('.')
+        field = parts[0]
+        if field not in fields:
+            fields[field] = {}
+        if len(parts) == 1:
+            fields[field]['type']=value
+        else:
+            fields[field][parts[1]] = value
+    
+    # Fill default values
+    for field, attributes in fields.items():
+        if 'name' not in attributes:
+            attributes['name'] = field
+
+        if 'label' not in attributes:
+            attributes['label'] = field
+
+        if 'value' not in attributes:
+            attributes['value'] = ''
+        
+        if 'order' not in attributes:
+            attributes['order'] = -1
+            
+        if 'options' not in attributes:
+            attributes['options'] = []
+        else:
+            attributes['options'] = attributes['options'].split('|')
+
+        if 'cols' not in attributes:
+            attributes['cols'] = 20
+
+        if 'rows' not in attributes:
+            attributes['rows'] = 20
+
+        if 'internal' not in attributes:
+            attributes['internal'] = 0
+
+    return fields
+    
+def get_custom_fields_config(config, section_name):
+    return parse_custom_fields_config(list(config.options(section_name)))
+
+class UserProfileManager(Component):
+    
+    
+    attachments_wikiPage = Option('user_manager', 'wiki_page_attachment', 'UserManagerPluginPictures',
+        """Wiki Page used by TracUserManager plugin to manage 
+        UserProfile's picture.""")
+    
+    SUPPORTED_FIELD_TYPES = ['text', 'select', 'multichecks', 'textarea', 'wikitext']
+    CONFIG_SECTION_NAME = 'um_profile-custom'
+    
+    def __init__(self, *args, **kwargs):
+        Component.__init__(self, *args, **kwargs)
+        
+        self.fields={  'name':dict(name='name', type='text',label=_('Name'), value='', order=-3, cols=20),
+                       'email':dict(name='email', type='text',label=_('Email'), value='', order=-2, cols=20 ),
+                       'role':dict(name='role', type='text',label=_('Role'), value='', order=-1, cols=20 ),
+                       'picture_href':dict(name='picture_href',type='file',label=_('Picture'), value='', order=0 )}
+    
+        for field, attributes in get_custom_fields_config(self.config, self.CONFIG_SECTION_NAME).items():
+            if attributes['order']==-1:
+                attributes['order']=len(self.fields)
+            else:
+                attributes['order']=int(attributes['order'])
+            self.fields[field] = attributes
+    
+    def get_user_profile_fields(self, all=True, ignore_internal=False):
+        if all:
+            if ignore_internal:
+                return dict(filter(lambda x:int(x[1].get('internal','0'))==0, self.fields.items()))
+            return self.fields
+        else:
+            return dict(filter(lambda x:not x[0] in ['name','email','role','picture_href'],self.fields.items()))
+    
+    def update_user_profile_field(self, field, create=False):
+        """ Update or create a new user profile field (if requested).
+        field is a dictionary with the following possible keys:
+            name = field's name
+            type = text|checkbox|select|radio|textarea
+            label = label description
+            value = default value for field content
+            options = options for select and multichecks types (list, leave first empty for optional)
+            cols = number of columns for text area/wikitext
+            rows = number of rows for text area/wikitext
+            order = specify sort order for field
+            internal = if True than field will be visible only on the admin page.
+        """
+        
+        # setting environment
+        env = self.env
+        
+        # Name, Type and Label is required
+        if not (field.has_key('name') and field.has_key('type') and field.has_key('label')):
+            raise TracError("Custom field needs at least a name, type and label.")
+        
+        # Only alphanumeric characters (and [-_]) allowed for custom fieldname
+        matchlen = re.search("[a-z0-9-_]+", field['name']).span()
+        namelen = len(field['name'])
+        if (matchlen[1]-matchlen[0] != namelen):
+            raise TracError("Only alphanumeric characters allowed for custom field name (a-z or 0-9 or -_).")
+        
+        # If Create, check that field does not already exist
+        if create and env.config.get(self.CONFIG_SECTION_NAME, field['name']):
+            raise TracError("Can not create as field already exists.")
+        
+        # Check that it is a valid field type
+        if not field['type'] in self.SUPPORTED_FIELD_TYPES:
+            raise TracError("%s is not a valid field type" % field['type'])
+        
+        # Create/update the field name and type
+        env.config.set(self.CONFIG_SECTION_NAME, field['name'], field['type'])
+        
+        # Set the field label