diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2011-07-23 18:18:13 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2011-07-23 18:18:13 +0000 |
commit | 578fdc62f26c23951b2d2c2b9be0040c7ade0634 (patch) | |
tree | 7f78426f78bdc325cae50368cc1bc3e23f4c5eaa /app/models | |
parent | e1832f25c9aa16f1a95434adc209ee937110228a (diff) | |
download | redmine-578fdc62f26c23951b2d2c2b9be0040c7ade0634.tar.gz redmine-578fdc62f26c23951b2d2c2b9be0040c7ade0634.zip |
Ability to assign issues to groups (#2964).
Option is disabled by default. It can be turned on in application settings.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@6306 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/group.rb | 17 | ||||
-rw-r--r-- | app/models/issue.rb | 20 | ||||
-rw-r--r-- | app/models/issue_category.rb | 2 | ||||
-rw-r--r-- | app/models/mail_handler.rb | 22 | ||||
-rw-r--r-- | app/models/principal.rb | 3 | ||||
-rw-r--r-- | app/models/project.rb | 5 | ||||
-rw-r--r-- | app/models/query.rb | 20 | ||||
-rw-r--r-- | app/models/user.rb | 18 |
8 files changed, 76 insertions, 31 deletions
diff --git a/app/models/group.rb b/app/models/group.rb index 1b55c2566..f3721f7fd 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -24,11 +24,15 @@ class Group < Principal validates_presence_of :lastname validates_uniqueness_of :lastname, :case_sensitive => false validates_length_of :lastname, :maximum => 30 - + + before_destroy :remove_references_before_destroy + def to_s lastname.to_s end + alias :name :to_s + def user_added(user) members.each do |member| next if member.project.nil? @@ -46,4 +50,13 @@ class Group < Principal :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy) end end + + private + + # Removes references that are not handled by associations + def remove_references_before_destroy + return if self.id.nil? + + Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id] + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 5ac166270..d7a3e6ecb 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -22,7 +22,7 @@ class Issue < ActiveRecord::Base belongs_to :tracker belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id' belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' - belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' + belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id' belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id' belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' @@ -93,9 +93,11 @@ class Issue < ActiveRecord::Base when 'all' nil when 'default' - "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})" + user_ids = [user.id] + user.groups.map(&:id) + "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids}))" when 'own' - "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id = #{user.id})" + user_ids = [user.id] + user.groups.map(&:id) + "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids}))" else '1=0' end @@ -109,9 +111,9 @@ class Issue < ActiveRecord::Base when 'all' true when 'default' - !self.is_private? || self.author == user || self.assigned_to == user + !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to) when 'own' - self.author == user || self.assigned_to == user + self.author == user || user.is_or_belongs_to?(assigned_to) else false end @@ -482,7 +484,13 @@ class Issue < ActiveRecord::Base # Author and assignee are always notified unless they have been # locked or don't want to be notified notified << author if author && author.active? && author.notify_about?(self) - notified << assigned_to if assigned_to && assigned_to.active? && assigned_to.notify_about?(self) + if assigned_to + if assigned_to.is_a?(Group) + notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)} + else + notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self) + end + end notified.uniq! # Remove users that can not view the issue notified.reject! {|user| !visible?(user)} diff --git a/app/models/issue_category.rb b/app/models/issue_category.rb index 96b8eeee1..19b2d906b 100644 --- a/app/models/issue_category.rb +++ b/app/models/issue_category.rb @@ -17,7 +17,7 @@ class IssueCategory < ActiveRecord::Base belongs_to :project - belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' + belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id' has_many :issues, :foreign_key => 'category_id', :dependent => :nullify validates_presence_of :name diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index dbbd4f5f3..0ba8d3fd2 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -261,8 +261,7 @@ class MailHandler < ActionMailer::Base # Returns a Hash of issue attributes extracted from keywords in the email body def issue_attributes_from_keywords(issue) - assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_user_from_keyword(k) - assigned_to = nil if assigned_to && !issue.assignable_users.include?(assigned_to) + assigned_to = (k = get_keyword(:assigned_to, :override => true)) && find_assignee_from_keyword(k, issue) attrs = { 'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id), @@ -353,14 +352,19 @@ class MailHandler < ActionMailer::Base end body.strip end - - def find_user_from_keyword(keyword) - user ||= User.find_by_mail(keyword) - user ||= User.find_by_login(keyword) - if user.nil? && keyword.match(/ /) + + def find_assignee_from_keyword(keyword, issue) + keyword = keyword.to_s.downcase + assignable = issue.assignable_users + assignee = nil + assignee ||= assignable.detect {|a| a.mail.to_s.downcase == keyword || a.login.to_s.downcase == keyword} + if assignee.nil? && keyword.match(/ /) firstname, lastname = *(keyword.split) # "First Last Throwaway" - user ||= User.find_by_firstname_and_lastname(firstname, lastname) + assignee ||= assignable.detect {|a| a.is_a?(User) && a.firstname.to_s.downcase == firstname && a.lastname.to_s.downcase == lastname} + end + if assignee.nil? + assignee ||= assignable.detect {|a| a.is_a?(Group) && a.name.downcase == keyword} end - user + assignee end end diff --git a/app/models/principal.rb b/app/models/principal.rb index b3e07dda5..a157cd22f 100644 --- a/app/models/principal.rb +++ b/app/models/principal.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -21,6 +21,7 @@ class Principal < ActiveRecord::Base 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 + has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify # 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)" diff --git a/app/models/project.rb b/app/models/project.rb index c3b553083..b4872a3b4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -426,9 +426,10 @@ class Project < ActiveRecord::Base Member.delete_all(['project_id = ?', id]) end - # Users issues can be assigned to + # Users/groups issues can be assigned to def assignable_users - members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort + assignable = Setting.issue_group_assignment? ? member_principals : members + assignable.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.principal}.sort end # Returns the mail adresses of users that should be always notified on project events diff --git a/app/models/query.rb b/app/models/query.rb index 3cb4c4507..28e1a6d18 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -223,15 +223,14 @@ class Query < ActiveRecord::Base "estimated_hours" => { :type => :float, :order => 13 }, "done_ratio" => { :type => :integer, :order => 14 }} - user_values = [] - user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + principals = [] if project - user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] } + principals += project.principals.sort else all_projects = Project.visible.all if all_projects.any? # members of visible projects - user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] } + principals += Principal.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort # project filter project_values = [] @@ -242,8 +241,17 @@ class Query < ActiveRecord::Base @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty? end end - @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty? - @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty? + users = principals.select {|p| p.is_a?(User)} + + assigned_to_values = [] + assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] } + @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty? + + author_values = [] + author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? + author_values += users.collect{|s| [s.name, s.id.to_s] } + @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty? group_values = Group.all.collect {|g| [g.name, g.id.to_s] } @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty? diff --git a/app/models/user.rb b/app/models/user.rb index 1b7680182..656110f73 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -45,7 +45,6 @@ class User < Principal 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' has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" @@ -388,6 +387,17 @@ class User < Principal @projects_by_role end + # Returns true if user is arg or belongs to arg + def is_or_belongs_to?(arg) + if arg.is_a?(User) + self == arg + elsif arg.is_a?(Group) + arg.users.include?(self) + else + false + end + end + # Return true if the user is allowed to do the specified action on a specific context # Action can be: # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit') @@ -469,7 +479,7 @@ class User < Principal true when 'selected' # user receives notifications for created/assigned issues on unselected projects - if object.is_a?(Issue) && (object.author == self || object.assigned_to == self) + if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to)) true else false @@ -477,13 +487,13 @@ class User < Principal when 'none' false when 'only_my_events' - if object.is_a?(Issue) && (object.author == self || object.assigned_to == self) + if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to)) true else false end when 'only_assigned' - if object.is_a?(Issue) && object.assigned_to == self + if object.is_a?(Issue) && is_or_belongs_to?(object.assigned_to) true else false |