コード例 #1
0
ファイル: profile.py プロジェクト: juhamust/multiproject
    def get_profile_actions(self, req, user):
        """
        Return list of actions
        """
        actions = []
        homeperm = self._get_home_perm(req)
        uresource = Resource('user', user.id)

        # Project settings for own account
        if user.username == req.authname:
            actions.append((-40, tag.a(_('View your projects'), href=homeperm.env.href('myprojects'))))
            actions.append((0, tag.a(_('Edit your settings'), href=homeperm.env.href('prefs'))))
        # View other user profile
        else:
            actions.append((-50, tag.a(_('View service profile'), href=homeperm.env.href('user', user.username))))

        # If user can fully manage account
        if homeperm.has_permission('USER_EDIT', uresource):
            label = 'Manage your account' if user.username == req.authname else 'Manage account'
            actions.append((-2, tag.a(_(label), href=homeperm.env.href('admin/users/manage', username=user.username))))

        # Tickets assigned to or created by user (if component is enabled and user has permissions to view them)
        # Note: href.kwargs cannot be used because of reserved word 'or' and un-ordered nature of dict
        if (self.env.is_component_enabled('trac.ticket.query.QueryModule') or
            self.env.is_component_enabled('multiproject.project.tickets.viewtickets.QueryModuleInterceptor')) \
            and 'TICKET_VIEW' in req.perm:
            qstring = 'owner={0}&or&reporter={0}&group=status'.format(user.username)
            query = Query.from_string(self.env, qstring)
            actions.append((5, (tag.a(_('View tickets'), href=query.get_href(req.href)))))

        return actions
コード例 #2
0
    def _do_save(self, req, user):
        """ Update user information into database
        """
        userstore = get_userstore()

        if not req.args.get('mail'):
            add_warning(req, _('User should have an e-mail address'))
            return user

        user.mail = req.args.get('mail')

        if not req.args.get('lastName'):
            add_warning(req, _('Last name required'))
            return user

        # NOTE: Values are escaped in userstore update
        user.lastName = req.args.get('lastName')
        user.givenName = req.args.get('givenName')
        user.mobile = req.args.get('mobile')

        if userstore.updateUser(user):
            user = userstore.getUser(user.username)
            add_notice(req, _('Updated user settings'))

            if req.args.get(
                    'approve') == 'on' and user.status == user.STATUS_INACTIVE:
                user.activate()
                add_notice(req, _("Your account is now activated."))

            return user

        add_warning(req, _('Failed to update user'))
        return user
コード例 #3
0
    def _do_save(self, req, user):
        """
        Save ssh key into database
        """
        if not req.args.get('ssh_key'):
            add_warning(req, _('Failed to add SSH key: Key is required'))
            return user

        ssh_key = req.args.get('ssh_key')
        ssh_key = SshKey.remove_comment_from_key_string(ssh_key)

        if not SshKey.validate_key_string(ssh_key):
            add_warning(
                req,
                _('Failed to add SSH key: invalid SSH key, key must be in open ssh format'
                  ))
            return user

        description = req.args.get('description')
        if len(description) > 200:
            add_warning(req, _('Failed to add SSH key: Too long description'))
            return user

        key_store = CQDESshKeyStore.instance()
        user_id = user.id

        if key_store.add_ssh_key(user_id, ssh_key, description):
            add_notice(
                req,
                _('New SSH key added (please allow 5 minutes for replication)')
            )
            return user

        add_warning(req, _('Failed to add SSH key: Server error'))
        return user
コード例 #4
0
    def changePassword(self, req):
        userstore = get_userstore()
        user = userstore.getUser(req.authname)

        oldpw = req.args.get('oldpassword')
        newpw = req.args.get('newpassword')

        if not oldpw or not userstore.userExists(req.authname, oldpw):
            add_warning(req, _('Old password is invalid'))
            return user

        if not newpw or len(newpw) < 7:
            add_warning(req,
                        _('New password should be at least 7 characters long'))
            return user

        if newpw != req.args.get('confirmpw'):
            add_warning(req, _('Passwords do not match'))
            return user

        if not userstore.updatePassword(user, newpw):
            add_warning(req, _('Failed to change the password'))
            return user

        add_notice(req, _('Password changed'))
        return user
コード例 #5
0
ファイル: watch.py プロジェクト: nagyistoce/trac-multiproject
    def expand_macro(self, formatter, name, content, args=None):
        """
        Returns the outcome from macro.
        Supported arguments:

        - project: Name of the project to show status / provide follow buttons. Defaults to current project

        """
        req = formatter.req

        # Parse optional arguments
        if args is None:
            args = parse_args(content)
            if len(args) > 1:
                args = args[1]

        # Read optional project name - fallback to current
        project_name = self.env.project_identifier
        if args and 'project' in args:
            project_name = args.get('project', '').strip()

        # Load project id from db
        project_id = Project.get(env_name=project_name).id
        watchers, is_watching = self._get_status(req, project_id)

        # If user is already watching, do not show the block
        if is_watching:
            return tag.div('')

        # Show macro only when user has permission to view timeline
        if name not in self.macros or 'TIMELINE_VIEW' not in req.perm or not project_id:
            # Return default content / instructions
            return tag.div(
                tag.h2(_('Follow project'), **{'class': 'title'}),
                tag.p(_('Project cannot be found or no permission to follow it')),
                **{'class': 'watch'}
            )

        # For anonymous users, advice login/registering
        if req.authname == 'anonymous':
            return tag.div(
                tag.h2(_('Follow project'), **{'class': 'title'}),
                tag.p(_('Only registered users can follow the project activity. ')),
                tag.p(tag.a('Please login or register to service first', href=req.href('../home/user'))),
                **{'class': 'watch'}
            )

        # Return rendered HTML with JS attached to it
        data = {
            'project_id': project_id,
            'env_name': self.env.project_identifier,
            'project_name': project_name
        }

        chrome = Chrome(self.env)
        stream = chrome.render_template(req, 'multiproject_watch.html', data, fragment=True)
        if req.form_token:
            stream |= chrome._add_form_token(req.form_token)

        return stream
コード例 #6
0
ファイル: ui.py プロジェクト: juhamust/multiproject
    def get_profile_actions(self, req, account):
        """
        Returns
        """
        actions = []

        # Check if user has MESSAGE_CREATE in home env
        home_env = HomeProject().get_env()
        home_perm = PermissionCache(home_env, req.authname)

        if 'MESSAGE_CREATE' not in home_perm:
            return []

        # Own account
        if req.authname == account.username:
            actions.append((200, tag.a(
                _('Send message to...'),
                **{'class': 'messages-dialog', 'href': '#'}
            )))
        else:
            actions.append((200, tag.a(
                _('Send message to %s' % account.username),
                **{'class': 'messages-dialog', 'href': '#user_id=%d' % account.id}
            )))

        return actions
コード例 #7
0
    def _do_save(self, req, user):
        """ Update user information into database
        """
        userstore = get_userstore()

        if not req.args.get('mail'):
            add_warning(req, _('User should have an e-mail address'))
            return user

        user.mail = req.args.get('mail')

        if not req.args.get('lastName'):
            add_warning(req, _('Last name required'))
            return user

        # NOTE: Values are escaped in userstore update
        user.lastName = req.args.get('lastName')
        user.givenName = req.args.get('givenName')
        user.mobile = req.args.get('mobile')

        if userstore.updateUser(user):
            user = userstore.getUser(user.username)
            add_notice(req, _('Updated user settings'))

            if req.args.get('approve') == 'on' and user.status == user.STATUS_INACTIVE:
                user.activate()
                add_notice(req, _("Your account is now activated."))

            return user

        add_warning(req, _('Failed to update user'))
        return user
コード例 #8
0
    def changePassword(self, req):
        userstore = get_userstore()
        user = userstore.getUser(req.authname)

        oldpw = req.args.get('oldpassword')
        newpw = req.args.get('newpassword')

        if not oldpw or not userstore.userExists(req.authname, oldpw):
            add_warning(req, _('Old password is invalid'))
            return user

        if not newpw or len(newpw) < 7:
            add_warning(req, _('New password should be at least 7 characters long'))
            return user

        if newpw != req.args.get('confirmpw'):
            add_warning(req, _('Passwords do not match'))
            return user

        if not userstore.updatePassword(user, newpw):
            add_warning(req, _('Failed to change the password'))
            return user

        add_notice(req, _('Password changed'))
        return user
コード例 #9
0
    def _do_save(self, req, user):
        """
        Save ssh key into database
        """
        if not req.args.get('ssh_key'):
            add_warning(req, _('Failed to add SSH key: Key is required'))
            return user

        ssh_key = req.args.get('ssh_key')
        ssh_key = SshKey.remove_comment_from_key_string(ssh_key)

        if not SshKey.validate_key_string(ssh_key):
            add_warning(req, _('Failed to add SSH key: invalid SSH key, key must be in open ssh format'))
            return user

        description = req.args.get('description')
        if len(description) > 200:
            add_warning(req, _('Failed to add SSH key: Too long description'))
            return user

        key_store = CQDESshKeyStore.instance()
        user_id = user.id

        if key_store.add_ssh_key(user_id, ssh_key, description):
            add_notice(req, _('New SSH key added (please allow 5 minutes for replication)'))
            return user

        add_warning(req, _('Failed to add SSH key: Server error'))
        return user
コード例 #10
0
ファイル: team.py プロジェクト: alvabai/trac-multiproject
    def expand_macro(self, formatter, name, content, args=None):
        """
        Returns the outcome from macro.
        """
        req = formatter.context.req

        # Check permissions
        if 'TIMELINE_VIEW' not in req.perm:
            # Return default content / instructions
            return tag.div(
                tag.h2(_('Project team'), **{'class': 'title'}),
                tag.p(_('Project team cannot be found or no permission to follow it')),
                **{'class': 'watch'}
            )

        # Load project info from optional project argument. Defaults to current env
        project = Project.get(self.env)
        team, members = self._get_team_info(project)

        # Return rendered HTML with JS attached to it
        data = {
            'project_id': project.id,
            'env_name': self.env.project_identifier,
            'project_name': self.env.project_identifier,  # TODO: redundant
            'team': team,
            'members': members
        }

        # NOTE: Use fragment to not to recreate chrome (add_script does not work) and run post processing manually
        chrome = Chrome(self.env)
        stream = chrome.render_template(req, 'multiproject_team.html', data, fragment=True)
        if req.form_token:
            stream |= chrome._add_form_token(req.form_token)

        return stream
コード例 #11
0
ファイル: watch.py プロジェクト: juhamust/multiproject
    def expand_macro(self, formatter, name, content, args=None):
        """
        Returns the outcome from macro.
        Supported arguments:

        - project: Name of the project to show status / provide follow buttons. Defaults to current project

        """
        req = formatter.req

        # Parse optional arguments
        if args is None:
            args = parse_args(content)
            if len(args) > 1:
                args = args[1]

        # Read optional project name - fallback to current
        project_name = self.env.project_identifier
        if args and 'project' in args:
            project_name = args.get('project', '').strip()

        # Load project id from db
        project_id = Project.get(env_name=project_name).id
        watchers, is_watching = self._get_status(req, project_id)

        # If user is already watching, do not show the block
        if is_watching:
            return tag.div('')

        # Show macro only when user has permission to view timeline
        if name not in self.macros or 'TIMELINE_VIEW' not in req.perm or not project_id:
            # Return default content / instructions
            return tag.div(
                tag.h2(_('Follow project'), **{'class': 'title'}),
                tag.p(_('Project cannot be found or no permission to follow it')),
                **{'class': 'watch'}
            )

        # For anonymous users, advice login/registering
        if req.authname == 'anonymous':
            return tag.div(
                tag.h2(_('Follow project'), **{'class': 'title'}),
                tag.p(_('Only registered users can follow the project activity. ')),
                tag.p(tag.a('Please login or register to service first', href=req.href('../home/user'))),
                **{'class': 'watch'}
            )

        # Return rendered HTML with JS attached to it
        data = {
            'project_id': project_id,
            'env_name': self.env.project_identifier,
            'project_name': project_name
        }

        chrome = Chrome(self.env)
        stream = chrome.render_template(req, 'multiproject_watch.html', data, fragment=True)
        if req.form_token:
            stream |= chrome._add_form_token(req.form_token)

        return stream
コード例 #12
0
    def render_preference_panel(self, req, panel):
        """ Renders preference panel and handles information change on POST
        """
        if req.authname == 'anonymous':
            raise TracError("User is not authenticated", "No access")

        data = {}
        key_store = CQDESshKeyStore.instance()
        user = get_userstore().getUser(req.authname)

        if req.method == 'POST':
            ssh_key = req.args.get('ssh_key')
            delete_key = req.args.get('deletelist')

            if ssh_key:
                user = self._do_save(req, user)
            elif delete_key:
                user = self._do_deletes(req, user)
            else:
                add_warning(req, _('Please provide data'))

        keys = key_store.get_ssh_keys_by_user_id(user.id)
        data['keys'] = keys if keys else None

        # This is to prevent adding of more than one ssh password.
        # Remove this if we support more in the future.
        if keys:
            data['hide_add_dialog'] = False

        data['domain'] = conf.domain_name
        data['user'] = user

        return 'multiproject_user_prefs_ssh_keys.html', data
コード例 #13
0
    def render_preference_panel(self, req, panel):
        """ Renders preference panel and handles information change on POST
        """
        if req.authname == 'anonymous':
            raise TracError("User is not authenticated", "No access")

        data = {}
        key_store = CQDESshKeyStore.instance()
        user = get_userstore().getUser(req.authname)

        if req.method == 'POST':
            ssh_key = req.args.get('ssh_key')
            delete_key = req.args.get('deletelist')

            if ssh_key:
                user = self._do_save(req, user)
            elif delete_key:
                user = self._do_deletes(req, user)
            else:
                add_warning(req, _('Please provide data'))

        keys = key_store.get_ssh_keys_by_user_id(user.id)
        data['keys'] = keys if keys else None

        # This is to prevent adding of more than one ssh password.
        # Remove this if we support more in the future.
        if keys:
            data['hide_add_dialog'] = False

        data['domain'] = conf.domain_name
        data['user'] = user

        return 'multiproject_user_prefs_ssh_keys.html', data
コード例 #14
0
    def _do_deletes(self, req, user):
        """ Delete ssh key from database
        """
        key_store = CQDESshKeyStore.instance()
        user_id = user.id

        key_ids = req.args.get('deletelist')
        key_ids = isinstance(key_ids, list) and key_ids or [key_ids]

        for key_id in key_ids:
            if key_id:
                if key_store.remove_ssh_key(user_id, key_id):
                    add_notice(req, _('SSH key deleted'))
                else:
                    add_warning(req, _('Failed to delete SSH key: Server error.'))

        return user
コード例 #15
0
    def _do_deletes(self, req, user):
        """ Delete ssh key from database
        """
        key_store = CQDESshKeyStore.instance()
        user_id = user.id

        key_ids = req.args.get('deletelist')
        key_ids = isinstance(key_ids, list) and key_ids or [key_ids]

        for key_id in key_ids:
            if key_id:
                if key_store.remove_ssh_key(user_id, key_id):
                    add_notice(req, _('SSH key deleted'))
                else:
                    add_warning(req,
                                _('Failed to delete SSH key: Server error.'))

        return user
コード例 #16
0
    def get_profile_actions(self, req, user):
        """
        Return list of actions
        """
        actions = []
        homeperm = self._get_home_perm(req)
        uresource = Resource('user', user.id)

        # Project settings for own account
        if user.username == req.authname:
            actions.append((-40,
                            tag.a(_('View your projects'),
                                  href=homeperm.env.href('myprojects'))))
            actions.append((0,
                            tag.a(_('Edit your settings'),
                                  href=homeperm.env.href('prefs'))))
        # View other user profile
        else:
            actions.append(
                (-50,
                 tag.a(_('View service profile'),
                       href=homeperm.env.href('user', user.username))))

        # If user can fully manage account
        if homeperm.has_permission('USER_EDIT', uresource):
            label = 'Manage your account' if user.username == req.authname else 'Manage account'
            actions.append(
                (-2,
                 tag.a(_(label),
                       href=homeperm.env.href('admin/users/manage',
                                              username=user.username))))

        # Tickets assigned to or created by user (if component is enabled and user has permissions to view them)
        # Note: href.kwargs cannot be used because of reserved word 'or' and un-ordered nature of dict
        if (self.env.is_component_enabled('trac.ticket.query.QueryModule') or
            self.env.is_component_enabled('multiproject.project.tickets.viewtickets.QueryModuleInterceptor')) \
            and 'TICKET_VIEW' in req.perm:
            qstring = 'owner={0}&or&reporter={0}&group=status'.format(
                user.username)
            query = Query.from_string(self.env, qstring)
            actions.append((5, (tag.a(_('View tickets'),
                                      href=query.get_href(req.href)))))

        return actions
コード例 #17
0
ファイル: team.py プロジェクト: nagyistoce/trac-multiproject
    def expand_macro(self, formatter, name, content, args=None):
        """
        Returns the outcome from macro.
        """
        req = formatter.context.req

        # Check permissions
        if 'TIMELINE_VIEW' not in req.perm:
            # Return default content / instructions
            return tag.div(
                tag.h2(_('Project team'), **{'class': 'title'}),
                tag.p(
                    _('Project team cannot be found or no permission to follow it'
                      )), **{'class': 'watch'})

        # Load project info from optional project argument. Defaults to current env
        project = Project.get(self.env)
        team, members = self._get_team_info(project)

        # Return rendered HTML with JS attached to it
        data = {
            'project_id': project.id,
            'env_name': self.env.project_identifier,
            'project_name': self.env.project_identifier,  # TODO: redundant
            'team': team,
            'members': members
        }

        # NOTE: Use fragment to not to recreate chrome (add_script does not work) and run post processing manually
        chrome = Chrome(self.env)
        stream = chrome.render_template(req,
                                        'multiproject_team.html',
                                        data,
                                        fragment=True)
        if req.form_token:
            stream |= chrome._add_form_token(req.form_token)

        return stream
コード例 #18
0
ファイル: actions.py プロジェクト: alvabai/trac-multiproject
    def __require_permissions_for_cloning(self, authname, parent_project):
        """
        Checks that user have permissions to clone project

        :param authname: Current user name
        :param parent_project: Project class instance
        :raises: PermissionError if no permission
        """
        clone_env = parent_project.get_env()
        perms = clone_env[PermissionSystem].get_user_permissions(authname)
        if not perms.get('VERSION_CONTROL_VIEW', False):
            self.log.error("Intrusive version control clone from " +
                           parent_project.env_name + " requested by " + authname + " !")
            raise PermissionError(msg=_("You need to have permissions to read repository to clone it."))
コード例 #19
0
ファイル: users.py プロジェクト: nagyistoce/trac-multiproject
    def remove_user(self, req):
        """
        Show removal form and handle POST as remove action
        """
        username = req.args.get('username')

        # Check method and permissions
        if not req.method.upper() == 'POST' or not username:
            raise PermissionError()

        # Load user
        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        account = userstore.getUser(username)

        if not account:
            add_warning(req, "Could not find user '{0}' from service".format(account.username))
            return req.redirect(req.href('admin/users/manage'))

        # Check permissions
        req.perm.require('USER_AUTHOR', Resource('user', id=account.id))

        # If removable user is project author, change the ownership to person who deletes the user
        papi = projects.Projects()
        for project in papi.get_authored_projects(account):
            project.author = user
            project.save()

            # Check if user has TRAC_ADMIN rights for the new project, if not, try setting
            if not req.perm.has_permission('TRAC_ADMIN', Resource('project', id=project.id)):
                groupstore = CQDEUserGroupStore(project.trac_environment_key)
                # Iterate existing project groups and put user into group with TRAC_ADMIN rights
                for gname, pname in groupstore.get_all_group_permissions():
                    if pname == 'TRAC_ADMIN':
                        groupstore.add_user_to_group(project.author.username, gname)
                        self.log.info('Added TRAC_ADMIN permissions to {0} at {0}'.format(project.author, project))

            self.log.info('Changed ownership of project {0} from {0} to {0}'.format(project, project.author, user))
            add_notice(req, tag(_("Changed ownership of the project to you: "), tag.a(project.project_name, href=req.href('..', project.env_name))))

        if userstore.deleteUser(account):
            add_notice(req, "Removed user '{0}' successfully from local store".format(account.username))
        else:
            add_warning(req, "Failed to remove user '{0}' from local store".format(account.username))

        # Redirect to user listing
        return req.redirect(req.href('admin/users/manage'))
コード例 #20
0
ファイル: users.py プロジェクト: juhamust/multiproject
    def remove_user(self, req):
        """
        Show removal form and handle POST as remove action
        """
        username = req.args.get('username')

        # Check method and permissions
        if not req.method.upper() == 'POST' or not username:
            raise PermissionError()

        # Load user
        userstore = get_userstore()
        user = userstore.getUser(req.authname)
        account = userstore.getUser(username)

        if not account:
            add_warning(req, "Could not find user '{0}' from service".format(account.username))
            return req.redirect(req.href('admin/users/manage'))

        # Check permissions
        req.perm.require('USER_AUTHOR', Resource('user', id=account.id))

        # If removable user is project author, change the ownership to person who deletes the user
        papi = projects.Projects()
        for project in papi.get_authored_projects(account):
            project.author = user
            project.save()

            # Check if user has TRAC_ADMIN rights for the new project, if not, try setting
            if not req.perm.has_permission('TRAC_ADMIN', Resource('project', id=project.id)):
                groupstore = CQDEUserGroupStore(project.trac_environment_key)
                # Iterate existing project groups and put user into group with TRAC_ADMIN rights
                for gname, pname in groupstore.get_all_group_permissions():
                    if pname == 'TRAC_ADMIN':
                        groupstore.add_user_to_group(project.author.username, gname)
                        self.log.info('Added TRAC_ADMIN permissions to {0} at {0}'.format(project.author, project))

            self.log.info('Changed ownership of project {0} from {0} to {0}'.format(project, project.author, user))
            add_notice(req, tag(_("Changed ownership of the project to you: "), tag.a(project.project_name, href=req.href('..', project.env_name))))

        if userstore.deleteUser(account):
            add_notice(req, "Removed user '{0}' successfully from local store".format(account.username))
        else:
            add_warning(req, "Failed to remove user '{0}' from local store".format(account.username))

        # Redirect to user listing
        return req.redirect(req.href('admin/users/manage'))
コード例 #21
0
    def __require_permissions_for_cloning(self, authname, parent_project):
        """
        Checks that user have permissions to clone project

        :param authname: Current user name
        :param parent_project: Project class instance
        :raises: PermissionError if no permission
        """
        clone_env = parent_project.get_env()
        perms = clone_env[PermissionSystem].get_user_permissions(authname)
        if not perms.get('VERSION_CONTROL_VIEW', False):
            self.log.error("Intrusive version control clone from " +
                           parent_project.env_name + " requested by " +
                           authname + " !")
            raise PermissionError(msg=_(
                "You need to have permissions to read repository to clone it.")
                                  )
コード例 #22
0
ファイル: users.py プロジェクト: nagyistoce/trac-multiproject
    def edit_user(self, req):
        """
        Handle user edit: view & save
        """
        changes = {}

        username = req.args.get('username')
        if not username:
            add_warning(req, _('Invalid username'))
            return self.list_users(req)

        # Load user being edited
        userstore = get_userstore()
        user = userstore.getUser(username)
        if not user:
            add_warning(req, _('Invalid username (or connection error)'))
            return self.list_users(req)

        # Load user who's doing the edit
        changed_by = userstore.getUser(req.authname)
        papi = Projects()

        # Check permissions and redirect to user listing (handy after editing the user)
        #req.perm.require('USER_AUTHOR', Resource('user', id=user.id))
        if self.check_author_and_deputies(changed_by.id,
            user.author_id, userstore.get_deputies(user.id), req, user.id) == False:
            add_warning(req, _("You don't have rights to edit user"))
            req.redirect(req.href("admin"))

        data = req.args
        data['user'] = user
        data['author'] = userstore.getUserWhereId(user.author_id) if user.author_id else None
        data['deputies'] = userstore.get_deputies(user.id)
        data['base_path'] = req.base_path
        data['dateformats'] = DATEFORMATS
        data['is_local'] = userstore.is_local(user)
        data['now'] = datetime.utcnow()
        data['expired'] = user.expires and ((user.expires - datetime.utcnow()).days < 0)
        data['states'] = userstore.USER_STATUS_LABELS
        data['projects'] = papi.get_authored_projects(user)

        # Add javascript libraries for datepicker and autocomplete
        add_script(req, 'multiproject/js/jquery-ui.js')
        add_stylesheet(req, 'multiproject/css/jquery-ui.css')
        add_script(req, 'multiproject/js/multiproject.js')
        add_script(req, 'multiproject/js/admin_user_edit.js')

        # If get request show edit
        if req.method.upper() == 'GET' and req.args.get('remove_deputy'):
            deputy = userstore.getUser(req.args.get('remove_deputy').strip())
            remove_res = userstore.remove_deputy(user.id, deputy.id)
            return req.send(remove_res, content_type='text/plain', status=200)
        elif req.method.upper() == 'GET':
            return 'admin_user_edit.html', data

        # Close pressed: get back to user listing
        if req.args.get('close'):
            return req.redirect(req.href('admin/users/manage'))

        if req.args.get('deputy_name'):
            deputy = userstore.getUser(req.args.get('deputy_name').strip())
            resource = Resource('user', id=deputy.id)
            perm = PermissionCache(self.env, username=deputy.username)
            if perm.has_permission('USER_AUTHOR', resource):
                if(userstore.add_deputy(user.id, deputy.username)):
                    add_notice(req, _("Deputy "+deputy.username+" added."))
                    return_url = 'home/admin/users/manage?username='******'home/admin/users/manage?username='******'t have enough rights"))
                return_url = 'home/admin/users/manage?username='******'limitexceeded' in req.args:
            add_warning(req, _('Picture you tried to upload was too big. Try a smaller one'))

        # Update author if changed
        author_id = req.args.get('author_id', None)

        # If id field is empty but name is not: manual input
        if not author_id and req.args.get('author_text'):
            add_warning(req, _('Author cannot be found'))
            return 'admin_user_edit.html', data

        # Check set reset the author
        if author_id:
            author = userstore.getUserWhereId(int(author_id))
            if not author:
                add_warning(req, _('Author cannot be found'))
                return 'admin_user_edit.html', data

            # Check if author is valid: has permission to author?
            perm = PermissionCache(self.env, author.username)
            if 'USER_AUTHOR' not in perm:
                add_warning(req, _('User %s cannot work as an author (does not have USER_AUTHOR permissions)' % author))
                return self.back(req)

            user.author_id = author.id
            changes['author'] = author
        else:
            user.author_id = None

        user.lastName = req.args.get('last')
        if not user.lastName:
            add_warning(req, _('Last name required'))
            return self.back(req)

        old_mail = user.mail
        user.mail = req.args.get('email')
        if not user.mail:
            add_warning(req, _('Email address required'))
            return self.back(req)

        if old_mail != user.mail:
            changes['email'] = user.mail
            org_store = CQDEOrganizationStore.instance()
            # TODO: is this correct?
            # When changing email, reset organizations to which the user belongs in
            user.organization_keys = org_store.get_organization_keys(user) or None

        # Update password if changed
        password = req.args.get('password')
        if password:
            if not userstore.is_local(user):
                add_warning(req, _("Can't change password for user that uses external authentication method"))
                return self.back(req)

            if len(password) < 7:
                add_warning(req, _("Password must be at least 7 characters long - please provide longer password"))
                return self.back(req)

            if password != req.args.get('confirmpw'):
                add_warning(req, _("Password do not match - please check"))
                return self.back(req)

        user.givenName = req.args.get('first')
        user.mobile = req.args.get('mobile')

        # Set or reset account expiration date
        expiration_str = req.args.get('expires', '')
        if expiration_str:
            try:
                # Parse date and set expiration time in the end of the day
                expires = datetime.strptime(expiration_str, DATEFORMATS['py'])
                expires += timedelta(hours=23, minutes=59, seconds=59)

                # If changed
                if expires != user.expires:
                    user.expires = expires
                    changes['expiration_date'] = user.expires.strftime(DATEFORMATS['py'])

            except Exception:
                self.log.exception('Date formatting failed')
                add_warning(req, _('Non-recognized expiration format'))
                pass

        # Remove expiration date
        elif user.expires:
            changes['expiration_date'] = 'Never expires'
            user.expires = None

        # Update status if set
        status = int(req.args.get('status', 0))
        if status and status in userstore.USER_STATUS_LABELS.keys() and user.status != status:
            changes['status'] = userstore.USER_STATUS_LABELS[status]
            user.status = status

        if req.args.get('removeicon'):
            user.icon = None
        else:
            icon = req.args.get('icon')
            if not isinstance(icon, unicode) and icon.filename:
                user.createIcon(req.args.get('icon'))

        self.log.info('Saving changes to user: %s' % user)
        ok = userstore.updateUser(user)
        if ok and password:
            changes['password'] = password
            ok = userstore.updatePassword(user, password)

        if not ok:
            add_warning(req, _("Could not save changes"))

        add_notice(req, _("User %s updated" % username))

        # Notify user about changes via email?
        if req.args.get('notify'):
            data = {
                'user':user,
                'changed_by':changed_by,
                'changes':changes
            }

            try:
                enotify = EmailNotifier(self.env, "Account updated", data)
                enotify.template_name = 'account_edited.txt'
                enotify.notify(user.mail)
                add_notice(req, _("Notified user about the changes"))
            except TracError:
                add_warning(req, _("Failed to send email notification - user changed anyway"))

        # Check if user has still (after modification) permission to modify user
        # NOTE: req.perm cannot be used here because it is not updated yet
        #resource = Resource('user', id=user.id)
        #perm = PermissionCache(self.env, username=req.authname)
        #if perm.has_permission('USER_AUTHOR', resource):
        #    return self.back(req)
        if self.check_author_and_deputies(changed_by.id,
            user.author_id, userstore.get_deputies(user.id), req, user.id) == True:
            return_url = 'home/admin/users/manage?username='******'You have no longer permission to modify the account: %s' % user.username))
            return req.redirect(req.href('admin/users/manage'))
コード例 #23
0
ファイル: ui.py プロジェクト: juhamust/multiproject
 def get_navigation_items(self, req):
     if 'MESSAGE_VIEW' in req.perm:
         yield ('metanav', 'messages', tag.a(_('Messages'), **{'class': 'messages', 'href': '#'}))
コード例 #24
0
ファイル: actions.py プロジェクト: alvabai/trac-multiproject
    def create_project(self, req):
        """ Handler for creating project request
        """
        req.perm.require("PROJECT_CREATE")
        if req.method != 'POST':
            return self.create_failure(req, 'POST request needed when creating a new project')
        author = get_context(req)['author']
        # If agreement needed but not getting it, show failure
        if conf.project_requires_agreed_terms and not self._is_active_user(req):
            return self.create_failure(req, 'You need to approve legal text to create a project!')

        # Read and transform some variables
        vcs_type = req.args.get('vcstype')
        vcs_name = req.args.get('vcs_name')
        if not self.validate_repository_name(vcs_name):
            return self.create_failure(req, 'Check repository name.')

        parent_project = None
        if "_project_" in req.args:
            parent_project = Project.get(env_name=req.args.get('_project_'))
            self.__require_permissions_for_cloning(req.authname, parent_project)
            vcs_type = conf.getVersionControlType(parent_project.env_name) # TODO: expensive call, probably needed

        # Read settings
        settings = {}
        if vcs_type:
            settings['vcs_type'] = vcs_type
        if vcs_name:
            settings['vcs_name'] = vcs_name

        identifier = req.args.get('prj_short_name')
        name = req.args.get('prj_long_name')
        project_visibility = 'prj_is_public' in req.args

        public = False
        published = None
        if project_visibility:
            public = True
            published = datetime.now()

        # Create project object
        project = Project(
            id = None,
            env_name = identifier,
            project_name = name,
            description = req.args.get('prj_description'),
            author_id = author.id,
            created = None, # Use default which is now()
            public = public,
            published = published
        )

        # Create project environment
        projects = Projects()
        try:
            projects.create_project(project, settings)
        except ProjectValidationException as exc:
            self.log.warning('Project creation failed due the validation: {0}'.format(exc.value))
            return self.create_failure(req, exc.value)
        except:
            self.log.exception('Project creation failed')
            return self.create_failure(req, _("Creating project failed. Try again later."))

        if public:
            projects.add_public_project_visibility(project.id)

        #Add author to follow project
        watch_store = CQDEWatchlistStore()
        watch_store.watch_project(author.id, project.id)

        #Change project trac.ini to support multiple repositories
        project_env_path = conf.getEnvironmentSysPath(project.env_name)
        repo_env_path = conf.getEnvironmentVcsPath(project.env_name, vcs_type, vcs_name)
        os.rename(project_env_path + '/conf/trac.ini', project_env_path + '/conf/trac.ini.bak')
        oldfile = open(project_env_path + '/conf/trac.ini.bak', 'r')
        newfile = open(project_env_path + '/conf/trac.ini', 'w')
        lines = oldfile.readlines()
        for line in lines:
            newfile.write(line)
            if line.startswith('database ='):
                break
        newfile.write('repository_dir =\nrepository_type = svn\n\n[repositories]\n')
        newfile.write('%s.dir = %s\n' % (vcs_name, repo_env_path))
        newfile.write('%s.type = %s\n' % (vcs_name, vcs_type))
        newfile.close()
        oldfile.close()
        os.remove(project_env_path + '/conf/trac.ini.bak')

        # Notify listeners. The project object still exists, but database does not
        for listener in self.project_change_listeners:
            try:
                listener.project_created(project)
                listener.project_watchers(project)
                if public:
                    listener.project_set_public(project)
            except:
                pass


        return self.create_success(req, project)
コード例 #25
0
    def process_request(self, req):
        """
        Handles the profile box request, which is expected to be in format::

            /user/<username>/profilebox

        :returns:
            Pre-rendered user profile box HTML using templates:

            - multiproject_user_profilebox.html: To show the content
            - multiproject_user_profilebox_default.html: In a case of failures, missing data etc
        """
        # Read and validate arguments
        #account_id = req.args.get('user_id', None)
        #account_username = req.args.get('username', None)

        match = MATCHREGX.match(req.path_info)
        if not match:
            msg = _('Account cannot be found')
            return 'multiproject_user_profilebox_default.html', {
                'msg': msg
            }, False

        # Load specified account
        userstore = get_userstore()
        account = userstore.getUser(match.group('username'))
        if not account:
            msg = _('Account cannot be found')
            return 'multiproject_user_profilebox_default.html', {
                'msg': msg
            }, False

        # Check if user has USER_VIEW permission to view other users in home project
        homeperm = self._get_home_perm(req)
        if req.authname != account.username and 'USER_VIEW' not in homeperm:
            msg = _('Access denied')
            return 'multiproject_user_profilebox_default.html', {
                'msg': msg
            }, False

        # Load registered actions
        actions = []
        for aprovider in self.profile_action_providers:
            # Iterate actions and validate them
            for new_action in aprovider.get_profile_actions(req, account):
                # Ensure each action is in tuple format: (order, fragment)
                if not isinstance(new_action, tuple):
                    new_action = (0, new_action)
                # Add to list
                actions.append(new_action)

        # Sort the actions by hints given in actions:
        # the smaller the value, the higher the priority
        actions.sort(key=lambda tup: tup[0])

        # Construct list items: put first and class values
        litems = []
        llen = len(actions)
        for index, action in enumerate(actions):
            classes = []
            # If last
            if index == 0:
                classes.append('first')
            if llen == index + 1:
                classes.append('last')

            litems.append(tag.li(action[1], **{'class': ' '.join(classes)}))

        # If empty, put empty list element in place for easier styling
        if not litems:
            litems.append(tag.li('li', **{'class': 'first last'}))

        # Pass data to template. Generate ul/li list from registered actions
        data = {'account': account, 'actionlist': tag.ul(*litems)}

        return 'multiproject_user_profilebox.html', data, False
コード例 #26
0
    def create_project(self, req):
        """ Handler for creating project request
        """
        req.perm.require("PROJECT_CREATE")
        if req.method != 'POST':
            return self.create_failure(
                req, 'POST request needed when creating a new project')
        author = get_context(req)['author']
        # If agreement needed but not getting it, show failure
        if conf.project_requires_agreed_terms and not self._is_active_user(
                req):
            return self.create_failure(
                req, 'You need to approve legal text to create a project!')

        # Read and transform some variables
        vcs_type = req.args.get('vcstype')
        vcs_name = req.args.get('vcs_name')
        if not self.validate_repository_name(vcs_name):
            return self.create_failure(req, 'Check repository name.')

        parent_project = None
        if "_project_" in req.args:
            parent_project = Project.get(env_name=req.args.get('_project_'))
            self.__require_permissions_for_cloning(req.authname,
                                                   parent_project)
            vcs_type = conf.getVersionControlType(
                parent_project.env_name
            )  # TODO: expensive call, probably needed

        # Read settings
        settings = {}
        if vcs_type:
            settings['vcs_type'] = vcs_type
        if vcs_name:
            settings['vcs_name'] = vcs_name

        identifier = req.args.get('prj_short_name')
        name = req.args.get('prj_long_name')
        project_visibility = 'prj_is_public' in req.args

        public = False
        published = None
        if project_visibility:
            public = True
            published = datetime.now()

        # Create project object
        project = Project(
            id=None,
            env_name=identifier,
            project_name=name,
            description=req.args.get('prj_description'),
            author_id=author.id,
            created=None,  # Use default which is now()
            public=public,
            published=published)

        # Create project environment
        projects = Projects()
        try:
            projects.create_project(project, settings)
        except ProjectValidationException as exc:
            self.log.warning(
                'Project creation failed due the validation: {0}'.format(
                    exc.value))
            return self.create_failure(req, exc.value)
        except:
            self.log.exception('Project creation failed')
            return self.create_failure(
                req, _("Creating project failed. Try again later."))

        if public:
            projects.add_public_project_visibility(project.id)

        #Add author to follow project
        watch_store = CQDEWatchlistStore()
        watch_store.watch_project(author.id, project.id)

        #Change project trac.ini to support multiple repositories
        project_env_path = conf.getEnvironmentSysPath(project.env_name)
        repo_env_path = conf.getEnvironmentVcsPath(project.env_name, vcs_type,
                                                   vcs_name)
        os.rename(project_env_path + '/conf/trac.ini',
                  project_env_path + '/conf/trac.ini.bak')
        oldfile = open(project_env_path + '/conf/trac.ini.bak', 'r')
        newfile = open(project_env_path + '/conf/trac.ini', 'w')
        lines = oldfile.readlines()
        for line in lines:
            newfile.write(line)
            if line.startswith('database ='):
                break
        newfile.write(
            'repository_dir =\nrepository_type = svn\n\n[repositories]\n')
        newfile.write('%s.dir = %s\n' % (vcs_name, repo_env_path))
        newfile.write('%s.type = %s\n' % (vcs_name, vcs_type))
        newfile.close()
        oldfile.close()
        os.remove(project_env_path + '/conf/trac.ini.bak')

        # Notify listeners. The project object still exists, but database does not
        for listener in self.project_change_listeners:
            try:
                listener.project_created(project)
                listener.project_watchers(project)
                if public:
                    listener.project_set_public(project)
            except:
                pass

        return self.create_success(req, project)
コード例 #27
0
ファイル: profile.py プロジェクト: juhamust/multiproject
    def process_request(self, req):
        """
        Handles the profile box request, which is expected to be in format::

            /user/<username>/profilebox

        :returns:
            Pre-rendered user profile box HTML using templates:

            - multiproject_user_profilebox.html: To show the content
            - multiproject_user_profilebox_default.html: In a case of failures, missing data etc
        """
        # Read and validate arguments
        #account_id = req.args.get('user_id', None)
        #account_username = req.args.get('username', None)

        match = MATCHREGX.match(req.path_info)
        if not match:
            msg = _('Account cannot be found')
            return 'multiproject_user_profilebox_default.html', {'msg': msg}, False

        # Load specified account
        userstore = get_userstore()
        account = userstore.getUser(match.group('username'))
        if not account:
            msg = _('Account cannot be found')
            return 'multiproject_user_profilebox_default.html', {'msg': msg}, False

        # Check if user has USER_VIEW permission to view other users in home project
        homeperm = self._get_home_perm(req)
        if req.authname != account.username and 'USER_VIEW' not in homeperm:
            msg = _('Access denied')
            return 'multiproject_user_profilebox_default.html', {'msg': msg}, False

        # Load registered actions
        actions = []
        for aprovider in self.profile_action_providers:
            # Iterate actions and validate them
            for new_action in aprovider.get_profile_actions(req, account):
                # Ensure each action is in tuple format: (order, fragment)
                if not isinstance(new_action, tuple):
                    new_action = (0, new_action)
                # Add to list
                actions.append(new_action)

        # Sort the actions by hints given in actions:
        # the smaller the value, the higher the priority
        actions.sort(key=lambda tup: tup[0])

        # Construct list items: put first and class values
        litems = []
        llen = len(actions)
        for index, action in enumerate(actions):
            classes = []
            # If last
            if index == 0:
                classes.append('first')
            if llen == index + 1:
                classes.append('last')

            litems.append(tag.li(action[1], **{'class': ' '.join(classes)}))

        # If empty, put empty list element in place for easier styling
        if not litems:
            litems.append(tag.li('li', **{'class':'first last'}))

        # Pass data to template. Generate ul/li list from registered actions
        data = {
            'account':account,
            'actionlist':tag.ul(*litems)
        }

        return 'multiproject_user_profilebox.html', data, False
コード例 #28
0
ファイル: users.py プロジェクト: juhamust/multiproject
    def edit_user(self, req):
        """
        Handle user edit: view & save
        """
        changes = {}

        username = req.args.get('username')
        if not username:
            add_warning(req, _('Invalid username'))
            return self.list_users(req)

        # Load user being edited
        userstore = get_userstore()
        user = userstore.getUser(username)
        if not user:
            add_warning(req, _('Invalid username (or connection error)'))
            return self.list_users(req)

        # Load user who's doing the edit
        changed_by = userstore.getUser(req.authname)
        papi = Projects()

        # Check permissions and redirect to user listing (handy after editing the user)
        req.perm.require('USER_AUTHOR', Resource('user', id=user.id))

        data = req.args
        data['user'] = user
        data['author'] = userstore.getUserWhereId(user.author_id) if user.author_id else None
        data['base_path'] = req.base_path
        data['dateformats'] = DATEFORMATS
        data['is_local'] = userstore.is_local(user)
        data['now'] = datetime.utcnow()
        data['expired'] = user.expires and ((user.expires - datetime.utcnow()).days < 0)
        data['states'] = userstore.USER_STATUS_LABELS
        data['projects'] = papi.get_authored_projects(user)

        # Add javascript libraries for datepicker and autocomplete
        add_script(req, 'multiproject/js/jquery-ui.js')
        add_stylesheet(req, 'multiproject/css/jquery-ui.css')
        add_script(req, 'multiproject/js/multiproject.js')
        add_script(req, 'multiproject/js/admin_user_edit.js')

        # If get request show edit
        if req.method.upper() == 'GET':
            return 'admin_user_edit.html', data

        # Close pressed: get back to user listing
        if req.args.get('close'):
            return req.redirect(req.href('admin/users/manage'))

        # Handle save
        if 'limitexceeded' in req.args:
            add_warning(req, _('Picture you tried to upload was too big. Try a smaller one'))

        # Update author if changed
        author_id = req.args.get('author_id', None)

        # If id field is empty but name is not: manual input
        if not author_id and req.args.get('author_text'):
            add_warning(req, _('Author cannot be found'))
            return 'admin_user_edit.html', data

        # Check set reset the author
        if author_id:
            author = userstore.getUserWhereId(int(author_id))
            if not author:
                add_warning(req, _('Author cannot be found'))
                return 'admin_user_edit.html', data

            # Check if author is valid: has permission to author?
            perm = PermissionCache(self.env, author.username)
            if 'USER_AUTHOR' not in perm:
                add_warning(req, _('User %s cannot work as an author (does not have USER_AUTHOR permissions)' % author))
                return self.back(req)

            user.author_id = author.id
            changes['author'] = author
        else:
            user.author_id = None

        user.lastName = req.args.get('last')
        if not user.lastName:
            add_warning(req, _('Last name required'))
            return self.back(req)

        old_mail = user.mail
        user.mail = req.args.get('email')
        if not user.mail:
            add_warning(req, _('Email address required'))
            return self.back(req)

        if old_mail != user.mail:
            changes['email'] = user.mail
            org_store = CQDEOrganizationStore.instance()
            # TODO: is this correct?
            # When changing email, reset organizations to which the user belongs in
            user.organization_keys = org_store.get_organization_keys(user) or None

        # Update password if changed
        password = req.args.get('password')
        if password:
            if not userstore.is_local(user):
                add_warning(req, _("Can't change password for user that uses external authentication method"))
                return self.back(req)

            if len(password) < 7:
                add_warning(req, _("Password must be at least 7 characters long - please provide longer password"))
                return self.back(req)

            if password != req.args.get('confirmpw'):
                add_warning(req, _("Password do not match - please check"))
                return self.back(req)

        user.givenName = req.args.get('first')
        user.mobile = req.args.get('mobile')

        # Set or reset account expiration date
        expiration_str = req.args.get('expires', '')
        if expiration_str:
            try:
                # Parse date and set expiration time in the end of the day
                expires = datetime.strptime(expiration_str, DATEFORMATS['py'])
                expires += timedelta(hours=23, minutes=59, seconds=59)

                # If changed
                if expires != user.expires:
                    user.expires = expires
                    changes['expiration_date'] = user.expires.strftime(DATEFORMATS['py'])

            except Exception:
                self.log.exception('Date formatting failed')
                add_warning(req, _('Non-recognized expiration format'))
                pass

        # Remove expiration date
        elif user.expires:
            changes['expiration_date'] = 'Never expires'
            user.expires = None

        # Update status if set
        status = int(req.args.get('status', 0))
        if status and status in userstore.USER_STATUS_LABELS.keys() and user.status != status:
            changes['status'] = userstore.USER_STATUS_LABELS[status]
            user.status = status

        if req.args.get('removeicon'):
            user.icon = None
        else:
            icon = req.args.get('icon')
            if not isinstance(icon, unicode) and icon.filename:
                user.createIcon(req.args.get('icon'))

        self.log.info('Saving changes to user: %s' % user)
        ok = userstore.updateUser(user)
        if ok and password:
            changes['password'] = password
            ok = userstore.updatePassword(user, password)

        if not ok:
            add_warning(req, _("Could not save changes"))

        add_notice(req, _("User %s updated" % username))

        # Notify user about changes via email?
        if req.args.get('notify'):
            data = {
                'user':user,
                'changed_by':changed_by,
                'changes':changes
            }

            try:
                enotify = EmailNotifier(self.env, "Account updated", data)
                enotify.template_name = 'account_edited.txt'
                enotify.notify(user.mail)
                add_notice(req, _("Notified user about the changes"))
            except TracError:
                add_warning(req, _("Failed to send email notification - user changed anyway"))

        # Check if user has still (after modification) permission to modify user
        # NOTE: req.perm cannot be used here because it is not updated yet
        resource = Resource('user', id=user.id)
        perm = PermissionCache(self.env, username=req.authname)
        if perm.has_permission('USER_AUTHOR', resource):
            return self.back(req)

        add_notice(req, _('You have no longer permission to modify the account: %s' % user.username))
        return req.redirect(req.href('admin/users/manage'))
コード例 #29
0
 def get_navigation_items(self, req):
     if req.authname and req.authname != 'anonymous':
         yield ('metanav', 'notifications', tag.a(_('Notifications'), **{'class': 'notifications', 'href': '#'}))
コード例 #30
0
 def get_navigation_items(self, req):
     yield ('metanav', 'site_admin', tag.a(_('Site admin'), **{'class': 'site_admin', 'href': '#'}))
コード例 #31
0
ファイル: actions.py プロジェクト: juhamust/multiproject
    def create_project(self, req):
        """ Handler for creating project request
        """
        req.perm.require("PROJECT_CREATE")
        if req.method != 'POST':
            return self.create_failure(req, 'POST request needed when creating a new project')

        author = get_context(req)['author']

        # If agreement needed but not getting it, show failure
        if conf.project_requires_agreed_terms and not self._is_active_user(req):
            return self.create_failure(req, 'You need to approve legal text to create a project!')

        # Read and transform some variables
        vcs_type = req.args.get('vcstype')
        parent_project = None
        if "_project_" in req.args:
            parent_project = Project.get(env_name=req.args.get('_project_'))
            self.__require_permissions_for_cloning(req.authname, parent_project)
            vcs_type = conf.getVersionControlType(parent_project.env_name) # TODO: expensive call, probably needed

        # Read settings
        settings = {}
        if vcs_type:
            settings['vcs_type'] = vcs_type

        identifier = req.args.get('prj_short_name')
        name = req.args.get('prj_long_name')
        public = 'prj_is_public' in req.args

        published = None
        if public:
            published = datetime.now()

        # Create project object
        project = Project(
            id = None,
            env_name = identifier,
            project_name = name,
            description = req.args.get('prj_description'),
            author_id = author.id,
            created = None, # Use default which is now()
            published = published
        )

        # Create project environment
        projects = Projects()
        try:
            projects.create_project(project, settings)
        except ProjectValidationException as exc:
            self.log.warning('Project creation failed due the validation: {0}'.format(exc.value))
            return self.create_failure(req, exc.value)
        except:
            self.log.exception('Project creation failed')
            return self.create_failure(req, _("Creating project failed. Try again later."))

        if public:
            projects.add_public_project_visibility(project.id)

        # Notify listeners. The project object still exists, but database does not
        for listener in self.project_change_listeners:
            listener.project_created(project)
            if public:
                listener.project_set_public(project)

        return self.create_success(req, project)