Ability to disable standard fields on a per tracker basis (#1091).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@9912 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -65,6 +65,7 @@ class ContextMenusController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
|
||||
@@ -92,6 +92,48 @@ module IssuesHelper
|
||||
s.html_safe
|
||||
end
|
||||
|
||||
class IssueFieldsRows
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def initialize
|
||||
@left = []
|
||||
@right = []
|
||||
end
|
||||
|
||||
def left(*args)
|
||||
args.any? ? @left << cells(*args) : @left
|
||||
end
|
||||
|
||||
def right(*args)
|
||||
args.any? ? @right << cells(*args) : @right
|
||||
end
|
||||
|
||||
def size
|
||||
@left.size > @right.size ? @left.size : @right.size
|
||||
end
|
||||
|
||||
def to_html
|
||||
html = ''.html_safe
|
||||
blank = content_tag('th', '') + content_tag('td', '')
|
||||
size.times do |i|
|
||||
left = @left[i] || blank
|
||||
right = @right[i] || blank
|
||||
html << content_tag('tr', left + right)
|
||||
end
|
||||
html
|
||||
end
|
||||
|
||||
def cells(label, text, options={})
|
||||
content_tag('th', "#{label}:", options) + content_tag('td', text, options)
|
||||
end
|
||||
end
|
||||
|
||||
def issue_fields_rows
|
||||
r = IssueFieldsRows.new
|
||||
yield r
|
||||
r.to_html
|
||||
end
|
||||
|
||||
def render_custom_fields_rows(issue)
|
||||
return if issue.custom_field_values.empty?
|
||||
ordered_values = []
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
|
||||
module VersionsHelper
|
||||
|
||||
STATUS_BY_CRITERIAS = %w(category tracker status priority author assigned_to)
|
||||
STATUS_BY_CRITERIAS = %w(tracker status priority author assigned_to category)
|
||||
|
||||
def render_issue_status_by(version, criteria)
|
||||
criteria = 'category' unless STATUS_BY_CRITERIAS.include?(criteria)
|
||||
criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria)
|
||||
|
||||
h = Hash.new {|k,v| k[v] = [0, 0]}
|
||||
begin
|
||||
|
||||
+16
-5
@@ -336,6 +336,12 @@ class Issue < ActiveRecord::Base
|
||||
:if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
|
||||
user.allowed_to?(:manage_subtasks, issue.project)}
|
||||
|
||||
def safe_attribute_names(*args)
|
||||
names = super(*args)
|
||||
names -= disabled_core_fields
|
||||
names
|
||||
end
|
||||
|
||||
# Safely sets attributes
|
||||
# Should be called from controllers instead of #attributes=
|
||||
# attr_accessible is too rough because we still want things like
|
||||
@@ -343,21 +349,22 @@ class Issue < ActiveRecord::Base
|
||||
def safe_attributes=(attrs, user=User.current)
|
||||
return unless attrs.is_a?(Hash)
|
||||
|
||||
# User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
|
||||
attrs = delete_unsafe_attributes(attrs, user)
|
||||
return if attrs.empty?
|
||||
attrs = attrs.dup
|
||||
|
||||
# Project and Tracker must be set before since new_statuses_allowed_to depends on it.
|
||||
if p = attrs.delete('project_id')
|
||||
if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
|
||||
if allowed_target_projects(user).collect(&:id).include?(p.to_i)
|
||||
self.project_id = p
|
||||
end
|
||||
end
|
||||
|
||||
if t = attrs.delete('tracker_id')
|
||||
if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
|
||||
self.tracker_id = t
|
||||
end
|
||||
|
||||
attrs = delete_unsafe_attributes(attrs, user)
|
||||
return if attrs.empty?
|
||||
|
||||
if attrs['status_id']
|
||||
unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
|
||||
attrs.delete('status_id')
|
||||
@@ -376,6 +383,10 @@ class Issue < ActiveRecord::Base
|
||||
assign_attributes attrs, :without_protection => true
|
||||
end
|
||||
|
||||
def disabled_core_fields
|
||||
tracker ? tracker.disabled_core_fields : []
|
||||
end
|
||||
|
||||
def done_ratio
|
||||
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
|
||||
status.default_done_ratio
|
||||
|
||||
+15
-2
@@ -213,11 +213,13 @@ class Query < ActiveRecord::Base
|
||||
is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
|
||||
end
|
||||
|
||||
def trackers
|
||||
@trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
|
||||
end
|
||||
|
||||
def available_filters
|
||||
return @available_filters if @available_filters
|
||||
|
||||
trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
|
||||
|
||||
@available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
|
||||
"tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
|
||||
"priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
|
||||
@@ -300,6 +302,11 @@ class Query < ActiveRecord::Base
|
||||
end
|
||||
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
|
||||
end
|
||||
|
||||
Tracker.disabled_core_fields(trackers).each {|field|
|
||||
@available_filters.delete field
|
||||
}
|
||||
|
||||
@available_filters
|
||||
end
|
||||
|
||||
@@ -380,6 +387,12 @@ class Query < ActiveRecord::Base
|
||||
:caption => :label_spent_time
|
||||
)
|
||||
end
|
||||
|
||||
disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
|
||||
@available_columns.reject! {|column|
|
||||
disabled_fields.include?(column.name.to_s)
|
||||
}
|
||||
|
||||
@available_columns
|
||||
end
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
class Tracker < ActiveRecord::Base
|
||||
|
||||
# Other fields should be appended, not inserted!
|
||||
CORE_FIELDS = %w(assigned_to_id category_id fixed_version_id parent_issue_id start_date due_date estimated_hours done_ratio)
|
||||
|
||||
before_destroy :check_integrity
|
||||
has_many :issues
|
||||
has_many :workflows, :dependent => :delete_all do
|
||||
@@ -28,6 +32,8 @@ class Tracker < ActiveRecord::Base
|
||||
has_and_belongs_to_many :custom_fields, :class_name => 'IssueCustomField', :join_table => "#{table_name_prefix}custom_fields_trackers#{table_name_suffix}", :association_foreign_key => 'custom_field_id'
|
||||
acts_as_list
|
||||
|
||||
attr_protected :field_bits
|
||||
|
||||
validates_presence_of :name
|
||||
validates_uniqueness_of :name
|
||||
validates_length_of :name, :maximum => 30
|
||||
@@ -58,6 +64,39 @@ class Tracker < ActiveRecord::Base
|
||||
@issue_statuses = IssueStatus.find_all_by_id(ids).sort
|
||||
end
|
||||
|
||||
def disabled_core_fields
|
||||
i = -1
|
||||
@disabled_core_fields ||= CORE_FIELDS.select { i += 1; (fields_bits || 0) & (2 ** i) != 0}
|
||||
end
|
||||
|
||||
def core_fields
|
||||
CORE_FIELDS - disabled_core_fields
|
||||
end
|
||||
|
||||
def core_fields=(fields)
|
||||
raise ArgumentError.new("Tracker.core_fields takes an array") unless fields.is_a?(Array)
|
||||
|
||||
bits = 0
|
||||
CORE_FIELDS.each_with_index do |field, i|
|
||||
unless fields.include?(field)
|
||||
bits |= 2 ** i
|
||||
end
|
||||
end
|
||||
self.fields_bits = bits
|
||||
@disabled_core_fields = nil
|
||||
core_fields
|
||||
end
|
||||
|
||||
# Returns the fields that are disabled for all the given trackers
|
||||
def self.disabled_core_fields(trackers)
|
||||
trackers.uniq.map(&:disabled_core_fields).reduce(:&)
|
||||
end
|
||||
|
||||
# Returns the fields that are enabled for one tracker at least
|
||||
def self.core_fields(trackers)
|
||||
trackers.uniq.map(&:core_fields).reduce(:|)
|
||||
end
|
||||
|
||||
private
|
||||
def check_integrity
|
||||
raise Exception.new("Can't delete tracker") if Issue.where(:tracker_id => self.id).any?
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% if @safe_attributes.include?('priority_id') -%>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_priority) %></a>
|
||||
<ul>
|
||||
@@ -42,9 +43,10 @@
|
||||
<% end -%>
|
||||
</ul>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% #TODO: allow editing versions when multiple projects %>
|
||||
<% unless @project.nil? || @project.shared_versions.open.empty? -%>
|
||||
<% if @safe_attributes.include?('fixed_version_id') && @project && @project.shared_versions.open.any? -%>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
|
||||
<ul>
|
||||
@@ -57,7 +59,8 @@
|
||||
</ul>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if @assignables.present? -%>
|
||||
|
||||
<% if @safe_attributes.include?('assigned_to_id') && @assignables.present? -%>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
|
||||
<ul>
|
||||
@@ -74,7 +77,8 @@
|
||||
</ul>
|
||||
</li>
|
||||
<% end %>
|
||||
<% unless @project.nil? || @project.issue_categories.empty? -%>
|
||||
|
||||
<% if @safe_attributes.include?('category_id') && @project && @project.issue_categories.any? -%>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_category) %></a>
|
||||
<ul>
|
||||
@@ -88,7 +92,7 @@
|
||||
</li>
|
||||
<% end -%>
|
||||
|
||||
<% if Issue.use_field_for_done_ratio? %>
|
||||
<% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
|
||||
<ul>
|
||||
|
||||
@@ -64,3 +64,5 @@
|
||||
<% end %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% include_calendar_headers_tags %>
|
||||
|
||||
@@ -33,28 +33,40 @@
|
||||
<%= select_tag('issue[status_id]',content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@available_statuses, :id, :name)) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if @safe_attributes.include?('priority_id') -%>
|
||||
<p>
|
||||
<label for='issue_priority_id'><%= l(:field_priority) %></label>
|
||||
<%= select_tag('issue[priority_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(IssuePriority.active, :id, :name)) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if @safe_attributes.include?('assigned_to_id') -%>
|
||||
<p>
|
||||
<label for='issue_assigned_to_id'><%= l(:field_assigned_to) %></label>
|
||||
<%= select_tag('issue[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') +
|
||||
content_tag('option', l(:label_nobody), :value => 'none') +
|
||||
principals_options_for_select(@assignables)) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if @safe_attributes.include?('category_id') -%>
|
||||
<p>
|
||||
<label for='issue_category_id'><%= l(:field_category) %></label>
|
||||
<%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') +
|
||||
content_tag('option', l(:label_none), :value => 'none') +
|
||||
options_from_collection_for_select(@categories, :id, :name)) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if @safe_attributes.include?('fixed_version_id') -%>
|
||||
<p>
|
||||
<label for='issue_fixed_version_id'><%= l(:field_fixed_version) %></label>
|
||||
<%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') +
|
||||
content_tag('option', l(:label_none), :value => 'none') +
|
||||
version_options_for_select(@versions.sort)) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% @custom_fields.each do |custom_field| %>
|
||||
<p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects) %></p>
|
||||
@@ -79,7 +91,8 @@
|
||||
content_tag('option', l(:general_text_No), :value => '0')) %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% if @project && User.current.allowed_to?(:manage_subtasks, @project) %>
|
||||
|
||||
<% if @safe_attributes.include?('parent_issue_id') && @project %>
|
||||
<p>
|
||||
<label for='issue_parent_issue_id'><%= l(:field_parent_issue) %></label>
|
||||
<%= text_field_tag 'issue[parent_issue_id]', '', :size => 10 %>
|
||||
@@ -87,15 +100,22 @@
|
||||
<div id="parent_issue_candidates" class="autocomplete"></div>
|
||||
<%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:project_id => @project) }')" %>
|
||||
<% end %>
|
||||
|
||||
<% if @safe_attributes.include?('start_date') %>
|
||||
<p>
|
||||
<label for='issue_start_date'><%= l(:field_start_date) %></label>
|
||||
<%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if @safe_attributes.include?('due_date') %>
|
||||
<p>
|
||||
<label for='issue_due_date'><%= l(:field_due_date) %></label>
|
||||
<%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %>
|
||||
</p>
|
||||
<% if Issue.use_field_for_done_ratio? %>
|
||||
<% end %>
|
||||
|
||||
<% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>
|
||||
<p>
|
||||
<label for='issue_done_ratio'><%= l(:field_done_ratio) %></label>
|
||||
<%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>
|
||||
|
||||
@@ -32,34 +32,36 @@
|
||||
</p>
|
||||
|
||||
<table class="attributes">
|
||||
<tr>
|
||||
<th class="status"><%=l(:field_status)%>:</th><td class="status"><%= h(@issue.status.name) %></td>
|
||||
<th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= h(@issue.priority.name) %></td>
|
||||
<th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
|
||||
<th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h(@issue.category ? @issue.category.name : "-") %></td>
|
||||
<% if User.current.allowed_to?(:view_time_entries, @project) %>
|
||||
<th class="spent-time"><%=l(:label_spent_time)%>:</th>
|
||||
<td class="spent-time"><%= @issue.total_spent_hours > 0 ? (link_to l_hours(@issue.total_spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
|
||||
<% else %>
|
||||
<th></th>
|
||||
<td></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
|
||||
<% if @issue.estimated_hours %>
|
||||
<th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<%= issue_fields_rows do |rows|
|
||||
rows.left l(:field_status), h(@issue.status.name), :class => 'status'
|
||||
rows.left l(:field_priority), h(@issue.priority.name), :class => 'priority'
|
||||
|
||||
unless @issue.disabled_core_fields.include?('assigned_to_id')
|
||||
rows.left l(:field_assigned_to), avatar(@issue.assigned_to, :size => "14").to_s + (@issue.assigned_to ? link_to_user(@issue.assigned_to) : "-"), :class => 'assigned-to'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('category_id')
|
||||
rows.left l(:field_category), h(@issue.category ? @issue.category.name : "-"), :class => 'category'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('fixed_version_id')
|
||||
rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
|
||||
end
|
||||
|
||||
unless @issue.disabled_core_fields.include?('start_date')
|
||||
rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('due_date')
|
||||
rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('done_ratio')
|
||||
rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress'
|
||||
end
|
||||
unless @issue.disabled_core_fields.include?('estimated_hours')
|
||||
rows.right l(:field_estimated_hours), l_hours(@issue.estimated_hours), :class => 'estimated-hours'
|
||||
end
|
||||
if User.current.allowed_to?(:view_time_entries, @project)
|
||||
rows.right l(:label_spent_time), (@issue.total_spent_hours > 0 ? (link_to l_hours(@issue.total_spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-"), :class => 'spent-time'
|
||||
end
|
||||
end %>
|
||||
<%= render_custom_fields_rows(@issue) %>
|
||||
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
|
||||
</table>
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
<p><%= f.text_field :name, :required => true %></p>
|
||||
<p><%= f.check_box :is_in_roadmap %></p>
|
||||
|
||||
<p>
|
||||
<label><%= l(:field_core_fields) %></label>
|
||||
<% Tracker::CORE_FIELDS.each do |field| %>
|
||||
<label class="block">
|
||||
<%= check_box_tag 'tracker[core_fields][]', field, @tracker.core_fields.include?(field) %>
|
||||
<%= l("field_#{field}".sub(/_id$/, '')) %>
|
||||
</label>
|
||||
<% end %>
|
||||
</p>
|
||||
<%= hidden_field_tag 'tracker[core_fields][]', '' %>
|
||||
|
||||
<% if IssueCustomField.all.any? %>
|
||||
<p>
|
||||
<label><%= l(:label_custom_field_plural) %></label>
|
||||
|
||||
Reference in New Issue
Block a user