diff --git a/groups/config/routes.rb b/groups/config/routes.rb
index 0edb71a06..bc4247837 100644
--- a/groups/config/routes.rb
+++ b/groups/config/routes.rb
@@ -1,4 +1,6 @@
ActionController::Routing::Routes.draw do |map|
+ map.resources :groups
+
# Add your own custom routes here.
# The priority is based upon order of creation: first created -> highest priority.
diff --git a/groups/db/migrate/093_create_groups.rb b/groups/db/migrate/093_create_groups.rb
new file mode 100644
index 000000000..ca21ba118
--- /dev/null
+++ b/groups/db/migrate/093_create_groups.rb
@@ -0,0 +1,11 @@
+class CreateGroups < ActiveRecord::Migration
+ def self.up
+ create_table :groups do |t|
+ t.column :name, :string, :null => false
+ end
+ end
+
+ def self.down
+ drop_table :groups
+ end
+end
diff --git a/groups/db/migrate/094_add_users_group_id.rb b/groups/db/migrate/094_add_users_group_id.rb
new file mode 100644
index 000000000..12578c683
--- /dev/null
+++ b/groups/db/migrate/094_add_users_group_id.rb
@@ -0,0 +1,9 @@
+class AddUsersGroupId < ActiveRecord::Migration
+ def self.up
+ add_column :users, :group_id, :integer
+ end
+
+ def self.down
+ remove_column :users, :group_id
+ end
+end
diff --git a/groups/db/migrate/095_change_members_users_association_to_polymorphic.rb b/groups/db/migrate/095_change_members_users_association_to_polymorphic.rb
new file mode 100644
index 000000000..a16a19c12
--- /dev/null
+++ b/groups/db/migrate/095_change_members_users_association_to_polymorphic.rb
@@ -0,0 +1,20 @@
+class ChangeMembersUsersAssociationToPolymorphic < ActiveRecord::Migration
+ def self.up
+ add_column :members, :principal_type, :string
+ add_column :members, :principal_id, :integer
+ add_column :members, :inherited_from, :integer
+ Member.update_all "principal_type = 'User', principal_id = user_id"
+ remove_column :members, :user_id
+ add_index :members, [:principal_type, :principal_id], :name => :members_principal
+ end
+
+ def self.down
+ # Remove inherited memberships
+ Member.delete_all "inherited_from IS NOT NULL"
+ add_column :members, :user_id, :integer, :default => 0, :null => false
+ Member.update_all "user_id = principal_id"
+ remove_column :members, :principal_type, :string
+ remove_column :members, :principal_id, :integer
+ remove_column :members, :inherited_from, :integer
+ end
+end
diff --git a/groups/lang/en.yml b/groups/lang/en.yml
index e39aec301..320501c2b 100644
--- a/groups/lang/en.yml
+++ b/groups/lang/en.yml
@@ -179,6 +179,7 @@ field_time_zone: Time zone
field_searchable: Searchable
field_default_value: Default value
field_comments_sorting: Display comments
+field_group: Group
setting_app_title: Application title
setting_app_subtitle: Application subtitle
@@ -510,6 +511,9 @@ label_preferences: Preferences
label_chronological_order: In chronological order
label_reverse_chronological_order: In reverse chronological order
label_planning: Planning
+label_group: Group
+label_group_plural: Groups
+label_group_new: New group
button_login: Login
button_submit: Submit
diff --git a/groups/public/images/22x22/user.png b/groups/public/images/22x22/user.png
new file mode 100644
index 0000000000000000000000000000000000000000..77f0f9c1977fff47a2f4e5a58cd006a7a3f98366
GIT binary patch
literal 781
zcmV+o1M>WdP)WdL(wZ7v`&G9Y1gaxNe;FfceEF)}(cF*-6hAS*C2FfedI9+Ln700(qQ
zO+^RT1Plr#27&0^o&W#<32;bRa{vGf5&!@T5&_cPe*6Fc00d`2O+f$vv5yPtm^~mBm
z*jwBJA&Yx&R~|HLMVjF#+XO+7#gD1y_itciM3NVXLCSbU?$t_ZJxwvJ+7fhC1}g6h
z_;>4#*N>M8l8PkGTOe9{)%=@hio|1y2gnEooeO7{Y&4xtz5kHO{Z=m;rGl`tA-80P
zp<*XyW3e#J*Z{zZL^R-YzI%Afx(cmXPAr5JMe}-{!Jroc#W4TH93Ft-c|SH3>ne~g
z2c{|Hex?hjQ^X_P!
zZzH9NRROC_t<}ud^hPi^Djq&&m3H|>`H2X&{iKJ?ta0n9NuBRNO7P5Sr;=M_-RC~J
zG<_++O^-)>YHmE*yvwob!O2+&NrbU3vm@S%2Gf8TLA2H4|S21&Z?a;676n)$MUY2ob=hetbLs!iay&|38RtKC?DWD;W}900000
LNkvXXu0mjf=3h { :name => 'New group' }
+ end
+ assert_redirected_to groups_path
+ assert_not_nil Group.find_by_name('New group')
+ end
+
+ def test_should_show_group
+ get :show, :id => 1
+ assert_response :success
+ end
+
+ def test_should_get_edit
+ get :edit, :id => 1
+ assert_response :success
+ end
+
+ def test_should_update_group
+ put :update, :id => 1, :group => { :name => 'Renamed' }
+ assert_redirected_to groups_path
+ assert_equal 'Renamed', Group.find(1).name
+ end
+
+ def test_should_destroy_group
+ assert_difference('Group.count', -1) do
+ delete :destroy, :id => 1
+ end
+ assert_redirected_to groups_path
+ end
+end
diff --git a/groups/test/functional/members_controller_test.rb b/groups/test/functional/members_controller_test.rb
new file mode 100644
index 000000000..851bda299
--- /dev/null
+++ b/groups/test/functional/members_controller_test.rb
@@ -0,0 +1,80 @@
+# Redmine - project management software
+# Copyright (C) 2008 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.
+
+require File.dirname(__FILE__) + '/../test_helper'
+require 'members_controller'
+
+# Re-raise errors caught by the controller.
+class MembersController; def rescue_action(e) raise e end; end
+
+class MembersControllerTest < Test::Unit::TestCase
+ fixtures :projects, :roles, :users, :groups, :members
+
+ def setup
+ @controller = MembersController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ User.current = nil
+ @request.session[:user_id] = 1 # admin
+ end
+
+ def test_should_create_user_member
+ p = Project.find(1)
+ u = users(:new_client)
+ assert_difference('Member.count') do
+ post :new, :id => p.id, :member => { :role_id => roles(:reporter).id }, :principal => "user_#{u.id}"
+ end
+ assert_redirected_to :controller => 'projects', :action => 'settings', :id => p, :tab => 'members'
+ assert u.reload.member_of?(p)
+ assert_equal roles(:reporter), u.role_for_project(p)
+ end
+
+ def test_should_create_group_member
+ p = Project.find(2)
+ assert_difference('Member.count') do
+ post :new, :id => p.id, :member => { :role_id => roles(:reporter) }, :principal => 'group_1'
+ end
+ assert_redirected_to :controller => 'projects', :action => 'settings', :id => p, :tab => 'members'
+ end
+
+ def test_should_update_user_member
+ u = User.find(3)
+ p = Project.find(1)
+ assert_equal roles(:developer), u.role_for_project(p)
+ assert_difference('Member.count', 0) do
+ post :edit, :id => 2, :member => { :role_id => roles(:manager).id }
+ end
+ assert_redirected_to :controller => 'projects', :action => 'settings', :id => p, :tab => 'members'
+ assert_equal roles(:manager), u.reload.role_for_project(p)
+ end
+
+ def test_should_destroy
+ p = Project.find(1)
+ assert_difference('Member.count', -1) do
+ post :destroy, :id => 2
+ end
+ assert_redirected_to :controller => 'projects', :action => 'settings', :id => p, :tab => 'members'
+ end
+
+ def test_should_not_destroy_inherited_membership
+ p = Project.find(1)
+ assert_difference('Member.count', 0) do
+ post :destroy, :id => 6
+ end
+ assert_response 404
+ end
+end
diff --git a/groups/test/functional/users_controller_test.rb b/groups/test/functional/users_controller_test.rb
index 8629a7131..84c10eb7d 100644
--- a/groups/test/functional/users_controller_test.rb
+++ b/groups/test/functional/users_controller_test.rb
@@ -47,6 +47,14 @@ class UsersControllerTest < Test::Unit::TestCase
assert_nil assigns(:users).detect {|u| !u.active?}
end
+ def test_should_add_membership
+ assert_difference('User.find(2).memberships.count') do
+ post :edit_membership, :id => 2, :membership => { :role_id => 1, :project_id => 3 }
+ assert_redirected_to 'users/edit/2'
+ assert User.find(2).member_of?(Project.find(3))
+ end
+ end
+
def test_edit_membership
post :edit_membership, :id => 2, :membership_id => 1,
:membership => { :role_id => 2}
diff --git a/groups/test/unit/group_test.rb b/groups/test/unit/group_test.rb
new file mode 100644
index 000000000..d93d2b61d
--- /dev/null
+++ b/groups/test/unit/group_test.rb
@@ -0,0 +1,129 @@
+# redMine - project management software
+# Copyright (C) 2008 FreeCode
+#
+# 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.
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class GroupTest < ActiveSupport::TestCase
+ fixtures :groups, :users, :projects, :roles, :members
+
+ def test_should_validate_presence_of_name
+ g = Group.new(:name => '')
+ assert !g.save
+ assert_equal 1, g.errors.size
+ end
+
+ def test_should_validate_uniqueness_of_name
+ g = Group.new(:name => groups(:clients).name)
+ assert !g.save
+ assert_equal 1, g.errors.size
+ end
+
+ def test_should_create
+ g = Group.new(:name => 'New group')
+ assert g.save
+ assert g.users.empty?
+ end
+
+ def test_should_destroy
+ g = groups(:clients)
+ p = Project.find(1)
+ u = users(:client)
+ assert u.member_of?(p)
+
+ assert_difference('Member.count', -2) do
+ g.destroy
+ end
+ u.reload
+ assert_nil u.group
+ assert !u.member_of?(p)
+ end
+
+ def test_should_add_user_to_group
+ g = groups(:clients)
+ p = Project.find(1)
+ u = users(:new_client)
+ r = roles(:reporter)
+ assert !u.member_of?(p)
+
+ assert_difference('Member.count') do
+ assert_difference('g.reload.users.size') do
+ u.group_id = g.id
+ assert u.save
+ end
+ end
+ u.reload
+ assert u.group = g
+ assert u.member_of?(p)
+ assert_equal r, u.role_for_project(p)
+ end
+
+ def test_should_add_group_to_project
+ g = groups(:clients)
+ p = Project.find(2)
+ u = users(:client)
+ r = roles(:reporter)
+ assert !u.member_of?(p)
+
+ assert_difference('Member.count', 2) do
+ assert_difference('p.reload.users.size') do
+ m = Member.new(:project => p, :principal => g, :role => r)
+ assert m.save
+ end
+ end
+ u.reload
+ assert u.member_of?(p)
+ assert_equal r, u.role_for_project(p)
+ end
+
+ def test_should_remove_user_from_group
+ g = groups(:clients)
+ p = Project.find(1)
+ u = users(:client)
+ assert u.member_of?(p)
+
+ assert_difference('Member.count', -1) do
+ assert_difference('g.reload.users.size', -1) do
+ u.group_id = nil
+ assert u.save
+ end
+ end
+ end
+
+ def test_should_override_group_role
+ g = groups(:clients)
+ p = Project.find(1)
+ u = users(:client)
+ assert u.member_of?(p)
+ assert_equal roles(:reporter), u.role_for_project(p)
+
+ assert_difference('Member.count', 1) do
+ assert_difference('p.reload.users.size', 0) do
+ m = Member.new(:project => p, :principal => u, :role => roles(:manager))
+ assert m.save
+ end
+ end
+ assert_equal roles(:manager), u.reload.role_for_project(p)
+
+ # Remove the group, user should still be a member
+ assert_difference('Member.count', -2) do
+ assert_difference('p.reload.users.size', 0) do
+ assert g.destroy
+ end
+ end
+ assert_equal roles(:manager), u.reload.role_for_project(p)
+ end
+end
diff --git a/groups/test/unit/member_test.rb b/groups/test/unit/member_test.rb
index 079782306..62b1776ec 100644
--- a/groups/test/unit/member_test.rb
+++ b/groups/test/unit/member_test.rb
@@ -25,7 +25,7 @@ class MemberTest < Test::Unit::TestCase
end
def test_create
- member = Member.new(:project_id => 1, :user_id => 4, :role_id => 1)
+ member = Member.new(:project_id => 1, :principal_type => 'User', :principal_id => 4, :role_id => 1)
assert member.save
end
@@ -39,7 +39,7 @@ class MemberTest < Test::Unit::TestCase
end
def test_validate
- member = Member.new(:project_id => 1, :user_id => 2, :role_id =>2)
+ member = Member.new(:project_id => 1, :principal_type => 'User', :principal_id => 2, :role_id =>2)
# same use can't have more than one role for a project
assert !member.save
end
diff --git a/groups/test/unit/project_test.rb b/groups/test/unit/project_test.rb
index 9af68c231..60106bc07 100644
--- a/groups/test/unit/project_test.rb
+++ b/groups/test/unit/project_test.rb
@@ -80,10 +80,10 @@ class ProjectTest < Test::Unit::TestCase
end
def test_destroy
- # 2 active members
- assert_equal 2, @ecookbook.members.size
+ # 3 active members
+ assert_equal 3, @ecookbook.members.size
# and 1 is locked
- assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
+ assert_equal 5, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
# some boards
assert @ecookbook.boards.any?