]> source.dussan.org Git - redmine.git/commitdiff
REST API for issue categories (#9553).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 20 Nov 2011 17:09:01 +0000 (17:09 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 20 Nov 2011 17:09:01 +0000 (17:09 +0000)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@7882 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/controllers/issue_categories_controller.rb
app/models/issue_category.rb
app/views/issue_categories/edit.html.erb
app/views/issue_categories/index.api.rsb [new file with mode: 0644]
app/views/issue_categories/new.html.erb
app/views/issue_categories/show.api.rsb [new file with mode: 0644]
lib/redmine.rb
test/functional/issue_categories_controller_test.rb
test/integration/api_test/issue_categories_test.rb [new file with mode: 0644]
test/integration/routing_test.rb

index b0d11dd8fd66330d1205564113a86e7c252c034e..c83246f0b9d003f46e2128f7f6d49d7c306c9bea 100644 (file)
 class IssueCategoriesController < ApplicationController
   menu_item :settings
   model_object IssueCategory
-  before_filter :find_model_object, :except => [:new, :create]
-  before_filter :find_project_from_association, :except => [:new, :create]
-  before_filter :find_project, :only => [:new, :create]
+  before_filter :find_model_object, :except => [:index, :new, :create]
+  before_filter :find_project_from_association, :except => [:index, :new, :create]
+  before_filter :find_project, :only => [:index, :new, :create]
   before_filter :authorize
+  accept_api_auth :index, :show, :create, :update, :destroy
+  
+  def index
+    respond_to do |format|
+      format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
+      format.api { @categories = @project.issue_categories.all }
+    end
+  end
+
+  def show
+    respond_to do |format|
+      format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project }
+      format.api
+    end
+  end
 
   def new
-    @category = @project.issue_categories.build(params[:category])
+    @category = @project.issue_categories.build(params[:issue_category])
   end
 
   verify :method => :post, :only => :create
   def create
-    @category = @project.issue_categories.build(params[:category])
+    @category = @project.issue_categories.build(params[:issue_category])
     if @category.save
       respond_to do |format|
         format.html do
@@ -42,6 +57,7 @@ class IssueCategoriesController < ApplicationController
             content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
           }
         end
+        format.api { render :action => 'show', :status => :created, :location => issue_category_path(@category) }
       end
     else
       respond_to do |format|
@@ -49,6 +65,7 @@ class IssueCategoriesController < ApplicationController
         format.js do
           render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
         end
+        format.api { render_validation_errors(@category) }
       end
     end
   end
@@ -58,26 +75,35 @@ class IssueCategoriesController < ApplicationController
 
   verify :method => :put, :only => :update
   def update
-    if @category.update_attributes(params[:category])
-      flash[:notice] = l(:notice_successful_update)
-      redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
+    if @category.update_attributes(params[:issue_category])
+      respond_to do |format|
+        format.html {
+          flash[:notice] = l(:notice_successful_update)
+          redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
+        }
+        format.api { head :ok }
+      end
     else
-      render :action => 'edit'
+      respond_to do |format|
+        format.html { render :action => 'edit' }
+        format.api { render_validation_errors(@category) }
+      end
     end
   end
 
   verify :method => :delete, :only => :destroy
   def destroy
     @issue_count = @category.issues.size
-    if @issue_count == 0
-      # No issue assigned to this category
-      @category.destroy
-      redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
-      return
-    elsif params[:todo]
-      reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
+    if @issue_count == 0 || params[:todo] || api_request? 
+      reassign_to = nil
+      if params[:reassign_to_id] && (params[:todo] == 'reassign' || params[:todo].blank?)
+        reassign_to = @project.issue_categories.find_by_id(params[:reassign_to_id])
+      end
       @category.destroy(reassign_to)
-      redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories'
+      respond_to do |format|
+        format.html { redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'categories' }
+        format.api { head :ok }
+      end
       return
     end
     @categories = @project.issue_categories - [@category]
index d6db3ed8dcb146748c49ca27b673c4eaf6fbcd8c..b05f58dd4b27c6cbf764d177ad1abdbecf0fcc67 100644 (file)
@@ -23,6 +23,8 @@ class IssueCategory < ActiveRecord::Base
   validates_presence_of :name
   validates_uniqueness_of :name, :scope => [:project_id]
   validates_length_of :name, :maximum => 30
+  
+  attr_protected :project_id
 
   named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
 
index 1e1a8fe7d017fa05149c2f26a5f1b5b4c1dedff1..917429640aca3f8da37f21e392b151a603ae7381 100644 (file)
@@ -1,6 +1,6 @@
 <h2><%=l(:label_issue_category)%></h2>
 
-<% labelled_tabular_form_for :category, @category, :url => issue_category_path(@category), :html => {:method => :put} do |f| %>
+<% labelled_tabular_form_for :issue_category, @category, :url => issue_category_path(@category), :html => {:method => :put} do |f| %>
 <%= render :partial => 'issue_categories/form', :locals => { :f => f } %>
 <%= submit_tag l(:button_save) %>
 <% end %>
diff --git a/app/views/issue_categories/index.api.rsb b/app/views/issue_categories/index.api.rsb
new file mode 100644 (file)
index 0000000..685d8a5
--- /dev/null
@@ -0,0 +1,10 @@
+api.array :issue_categories, api_meta(:total_count => @categories.size) do
+  @categories.each do |category|
+    api.issue_category do
+      api.id category.id
+      api.project(:id => category.project_id, :name => category.project.name) unless category.project.nil?
+      api.name category.name
+      api.assigned_to(:id => category.assigned_to_id, :name => category.assigned_to.name) unless category.assigned_to.nil?
+    end
+  end
+end
index ccd88c5cdd818581efbc50840d4c1cc53b3772e4..f99c9b35823025fae88771c3e716cd383f2b04ed 100644 (file)
@@ -1,6 +1,6 @@
 <h2><%=l(:label_issue_category_new)%></h2>
 
-<% labelled_tabular_form_for :category, @category, :url => project_issue_categories_path(@project) do |f| %>
+<% labelled_tabular_form_for :issue_category, @category, :url => project_issue_categories_path(@project) do |f| %>
 <%= render :partial => 'issue_categories/form', :locals => { :f => f } %>
 <%= submit_tag l(:button_create) %>
 <% end %>
diff --git a/app/views/issue_categories/show.api.rsb b/app/views/issue_categories/show.api.rsb
new file mode 100644 (file)
index 0000000..cefa7c8
--- /dev/null
@@ -0,0 +1,6 @@
+api.issue_category do
+  api.id @category.id
+  api.project(:id => @category.project_id, :name => @category.project.name) unless @category.project.nil?
+  api.name @category.name
+  api.assigned_to(:id => @category.assigned_to_id, :name => @category.assigned_to.name) unless @category.assigned_to.nil?
+end
index 746b623ec8cc2894c4db8d4b58bb2566ccc6c1b6..129b393158b6fbcb870df471aa74fbce930024d3 100644 (file)
@@ -58,7 +58,7 @@ Redmine::AccessControl.map do |map|
 
   map.project_module :issue_tracking do |map|
     # Issue categories
-    map.permission :manage_categories, {:projects => :settings, :issue_categories => [:new, :create, :edit, :update, :destroy]}, :require => :member
+    map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member
     # Issues
     map.permission :view_issues, {:issues => [:index, :show],
                                   :auto_complete => [:issues],
index c9dbbe1f80ede7d39f2ed031704641a60d2a0500..221d5d1cc3e2383fc86e22eb241f2a559e866199 100644 (file)
@@ -42,7 +42,7 @@ class IssueCategoriesControllerTest < ActionController::TestCase
   def test_create
     @request.session[:user_id] = 2 # manager
     assert_difference 'IssueCategory.count' do
-      post :create, :project_id => '1', :category => {:name => 'New category'}
+      post :create, :project_id => '1', :issue_category => {:name => 'New category'}
     end
     assert_redirected_to '/projects/ecookbook/settings/categories'
     category = IssueCategory.find_by_name('New category')
@@ -52,7 +52,7 @@ class IssueCategoriesControllerTest < ActionController::TestCase
 
   def test_create_failure
     @request.session[:user_id] = 2
-    post :create, :project_id => '1', :category => {:name => ''}
+    post :create, :project_id => '1', :issue_category => {:name => ''}
     assert_response :success
     assert_template 'new'
   end
@@ -66,20 +66,20 @@ class IssueCategoriesControllerTest < ActionController::TestCase
 
   def test_update
     assert_no_difference 'IssueCategory.count' do
-      put :update, :id => 2, :category => { :name => 'Testing' }
+      put :update, :id => 2, :issue_category => { :name => 'Testing' }
     end
     assert_redirected_to '/projects/ecookbook/settings/categories'
     assert_equal 'Testing', IssueCategory.find(2).name
   end
 
   def test_update_failure
-    put :update, :id => 2, :category => { :name => '' }
+    put :update, :id => 2, :issue_category => { :name => '' }
     assert_response :success
     assert_template 'edit'
   end
 
   def test_update_not_found
-    put :update, :id => 97, :category => { :name => 'Testing' }
+    put :update, :id => 97, :issue_category => { :name => 'Testing' }
     assert_response 404
   end
 
diff --git a/test/integration/api_test/issue_categories_test.rb b/test/integration/api_test/issue_categories_test.rb
new file mode 100644 (file)
index 0000000..df5967a
--- /dev/null
@@ -0,0 +1,127 @@
+# Redmine - project management software
+# Copyright (C) 2006-2011  Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+require File.expand_path('../../../test_helper', __FILE__)
+
+class ApiTest::IssueCategoriesTest < ActionController::IntegrationTest
+  fixtures :projects, :users, :issue_categories, :issues,
+           :roles,
+           :member_roles,
+           :members,
+           :enabled_modules
+
+  def setup
+    Setting.rest_api_enabled = '1'
+  end
+
+  context "GET /projects/:project_id/issue_categories.xml" do
+    should "return issue categories" do
+      get '/projects/1/issue_categories.xml', {}, :authorization => credentials('jsmith')
+      assert_response :success
+      assert_equal 'application/xml', @response.content_type
+      assert_tag :tag => 'issue_categories',
+        :child => {:tag => 'issue_category', :child => {:tag => 'id', :content => '2'}}
+    end
+  end
+
+  context "GET /issue_categories/2.xml" do
+    should "return requested issue category" do
+      get '/issue_categories/2.xml', {}, :authorization => credentials('jsmith')
+      assert_response :success
+      assert_equal 'application/xml', @response.content_type
+      assert_tag :tag => 'issue_category',
+        :child => {:tag => 'id', :content => '2'}
+    end
+  end
+
+  context "POST /projects/:project_id/issue_categories.xml" do
+    should "return create issue category" do
+      assert_difference 'IssueCategory.count' do
+        post '/projects/1/issue_categories.xml', {:issue_category => {:name => 'API'}}, :authorization => credentials('jsmith')
+      end
+      assert_response :created
+      assert_equal 'application/xml', @response.content_type
+
+      category = IssueCategory.first(:order => 'id DESC')
+      assert_equal 'API', category.name
+      assert_equal 1, category.project_id
+    end
+
+    context "with invalid parameters" do
+      should "return errors" do
+        assert_no_difference 'IssueCategory.count' do
+          post '/projects/1/issue_categories.xml', {:issue_category => {:name => ''}}, :authorization => credentials('jsmith')
+        end
+        assert_response :unprocessable_entity
+        assert_equal 'application/xml', @response.content_type
+
+        assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"}
+      end
+    end
+  end
+
+  context "PUT /issue_categories/2.xml" do
+    context "with valid parameters" do
+      should "update issue category" do
+        assert_no_difference 'IssueCategory.count' do
+          put '/issue_categories/2.xml', {:issue_category => {:name => 'API Update'}}, :authorization => credentials('jsmith')
+        end
+        assert_response :ok
+        assert_equal 'API Update', IssueCategory.find(2).name
+      end
+    end
+
+    context "with invalid parameters" do
+      should "return errors" do
+        assert_no_difference 'IssueCategory.count' do
+          put '/issue_categories/2.xml', {:issue_category => {:name => ''}}, :authorization => credentials('jsmith')
+        end
+        assert_response :unprocessable_entity
+        assert_equal 'application/xml', @response.content_type
+
+        assert_tag 'errors', :child => {:tag => 'error', :content => "Name can't be blank"}
+      end
+    end
+  end
+
+  context "DELETE /issue_categories/1.xml" do
+    should "destroy issue categories" do
+      assert_difference 'IssueCategory.count', -1 do
+        delete '/issue_categories/1.xml', {}, :authorization => credentials('jsmith')
+      end
+      assert_response :ok
+      assert_nil IssueCategory.find_by_id(1)
+    end
+    
+    should "reassign issues with :reassign_to_id param" do
+      issue_count = Issue.count(:conditions => {:category_id => 1})
+      assert issue_count > 0
+
+      assert_difference 'IssueCategory.count', -1 do
+        assert_difference 'Issue.count(:conditions => {:category_id => 2})', 3 do
+          delete '/issue_categories/1.xml', {:reassign_to_id => 2}, :authorization => credentials('jsmith')
+        end
+      end
+      assert_response :ok
+      assert_nil IssueCategory.find_by_id(1)
+    end
+  end
+
+  def credentials(user, password=nil)
+    ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
+  end
+end
index 1f978c36e85c86257779c7e475a08aa77166f23c..7fb277808e708e5af05126f1a67233dfbfd96d9d 100644 (file)
@@ -114,9 +114,29 @@ class RoutingTest < ActionController::IntegrationTest
   end
 
   context "issue categories" do
-    should_route :get, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
+    should_route :get, "/projects/foo/issue_categories", :controller => 'issue_categories', :action => 'index', :project_id => 'foo'
+    should_route :get, "/projects/foo/issue_categories.xml", :controller => 'issue_categories', :action => 'index', :project_id => 'foo', :format => 'xml'
+    should_route :get, "/projects/foo/issue_categories.json", :controller => 'issue_categories', :action => 'index', :project_id => 'foo', :format => 'json'
 
-    should_route :post, "/projects/test/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'test'
+    should_route :get, "/projects/foo/issue_categories/new", :controller => 'issue_categories', :action => 'new', :project_id => 'foo'
+
+    should_route :post, "/projects/foo/issue_categories", :controller => 'issue_categories', :action => 'create', :project_id => 'foo'
+    should_route :post, "/projects/foo/issue_categories.xml", :controller => 'issue_categories', :action => 'create', :project_id => 'foo', :format => 'xml'
+    should_route :post, "/projects/foo/issue_categories.json", :controller => 'issue_categories', :action => 'create', :project_id => 'foo', :format => 'json'
+
+    should_route :get, "/issue_categories/1", :controller => 'issue_categories', :action => 'show', :id => '1'
+    should_route :get, "/issue_categories/1.xml", :controller => 'issue_categories', :action => 'show', :id => '1', :format => 'xml'
+    should_route :get, "/issue_categories/1.json", :controller => 'issue_categories', :action => 'show', :id => '1', :format => 'json'
+
+    should_route :get, "/issue_categories/1/edit", :controller => 'issue_categories', :action => 'edit', :id => '1'
+
+    should_route :put, "/issue_categories/1", :controller => 'issue_categories', :action => 'update', :id => '1'
+    should_route :put, "/issue_categories/1.xml", :controller => 'issue_categories', :action => 'update', :id => '1', :format => 'xml'
+    should_route :put, "/issue_categories/1.json", :controller => 'issue_categories', :action => 'update', :id => '1', :format => 'json'
+
+    should_route :delete, "/issue_categories/1", :controller => 'issue_categories', :action => 'destroy', :id => '1'
+    should_route :delete, "/issue_categories/1.xml", :controller => 'issue_categories', :action => 'destroy', :id => '1', :format => 'xml'
+    should_route :delete, "/issue_categories/1.json", :controller => 'issue_categories', :action => 'destroy', :id => '1', :format => 'json'
   end
 
   context "issue relations" do