Patch #2696 ยป add_subcategory_to_categories.diff
| test/unit/issue_subcategory_test.rb (revision 0) | ||
|---|---|---|
| 1 |
require 'test_helper' |
|
| 2 | ||
| 3 |
class IssueCategorySubcategoryTest < ActiveSupport::TestCase |
|
| 4 |
fixtures :issue_subcategories, :issues |
|
| 5 | ||
| 6 |
def setup |
|
| 7 |
@subcategory = IssueSubcategory.find(1) |
|
| 8 |
end |
|
| 9 | ||
| 10 |
def test_destroy |
|
| 11 |
issue = @subcategory.issues.first |
|
| 12 |
@subcategory.destroy |
|
| 13 |
# Make sure the category was nullified on the issue |
|
| 14 |
assert_nil issue.reload.subcategory |
|
| 15 |
end |
|
| 16 | ||
| 17 |
def test_destroy_with_reassign |
|
| 18 |
issue = @subcategory.issues.first |
|
| 19 |
reassign_to = IssueSubcategory.find(2) |
|
| 20 |
@subcategory.destroy(reassign_to) |
|
| 21 |
# Make sure the issue was reassigned |
|
| 22 |
assert_equal reassign_to, issue.reload.subcategory |
|
| 23 |
end |
|
| 24 |
end |
|
| test/fixtures/issue_subcategories.yml (revision 0) | ||
|---|---|---|
| 1 |
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html |
|
| 2 | ||
| 3 |
one: |
|
| 4 |
issue_category_id: 1 |
|
| 5 |
name: Toner |
|
| 6 | ||
| 7 |
two: |
|
| 8 |
issue_category_id: 1 |
|
| 9 |
name: Paper |
|
| app/helpers/issue_categories_helper.rb (working copy) | ||
|---|---|---|
| 16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | 17 | |
| 18 | 18 |
module IssueCategoriesHelper |
| 19 |
def add_issue_subcategory_link(name) |
|
| 20 |
link_to_function name do |page| |
|
| 21 |
page.insert_html :bottom, :issue_subcategories, :partial => "subcategories", :object => IssueSubcategory.new |
|
| 22 |
end |
|
| 23 |
end |
|
| 19 | 24 |
end |
| app/helpers/issues_helper.rb (working copy) | ||
|---|---|---|
| 80 | 80 |
when 'category_id' |
| 81 | 81 |
c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value |
| 82 | 82 |
c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value |
| 83 |
when 'subcategory_id' |
|
| 84 |
c = IssueSubcategory.find_by_id(detail.value) and value = c.name if detail.value |
|
| 85 |
c = IssueSubcategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value |
|
| 83 | 86 |
when 'fixed_version_id' |
| 84 | 87 |
v = Version.find_by_id(detail.value) and value = v.name if detail.value |
| 85 | 88 |
v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value |
| ... | ... | |
| 150 | 153 |
l(:field_subject), |
| 151 | 154 |
l(:field_assigned_to), |
| 152 | 155 |
l(:field_category), |
| 156 |
l(:field_sub_category), |
|
| 153 | 157 |
l(:field_fixed_version), |
| 154 | 158 |
l(:field_author), |
| 155 | 159 |
l(:field_start_date), |
| ... | ... | |
| 176 | 180 |
issue.subject, |
| 177 | 181 |
issue.assigned_to, |
| 178 | 182 |
issue.category, |
| 183 |
issue.subcategory, |
|
| 179 | 184 |
issue.fixed_version, |
| 180 | 185 |
issue.author.name, |
| 181 | 186 |
format_date(issue.start_date), |
| app/models/issue_category.rb (working copy) | ||
|---|---|---|
| 18 | 18 |
class IssueCategory < ActiveRecord::Base |
| 19 | 19 |
belongs_to :project |
| 20 | 20 |
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id' |
| 21 |
has_many :subcategories, :class_name => 'IssueSubcategory', :foreign_key => 'category_id', :dependent => :nullify |
|
| 21 | 22 |
has_many :issues, :foreign_key => 'category_id', :dependent => :nullify |
| 22 | 23 |
|
| 23 | 24 |
validates_presence_of :name |
| 24 | 25 |
validates_uniqueness_of :name, :scope => [:project_id] |
| 25 | 26 |
validates_length_of :name, :maximum => 30 |
| 27 | ||
| 28 |
# after something changes, we need to save the changes! |
|
| 29 |
after_update :save_sub_categories |
|
| 26 | 30 |
|
| 27 | 31 |
alias :destroy_without_reassign :destroy |
| 28 | 32 |
|
| ... | ... | |
| 34 | 38 |
end |
| 35 | 39 |
destroy_without_reassign |
| 36 | 40 |
end |
| 37 |
|
|
| 41 | ||
| 42 |
# we get passed a hash of key => value pairs that describe |
|
| 43 |
# the new subcategories. we need to set their ID to this |
|
| 44 |
# objects ID |
|
| 45 |
def new_issue_subcategories=(subcategory_elements) |
|
| 46 |
subcategory_elements.each do |subcat| |
|
| 47 |
subcat[:category_id] = :id |
|
| 48 |
subcategories.build(subcat) |
|
| 49 |
end |
|
| 50 |
end |
|
| 51 | ||
| 52 |
# update or delete existing address lines |
|
| 53 |
def existing_issue_subcategories=(subcategory_elements) |
|
| 54 |
subcategories.reject(&:new_record?).each do |line| |
|
| 55 |
attributes = subcategory_elements[line.id.to_s] |
|
| 56 |
if attributes |
|
| 57 |
line.attributes = attributes |
|
| 58 |
else |
|
| 59 |
subcategories.delete(line) |
|
| 60 |
end |
|
| 61 |
end |
|
| 62 |
end |
|
| 63 | ||
| 38 | 64 |
def <=>(category) |
| 39 | 65 |
name <=> category.name |
| 40 | 66 |
end |
| 41 | 67 |
|
| 42 | 68 |
def to_s; name end |
| 69 | ||
| 70 |
protected |
|
| 71 |
#called after a change (add/update/delete) to save to the database |
|
| 72 |
def save_sub_categories |
|
| 73 |
subcategories.each do |subcategory| |
|
| 74 |
subcategory.save(false) |
|
| 75 |
end |
|
| 76 |
end |
|
| 43 | 77 |
end |
| app/models/issue.rb (working copy) | ||
|---|---|---|
| 24 | 24 |
belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id' |
| 25 | 25 |
belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id' |
| 26 | 26 |
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id' |
| 27 |
belongs_to :subcategory, :class_name => 'IssueSubcategory', :foreign_key => 'subcategory_id' |
|
| 27 | 28 | |
| 28 | 29 |
has_many :journals, :as => :journalized, :dependent => :destroy |
| 29 | 30 |
has_many :time_entries, :dependent => :delete_all |
| app/models/issue_subcategory.rb (revision 0) | ||
|---|---|---|
| 1 |
class IssueSubcategory < ActiveRecord::Base |
|
| 2 |
belongs_to :issue_category, :foreign_key => 'category_id' |
|
| 3 |
validates_presence_of :name |
|
| 4 |
validates_uniqueness_of :name, :scope => [:category_id] |
|
| 5 |
validates_length_of :name, :maximum => 30 |
|
| 6 | ||
| 7 |
def <=>(subcategory) |
|
| 8 |
name <=> subcategory.name |
|
| 9 |
end |
|
| 10 | ||
| 11 |
def to_s; name end |
|
| 12 |
end |
|
| app/controllers/issues_controller.rb (working copy) | ||
|---|---|---|
| 21 | 21 |
before_filter :find_issue, :only => [:show, :edit, :reply] |
| 22 | 22 |
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy] |
| 23 | 23 |
before_filter :find_project, :only => [:new, :update_form, :preview] |
| 24 |
before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu] |
|
| 24 |
before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu, :update_issue_subcategories]
|
|
| 25 | 25 |
before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar] |
| 26 | 26 |
accept_key_auth :index, :changes |
| 27 | 27 | |
| ... | ... | |
| 43 | 43 |
helper :timelog |
| 44 | 44 |
include Redmine::Export::PDF |
| 45 | 45 | |
| 46 |
def update_issue_subcategories |
|
| 47 |
results = IssueSubcategory.find(:all, :conditions=>{"category_id"=> params[:category_id]})
|
|
| 48 |
render :update do |page| |
|
| 49 |
page.replace_html 'issue_subcategory', :partial => 'subcategories', :object => results |
|
| 50 |
end |
|
| 51 | ||
| 52 |
end |
|
| 53 | ||
| 46 | 54 |
def index |
| 47 | 55 |
retrieve_query |
| 48 | 56 |
sort_init 'id', 'desc' |
| app/views/issue_categories/_subcategories.rhtml (revision 0) | ||
|---|---|---|
| 1 |
<span class="issue_subcategory"> |
|
| 2 |
<% new_or_existing = subcategories.new_record? ? 'new' : 'existing' %> |
|
| 3 |
<% prefix = "category[#{new_or_existing}_issue_subcategories][]" %>
|
|
| 4 |
<% fields_for prefix, subcategories do |subcategory_form| -%> |
|
| 5 |
<p> |
|
| 6 |
<%= subcategory_form.text_field :name %> |
|
| 7 |
<%= link_to_function l(:button_delete) , "$(this).up('.issue_subcategory').remove()" %>
|
|
| 8 |
</p> |
|
| 9 |
<% end %> |
|
| 10 |
</span> |
|
| app/views/issue_categories/_form.rhtml (working copy) | ||
|---|---|---|
| 3 | 3 |
<div class="box"> |
| 4 | 4 |
<p><%= f.text_field :name, :size => 30, :required => true %></p> |
| 5 | 5 |
<p><%= f.select :assigned_to_id, @project.users.collect{|u| [u.name, u.id]}, :include_blank => true %></p>
|
| 6 |
<p> |
|
| 7 |
<%= add_issue_subcategory_link l(:label_issue_subcategory_new) %> |
|
| 8 |
<div id="issue_subcategories"> |
|
| 9 |
<%= render :partial => 'subcategories', :collection => @category.subcategories %> |
|
| 6 | 10 |
</div> |
| 11 | ||
| 12 |
</p> |
|
| 13 | ||
| 14 |
</div> |
|
| app/views/issues/_subcategories.rhtml (revision 0) | ||
|---|---|---|
| 1 |
<%= subcategories.nil? ? "" : collection_select(:issue, :subcategory_id, subcategories, :id, :name,:include_blank=>true) %> |
|
| app/views/issues/_form.rhtml (working copy) | ||
|---|---|---|
| 1 | 1 |
<% if @issue.new_record? %> |
| 2 | 2 |
<p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
|
| 3 | 3 |
<%= observe_field :issue_tracker_id, :url => { :action => :new },
|
| 4 |
:update => :content,
|
|
| 5 |
:with => "Form.serialize('issue-form')" %>
|
|
| 4 |
:update => :content, |
|
| 5 |
:with => "Form.serialize('issue-form')" %>
|
|
| 6 | 6 |
<hr /> |
| 7 | 7 |
<% end %> |
| 8 | 8 | |
| ... | ... | |
| 26 | 26 |
<p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
|
| 27 | 27 |
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
|
| 28 | 28 |
<% unless @project.issue_categories.empty? %> |
| 29 |
<p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
|
|
| 30 |
<%= prompt_to_remote(l(:label_issue_category_new), |
|
| 31 |
l(:label_issue_category_new), 'category[name]', |
|
| 32 |
{:controller => 'projects', :action => 'add_issue_category', :id => @project},
|
|
| 33 |
:class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
|
|
| 29 |
<p> |
|
| 30 |
<%= f.select(:category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), {:include_blank => true}, {:onchange => "#{remote_function(:url => {:action => "update_issue_subcategories"},
|
|
| 31 |
:with => "'category_id='+value")}"} )%> |
|
| 32 |
<span id="issue_subcategory"> |
|
| 33 |
<% unless @issue.category.nil? %> |
|
| 34 |
<%= f.collection_select(:subcategory_id, @issue.category.subcategories, :id, :name,:include_blank=>true) %> |
|
| 34 | 35 |
<% end %> |
| 35 |
<%= content_tag('p', f.select(:fixed_version_id,
|
|
| 36 |
(@project.versions.sort.collect {|v| [v.name, v.id]}),
|
|
| 37 |
{ :include_blank => true })) unless @project.versions.empty? %>
|
|
| 36 |
</span> |
|
| 37 |
<%= prompt_to_remote(l(:label_issue_category_new), |
|
| 38 |
l(:label_issue_category_new), 'category[name]', |
|
| 39 |
{:controller => 'projects', :action => 'add_issue_category', :id => @project},
|
|
| 40 |
:class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %>
|
|
| 41 |
</p> |
|
| 42 |
<% end %> |
|
| 43 |
<%= content_tag('p', f.select(:fixed_version_id,
|
|
| 44 |
(@project.versions.sort.collect {|v| [v.name, v.id]}),
|
|
| 45 |
{ :include_blank => true })) unless @project.versions.empty? %>
|
|
| 38 | 46 |
</div> |
| 39 | 47 | |
| 40 | 48 |
<div class="splitcontentright"> |
| app/views/issues/show.rhtml (working copy) | ||
|---|---|---|
| 31 | 31 |
<td class="progress"><b><%=l(:field_done_ratio)%>:</b></td><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
|
| 32 | 32 |
</tr> |
| 33 | 33 |
<tr> |
| 34 |
<td class="category"><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td> |
|
| 34 |
<td class="category"><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %> |
|
| 35 |
<%=h @issue.subcategory ? " / "+@issue.subcategory.name : "-" %></td> |
|
| 35 | 36 |
<% if User.current.allowed_to?(:view_time_entries, @project) %> |
| 36 | 37 |
<td class="spent-time"><b><%=l(:label_spent_time)%>:</b></td> |
| 37 | 38 |
<td class="spent-hours"><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}) : "-" %></td>
|
| lang/en.yml (working copy) | ||
|---|---|---|
| 122 | 122 |
field_max_length: Maximum length |
| 123 | 123 |
field_value: Value |
| 124 | 124 |
field_category: Category |
| 125 |
field_subcategory: Subcategory |
|
| 125 | 126 |
field_title: Title |
| 126 | 127 |
field_project: Project |
| 127 | 128 |
field_issue: Issue |
| ... | ... | |
| 321 | 322 |
label_issue_status_plural: Issue statuses |
| 322 | 323 |
label_issue_status_new: New status |
| 323 | 324 |
label_issue_category: Issue category |
| 325 |
label_issue_subcategory: Issue subcategory |
|
| 324 | 326 |
label_issue_category_plural: Issue categories |
| 327 |
label_issue_subcategory_plural: Issue subcategories |
|
| 325 | 328 |
label_issue_category_new: New category |
| 329 |
label_issue_subcategory_new: New subcategory |
|
| 326 | 330 |
label_custom_field: Custom field |
| 327 | 331 |
label_custom_field_plural: Custom fields |
| 328 | 332 |
label_custom_field_new: New custom field |
| db/migrate/20090125190215_create_issue_category_subcategories.rb (revision 0) | ||
|---|---|---|
| 1 |
class CreateIssueCategorySubcategories < ActiveRecord::Migration |
|
| 2 |
def self.up |
|
| 3 |
create_table :issue_subcategories do |t| |
|
| 4 |
t.integer :category_id |
|
| 5 |
t.string :name, :limit => 30, :null => false |
|
| 6 |
t.timestamps |
|
| 7 |
end |
|
| 8 |
end |
|
| 9 | ||
| 10 |
def self.down |
|
| 11 |
drop_table :issue_subcategories |
|
| 12 |
end |
|
| 13 |
end |
|
| db/migrate/20090125191829_add_sub_categories_to_issues.rb (revision 0) | ||
|---|---|---|
| 1 |
class AddSubCategoriesToIssues < ActiveRecord::Migration |
|
| 2 |
def self.up |
|
| 3 |
add_column :issues, :subcategory_id, :integer |
|
| 4 |
end |
|
| 5 | ||
| 6 |
def self.down |
|
| 7 |
remove_column :issues, :subcategory_id |
|
| 8 |
end |
|
| 9 |
end |
|