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:
Jean-Philippe Lang 2012-07-05 12:20:07 +00:00
parent 51a1bf90dd
commit 4cecc1beed
18 changed files with 348 additions and 95 deletions

View File

@ -65,6 +65,7 @@ class ContextMenusController < ApplicationController
end end
end end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false render :layout => false
end end

View File

@ -92,6 +92,48 @@ module IssuesHelper
s.html_safe s.html_safe
end 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) def render_custom_fields_rows(issue)
return if issue.custom_field_values.empty? return if issue.custom_field_values.empty?
ordered_values = [] ordered_values = []

View File

@ -19,10 +19,10 @@
module VersionsHelper 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) 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]} h = Hash.new {|k,v| k[v] = [0, 0]}
begin begin

View File

@ -336,6 +336,12 @@ class Issue < ActiveRecord::Base
:if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) && :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
user.allowed_to?(:manage_subtasks, 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 # Safely sets attributes
# Should be called from controllers instead of #attributes= # Should be called from controllers instead of #attributes=
# attr_accessible is too rough because we still want things like # 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) def safe_attributes=(attrs, user=User.current)
return unless attrs.is_a?(Hash) 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 = attrs.dup
attrs = delete_unsafe_attributes(attrs, user)
return if attrs.empty?
# Project and Tracker must be set before since new_statuses_allowed_to depends on it. # 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) if allowed_target_projects(user).collect(&:id).include?(p.to_i)
self.project_id = p self.project_id = p
end end
end end
if t = attrs.delete('tracker_id') if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
self.tracker_id = t self.tracker_id = t
end end
attrs = delete_unsafe_attributes(attrs, user)
return if attrs.empty?
if attrs['status_id'] if attrs['status_id']
unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i) unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
attrs.delete('status_id') attrs.delete('status_id')
@ -376,6 +383,10 @@ class Issue < ActiveRecord::Base
assign_attributes attrs, :without_protection => true assign_attributes attrs, :without_protection => true
end end
def disabled_core_fields
tracker ? tracker.disabled_core_fields : []
end
def done_ratio def done_ratio
if Issue.use_status_for_done_ratio? && status && status.default_done_ratio if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
status.default_done_ratio status.default_done_ratio

View File

@ -213,11 +213,13 @@ class Query < ActiveRecord::Base
is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
end end
def trackers
@trackers ||= project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
end
def available_filters def available_filters
return @available_filters if @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] } }, @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] } }, "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] } }, "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 end
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
end end
Tracker.disabled_core_fields(trackers).each {|field|
@available_filters.delete field
}
@available_filters @available_filters
end end
@ -380,6 +387,12 @@ class Query < ActiveRecord::Base
:caption => :label_spent_time :caption => :label_spent_time
) )
end 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 @available_columns
end end

View File

@ -16,6 +16,10 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class Tracker < ActiveRecord::Base 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 before_destroy :check_integrity
has_many :issues has_many :issues
has_many :workflows, :dependent => :delete_all do 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' 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 acts_as_list
attr_protected :field_bits
validates_presence_of :name validates_presence_of :name
validates_uniqueness_of :name validates_uniqueness_of :name
validates_length_of :name, :maximum => 30 validates_length_of :name, :maximum => 30
@ -58,6 +64,39 @@ class Tracker < ActiveRecord::Base
@issue_statuses = IssueStatus.find_all_by_id(ids).sort @issue_statuses = IssueStatus.find_all_by_id(ids).sort
end 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 private
def check_integrity def check_integrity
raise Exception.new("Can't delete tracker") if Issue.where(:tracker_id => self.id).any? raise Exception.new("Can't delete tracker") if Issue.where(:tracker_id => self.id).any?

View File

@ -33,6 +33,7 @@
</li> </li>
<% end %> <% end %>
<% if @safe_attributes.include?('priority_id') -%>
<li class="folder"> <li class="folder">
<a href="#" class="submenu"><%= l(:field_priority) %></a> <a href="#" class="submenu"><%= l(:field_priority) %></a>
<ul> <ul>
@ -42,9 +43,10 @@
<% end -%> <% end -%>
</ul> </ul>
</li> </li>
<% end %>
<% #TODO: allow editing versions when multiple projects %> <% #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"> <li class="folder">
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a> <a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
<ul> <ul>
@ -57,7 +59,8 @@
</ul> </ul>
</li> </li>
<% end %> <% end %>
<% if @assignables.present? -%>
<% if @safe_attributes.include?('assigned_to_id') && @assignables.present? -%>
<li class="folder"> <li class="folder">
<a href="#" class="submenu"><%= l(:field_assigned_to) %></a> <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
<ul> <ul>
@ -74,7 +77,8 @@
</ul> </ul>
</li> </li>
<% end %> <% end %>
<% unless @project.nil? || @project.issue_categories.empty? -%>
<% if @safe_attributes.include?('category_id') && @project && @project.issue_categories.any? -%>
<li class="folder"> <li class="folder">
<a href="#" class="submenu"><%= l(:field_category) %></a> <a href="#" class="submenu"><%= l(:field_category) %></a>
<ul> <ul>
@ -88,7 +92,7 @@
</li> </li>
<% end -%> <% end -%>
<% if Issue.use_field_for_done_ratio? %> <% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>
<li class="folder"> <li class="folder">
<a href="#" class="submenu"><%= l(:field_done_ratio) %></a> <a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
<ul> <ul>

View File

@ -64,3 +64,5 @@
<% end %> <% end %>
<% end %> <% end %>
<% include_calendar_headers_tags %>

View File

@ -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)) %> <%= select_tag('issue[status_id]',content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(@available_statuses, :id, :name)) %>
</p> </p>
<% end %> <% end %>
<% if @safe_attributes.include?('priority_id') -%>
<p> <p>
<label for='issue_priority_id'><%= l(:field_priority) %></label> <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)) %> <%= select_tag('issue[priority_id]', content_tag('option', l(:label_no_change_option), :value => '') + options_from_collection_for_select(IssuePriority.active, :id, :name)) %>
</p> </p>
<% end %>
<% if @safe_attributes.include?('assigned_to_id') -%>
<p> <p>
<label for='issue_assigned_to_id'><%= l(:field_assigned_to) %></label> <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 => '') + <%= select_tag('issue[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_nobody), :value => 'none') + content_tag('option', l(:label_nobody), :value => 'none') +
principals_options_for_select(@assignables)) %> principals_options_for_select(@assignables)) %>
</p> </p>
<% end %>
<% if @safe_attributes.include?('category_id') -%>
<p> <p>
<label for='issue_category_id'><%= l(:field_category) %></label> <label for='issue_category_id'><%= l(:field_category) %></label>
<%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') + <%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_none), :value => 'none') + content_tag('option', l(:label_none), :value => 'none') +
options_from_collection_for_select(@categories, :id, :name)) %> options_from_collection_for_select(@categories, :id, :name)) %>
</p> </p>
<% end %>
<% if @safe_attributes.include?('fixed_version_id') -%>
<p> <p>
<label for='issue_fixed_version_id'><%= l(:field_fixed_version) %></label> <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 => '') + <%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_none), :value => 'none') + content_tag('option', l(:label_none), :value => 'none') +
version_options_for_select(@versions.sort)) %> version_options_for_select(@versions.sort)) %>
</p> </p>
<% end %>
<% @custom_fields.each do |custom_field| %> <% @custom_fields.each do |custom_field| %>
<p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects) %></p> <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')) %> content_tag('option', l(:general_text_No), :value => '0')) %>
</p> </p>
<% end %> <% end %>
<% if @project && User.current.allowed_to?(:manage_subtasks, @project) %>
<% if @safe_attributes.include?('parent_issue_id') && @project %>
<p> <p>
<label for='issue_parent_issue_id'><%= l(:field_parent_issue) %></label> <label for='issue_parent_issue_id'><%= l(:field_parent_issue) %></label>
<%= text_field_tag 'issue[parent_issue_id]', '', :size => 10 %> <%= text_field_tag 'issue[parent_issue_id]', '', :size => 10 %>
@ -87,15 +100,22 @@
<div id="parent_issue_candidates" class="autocomplete"></div> <div id="parent_issue_candidates" class="autocomplete"></div>
<%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:project_id => @project) }')" %> <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:project_id => @project) }')" %>
<% end %> <% end %>
<% if @safe_attributes.include?('start_date') %>
<p> <p>
<label for='issue_start_date'><%= l(:field_start_date) %></label> <label for='issue_start_date'><%= l(:field_start_date) %></label>
<%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %> <%= text_field_tag 'issue[start_date]', '', :size => 10 %><%= calendar_for('issue_start_date') %>
</p> </p>
<% end %>
<% if @safe_attributes.include?('due_date') %>
<p> <p>
<label for='issue_due_date'><%= l(:field_due_date) %></label> <label for='issue_due_date'><%= l(:field_due_date) %></label>
<%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %> <%= text_field_tag 'issue[due_date]', '', :size => 10 %><%= calendar_for('issue_due_date') %>
</p> </p>
<% if Issue.use_field_for_done_ratio? %> <% end %>
<% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>
<p> <p>
<label for='issue_done_ratio'><%= l(:field_done_ratio) %></label> <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] }) %> <%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>

View File

@ -32,34 +32,36 @@
</p> </p>
<table class="attributes"> <table class="attributes">
<tr> <%= issue_fields_rows do |rows|
<th class="status"><%=l(:field_status)%>:</th><td class="status"><%= h(@issue.status.name) %></td> rows.left l(:field_status), h(@issue.status.name), :class => 'status'
<th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td> rows.left l(:field_priority), h(@issue.priority.name), :class => 'priority'
</tr>
<tr> unless @issue.disabled_core_fields.include?('assigned_to_id')
<th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= h(@issue.priority.name) %></td> 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'
<th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td> end
</tr> unless @issue.disabled_core_fields.include?('category_id')
<tr> rows.left l(:field_category), h(@issue.category ? @issue.category.name : "-"), :class => 'category'
<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> end
<th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td> unless @issue.disabled_core_fields.include?('fixed_version_id')
</tr> rows.left l(:field_fixed_version), (@issue.fixed_version ? link_to_version(@issue.fixed_version) : "-"), :class => 'fixed-version'
<tr> end
<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) %> unless @issue.disabled_core_fields.include?('start_date')
<th class="spent-time"><%=l(:label_spent_time)%>:</th> rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
<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> end
<% else %> unless @issue.disabled_core_fields.include?('due_date')
<th></th> rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
<td></td> end
<% end %> unless @issue.disabled_core_fields.include?('done_ratio')
</tr> rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress'
<tr> end
<th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td> unless @issue.disabled_core_fields.include?('estimated_hours')
<% if @issue.estimated_hours %> rows.right l(:field_estimated_hours), l_hours(@issue.estimated_hours), :class => 'estimated-hours'
<th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td> end
<% end %> if User.current.allowed_to?(:view_time_entries, @project)
</tr> 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) %> <%= render_custom_fields_rows(@issue) %>
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %> <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
</table> </table>

View File

@ -6,6 +6,17 @@
<p><%= f.text_field :name, :required => true %></p> <p><%= f.text_field :name, :required => true %></p>
<p><%= f.check_box :is_in_roadmap %></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? %> <% if IssueCustomField.all.any? %>
<p> <p>
<label><%= l(:label_custom_field_plural) %></label> <label><%= l(:label_custom_field_plural) %></label>

View File

@ -328,6 +328,7 @@ en:
field_repository_is_default: Main repository field_repository_is_default: Main repository
field_multiple: Multiple values field_multiple: Multiple values
field_ldap_filter: LDAP filter field_ldap_filter: LDAP filter
field_core_fields: Standard fields
setting_app_title: Application title setting_app_title: Application title
setting_app_subtitle: Application subtitle setting_app_subtitle: Application subtitle

View File

@ -327,6 +327,7 @@ fr:
field_repository_is_default: Dépôt principal field_repository_is_default: Dépôt principal
field_multiple: Valeurs multiples field_multiple: Valeurs multiples
field_ldap_filter: Filtre LDAP field_ldap_filter: Filtre LDAP
field_core_fields: Champs standards
setting_app_title: Titre de l'application setting_app_title: Titre de l'application
setting_app_subtitle: Sous-titre de l'application setting_app_subtitle: Sous-titre de l'application

View File

@ -0,0 +1,9 @@
class AddTrackersFieldsBits < ActiveRecord::Migration
def self.up
add_column :trackers, :fields_bits, :integer, :default => 0
end
def self.down
remove_column :trackers, :fields_bits
end
end

View File

@ -18,6 +18,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require 'iconv' require 'iconv'
require 'tcpdf'
require 'fpdf/chinese' require 'fpdf/chinese'
require 'fpdf/japanese' require 'fpdf/japanese'
require 'fpdf/korean' require 'fpdf/korean'
@ -497,14 +498,13 @@ module Redmine
# Returns a PDF string of a single issue # Returns a PDF string of a single issue
def issue_to_pdf(issue) def issue_to_pdf(issue)
pdf = ITCPDF.new(current_language) pdf = ITCPDF.new(current_language)
pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}") pdf.SetTitle("#{issue.project} - #{issue.tracker} ##{issue.id}")
pdf.alias_nb_pages pdf.alias_nb_pages
pdf.footer_date = format_date(Date.today) pdf.footer_date = format_date(Date.today)
pdf.AddPage pdf.AddPage
pdf.SetFontStyle('B',11) pdf.SetFontStyle('B',11)
buf = "#{issue.project} - #{issue.tracker} # #{issue.id}" buf = "#{issue.project} - #{issue.tracker} ##{issue.id}"
pdf.RDMMultiCell(190, 5, buf) pdf.RDMMultiCell(190, 5, buf)
pdf.Ln
pdf.SetFontStyle('',8) pdf.SetFontStyle('',8)
base_x = pdf.GetX base_x = pdf.GetX
i = 1 i = 1
@ -514,62 +514,54 @@ module Redmine
pdf.RDMMultiCell(190 - i, 5, buf) pdf.RDMMultiCell(190 - i, 5, buf)
i += 1 if i < 35 i += 1 if i < 35
end end
pdf.SetFontStyle('B',11)
pdf.RDMMultiCell(190 - i, 5, issue.subject.to_s)
pdf.SetFontStyle('',8)
pdf.RDMMultiCell(190, 5, "#{format_time(issue.created_on)} - #{issue.author}")
pdf.Ln pdf.Ln
pdf.SetFontStyle('B',9) left = []
pdf.RDMCell(35,5, l(:field_status) + ":","LT") left << [l(:field_status), issue.status]
pdf.SetFontStyle('',9) left << [l(:field_priority), issue.priority]
pdf.RDMCell(60,5, issue.status.to_s,"RT") left << [l(:field_assigned_to), issue.assigned_to] unless issue.disabled_core_fields.include?('assigned_to_id')
pdf.SetFontStyle('B',9) left << [l(:field_category), issue.category] unless issue.disabled_core_fields.include?('category_id')
pdf.RDMCell(35,5, l(:field_priority) + ":","LT") left << [l(:field_fixed_version), issue.fixed_version] unless issue.disabled_core_fields.include?('fixed_version_id')
pdf.SetFontStyle('',9)
pdf.RDMCell(60,5, issue.priority.to_s,"RT")
pdf.Ln
pdf.SetFontStyle('B',9) right = []
pdf.RDMCell(35,5, l(:field_author) + ":","L") right << [l(:field_start_date), format_date(issue.start_date)] unless issue.disabled_core_fields.include?('start_date')
pdf.SetFontStyle('',9) right << [l(:field_due_date), format_date(issue.due_date)] unless issue.disabled_core_fields.include?('due_date')
pdf.RDMCell(60,5, issue.author.to_s,"R") right << [l(:field_done_ratio), "#{issue.done_ratio}%"] unless issue.disabled_core_fields.include?('done_ratio')
pdf.SetFontStyle('B',9) right << [l(:field_estimated_hours), l_hours(issue.estimated_hours)] unless issue.disabled_core_fields.include?('estimated_hours')
pdf.RDMCell(35,5, l(:field_category) + ":","L") right << [l(:label_spent_time), l_hours(issue.total_spent_hours)] if User.current.allowed_to?(:view_time_entries, issue.project)
pdf.SetFontStyle('',9)
pdf.RDMCell(60,5, issue.category.to_s,"R")
pdf.Ln
pdf.SetFontStyle('B',9) rows = left.size > right.size ? left.size : right.size
pdf.RDMCell(35,5, l(:field_created_on) + ":","L") while left.size < rows
pdf.SetFontStyle('',9) left << nil
pdf.RDMCell(60,5, format_date(issue.created_on),"R") end
pdf.SetFontStyle('B',9) while right.size < rows
pdf.RDMCell(35,5, l(:field_assigned_to) + ":","L") right << nil
pdf.SetFontStyle('',9)
pdf.RDMCell(60,5, issue.assigned_to.to_s,"R")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.RDMCell(35,5, l(:field_updated_on) + ":","LB")
pdf.SetFontStyle('',9)
pdf.RDMCell(60,5, format_date(issue.updated_on),"RB")
pdf.SetFontStyle('B',9)
pdf.RDMCell(35,5, l(:field_due_date) + ":","LB")
pdf.SetFontStyle('',9)
pdf.RDMCell(60,5, format_date(issue.due_date),"RB")
pdf.Ln
for custom_value in issue.custom_field_values
pdf.SetFontStyle('B',9)
pdf.RDMCell(35,5, custom_value.custom_field.name + ":","L")
pdf.SetFontStyle('',9)
pdf.RDMMultiCell(155,5, (show_value custom_value),"R")
end end
y0 = pdf.GetY half = (issue.custom_field_values.size / 2.0).ceil
issue.custom_field_values.each_with_index do |custom_value, i|
(i < half ? left : right) << [custom_value.custom_field.name, show_value(custom_value)]
end
pdf.SetFontStyle('B',9) rows = left.size > right.size ? left.size : right.size
pdf.RDMCell(35,5, l(:field_subject) + ":","LT") rows.times do |i|
pdf.SetFontStyle('',9) item = left[i]
pdf.RDMMultiCell(155,5, issue.subject,"RT") pdf.SetFontStyle('B',9)
pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY) pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
pdf.SetFontStyle('',9)
pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
item = right[i]
pdf.SetFontStyle('B',9)
pdf.RDMCell(35,5, item ? "#{item.first}:" : "", i == 0 ? "LT" : "L")
pdf.SetFontStyle('',9)
pdf.RDMCell(60,5, item ? item.last.to_s : "", i == 0 ? "RT" : "R")
pdf.Ln
end
pdf.SetFontStyle('B',9) pdf.SetFontStyle('B',9)
pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1) pdf.RDMCell(35+155, 5, l(:field_description), "LRT", 1)

View File

@ -64,10 +64,21 @@ class TrackersControllerTest < ActionController::TestCase
tracker = Tracker.first(:order => 'id DESC') tracker = Tracker.first(:order => 'id DESC')
assert_equal 'New tracker', tracker.name assert_equal 'New tracker', tracker.name
assert_equal [1], tracker.project_ids.sort assert_equal [1], tracker.project_ids.sort
assert_equal Tracker::CORE_FIELDS, tracker.core_fields
assert_equal [1, 6], tracker.custom_field_ids.sort assert_equal [1, 6], tracker.custom_field_ids.sort
assert_equal 0, tracker.workflows.count assert_equal 0, tracker.workflows.count
end end
def create_with_disabled_core_fields
assert_difference 'Tracker.count' do
post :create, :tracker => { :name => 'New tracker', :core_fields => ['assigned_to_id', 'fixed_version_id', ''] }
end
assert_redirected_to :action => 'index'
tracker = Tracker.first(:order => 'id DESC')
assert_equal 'New tracker', tracker.name
assert_equal %w(assigned_to_id fixed_version_id), tracker.core_fields
end
def test_create_new_with_workflow_copy def test_create_new_with_workflow_copy
assert_difference 'Tracker.count' do assert_difference 'Tracker.count' do
post :create, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1 post :create, :tracker => { :name => 'New tracker' }, :copy_workflow_from => 1
@ -107,6 +118,24 @@ class TrackersControllerTest < ActionController::TestCase
:type => 'hidden'} :type => 'hidden'}
end end
def test_edit_should_check_core_fields
tracker = Tracker.find(1)
tracker.core_fields = %w(assigned_to_id fixed_version_id)
tracker.save!
get :edit, :id => 1
assert_response :success
assert_template 'edit'
assert_select 'input[name=?][value=assigned_to_id][checked=checked]', 'tracker[core_fields][]'
assert_select 'input[name=?][value=fixed_version_id][checked=checked]', 'tracker[core_fields][]'
assert_select 'input[name=?][value=category_id]', 'tracker[core_fields][]'
assert_select 'input[name=?][value=category_id][checked=checked]', 'tracker[core_fields][]', 0
assert_select 'input[name=?][value=][type=hidden]', 'tracker[core_fields][]'
end
def test_update def test_update
put :update, :id => 1, :tracker => { :name => 'Renamed', put :update, :id => 1, :tracker => { :name => 'Renamed',
:project_ids => ['1', '2', ''] } :project_ids => ['1', '2', ''] }
@ -121,6 +150,12 @@ class TrackersControllerTest < ActionController::TestCase
assert Tracker.find(1).project_ids.empty? assert Tracker.find(1).project_ids.empty?
end end
def test_update_without_core_fields
put :update, :id => 1, :tracker => { :name => 'Renamed', :core_fields => [''] }
assert_redirected_to :action => 'index'
assert Tracker.find(1).core_fields.empty?
end
def test_update_with_failure def test_update_with_failure
put :update, :id => 1, :tracker => { :name => '' } put :update, :id => 1, :tracker => { :name => '' }
assert_response :success assert_response :success

View File

@ -403,6 +403,52 @@ class IssueTest < ActiveSupport::TestCase
assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id) assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
end end
def test_safe_attributes_should_not_include_disabled_field
tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
issue = Issue.new(:tracker => tracker)
assert_include 'tracker_id', issue.safe_attribute_names
assert_include 'status_id', issue.safe_attribute_names
assert_include 'subject', issue.safe_attribute_names
assert_include 'description', issue.safe_attribute_names
assert_include 'custom_field_values', issue.safe_attribute_names
assert_include 'custom_fields', issue.safe_attribute_names
assert_include 'lock_version', issue.safe_attribute_names
tracker.core_fields.each do |field|
assert_include field, issue.safe_attribute_names
end
tracker.disabled_core_fields.each do |field|
assert_not_include field, issue.safe_attribute_names
end
end
def test_safe_attributes_should_ignore_disabled_fields
tracker = Tracker.find(1)
tracker.core_fields = %w(assigned_to_id due_date)
tracker.save!
issue = Issue.new(:tracker => tracker)
issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
assert_nil issue.start_date
assert_equal Date.parse('2012-07-14'), issue.due_date
end
def test_safe_attributes_should_accept_target_tracker_fields
source = Tracker.find(1)
source.core_fields = []
source.save!
target = Tracker.find(2)
target.core_fields = %w(assigned_to_id due_date)
target.save!
issue = Issue.new(:tracker => source)
issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
assert_equal target, issue.tracker
assert_equal Date.parse('2012-07-14'), issue.due_date
end
def test_copy def test_copy
issue = Issue.new.copy_from(1) issue = Issue.new.copy_from(1)
assert issue.copy? assert issue.copy?

View File

@ -59,6 +59,30 @@ class TrackerTest < ActiveSupport::TestCase
assert_equal [], Tracker.new.issue_statuses assert_equal [], Tracker.new.issue_statuses
end end
def test_core_fields_should_be_enabled_by_default
tracker = Tracker.new
assert_equal Tracker::CORE_FIELDS, tracker.core_fields
assert_equal [], tracker.disabled_core_fields
end
def test_core_fields
tracker = Tracker.new
tracker.core_fields = %w(assigned_to_id due_date)
assert_equal %w(assigned_to_id due_date), tracker.core_fields
assert_equal Tracker::CORE_FIELDS - %w(assigned_to_id due_date), tracker.disabled_core_fields
end
def test_core_fields_should_return_fields_enabled_for_any_tracker
trackers = []
trackers << Tracker.new(:core_fields => %w(assigned_to_id due_date))
trackers << Tracker.new(:core_fields => %w(assigned_to_id done_ratio))
trackers << Tracker.new(:core_fields => [])
assert_equal %w(assigned_to_id due_date done_ratio), Tracker.core_fields(trackers)
assert_equal Tracker::CORE_FIELDS - %w(assigned_to_id due_date done_ratio), Tracker.disabled_core_fields(trackers)
end
def test_sort_should_sort_by_position def test_sort_should_sort_by_position
a = Tracker.new(:name => 'Tracker A', :position => 2) a = Tracker.new(:name => 'Tracker A', :position => 2)
b = Tracker.new(:name => 'Tracker B', :position => 1) b = Tracker.new(:name => 'Tracker B', :position => 1)