OSDN Git Service

Added per-project tracker selection. Trackers can be selected on project settings.
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Tue, 20 Nov 2007 20:29:03 +0000 (20:29 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Tue, 20 Nov 2007 20:29:03 +0000 (20:29 +0000)
git-svn-id: http://redmine.rubyforge.org/svn/trunk@920 e93f8b46-1217-0410-a6f0-8f06a7374b81

18 files changed:
app/controllers/issues_controller.rb
app/controllers/projects_controller.rb
app/controllers/reports_controller.rb
app/helpers/projects_helper.rb
app/models/custom_value.rb
app/models/issue.rb
app/models/project.rb
app/models/tracker.rb
app/views/issues/_sidebar.rhtml
app/views/issues/context_menu.rhtml
app/views/projects/_form.rhtml
app/views/projects/add.rhtml
app/views/projects/move_issues.rhtml
app/views/projects/show.rhtml
db/migrate/081_create_projects_trackers.rb [new file with mode: 0644]
test/fixtures/projects_trackers.yml [new file with mode: 0644]
test/functional/projects_controller_test.rb
test/unit/issue_test.rb

index cca3fe6..081d8f8 100644 (file)
@@ -194,6 +194,7 @@ class IssuesController < ApplicationController
             :change_status => User.current.allowed_to?(:change_issue_status, @project),
             :add => User.current.allowed_to?(:add_issues, @project),
             :move => User.current.allowed_to?(:move_issues, @project),
+            :copy => (@project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
             :delete => User.current.allowed_to?(:delete_issues, @project)}
     render :layout => false
   end
index 0f50cd7..37a7886 100644 (file)
@@ -57,11 +57,13 @@ class ProjectsController < ApplicationController
   # Add a new project
   def add
     @custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
+    @trackers = Tracker.all
     @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
     @project = Project.new(params[:project])
     @project.enabled_module_names = Redmine::AccessControl.available_project_modules
     if request.get?
       @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
+      @project.trackers = Tracker.all
     else
       @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
       @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
@@ -80,7 +82,7 @@ class ProjectsController < ApplicationController
     @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
     @subprojects = @project.active_children
     @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
-    @trackers = Tracker.find(:all, :order => 'position')
+    @trackers = @project.trackers
     @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
     @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
     @total_hours = @project.time_entries.sum(:hours)
@@ -92,6 +94,7 @@ class ProjectsController < ApplicationController
     @custom_fields = IssueCustomField.find(:all)
     @issue_category ||= IssueCategory.new
     @member ||= @project.members.new
+    @trackers = Tracker.all
     @custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
     @repository ||= @project.repository
     @wiki ||= @project.wiki
@@ -207,7 +210,7 @@ class ProjectsController < ApplicationController
     @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
     @issue.project = @project
     @issue.author = User.current
-    @issue.tracker ||= Tracker.find(params[:tracker_id])
+    @issue.tracker ||= @project.trackers.find(params[:tracker_id])
     
     default_status = IssueStatus.default
     unless default_status
@@ -293,6 +296,7 @@ class ProjectsController < ApplicationController
   def move_issues
     @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
     redirect_to :controller => 'issues', :action => 'index', :project_id => @project and return unless @issues
+    
     @projects = []
     # find projects to which the user is allowed to move the issue
     if User.current.admin?
@@ -301,14 +305,14 @@ class ProjectsController < ApplicationController
     else
       User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:move_issues)}
     end
-    # issue can be moved to any tracker
-    @trackers = Tracker.find(:all)
-    if request.post? && params[:new_project_id] && @projects.collect(&:id).include?(params[:new_project_id].to_i) && params[:new_tracker_id]    
-      new_project = Project.find_by_id(params[:new_project_id])
-      new_tracker = params[:new_tracker_id].blank? ? nil : Tracker.find_by_id(params[:new_tracker_id])
+    @target_project = @projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
+    @target_project ||= @project    
+    @trackers = @target_project.trackers
+    if request.post?
+      new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
       unsaved_issue_ids = []
       @issues.each do |issue|
-        unsaved_issue_ids << issue.id unless issue.move_to(new_project, new_tracker)
+        unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
       end
       if unsaved_issue_ids.empty?
         flash[:notice] = l(:notice_successful_update) unless @issues.empty?
@@ -316,7 +320,9 @@ class ProjectsController < ApplicationController
         flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
       end
       redirect_to :controller => 'issues', :action => 'index', :project_id => @project
+      return
     end
+    render :layout => false if request.xhr?
   end
 
   # Add a news to @project
@@ -354,13 +360,13 @@ class ProjectsController < ApplicationController
   
   # Show changelog for @project
   def changelog
-    @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
+    @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
     retrieve_selected_tracker_ids(@trackers)    
     @versions = @project.versions.sort
   end
 
   def roadmap
-    @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
+    @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
     retrieve_selected_tracker_ids(@trackers)
     @versions = @project.versions.sort
     @versions = @versions.select {|v| !v.completed? } unless params[:completed]
index 6b95944..e18e117 100644 (file)
@@ -25,7 +25,7 @@ class ReportsController < ApplicationController
     case params[:detail]
     when "tracker"
       @field = "tracker_id"
-      @rows = Tracker.find :all, :order => 'position'
+      @rows = @project.trackers
       @data = issues_by_tracker
       @report_title = l(:field_tracker)
       render :template => "reports/issue_report_details"
@@ -60,7 +60,7 @@ class ReportsController < ApplicationController
       @report_title = l(:field_subproject)
       render :template => "reports/issue_report_details"  
     else
-      @trackers = Tracker.find(:all, :order => 'position')
+      @trackers = @project.trackers
       @versions = @project.versions.sort
       @priorities = Enumeration::get_values('IPRI')
       @categories = @project.issue_categories
index 5b78db7..4b1b1a9 100644 (file)
@@ -190,7 +190,7 @@ module ProjectsHelper
   end if Object.const_defined?(:Magick)
   
   def new_issue_selector
-    trackers = Tracker.find(:all, :order => 'position')
+    trackers = @project.trackers
     # can't use form tag inside helper
     content_tag('form',
       select_tag('tracker_id', '<option></option>' + options_from_collection_for_select(trackers, 'id', 'name'), :onchange => "if (this.value != '') {this.form.submit()}"),
index afe4c1a..c3d6b7b 100644 (file)
@@ -31,9 +31,9 @@ protected
     when 'float'
       begin; !value.blank? && Kernel.Float(value); rescue; errors.add(:value, :activerecord_error_invalid) end
     when 'date'
-      errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.empty?
+      errors.add(:value, :activerecord_error_not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ or value.blank?
     when 'list'
-      errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include? value or value.empty?
+      errors.add(:value, :activerecord_error_inclusion) unless custom_field.possible_values.include?(value) or value.blank?
     end
   end
 end
index 60cca40..f7b01ea 100644 (file)
@@ -40,7 +40,7 @@ class Issue < ActiveRecord::Base
   acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
                 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}                
   
-  validates_presence_of :subject, :description, :priority, :tracker, :author, :status
+  validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
   validates_length_of :subject, :maximum => 255
   validates_inclusion_of :done_ratio, :in => 0..100
   validates_numericality_of :estimated_hours, :allow_nil => true
@@ -106,6 +106,10 @@ class Issue < ActiveRecord::Base
     end
   end
   
+  def validate_on_create
+    errors.add :tracker_id, :activerecord_error_invalid unless project.trackers.include?(tracker)
+  end
+  
   def before_create
     # default assignment based on category
     if assigned_to.nil? && category && category.assigned_to
index afaa049..be46d61 100644 (file)
@@ -24,6 +24,7 @@ class Project < ActiveRecord::Base
   has_many :users, :through => :members
   has_many :custom_values, :dependent => :delete_all, :as => :customized
   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]
   has_many :issue_changes, :through => :issues, :source => :journals
   has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
index 90ef319..6de2a09 100644 (file)
@@ -29,6 +29,10 @@ class Tracker < ActiveRecord::Base
 
   def to_s; name end
   
+  def self.all
+    find(:all, :order => 'position')
+  end
+  
 private
   def check_integrity
     raise "Can't delete tracker" if Issue.find(:first, :conditions => ["tracker_id=?", self.id])
index 3b42ce4..6e61755 100644 (file)
@@ -1,4 +1,4 @@
-<% if authorize_for('projects', 'add_issue') %>
+<% if authorize_for('projects', 'add_issue') && @project.trackers.any? %>
 <h3><%= l(:label_issue_new) %></h3>
 <%= l(:label_tracker) %>: <%= new_issue_selector %>
 <% end %>
index e44911d..3af49fb 100644 (file)
@@ -32,7 +32,7 @@
                </ul>
        </li>
        <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},
-               :class => 'icon-copy', :disabled => !@can[:add] %></li>
+               :class => 'icon-copy', :disabled => !@can[:copy] %></li>
        <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
                                  :class => 'icon-move', :disabled => !@can[:move]  %>
     <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
index aa30f1e..885ccf4 100644 (file)
 <% for @custom_value in @custom_values %>
        <p><%= custom_field_tag_with_label @custom_value %></p>
 <% end %>
+</div>
+
+<% unless @trackers.empty? %>
+<fieldset class="box"><legend><%=l(:label_tracker_plural)%></legend>
+<% @trackers.each do |tracker| %>
+    <label class="floating">
+    <%= check_box_tag 'project[tracker_ids][]', tracker.id, @project.trackers.include?(tracker) %>
+    <%= tracker %>
+    </label>
+<% end %>
+<%= hidden_field_tag 'project[tracker_ids][]', '' %>
+</fieldset>
+<% end %>
 
 <% unless @custom_fields.empty? %>
-<p><label><%=l(:label_custom_field_plural)%></label>
+<fieldset class="box"><legend><%=l(:label_custom_field_plural)%></legend>
 <% for custom_field in @custom_fields %>
+    <label class="floating">
        <%= check_box_tag "custom_field_ids[]", custom_field.id, ((@project.custom_fields.include? custom_field) or custom_field.is_for_all?), (custom_field.is_for_all? ? {:disabled => "disabled"} : {}) %>
-       <%= custom_field.name %>        
-<% end %></p>
+       <%= custom_field.name %>
+       </label>
+<% end %>
+</fieldset>
 <% end %>
 <!--[eoform:project]-->
-</div>
+
 
 <% content_for :header_tags do %>
 <%= javascript_include_tag 'calendar/calendar' %>
index 4818cae..e3ee0b5 100644 (file)
@@ -3,13 +3,13 @@
 <% labelled_tabular_form_for :project, @project, :url => { :action => "add" } do |f| %>
 <%= render :partial => 'form', :locals => { :f => f } %>
 
-<div class="box">
-<p><label><%= l(:label_module_plural) %></label>
+<fieldset class="box"><legend><%= l(:label_module_plural) %></legend>
 <% Redmine::AccessControl.available_project_modules.each do |m| %>
-<%= check_box_tag 'enabled_modules[]', m, @project.module_enabled?(m) %> <%= m.to_s.humanize %>
-<% end %></p>
-</div>
-
+    <label class="floating">
+    <%= check_box_tag 'enabled_modules[]', m, @project.module_enabled?(m) %> <%= m.to_s.humanize %>
+    </label>
+<% end %>
+</fieldset>
 
 <%= submit_tag l(:button_save) %>
 <% end %>
index b296530..95eaf9d 100644 (file)
@@ -1,7 +1,7 @@
 <h2><%=l(:button_move)%></h2>
 
 
-<% form_tag({:action => 'move_issues', :id => @project}, :class => "tabular") do %>
+<% form_tag({:action => 'move_issues', :id => @project}, :class => 'tabular', :id => 'move_form') do %>
 
 <div class="box">
 <p><label><%= l(:label_issue_plural) %> :</label>
 
 <!--[form:issue]-->
 <p><label for="new_project_id"><%=l(:field_project)%> :</label>
-<%= select_tag "new_project_id", options_from_collection_for_select(@projects, "id", "name", @project.id) %></p>
+<%= select_tag "new_project_id",
+               options_from_collection_for_select(@projects, 'id', 'name', @target_project.id),
+               :onchange => remote_function(:url => {:action => 'move_issues' , :id => @project},
+                                            :method => :get,
+                                            :update => 'content',
+                                            :with => "Form.serialize('move_form')") %></p>
 
 <p><label for="new_tracker_id"><%=l(:field_tracker)%> :</label>
 <%= select_tag "new_tracker_id", "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@trackers, "id", "name") %></p>
index 94df0bf..458e797 100644 (file)
@@ -56,7 +56,7 @@
 </div>
 
 <% content_for :sidebar do %>
-    <% if authorize_for('projects', 'add_issue') %>
+    <% if authorize_for('projects', 'add_issue') && @project.trackers.any? %>
     <h3><%= l(:label_issue_new) %></h3>
     <%= l(:label_tracker) %>: <%= new_issue_selector %>
     <% end %>
diff --git a/db/migrate/081_create_projects_trackers.rb b/db/migrate/081_create_projects_trackers.rb
new file mode 100644 (file)
index 0000000..70fea18
--- /dev/null
@@ -0,0 +1,19 @@
+class CreateProjectsTrackers < ActiveRecord::Migration
+  def self.up
+    create_table :projects_trackers, :id => false do |t|
+      t.column :project_id, :integer, :default => 0, :null => false
+      t.column :tracker_id, :integer, :default => 0, :null => false
+    end
+    add_index :projects_trackers, :project_id, :name => :projects_trackers_project_id
+    
+    # Associates all trackers to all projects (as it was before)
+    tracker_ids = Tracker.find(:all).collect(&:id)
+    Project.find(:all).each do |project|
+      project.tracker_ids = tracker_ids
+    end
+  end
+
+  def self.down
+    drop_table :projects_trackers
+  end
+end
diff --git a/test/fixtures/projects_trackers.yml b/test/fixtures/projects_trackers.yml
new file mode 100644 (file)
index 0000000..cfca5b2
--- /dev/null
@@ -0,0 +1,46 @@
+--- 
+projects_trackers_012: 
+  project_id: 4
+  tracker_id: 3
+projects_trackers_001: 
+  project_id: 1
+  tracker_id: 1
+projects_trackers_013: 
+  project_id: 5
+  tracker_id: 1
+projects_trackers_002: 
+  project_id: 1
+  tracker_id: 2
+projects_trackers_014: 
+  project_id: 5
+  tracker_id: 2
+projects_trackers_003: 
+  project_id: 1
+  tracker_id: 3
+projects_trackers_015: 
+  project_id: 5
+  tracker_id: 3
+projects_trackers_004: 
+  project_id: 2
+  tracker_id: 1
+projects_trackers_005: 
+  project_id: 2
+  tracker_id: 2
+projects_trackers_006: 
+  project_id: 2
+  tracker_id: 3
+projects_trackers_007: 
+  project_id: 3
+  tracker_id: 1
+projects_trackers_008: 
+  project_id: 3
+  tracker_id: 2
+projects_trackers_009: 
+  project_id: 3
+  tracker_id: 3
+projects_trackers_010: 
+  project_id: 4
+  tracker_id: 1
+projects_trackers_011: 
+  project_id: 4
+  tracker_id: 2
index e6c06cf..c41adaa 100644 (file)
@@ -22,7 +22,7 @@ require 'projects_controller'
 class ProjectsController; def rescue_action(e) raise e end; end
 
 class ProjectsControllerTest < Test::Unit::TestCase
-  fixtures :projects, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :issue_statuses, :enabled_modules, :enumerations
+  fixtures :projects, :users, :roles, :members, :issues, :journals, :journal_details, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations
 
   def setup
     @controller = ProjectsController.new
index 6ffbe0a..da91dd0 100644 (file)
@@ -18,7 +18,7 @@
 require File.dirname(__FILE__) + '/../test_helper'
 
 class IssueTest < Test::Unit::TestCase
-  fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :time_entries
+  fixtures :projects, :users, :members, :trackers, :projects_trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :time_entries
 
   def test_category_based_assignment
     issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)