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 |