User groups branch merged.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2869 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
847c7367b4
commit
7707457145
|
@ -0,0 +1,162 @@
|
|||
# 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 GroupsController < ApplicationController
|
||||
layout 'base'
|
||||
before_filter :require_admin
|
||||
|
||||
helper :custom_fields
|
||||
|
||||
# GET /groups
|
||||
# GET /groups.xml
|
||||
def index
|
||||
@groups = Group.find(:all, :order => 'lastname')
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.html.erb
|
||||
format.xml { render :xml => @groups }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /groups/1
|
||||
# GET /groups/1.xml
|
||||
def show
|
||||
@group = Group.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.html # show.html.erb
|
||||
format.xml { render :xml => @group }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /groups/new
|
||||
# GET /groups/new.xml
|
||||
def new
|
||||
@group = Group.new
|
||||
|
||||
respond_to do |format|
|
||||
format.html # new.html.erb
|
||||
format.xml { render :xml => @group }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /groups/1/edit
|
||||
def edit
|
||||
@group = Group.find(params[:id])
|
||||
end
|
||||
|
||||
# POST /groups
|
||||
# POST /groups.xml
|
||||
def create
|
||||
@group = Group.new(params[:group])
|
||||
|
||||
respond_to do |format|
|
||||
if @group.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
format.html { redirect_to(groups_path) }
|
||||
format.xml { render :xml => @group, :status => :created, :location => @group }
|
||||
else
|
||||
format.html { render :action => "new" }
|
||||
format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /groups/1
|
||||
# PUT /groups/1.xml
|
||||
def update
|
||||
@group = Group.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
if @group.update_attributes(params[:group])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
format.html { redirect_to(groups_path) }
|
||||
format.xml { head :ok }
|
||||
else
|
||||
format.html { render :action => "edit" }
|
||||
format.xml { render :xml => @group.errors, :status => :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /groups/1
|
||||
# DELETE /groups/1.xml
|
||||
def destroy
|
||||
@group = Group.find(params[:id])
|
||||
@group.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to(groups_url) }
|
||||
format.xml { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
def add_users
|
||||
@group = Group.find(params[:id])
|
||||
users = User.find_all_by_id(params[:user_ids])
|
||||
@group.users << users if request.post?
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
|
||||
format.js {
|
||||
render(:update) {|page|
|
||||
page.replace_html "tab-content-users", :partial => 'groups/users'
|
||||
users.each {|user| page.visual_effect(:highlight, "user-#{user.id}") }
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def remove_user
|
||||
@group = Group.find(params[:id])
|
||||
@group.users.delete(User.find(params[:user_id])) if request.post?
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'users' }
|
||||
format.js { render(:update) {|page| page.replace_html "tab-content-users", :partial => 'groups/users'} }
|
||||
end
|
||||
end
|
||||
|
||||
def autocomplete_for_user
|
||||
@group = Group.find(params[:id])
|
||||
@users = User.active.like(params[:q]).find(:all, :limit => 100) - @group.users
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
def edit_membership
|
||||
@group = Group.find(params[:id])
|
||||
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:principal => @group)
|
||||
@membership.attributes = params[:membership]
|
||||
@membership.save if request.post?
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
|
||||
format.js {
|
||||
render(:update) {|page|
|
||||
page.replace_html "tab-content-memberships", :partial => 'groups/memberships'
|
||||
page.visual_effect(:highlight, "member-#{@membership.id}")
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_membership
|
||||
@group = Group.find(params[:id])
|
||||
Member.find(params[:membership_id]).destroy if request.post?
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'groups', :action => 'edit', :id => @group, :tab => 'memberships' }
|
||||
format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'groups/memberships'} }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,8 +16,8 @@
|
|||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class MembersController < ApplicationController
|
||||
before_filter :find_member, :except => [:new, :autocomplete_for_member_login]
|
||||
before_filter :find_project, :only => [:new, :autocomplete_for_member_login]
|
||||
before_filter :find_member, :except => [:new, :autocomplete_for_member]
|
||||
before_filter :find_project, :only => [:new, :autocomplete_for_member]
|
||||
before_filter :authorize
|
||||
|
||||
def new
|
||||
|
@ -59,17 +59,17 @@ class MembersController < ApplicationController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@member.destroy
|
||||
respond_to do |format|
|
||||
if request.post? && @member.deletable?
|
||||
@member.destroy
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
|
||||
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} }
|
||||
end
|
||||
end
|
||||
|
||||
def autocomplete_for_member_login
|
||||
@users = User.active.find(:all, :conditions => ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ?", "#{params[:user]}%", "#{params[:user]}%", "#{params[:user]}%"],
|
||||
:limit => 10,
|
||||
:order => 'login ASC') - @project.users
|
||||
def autocomplete_for_member
|
||||
@principals = Principal.active.like(params[:q]).find(:all, :limit => 100) - @project.principals
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ class UsersController < ApplicationController
|
|||
if @user.save
|
||||
Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :action => 'list'
|
||||
redirect_to :controller => 'users', :action => 'edit', :id => @user
|
||||
end
|
||||
end
|
||||
@auth_sources = AuthSource.find(:all)
|
||||
|
@ -75,6 +75,7 @@ class UsersController < ApplicationController
|
|||
@user.admin = params[:user][:admin] if params[:user][:admin]
|
||||
@user.login = params[:user][:login] if params[:user][:login]
|
||||
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
|
||||
@user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
|
||||
@user.attributes = params[:user]
|
||||
# Was the account actived ? (do it before User#save clears the change)
|
||||
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
|
||||
|
@ -85,17 +86,18 @@ class UsersController < ApplicationController
|
|||
Mailer.deliver_account_information(@user, params[:password])
|
||||
end
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
# Give a string to redirect_to otherwise it would use status param as the response code
|
||||
redirect_to(url_for(:action => 'list', :status => params[:status], :page => params[:page]))
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
||||
@auth_sources = AuthSource.find(:all)
|
||||
@membership ||= Member.new
|
||||
rescue ::ActionController::RedirectBackError
|
||||
redirect_to :controller => 'users', :action => 'edit', :id => @user
|
||||
end
|
||||
|
||||
def edit_membership
|
||||
@user = User.find(params[:id])
|
||||
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:user => @user)
|
||||
@membership = params[:membership_id] ? Member.find(params[:membership_id]) : Member.new(:principal => @user)
|
||||
@membership.attributes = params[:membership]
|
||||
@membership.save if request.post?
|
||||
respond_to do |format|
|
||||
|
@ -111,7 +113,10 @@ class UsersController < ApplicationController
|
|||
|
||||
def destroy_membership
|
||||
@user = User.find(params[:id])
|
||||
Member.find(params[:membership_id]).destroy if request.post?
|
||||
@membership = Member.find(params[:membership_id])
|
||||
if request.post? && @membership.deletable?
|
||||
@membership.destroy
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'users', :action => 'edit', :id => @user, :tab => 'memberships' }
|
||||
format.js { render(:update) {|page| page.replace_html "tab-content-memberships", :partial => 'users/memberships'} }
|
||||
|
|
|
@ -46,7 +46,11 @@ module ApplicationHelper
|
|||
|
||||
# Display a link to user's account page
|
||||
def link_to_user(user, options={})
|
||||
(user && !user.anonymous?) ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
|
||||
if user.is_a?(User)
|
||||
!user.anonymous? ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
|
||||
else
|
||||
user.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_issue(issue, options={})
|
||||
|
@ -190,6 +194,14 @@ module ApplicationHelper
|
|||
end
|
||||
s
|
||||
end
|
||||
|
||||
def principals_check_box_tags(name, principals)
|
||||
s = ''
|
||||
principals.each do |principal|
|
||||
s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
|
||||
end
|
||||
s
|
||||
end
|
||||
|
||||
# Truncates and returns the string as a single line
|
||||
def truncate_single_line(string, *args)
|
||||
|
|
|
@ -21,7 +21,8 @@ module CustomFieldsHelper
|
|||
tabs = [{:name => 'IssueCustomField', :label => :label_issue_plural},
|
||||
{:name => 'TimeEntryCustomField', :label => :label_spent_time},
|
||||
{:name => 'ProjectCustomField', :label => :label_project_plural},
|
||||
{:name => 'UserCustomField', :label => :label_user_plural}
|
||||
{:name => 'UserCustomField', :label => :label_user_plural},
|
||||
{:name => 'GroupCustomField', :label => :label_group_plural}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# 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.
|
||||
|
||||
module GroupsHelper
|
||||
# Options for the new membership projects combo-box
|
||||
def options_for_membership_project_select(user, projects)
|
||||
options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
|
||||
options << project_tree_options_for_select(projects) do |p|
|
||||
{:disabled => (user.projects.include?(p))}
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
def group_settings_tabs
|
||||
tabs = [{:name => 'general', :partial => 'groups/general', :label => :label_general},
|
||||
{:name => 'users', :partial => 'groups/users', :label => :label_user_plural},
|
||||
{:name => 'memberships', :partial => 'groups/memberships', :label => :label_project_plural}
|
||||
]
|
||||
end
|
||||
end
|
|
@ -47,6 +47,7 @@ module UsersHelper
|
|||
|
||||
def user_settings_tabs
|
||||
tabs = [{:name => 'general', :partial => 'users/general', :label => :label_general},
|
||||
{:name => 'groups', :partial => 'users/groups', :label => :label_group_plural},
|
||||
{:name => 'memberships', :partial => 'users/memberships', :label => :label_project_plural}
|
||||
]
|
||||
end
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# 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 Group < Principal
|
||||
has_and_belongs_to_many :users, :after_add => :user_added,
|
||||
:after_remove => :user_removed
|
||||
|
||||
acts_as_customizable
|
||||
|
||||
validates_presence_of :lastname
|
||||
validates_uniqueness_of :lastname, :case_sensitive => false
|
||||
validates_length_of :lastname, :maximum => 30
|
||||
|
||||
def to_s
|
||||
lastname.to_s
|
||||
end
|
||||
|
||||
def user_added(user)
|
||||
members.each do |member|
|
||||
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
|
||||
member.member_roles.each do |member_role|
|
||||
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
|
||||
end
|
||||
user_member.save!
|
||||
end
|
||||
end
|
||||
|
||||
def user_removed(user)
|
||||
members.each do |member|
|
||||
MemberRole.find(:all, :include => :member,
|
||||
:conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# 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 GroupCustomField < CustomField
|
||||
def type_name
|
||||
:label_group_plural
|
||||
end
|
||||
end
|
|
@ -17,40 +17,50 @@
|
|||
|
||||
class Member < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
has_many :member_roles, :dependent => :delete_all
|
||||
belongs_to :principal, :foreign_key => 'user_id'
|
||||
has_many :member_roles, :dependent => :destroy
|
||||
has_many :roles, :through => :member_roles
|
||||
belongs_to :project
|
||||
|
||||
validates_presence_of :user, :project
|
||||
validates_presence_of :principal, :project
|
||||
validates_uniqueness_of :user_id, :scope => :project_id
|
||||
|
||||
def name
|
||||
self.user.name
|
||||
end
|
||||
|
||||
# Sets user by login
|
||||
def user_login=(login)
|
||||
login = login.to_s
|
||||
unless login.blank?
|
||||
if (u = User.find_by_login(login))
|
||||
self.user = u
|
||||
end
|
||||
end
|
||||
alias :base_role_ids= :role_ids=
|
||||
def role_ids=(arg)
|
||||
ids = (arg || []).collect(&:to_i) - [0]
|
||||
# Keep inherited roles
|
||||
ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
|
||||
|
||||
new_role_ids = ids - role_ids
|
||||
# Add new roles
|
||||
new_role_ids.each {|id| member_roles << MemberRole.new(:role_id => id) }
|
||||
# Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
|
||||
member_roles.select {|mr| !ids.include?(mr.role_id)}.each(&:destroy)
|
||||
end
|
||||
|
||||
def <=>(member)
|
||||
a, b = roles.sort.first, member.roles.sort.first
|
||||
a == b ? (user <=> member.user) : (a <=> b)
|
||||
a == b ? (principal <=> member.principal) : (a <=> b)
|
||||
end
|
||||
|
||||
def deletable?
|
||||
member_roles.detect {|mr| mr.inherited_from}.nil?
|
||||
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]
|
||||
if user
|
||||
# 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
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def validate
|
||||
errors.add_to_base "Role can't be blank" if roles.empty?
|
||||
errors.add_to_base "Role can't be blank" if member_roles.empty? && roles.empty?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,9 +19,36 @@ class MemberRole < ActiveRecord::Base
|
|||
belongs_to :member
|
||||
belongs_to :role
|
||||
|
||||
after_destroy :remove_member_if_empty
|
||||
|
||||
after_create :add_role_to_group_users
|
||||
after_destroy :remove_role_from_group_users
|
||||
|
||||
validates_presence_of :role
|
||||
|
||||
def validate
|
||||
errors.add :role_id, :invalid if role && !role.member?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remove_member_if_empty
|
||||
if member.roles.empty?
|
||||
member.destroy
|
||||
end
|
||||
end
|
||||
|
||||
def add_role_to_group_users
|
||||
if member.principal.is_a?(Group)
|
||||
member.principal.users.each do |user|
|
||||
user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
|
||||
user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id)
|
||||
user_member.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def remove_role_from_group_users
|
||||
MemberRole.find(:all, :conditions => { :inherited_from => id }).each(&:destroy)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# 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 Principal < ActiveRecord::Base
|
||||
set_table_name 'users'
|
||||
|
||||
has_many :members, :foreign_key => 'user_id', :dependent => :destroy
|
||||
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"
|
||||
has_many :projects, :through => :memberships
|
||||
|
||||
# Groups and active users
|
||||
named_scope :active, :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status = 1)"
|
||||
|
||||
named_scope :like, lambda {|q|
|
||||
s = "%#{q.to_s.strip.downcase}%"
|
||||
{:conditions => ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ?", s, s, s],
|
||||
:order => 'type, login, lastname, firstname'
|
||||
}
|
||||
}
|
||||
|
||||
def <=>(principal)
|
||||
self.to_s.downcase <=> principal.to_s.downcase
|
||||
end
|
||||
end
|
|
@ -20,8 +20,13 @@ class Project < ActiveRecord::Base
|
|||
STATUS_ACTIVE = 1
|
||||
STATUS_ARCHIVED = 9
|
||||
|
||||
has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
|
||||
has_many :members, :include => :user, :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
|
||||
has_many :member_principals, :class_name => 'Member',
|
||||
:include => :principal,
|
||||
:conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
|
||||
has_many :users, :through => :members
|
||||
has_many :principals, :through => :member_principals, :source => :principal
|
||||
|
||||
has_many :enabled_modules, :dependent => :delete_all
|
||||
has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
|
||||
has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# redMine - project management software
|
||||
# Copyright (C) 2006-2007 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,7 +17,7 @@
|
|||
|
||||
require "digest/sha1"
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
class User < Principal
|
||||
|
||||
# Account statuses
|
||||
STATUS_ANONYMOUS = 0
|
||||
|
@ -33,9 +33,8 @@ class User < ActiveRecord::Base
|
|||
:username => '#{login}'
|
||||
}
|
||||
|
||||
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_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
|
||||
:after_remove => Proc.new {|user, group| group.user_removed(user)}
|
||||
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
|
||||
has_many :changesets, :dependent => :nullify
|
||||
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
|
||||
|
@ -50,7 +49,7 @@ class User < ActiveRecord::Base
|
|||
attr_accessor :password, :password_confirmation
|
||||
attr_accessor :last_before_login_on
|
||||
# Prevents unauthorized assignments
|
||||
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
|
||||
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
|
||||
|
||||
validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
|
||||
validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
|
||||
|
@ -317,7 +316,7 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
# Return password digest
|
||||
def self.hash_password(clear_password)
|
||||
Digest::SHA1.hexdigest(clear_password || "")
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
<%= link_to l(:label_new), :controller => 'users', :action => 'add' %>
|
||||
</p>
|
||||
|
||||
<p class="icon22 icon22-groups">
|
||||
<%= link_to l(:label_group_plural), :controller => 'groups' %> |
|
||||
<%= link_to l(:label_new), :controller => 'groups', :action => 'new' %>
|
||||
</p>
|
||||
|
||||
<p class="icon22 icon22-role">
|
||||
<%= link_to l(:label_role_and_permissions), :controller => 'roles' %>
|
||||
</p>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<%= error_messages_for :group %>
|
||||
|
||||
<div class="box tabular">
|
||||
<p><%= f.text_field :lastname, :label => :field_name %></p>
|
||||
<% @group.custom_field_values.each do |value| %>
|
||||
<p><%= custom_field_tag_with_label :group, value %></p>
|
||||
<% end %>
|
||||
</div>
|
|
@ -0,0 +1,4 @@
|
|||
<% labelled_tabular_form_for :group, @group, :url => { :controller => 'group', :action => 'update', :tab => nil } do |f| %>
|
||||
<%= render :partial => 'form', :locals => { :f => f } %>
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<% end %>
|
|
@ -0,0 +1,56 @@
|
|||
<% roles = Role.find_all_givable %>
|
||||
<% projects = Project.active.find(:all, :order => 'lft') %>
|
||||
|
||||
<div class="splitcontentleft">
|
||||
<% if @group.memberships.any? %>
|
||||
<table class="list memberships">
|
||||
<thead>
|
||||
<th><%= l(:label_project) %></th>
|
||||
<th><%= l(:label_role_plural) %></th>
|
||||
<th style="width:15%"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @group.memberships.each do |membership| %>
|
||||
<% next if membership.new_record? %>
|
||||
<tr id="member-<%= membership.id %>" class="<%= cycle 'odd', 'even' %> class">
|
||||
<td class="project"><%=h membership.project %></td>
|
||||
<td class="roles">
|
||||
<span id="member-<%= membership.id %>-roles"><%=h membership.roles.sort.collect(&:to_s).join(', ') %></span>
|
||||
<% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @group, :membership_id => membership },
|
||||
:html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %>
|
||||
<p><% roles.each do |role| %>
|
||||
<label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role) %> <%=h role %></label><br />
|
||||
<% end %></p>
|
||||
<p><%= submit_tag l(:button_change) %>
|
||||
<%= link_to_function l(:button_cancel), "$('member-#{membership.id}-roles').show(); $('member-#{membership.id}-roles-form').hide(); return false;" %></p>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="buttons">
|
||||
<%= link_to_function l(:button_edit), "$('member-#{membership.id}-roles').hide(); $('member-#{membership.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %>
|
||||
<%= link_to_remote l(:button_delete), { :url => { :controller => 'groups', :action => 'destroy_membership', :id => @group, :membership_id => membership },
|
||||
:method => :post },
|
||||
:class => 'icon icon-del' %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<% end; reset_cycle %>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="splitcontentright">
|
||||
<% if projects.any? %>
|
||||
<fieldset><legend><%=l(:label_project_new)%></legend>
|
||||
<% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @group }) do %>
|
||||
<%= select_tag 'membership[project_id]', options_for_membership_project_select(@group, projects) %>
|
||||
<p><%= l(:label_role_plural) %>:
|
||||
<% roles.each do |role| %>
|
||||
<label><%= check_box_tag 'membership[role_ids][]', role.id %> <%=h role %></label>
|
||||
<% end %></p>
|
||||
<p><%= submit_tag l(:button_add) %></p>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
</div>
|
|
@ -0,0 +1,49 @@
|
|||
<div class="splitcontentleft">
|
||||
<% if @group.users.any? %>
|
||||
<table class="list users">
|
||||
<thead>
|
||||
<th><%= l(:label_user) %></th>
|
||||
<th style="width:15%"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @group.users.sort.each do |user| %>
|
||||
<tr id="user-<%= user.id %>" class="<%= cycle 'odd', 'even' %>">
|
||||
<td class="user"><%= link_to_user user %></td>
|
||||
<td class="buttons">
|
||||
<%= link_to_remote l(:button_delete), { :url => { :controller => 'groups', :action => 'remove_user', :id => @group, :user_id => user },
|
||||
:method => :post },
|
||||
:class => 'icon icon-del' %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="splitcontentright">
|
||||
<% users = User.active.find(:all, :limit => 100) - @group.users %>
|
||||
<% if users.any? %>
|
||||
<% remote_form_for(:group, @group, :url => {:controller => 'groups', :action => 'add_users', :id => @group}, :method => :post) do |f| %>
|
||||
<fieldset><legend><%=l(:label_user_new)%></legend>
|
||||
|
||||
<p><%= text_field_tag 'user_search', nil, :size => "40" %></p>
|
||||
<%= observe_field(:user_search,
|
||||
:frequency => 0.5,
|
||||
:update => :users,
|
||||
:url => { :controller => 'groups', :action => 'autocomplete_for_user', :id => @group },
|
||||
:with => 'q')
|
||||
%>
|
||||
|
||||
<div id="users">
|
||||
<%= principals_check_box_tags 'user_ids[]', users %>
|
||||
</div>
|
||||
|
||||
<p><%= submit_tag l(:button_add) %></p>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
<%= principals_check_box_tags 'user_ids[]', @users %>
|
|
@ -0,0 +1,23 @@
|
|||
<h2><%= link_to l(:label_group_plural), groups_path %> » <%= h(@group) %></h2>
|
||||
|
||||
<% selected_tab = params[:tab] ? params[:tab].to_s : group_settings_tabs.first[:name] %>
|
||||
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<% group_settings_tabs.each do |tab| -%>
|
||||
<li><%= link_to l(tab[:label]), { :tab => tab[:name] },
|
||||
:id => "tab-#{tab[:name]}",
|
||||
:class => (tab[:name] != selected_tab ? nil : 'selected'),
|
||||
:onclick => "showTab('#{tab[:name]}'); this.blur(); return false;" %></li>
|
||||
<% end -%>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<% group_settings_tabs.each do |tab| -%>
|
||||
<%= content_tag('div', render(:partial => tab[:partial]),
|
||||
:id => "tab-content-#{tab[:name]}",
|
||||
:style => (tab[:name] != selected_tab ? 'display:none' : nil),
|
||||
:class => 'tab-content') %>
|
||||
<% end -%>
|
||||
|
||||
<% html_title(l(:label_group), @group, l(:label_administration)) -%>
|
|
@ -0,0 +1,25 @@
|
|||
<div class="contextual">
|
||||
<%= link_to l(:label_group_new), new_group_path, :class => 'icon icon-add' %>
|
||||
</div>
|
||||
|
||||
<h2><%= l(:label_group_plural) %></h2>
|
||||
|
||||
<% if @groups.any? %>
|
||||
<table class="list groups">
|
||||
<thead><tr>
|
||||
<th><%=l(:label_group)%></th>
|
||||
<th><%=l(:label_user_plural)%></th>
|
||||
<th></th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<% @groups.each do |group| %>
|
||||
<tr class="<%= cycle 'odd', 'even' %>">
|
||||
<td><%= link_to h(group), :action => 'edit', :id => group %></td>
|
||||
<td align="center"><%= group.users.size %></td>
|
||||
<td class="buttons"><%= link_to l(:button_delete), group, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="nodata"><%= l(:label_no_data) %></p>
|
||||
<% end %>
|
|
@ -0,0 +1,8 @@
|
|||
<h2><%= link_to l(:label_group_plural), groups_path %> » <%= l(:label_group_new) %></h2>
|
||||
|
||||
<%= error_messages_for :group %>
|
||||
|
||||
<% form_for(@group, :builder => TabularFormBuilder, :lang => current_language) do |f| %>
|
||||
<%= render :partial => 'form', :locals => { :f => f } %>
|
||||
<p><%= f.submit l(:button_create) %></p>
|
||||
<% end %>
|
|
@ -0,0 +1,7 @@
|
|||
<h2><%= link_to l(:label_group_plural), groups_path %> » <%=h @group %></h2>
|
||||
|
||||
<ul>
|
||||
<% @group.users.each do |user| %>
|
||||
<li><%=h user %></li>
|
||||
<% end %>
|
||||
</ul>
|
|
@ -0,0 +1 @@
|
|||
<%= principals_check_box_tags 'member[user_ids][]', @principals %>
|
|
@ -1,5 +0,0 @@
|
|||
<ul>
|
||||
<% @users.each do |user| -%>
|
||||
<li><%= h user.login %><span class="informal"> (<%= h(user.name(:lastname_coma_firstname)) %>)</span></li>
|
||||
<% end -%>
|
||||
</ul>
|
|
@ -1,12 +1,12 @@
|
|||
<%= error_messages_for 'member' %>
|
||||
<% roles = Role.find_all_givable
|
||||
members = @project.members.find(:all, :include => [:roles, :user]).sort %>
|
||||
members = @project.member_principals.find(:all, :include => [:roles, :principal]).sort %>
|
||||
|
||||
<div class="splitcontentleft">
|
||||
<% if members.any? %>
|
||||
<table class="list members">
|
||||
<thead>
|
||||
<th><%= l(:label_user) %></th>
|
||||
<th><%= l(:label_user) %> / <%= l(:label_group) %></th>
|
||||
<th><%= l(:label_role_plural) %></th>
|
||||
<th style="width:15%"></th>
|
||||
<%= call_hook(:view_projects_settings_members_table_header, :project => @project) %>
|
||||
|
@ -15,7 +15,7 @@
|
|||
<% members.each do |member| %>
|
||||
<% next if member.new_record? %>
|
||||
<tr id="member-<%= member.id %>" class="<%= cycle 'odd', 'even' %> member">
|
||||
<td class="user"><%= link_to_user member.user %></td>
|
||||
<td class="<%= member.principal.class.name.downcase %>"><%= link_to_user member.principal %></td>
|
||||
<td class="roles">
|
||||
<span id="member-<%= member.id %>-roles"><%=h member.roles.sort.collect(&:to_s).join(', ') %></span>
|
||||
<% if authorize_for('members', 'edit') %>
|
||||
|
@ -23,8 +23,10 @@
|
|||
:method => :post,
|
||||
:html => { :id => "member-#{member.id}-roles-form", :style => 'display:none;' }) do |f| %>
|
||||
<p><% roles.each do |role| %>
|
||||
<label><%= check_box_tag 'member[role_ids][]', role.id, member.roles.include?(role) %> <%=h role %></label><br />
|
||||
<label><%= check_box_tag 'member[role_ids][]', role.id, member.roles.include?(role),
|
||||
:disabled => member.member_roles.detect {|mr| mr.role_id == role.id && !mr.inherited_from.nil?} %> <%=h role %></label><br />
|
||||
<% end %></p>
|
||||
<%= hidden_field_tag 'member[role_ids][]', '' %>
|
||||
<p><%= submit_tag l(:button_change), :class => "small" %>
|
||||
<%= link_to_function l(:button_cancel), "$('member-#{member.id}-roles').show(); $('member-#{member.id}-roles-form').hide(); return false;" %></p>
|
||||
<% end %>
|
||||
|
@ -32,10 +34,10 @@
|
|||
</td>
|
||||
<td class="buttons">
|
||||
<%= link_to_function l(:button_edit), "$('member-#{member.id}-roles').hide(); $('member-#{member.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %>
|
||||
<%= link_to_remote l(:button_delete), { :url => {:controller => 'members', :action => 'destroy', :id => member},
|
||||
<%= link_to_remote(l(:button_delete), { :url => {:controller => 'members', :action => 'destroy', :id => member},
|
||||
:method => :post
|
||||
}, :title => l(:button_delete),
|
||||
:class => 'icon icon-del' %>
|
||||
:class => 'icon icon-del') if member.deletable? %>
|
||||
</td>
|
||||
<%= call_hook(:view_projects_settings_members_table_row, { :project => @project, :member => member}) %>
|
||||
</tr>
|
||||
|
@ -48,27 +50,30 @@
|
|||
</div>
|
||||
|
||||
|
||||
<% users_count = User.active.count - @project.users.count
|
||||
users = (users_count < 300) ? User.active.find(:all, :limit => 200).sort - @project.users : [] %>
|
||||
<% principals = Principal.active.find(:all, :limit => 100, :order => 'type, login, lastname ASC') - @project.principals %>
|
||||
|
||||
<div class="splitcontentright">
|
||||
<% if roles.any? && users_count > 0 %>
|
||||
<% if roles.any? && principals.any? %>
|
||||
<% remote_form_for(:member, @member, :url => {:controller => 'members', :action => 'new', :id => @project}, :method => :post) do |f| %>
|
||||
<fieldset><legend><%=l(:label_member_new)%></legend>
|
||||
<p><%= text_field_tag 'member[user_login]', nil, :size => "40" %></p>
|
||||
<div id="member_user_login_choices" class="autocomplete">sqd</div>
|
||||
<%= javascript_tag "new Ajax.Autocompleter('member_user_login', 'member_user_login_choices', '#{ url_for(:controller => 'members', :action => 'autocomplete_for_member_login', :id => @project) }', { minChars: 1, frequency: 0.5, paramName: 'user' });" %>
|
||||
<% unless users.empty? %>
|
||||
<div>
|
||||
<% users.each do |user| -%>
|
||||
<label><%= check_box_tag 'member[user_ids][]', user.id, false %> <%= user %></label>
|
||||
<% end -%>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<p><%= text_field_tag 'principal_search', nil, :size => "40" %></p>
|
||||
<%= observe_field(:principal_search,
|
||||
:frequency => 0.5,
|
||||
:update => :principals,
|
||||
:url => { :controller => 'members', :action => 'autocomplete_for_member', :id => @project },
|
||||
:with => 'q')
|
||||
%>
|
||||
|
||||
<div id="principals">
|
||||
<%= principals_check_box_tags 'member[user_ids][]', principals %>
|
||||
</div>
|
||||
|
||||
<p><%= l(:label_role_plural) %>:
|
||||
<% roles.each do |role| %>
|
||||
<label><%= check_box_tag 'member[role_ids][]', role.id %> <%=h role %></label>
|
||||
<% end %></p>
|
||||
|
||||
<p><%= submit_tag l(:button_add) %></p>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<% form_for(:user, :url => { :action => 'edit' }) do %>
|
||||
<div class="box">
|
||||
<% Group.all.each do |group| %>
|
||||
<label><%= check_box_tag 'user[group_ids][]', group.id, @user.groups.include?(group) %> <%=h group %></label><br />
|
||||
<% end %>
|
||||
<%= hidden_field_tag 'user[group_ids][]', '' %>
|
||||
</div>
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<% end %>
|
|
@ -20,17 +20,19 @@
|
|||
<% remote_form_for(:membership, :url => { :action => 'edit_membership', :id => @user, :membership_id => membership },
|
||||
:html => { :id => "member-#{membership.id}-roles-form", :style => 'display:none;'}) do %>
|
||||
<p><% roles.each do |role| %>
|
||||
<label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role) %> <%=h role %></label><br />
|
||||
<label><%= check_box_tag 'membership[role_ids][]', role.id, membership.roles.include?(role),
|
||||
:disabled => membership.member_roles.detect {|mr| mr.role_id == role.id && !mr.inherited_from.nil?} %> <%=h role %></label><br />
|
||||
<% end %></p>
|
||||
<%= hidden_field_tag 'membership[role_ids][]', '' %>
|
||||
<p><%= submit_tag l(:button_change) %>
|
||||
<%= link_to_function l(:button_cancel), "$('member-#{membership.id}-roles').show(); $('member-#{membership.id}-roles-form').hide(); return false;" %></p>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="buttons">
|
||||
<%= link_to_function l(:button_edit), "$('member-#{membership.id}-roles').hide(); $('member-#{membership.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %>
|
||||
<%= link_to_remote l(:button_delete), { :url => { :controller => 'users', :action => 'destroy_membership', :id => @user, :membership_id => membership },
|
||||
<%= link_to_remote(l(:button_delete), { :url => { :controller => 'users', :action => 'destroy_membership', :id => @user, :membership_id => membership },
|
||||
:method => :post },
|
||||
:class => 'icon icon-del' %>
|
||||
:class => 'icon icon-del') if membership.deletable? %>
|
||||
</td>
|
||||
<%= call_hook(:view_users_memberships_table_row, :user => @user, :membership => membership, :roles => roles, :projects => projects )%>
|
||||
</tr>
|
||||
|
|
|
@ -683,6 +683,9 @@ en:
|
|||
label_date_from_to: From {{start}} to {{end}}
|
||||
label_wiki_content_added: Wiki page added
|
||||
label_wiki_content_updated: Wiki page updated
|
||||
label_group: Group
|
||||
label_group_plural: Groups
|
||||
label_group_new: New group
|
||||
|
||||
button_login: Login
|
||||
button_submit: Submit
|
||||
|
|
|
@ -229,7 +229,8 @@ ActionController::Routing::Routes.draw do |map|
|
|||
map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
|
||||
map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
|
||||
|
||||
|
||||
map.resources :groups
|
||||
|
||||
#left old routes at the bottom for backwards compat
|
||||
map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
|
||||
map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
class PopulateUsersType < ActiveRecord::Migration
|
||||
def self.up
|
||||
Principal.update_all("type = 'User'", "type IS NULL")
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
class CreateGroupsUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :groups_users, :id => false do |t|
|
||||
t.column :group_id, :integer, :null => false
|
||||
t.column :user_id, :integer, :null => false
|
||||
end
|
||||
add_index :groups_users, [:group_id, :user_id], :unique => true, :name => :groups_users_ids
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :groups_users
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class AddMemberRolesInheritedFrom < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :member_roles, :inherited_from, :integer
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :member_roles, :inherited_from
|
||||
end
|
||||
end
|
|
@ -23,7 +23,7 @@ Redmine::AccessControl.map do |map|
|
|||
map.permission :add_project, {:projects => :add}, :require => :loggedin
|
||||
map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
|
||||
map.permission :select_project_modules, {:projects => :modules}, :require => :member
|
||||
map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member_login]}, :require => :member
|
||||
map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
|
||||
map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
|
||||
|
||||
map.project_module :issue_tracking do |map|
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.0 KiB |
|
@ -342,12 +342,14 @@ p.other-formats { text-align: right; font-size:0.9em; color: #666; }
|
|||
a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
|
||||
|
||||
/* Project members tab */
|
||||
div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft { width: 64% }
|
||||
div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright { width: 34% }
|
||||
div#tab-content-members fieldset, div#tab-content-memberships fieldset { padding:1em; margin-bottom: 1em; }
|
||||
div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend { font-weight: bold; }
|
||||
div#tab-content-members fieldset label, div#tab-content-memberships fieldset label { display: block; }
|
||||
div#tab-content-members fieldset div { max-height: 400px; overflow:auto; }
|
||||
div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
|
||||
div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
|
||||
div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
|
||||
div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
|
||||
div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
|
||||
div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
|
||||
|
||||
table.members td.group { padding-left: 20px; background: url(../images/users.png) no-repeat 0% 0%; }
|
||||
|
||||
* html div#tab-content-members fieldset div { height: 450px; }
|
||||
|
||||
|
@ -725,6 +727,7 @@ vertical-align: middle;
|
|||
|
||||
.icon22-projects { background-image: url(../images/22x22/projects.png); }
|
||||
.icon22-users { background-image: url(../images/22x22/users.png); }
|
||||
.icon22-groups { background-image: url(../images/22x22/groups.png); }
|
||||
.icon22-tracker { background-image: url(../images/22x22/tracker.png); }
|
||||
.icon22-role { background-image: url(../images/22x22/role.png); }
|
||||
.icon22-workflow { background-image: url(../images/22x22/workflow.png); }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
groups_users_001:
|
||||
group_id: 10
|
||||
user_id: 8
|
||||
|
|
@ -19,5 +19,21 @@ member_roles_005:
|
|||
id: 5
|
||||
role_id: 1
|
||||
member_id: 5
|
||||
|
||||
|
||||
member_roles_006:
|
||||
id: 6
|
||||
role_id: 1
|
||||
member_id: 6
|
||||
member_roles_007:
|
||||
id: 7
|
||||
role_id: 2
|
||||
member_id: 6
|
||||
member_roles_008:
|
||||
id: 8
|
||||
role_id: 1
|
||||
member_id: 7
|
||||
inherited_from: 6
|
||||
member_roles_009:
|
||||
id: 9
|
||||
role_id: 2
|
||||
member_id: 7
|
||||
inherited_from: 7
|
||||
|
|
|
@ -30,4 +30,16 @@ members_005:
|
|||
project_id: 5
|
||||
user_id: 2
|
||||
mail_notification: true
|
||||
members_006:
|
||||
id: 6
|
||||
created_on: 2006-07-19 19:35:33 +02:00
|
||||
project_id: 5
|
||||
user_id: 10
|
||||
mail_notification: false
|
||||
members_007:
|
||||
id: 7
|
||||
created_on: 2006-07-19 19:35:33 +02:00
|
||||
project_id: 5
|
||||
user_id: 8
|
||||
mail_notification: false
|
||||
|
|
@ -144,5 +144,13 @@ users_009:
|
|||
mail_notification: false
|
||||
login: miscuser9
|
||||
type: User
|
||||
groups_010:
|
||||
id: 10
|
||||
lastname: A Team
|
||||
type: Group
|
||||
groups_011:
|
||||
id: 11
|
||||
lastname: B Team
|
||||
type: Group
|
||||
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
# 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.
|
||||
|
||||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
require 'groups_controller'
|
||||
|
||||
# Re-raise errors caught by the controller.
|
||||
class GroupsController; def rescue_action(e) raise e end; end
|
||||
|
||||
class GroupsControllerTest < Test::Unit::TestCase
|
||||
fixtures :projects, :users, :members, :member_roles
|
||||
|
||||
def setup
|
||||
@controller = GroupsController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
User.current = nil
|
||||
@request.session[:user_id] = 1
|
||||
end
|
||||
|
||||
def test_index
|
||||
get :index
|
||||
assert_response :success
|
||||
assert_template 'index'
|
||||
end
|
||||
|
||||
def test_show
|
||||
get :show, :id => 10
|
||||
assert_response :success
|
||||
assert_template 'show'
|
||||
end
|
||||
|
||||
def test_new
|
||||
get :new
|
||||
assert_response :success
|
||||
assert_template 'new'
|
||||
end
|
||||
|
||||
def test_create
|
||||
assert_difference 'Group.count' do
|
||||
post :create, :group => {:lastname => 'New group'}
|
||||
end
|
||||
assert_redirected_to 'groups'
|
||||
end
|
||||
|
||||
def test_edit
|
||||
get :edit, :id => 10
|
||||
assert_response :success
|
||||
assert_template 'edit'
|
||||
end
|
||||
|
||||
def test_update
|
||||
post :update, :id => 10
|
||||
assert_redirected_to 'groups'
|
||||
end
|
||||
|
||||
def test_destroy
|
||||
assert_difference 'Group.count', -1 do
|
||||
post :destroy, :id => 10
|
||||
end
|
||||
assert_redirected_to 'groups'
|
||||
end
|
||||
|
||||
def test_add_users
|
||||
assert_difference 'Group.find(10).users.count', 2 do
|
||||
post :add_users, :id => 10, :user_ids => ['2', '3']
|
||||
end
|
||||
end
|
||||
|
||||
def test_remove_user
|
||||
assert_difference 'Group.find(10).users.count', -1 do
|
||||
post :remove_user, :id => 10, :user_id => '8'
|
||||
end
|
||||
end
|
||||
|
||||
def test_new_membership
|
||||
assert_difference 'Group.find(10).members.count' do
|
||||
post :edit_membership, :id => 10, :membership => { :project_id => 2, :role_ids => ['1', '2']}
|
||||
end
|
||||
end
|
||||
|
||||
def test_edit_membership
|
||||
assert_no_difference 'Group.find(10).members.count' do
|
||||
post :edit_membership, :id => 10, :membership_id => 6, :membership => { :role_ids => ['1', '3']}
|
||||
end
|
||||
end
|
||||
|
||||
def test_destroy_membership
|
||||
assert_difference 'Group.find(10).members.count', -1 do
|
||||
post :destroy_membership, :id => 10, :membership_id => 6
|
||||
end
|
||||
end
|
||||
end
|
|
@ -48,14 +48,6 @@ class MembersControllerTest < Test::Unit::TestCase
|
|||
assert User.find(7).member_of?(Project.find(1))
|
||||
end
|
||||
|
||||
def test_create_by_user_login
|
||||
assert_difference 'Member.count' do
|
||||
post :new, :id => 1, :member => {:role_ids => [1], :user_login => 'someone'}
|
||||
end
|
||||
assert_redirected_to '/projects/ecookbook/settings/members'
|
||||
assert User.find(7).member_of?(Project.find(1))
|
||||
end
|
||||
|
||||
def test_create_multiple
|
||||
assert_difference 'Member.count', 3 do
|
||||
post :new, :id => 1, :member => {:role_ids => [1], :user_ids => [7, 8, 9]}
|
||||
|
@ -79,11 +71,12 @@ class MembersControllerTest < Test::Unit::TestCase
|
|||
assert !User.find(3).member_of?(Project.find(1))
|
||||
end
|
||||
|
||||
def test_autocomplete_for_member_login
|
||||
get :autocomplete_for_member_login, :id => 1, :user => 'mis'
|
||||
def test_autocomplete_for_member
|
||||
get :autocomplete_for_member, :id => 1, :q => 'mis'
|
||||
assert_response :success
|
||||
assert_template 'autocomplete_for_member_login'
|
||||
assert_template 'autocomplete_for_member'
|
||||
|
||||
assert_tag :ul, :child => {:tag => 'li', :content => /miscuser8/}
|
||||
assert_tag :label, :content => /User Misc/,
|
||||
:child => { :tag => 'input', :attributes => { :name => 'member[user_ids][]', :value => '8' } }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
require "#{File.dirname(__FILE__)}/../test_helper"
|
||||
|
||||
class AdminTest < ActionController::IntegrationTest
|
||||
fixtures :users
|
||||
fixtures :all
|
||||
|
||||
def test_add_user
|
||||
log_user("admin", "admin")
|
||||
|
@ -26,16 +26,17 @@ class AdminTest < ActionController::IntegrationTest
|
|||
assert_response :success
|
||||
assert_template "users/add"
|
||||
post "/users/add", :user => { :login => "psmith", :firstname => "Paul", :lastname => "Smith", :mail => "psmith@somenet.foo", :language => "en" }, :password => "psmith09", :password_confirmation => "psmith09"
|
||||
assert_redirected_to "/users"
|
||||
|
||||
user = User.find_by_login("psmith")
|
||||
assert_kind_of User, user
|
||||
assert_redirected_to "/users/#{ user.id }/edit"
|
||||
|
||||
logged_user = User.try_to_login("psmith", "psmith09")
|
||||
assert_kind_of User, logged_user
|
||||
assert_equal "Paul", logged_user.firstname
|
||||
|
||||
post "users/edit", :id => user.id, :user => { :status => User::STATUS_LOCKED }
|
||||
assert_redirected_to "/users"
|
||||
assert_redirected_to "/users/#{ user.id }/edit"
|
||||
locked_user = User.try_to_login("psmith", "psmith09")
|
||||
assert_equal nil, locked_user
|
||||
end
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
# 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.
|
||||
|
||||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class GroupTest < Test::Unit::TestCase
|
||||
fixtures :all
|
||||
|
||||
def test_create
|
||||
g = Group.new(:lastname => 'New group')
|
||||
assert g.save
|
||||
end
|
||||
|
||||
def test_roles_given_to_new_user
|
||||
group = Group.find(11)
|
||||
user = User.find(9)
|
||||
project = Project.first
|
||||
|
||||
Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
|
||||
group.users << user
|
||||
assert user.member_of?(project)
|
||||
end
|
||||
|
||||
def test_roles_given_to_existing_user
|
||||
group = Group.find(11)
|
||||
user = User.find(9)
|
||||
project = Project.first
|
||||
|
||||
group.users << user
|
||||
m = Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
|
||||
assert user.member_of?(project)
|
||||
end
|
||||
|
||||
def test_roles_updated
|
||||
group = Group.find(11)
|
||||
user = User.find(9)
|
||||
project = Project.first
|
||||
group.users << user
|
||||
m = Member.create!(:principal => group, :project => project, :role_ids => [1])
|
||||
assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort
|
||||
|
||||
m.role_ids = [1, 2]
|
||||
assert_equal [1, 2], user.reload.roles_for_project(project).collect(&:id).sort
|
||||
|
||||
m.role_ids = [2]
|
||||
assert_equal [2], user.reload.roles_for_project(project).collect(&:id).sort
|
||||
|
||||
m.role_ids = [1]
|
||||
assert_equal [1], user.reload.roles_for_project(project).collect(&:id).sort
|
||||
end
|
||||
|
||||
def test_roles_removed_when_removing_group_membership
|
||||
assert User.find(8).member_of?(Project.find(5))
|
||||
Member.find_by_project_id_and_user_id(5, 10).destroy
|
||||
assert !User.find(8).member_of?(Project.find(5))
|
||||
end
|
||||
|
||||
def test_roles_removed_when_removing_user_from_group
|
||||
assert User.find(8).member_of?(Project.find(5))
|
||||
User.find(8).groups.clear
|
||||
assert !User.find(8).member_of?(Project.find(5))
|
||||
end
|
||||
end
|
|
@ -63,6 +63,18 @@ class ProjectTest < Test::Unit::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_members_should_be_active_users
|
||||
Project.all.each do |project|
|
||||
assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
|
||||
end
|
||||
end
|
||||
|
||||
def test_users_should_be_active_users
|
||||
Project.all.each do |project|
|
||||
assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
|
||||
end
|
||||
end
|
||||
|
||||
def test_archive
|
||||
user = @ecookbook.members.first.user
|
||||
@ecookbook.archive
|
||||
|
|
Loading…
Reference in New Issue