##// END OF EJS Templates
Merged r8863 from trunk....
Jean-Philippe Lang -
r8992:92dde0d75f33
parent child
Show More
@@ -1,96 +1,106
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Member < ActiveRecord::Base
18 class Member < ActiveRecord::Base
19 belongs_to :user
19 belongs_to :user
20 belongs_to :principal, :foreign_key => 'user_id'
20 belongs_to :principal, :foreign_key => 'user_id'
21 has_many :member_roles, :dependent => :destroy
21 has_many :member_roles, :dependent => :destroy
22 has_many :roles, :through => :member_roles
22 has_many :roles, :through => :member_roles
23 belongs_to :project
23 belongs_to :project
24
24
25 validates_presence_of :principal, :project
25 validates_presence_of :principal, :project
26 validates_uniqueness_of :user_id, :scope => :project_id
26 validates_uniqueness_of :user_id, :scope => :project_id
27
27
28 after_destroy :unwatch_from_permission_change
28 after_destroy :unwatch_from_permission_change
29
29
30 def name
30 def name
31 self.user.name
31 self.user.name
32 end
32 end
33
33
34 alias :base_role_ids= :role_ids=
34 alias :base_role_ids= :role_ids=
35 def role_ids=(arg)
35 def role_ids=(arg)
36 ids = (arg || []).collect(&:to_i) - [0]
36 ids = (arg || []).collect(&:to_i) - [0]
37 # Keep inherited roles
37 # Keep inherited roles
38 ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
38 ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
39
39
40 new_role_ids = ids - role_ids
40 new_role_ids = ids - role_ids
41 # Add new roles
41 # Add new roles
42 new_role_ids.each {|id| member_roles << MemberRole.new(:role_id => id) }
42 new_role_ids.each {|id| member_roles << MemberRole.new(:role_id => id) }
43 # Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
43 # Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
44 member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)}
44 member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)}
45 if member_roles_to_destroy.any?
45 if member_roles_to_destroy.any?
46 member_roles_to_destroy.each(&:destroy)
46 member_roles_to_destroy.each(&:destroy)
47 unwatch_from_permission_change
47 unwatch_from_permission_change
48 end
48 end
49 end
49 end
50
50
51 def <=>(member)
51 def <=>(member)
52 a, b = roles.sort.first, member.roles.sort.first
52 a, b = roles.sort.first, member.roles.sort.first
53 a == b ? (principal <=> member.principal) : (a <=> b)
53 if a == b
54 if principal
55 principal <=> member.principal
56 else
57 1
58 end
59 elsif a
60 a <=> b
61 else
62 1
63 end
54 end
64 end
55
65
56 def deletable?
66 def deletable?
57 member_roles.detect {|mr| mr.inherited_from}.nil?
67 member_roles.detect {|mr| mr.inherited_from}.nil?
58 end
68 end
59
69
60 def include?(user)
70 def include?(user)
61 if principal.is_a?(Group)
71 if principal.is_a?(Group)
62 !user.nil? && user.groups.include?(principal)
72 !user.nil? && user.groups.include?(principal)
63 else
73 else
64 self.user == user
74 self.user == user
65 end
75 end
66 end
76 end
67
77
68 def before_destroy
78 def before_destroy
69 if user
79 if user
70 # remove category based auto assignments for this member
80 # remove category based auto assignments for this member
71 IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
81 IssueCategory.update_all "assigned_to_id = NULL", ["project_id = ? AND assigned_to_id = ?", project.id, user.id]
72 end
82 end
73 end
83 end
74
84
75 # Find or initilize a Member with an id, attributes, and for a Principal
85 # Find or initilize a Member with an id, attributes, and for a Principal
76 def self.edit_membership(id, new_attributes, principal=nil)
86 def self.edit_membership(id, new_attributes, principal=nil)
77 @membership = id.present? ? Member.find(id) : Member.new(:principal => principal)
87 @membership = id.present? ? Member.find(id) : Member.new(:principal => principal)
78 @membership.attributes = new_attributes
88 @membership.attributes = new_attributes
79 @membership
89 @membership
80 end
90 end
81
91
82 protected
92 protected
83
93
84 def validate
94 def validate
85 errors.add_on_empty :role if member_roles.empty? && roles.empty?
95 errors.add_on_empty :role if member_roles.empty? && roles.empty?
86 end
96 end
87
97
88 private
98 private
89
99
90 # Unwatch things that the user is no longer allowed to view inside project
100 # Unwatch things that the user is no longer allowed to view inside project
91 def unwatch_from_permission_change
101 def unwatch_from_permission_change
92 if user
102 if user
93 Watcher.prune(:user => user, :project => project)
103 Watcher.prune(:user => user, :project => project)
94 end
104 end
95 end
105 end
96 end
106 end
@@ -1,62 +1,64
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Principal < ActiveRecord::Base
18 class Principal < ActiveRecord::Base
19 set_table_name "#{table_name_prefix}users#{table_name_suffix}"
19 set_table_name "#{table_name_prefix}users#{table_name_suffix}"
20
20
21 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
21 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
22 has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
22 has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
23 has_many :projects, :through => :memberships
23 has_many :projects, :through => :memberships
24 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
24 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
25
25
26 # Groups and active users
26 # Groups and active users
27 named_scope :active, :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status = 1)"
27 named_scope :active, :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status = 1)"
28
28
29 named_scope :like, lambda {|q|
29 named_scope :like, lambda {|q|
30 s = "%#{q.to_s.strip.downcase}%"
30 s = "%#{q.to_s.strip.downcase}%"
31 {:conditions => ["LOWER(login) LIKE :s OR LOWER(firstname) LIKE :s OR LOWER(lastname) LIKE :s OR LOWER(mail) LIKE :s", {:s => s}],
31 {:conditions => ["LOWER(login) LIKE :s OR LOWER(firstname) LIKE :s OR LOWER(lastname) LIKE :s OR LOWER(mail) LIKE :s", {:s => s}],
32 :order => 'type, login, lastname, firstname, mail'
32 :order => 'type, login, lastname, firstname, mail'
33 }
33 }
34 }
34 }
35
35
36 before_create :set_default_empty_values
36 before_create :set_default_empty_values
37
37
38 def name(formatter = nil)
38 def name(formatter = nil)
39 to_s
39 to_s
40 end
40 end
41
41
42 def <=>(principal)
42 def <=>(principal)
43 if self.class.name == principal.class.name
43 if principal.nil?
44 -1
45 elsif self.class.name == principal.class.name
44 self.to_s.downcase <=> principal.to_s.downcase
46 self.to_s.downcase <=> principal.to_s.downcase
45 else
47 else
46 # groups after users
48 # groups after users
47 principal.class.name <=> self.class.name
49 principal.class.name <=> self.class.name
48 end
50 end
49 end
51 end
50
52
51 protected
53 protected
52
54
53 # Make sure we don't try to insert NULL values (see #4632)
55 # Make sure we don't try to insert NULL values (see #4632)
54 def set_default_empty_values
56 def set_default_empty_values
55 self.login ||= ''
57 self.login ||= ''
56 self.hashed_password ||= ''
58 self.hashed_password ||= ''
57 self.firstname ||= ''
59 self.firstname ||= ''
58 self.lastname ||= ''
60 self.lastname ||= ''
59 self.mail ||= ''
61 self.mail ||= ''
60 true
62 true
61 end
63 end
62 end
64 end
@@ -1,150 +1,167
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class MemberTest < ActiveSupport::TestCase
20 class MemberTest < ActiveSupport::TestCase
21 fixtures :projects, :trackers, :issue_statuses, :issues,
21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 :enumerations, :users, :issue_categories,
22 :enumerations, :users, :issue_categories,
23 :projects_trackers,
23 :projects_trackers,
24 :roles,
24 :roles,
25 :member_roles,
25 :member_roles,
26 :members,
26 :members,
27 :enabled_modules,
27 :enabled_modules,
28 :workflows,
28 :workflows,
29 :groups_users,
29 :groups_users,
30 :watchers,
30 :watchers,
31 :journals, :journal_details,
31 :journals, :journal_details,
32 :messages,
32 :messages,
33 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
33 :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions,
34 :boards
34 :boards
35
35
36 def setup
36 def setup
37 @jsmith = Member.find(1)
37 @jsmith = Member.find(1)
38 end
38 end
39
39
40 def test_create
40 def test_create
41 member = Member.new(:project_id => 1, :user_id => 4, :role_ids => [1, 2])
41 member = Member.new(:project_id => 1, :user_id => 4, :role_ids => [1, 2])
42 assert member.save
42 assert member.save
43 member.reload
43 member.reload
44
44
45 assert_equal 2, member.roles.size
45 assert_equal 2, member.roles.size
46 assert_equal Role.find(1), member.roles.sort.first
46 assert_equal Role.find(1), member.roles.sort.first
47 end
47 end
48
48
49 def test_update
49 def test_update
50 assert_equal "eCookbook", @jsmith.project.name
50 assert_equal "eCookbook", @jsmith.project.name
51 assert_equal "Manager", @jsmith.roles.first.name
51 assert_equal "Manager", @jsmith.roles.first.name
52 assert_equal "jsmith", @jsmith.user.login
52 assert_equal "jsmith", @jsmith.user.login
53
53
54 @jsmith.mail_notification = !@jsmith.mail_notification
54 @jsmith.mail_notification = !@jsmith.mail_notification
55 assert @jsmith.save
55 assert @jsmith.save
56 end
56 end
57
57
58 def test_update_roles
58 def test_update_roles
59 assert_equal 1, @jsmith.roles.size
59 assert_equal 1, @jsmith.roles.size
60 @jsmith.role_ids = [1, 2]
60 @jsmith.role_ids = [1, 2]
61 assert @jsmith.save
61 assert @jsmith.save
62 assert_equal 2, @jsmith.reload.roles.size
62 assert_equal 2, @jsmith.reload.roles.size
63 end
63 end
64
64
65 def test_validate
65 def test_validate
66 member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [2])
66 member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [2])
67 # same use can't have more than one membership for a project
67 # same use can't have more than one membership for a project
68 assert !member.save
68 assert !member.save
69
69
70 member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [])
70 member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [])
71 # must have one role at least
71 # must have one role at least
72 assert !member.save
72 assert !member.save
73 end
73 end
74
74
75 def test_destroy
75 def test_destroy
76 assert_difference 'Member.count', -1 do
76 assert_difference 'Member.count', -1 do
77 assert_difference 'MemberRole.count', -1 do
77 assert_difference 'MemberRole.count', -1 do
78 @jsmith.destroy
78 @jsmith.destroy
79 end
79 end
80 end
80 end
81
81
82 assert_raise(ActiveRecord::RecordNotFound) { Member.find(@jsmith.id) }
82 assert_raise(ActiveRecord::RecordNotFound) { Member.find(@jsmith.id) }
83 end
83 end
84
84
85 def test_sort_without_roles
86 a = Member.new(:roles => [Role.first])
87 b = Member.new
88
89 assert_equal -1, a <=> b
90 assert_equal 1, b <=> a
91 end
92
93 def test_sort_without_principal
94 role = Role.first
95 a = Member.new(:roles => [role], :principal => User.first)
96 b = Member.new(:roles => [role])
97
98 assert_equal -1, a <=> b
99 assert_equal 1, b <=> a
100 end
101
85 context "removing permissions" do
102 context "removing permissions" do
86 setup do
103 setup do
87 Watcher.delete_all("user_id = 9")
104 Watcher.delete_all("user_id = 9")
88 user = User.find(9)
105 user = User.find(9)
89 # public
106 # public
90 Watcher.create!(:watchable => Issue.find(1), :user => user)
107 Watcher.create!(:watchable => Issue.find(1), :user => user)
91 # private
108 # private
92 Watcher.create!(:watchable => Issue.find(4), :user => user)
109 Watcher.create!(:watchable => Issue.find(4), :user => user)
93 Watcher.create!(:watchable => Message.find(7), :user => user)
110 Watcher.create!(:watchable => Message.find(7), :user => user)
94 Watcher.create!(:watchable => Wiki.find(2), :user => user)
111 Watcher.create!(:watchable => Wiki.find(2), :user => user)
95 Watcher.create!(:watchable => WikiPage.find(3), :user => user)
112 Watcher.create!(:watchable => WikiPage.find(3), :user => user)
96 end
113 end
97
114
98 context "of user" do
115 context "of user" do
99 setup do
116 setup do
100 @member = Member.create!(:project => Project.find(2), :principal => User.find(9), :role_ids => [1, 2])
117 @member = Member.create!(:project => Project.find(2), :principal => User.find(9), :role_ids => [1, 2])
101 end
118 end
102
119
103 context "by deleting membership" do
120 context "by deleting membership" do
104 should "prune watchers" do
121 should "prune watchers" do
105 assert_difference 'Watcher.count', -4 do
122 assert_difference 'Watcher.count', -4 do
106 @member.destroy
123 @member.destroy
107 end
124 end
108 end
125 end
109 end
126 end
110
127
111 context "by updating roles" do
128 context "by updating roles" do
112 should "prune watchers" do
129 should "prune watchers" do
113 Role.find(2).remove_permission! :view_wiki_pages
130 Role.find(2).remove_permission! :view_wiki_pages
114 member = Member.first(:order => 'id desc')
131 member = Member.first(:order => 'id desc')
115 assert_difference 'Watcher.count', -2 do
132 assert_difference 'Watcher.count', -2 do
116 member.role_ids = [2]
133 member.role_ids = [2]
117 member.save
134 member.save
118 end
135 end
119 assert !Message.find(7).watched_by?(@user)
136 assert !Message.find(7).watched_by?(@user)
120 end
137 end
121 end
138 end
122 end
139 end
123
140
124 context "of group" do
141 context "of group" do
125 setup do
142 setup do
126 group = Group.find(10)
143 group = Group.find(10)
127 @member = Member.create!(:project => Project.find(2), :principal => group, :role_ids => [1, 2])
144 @member = Member.create!(:project => Project.find(2), :principal => group, :role_ids => [1, 2])
128 group.users << User.find(9)
145 group.users << User.find(9)
129 end
146 end
130
147
131 context "by deleting membership" do
148 context "by deleting membership" do
132 should "prune watchers" do
149 should "prune watchers" do
133 assert_difference 'Watcher.count', -4 do
150 assert_difference 'Watcher.count', -4 do
134 @member.destroy
151 @member.destroy
135 end
152 end
136 end
153 end
137 end
154 end
138
155
139 context "by updating roles" do
156 context "by updating roles" do
140 should "prune watchers" do
157 should "prune watchers" do
141 Role.find(2).remove_permission! :view_wiki_pages
158 Role.find(2).remove_permission! :view_wiki_pages
142 assert_difference 'Watcher.count', -2 do
159 assert_difference 'Watcher.count', -2 do
143 @member.role_ids = [2]
160 @member.role_ids = [2]
144 @member.save
161 @member.save
145 end
162 end
146 end
163 end
147 end
164 end
148 end
165 end
149 end
166 end
150 end
167 end
General Comments 0
You need to be logged in to leave comments. Login now