Allows multiple roles on the same project (#706). Prerequisite for user groups feature.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2726 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang
2009-05-10 10:54:31 +00:00
parent 814e138c2a
commit 7dccf9fda6
65 changed files with 340 additions and 175 deletions
+1 -1
View File
@@ -205,7 +205,7 @@ class Issue < ActiveRecord::Base
# Returns an array of status that user is able to apply
def new_statuses_allowed_to(user)
statuses = status.find_new_statuses_allowed_to(user.role_for_project(project), tracker)
statuses = status.find_new_statuses_allowed_to(user.roles_for_project(project), tracker)
statuses << status unless statuses.empty?
statuses.uniq.sort
end
+21 -11
View File
@@ -36,24 +36,34 @@ class IssueStatus < ActiveRecord::Base
# Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time
def new_statuses_allowed_to(role, tracker)
new_statuses = workflows.select {|w| w.role_id == role.id && w.tracker_id == tracker.id}.collect{|w| w.new_status} if role && tracker
new_statuses ? new_statuses.compact.sort{|x, y| x.position <=> y.position } : []
def new_statuses_allowed_to(roles, tracker)
if roles && tracker
role_ids = roles.collect(&:id)
new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
else
[]
end
end
# Same thing as above but uses a database query
# More efficient than the previous method if called just once
def find_new_statuses_allowed_to(role, tracker)
new_statuses = workflows.find(:all,
:include => :new_status,
:conditions => ["role_id=? and tracker_id=?", role.id, tracker.id]).collect{ |w| w.new_status }.compact if role && tracker
new_statuses ? new_statuses.sort{|x, y| x.position <=> y.position } : []
def find_new_statuses_allowed_to(roles, tracker)
if roles && tracker
workflows.find(:all,
:include => :new_status,
:conditions => { :role_id => roles.collect(&:id),
:tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
else
[]
end
end
def new_status_allowed_to?(status, role, tracker)
status && role && tracker ?
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => role.id, :tracker_id => tracker.id}).nil? :
def new_status_allowed_to?(status, roles, tracker)
if status && roles && tracker
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
else
false
end
end
def <=>(status)
+13 -9
View File
@@ -1,5 +1,5 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
# Redmine - project management software
# Copyright (C) 2006-2009 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -17,15 +17,12 @@
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :role
has_many :member_roles, :dependent => :delete_all
has_many :roles, :through => :member_roles
belongs_to :project
validates_presence_of :role, :user, :project
validates_presence_of :user, :project
validates_uniqueness_of :user_id, :scope => :project_id
def validate
errors.add :role_id, :invalid if role && !role.member?
end
def name
self.user.name
@@ -42,11 +39,18 @@ class Member < ActiveRecord::Base
end
def <=>(member)
role == member.role ? (user <=> member.user) : (role <=> member.role)
a, b = roles.sort.first, member.roles.sort.first
a == b ? (user <=> member.user) : (a <=> b)
end
def before_destroy
# remove category based auto assignments for this member
IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
end
protected
def validate
errors.add_to_base "Role can't be blank" if roles.empty?
end
end
+27
View File
@@ -0,0 +1,27 @@
# Redmine - project management software
# Copyright (C) 2006-2009 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class MemberRole < ActiveRecord::Base
belongs_to :member
belongs_to :role
validates_presence_of :role
def validate
errors.add :role_id, :invalid if role && !role.member?
end
end
+4 -2
View File
@@ -135,7 +135,7 @@ class Project < ActiveRecord::Base
statements << "1=0"
if user.logged?
statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
elsif Role.anonymous.allowed_to?(permission)
# anonymous user allowed on public project
@@ -247,12 +247,14 @@ class Project < ActiveRecord::Base
# Deletes all project's members
def delete_all_members
me, mr = Member.table_name, MemberRole.table_name
connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
Member.delete_all(['project_id = ?', id])
end
# Users issues can be assigned to
def assignable_users
members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
end
# Returns the mail adresses of users that should be always notified on project events
+7 -2
View File
@@ -38,7 +38,8 @@ class Role < ActiveRecord::Base
end
end
has_many :members
has_many :member_roles, :dependent => :destroy
has_many :members, :through => :member_roles
acts_as_list
serialize :permissions, Array
@@ -82,7 +83,11 @@ class Role < ActiveRecord::Base
end
def <=>(role)
position <=> role.position
role ? position <=> role.position : -1
end
def to_s
name
end
# Return true if the role is a builtin role
+14 -10
View File
@@ -33,7 +33,7 @@ class User < ActiveRecord::Base
:username => '#{login}'
}
has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
has_many :memberships, :class_name => 'Member', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
has_many :members, :dependent => :delete_all
has_many :projects, :through => :memberships
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
@@ -229,26 +229,30 @@ class User < ActiveRecord::Base
!logged?
end
# Return user's role for project
def role_for_project(project)
# Return user's roles for project
def roles_for_project(project)
roles = []
# No role on archived projects
return nil unless project && project.active?
return roles unless project && project.active?
if logged?
# Find project membership
membership = memberships.detect {|m| m.project_id == project.id}
if membership
membership.role
roles = membership.roles
else
@role_non_member ||= Role.non_member
roles << @role_non_member
end
else
@role_anonymous ||= Role.anonymous
roles << @role_anonymous
end
roles
end
# Return true if the user is a member of project
def member_of?(project)
role_for_project(project).member?
!roles_for_project(project).detect {|role| role.member?}.nil?
end
# Return true if the user is allowed to do the specified action on project
@@ -264,13 +268,13 @@ class User < ActiveRecord::Base
# Admin users are authorized for anything else
return true if admin?
role = role_for_project(project)
return false unless role
role.allowed_to?(action) && (project.is_public? || role.member?)
roles = roles_for_project(project)
return false unless roles
roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
elsif options[:global]
# authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.role}.uniq
roles = memberships.collect {|m| m.roles}.flatten.uniq
roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
else
false