XML REST API for Projects (#296).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3313 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2010-01-14 20:00:17 +00:00
parent 64f4b50139
commit 68a4cd38f5
7 changed files with 223 additions and 11 deletions

View File

@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base
include Redmine::I18n include Redmine::I18n
layout 'base' layout 'base'
exempt_from_layout 'builder'
# Remove broken cookie after upgrade from 0.8.x (#4292) # Remove broken cookie after upgrade from 0.8.x (#4292)
# See https://rails.lighthouseapp.com/projects/8994/tickets/3360 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360

View File

@ -53,6 +53,9 @@ class ProjectsController < ApplicationController
format.html { format.html {
@projects = Project.visible.find(:all, :order => 'lft') @projects = Project.visible.find(:all, :order => 'lft')
} }
format.xml {
@projects = Project.visible.find(:all, :order => 'lft')
}
format.atom { format.atom {
projects = Project.visible.find(:all, :order => 'created_on DESC', projects = Project.visible.find(:all, :order => 'created_on DESC',
:limit => Setting.feeds_limit.to_i) :limit => Setting.feeds_limit.to_i)
@ -81,8 +84,18 @@ class ProjectsController < ApplicationController
m = Member.new(:user => User.current, :roles => [r]) m = Member.new(:user => User.current, :roles => [r])
@project.members << m @project.members << m
end end
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project redirect_to :controller => 'projects', :action => 'settings', :id => @project
}
format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end
else
respond_to do |format|
format.html
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
end
end end
end end
end end
@ -147,6 +160,11 @@ class ProjectsController < ApplicationController
:conditions => cond).to_f :conditions => cond).to_f
end end
@key = User.current.rss_key @key = User.current.rss_key
respond_to do |format|
format.html
format.xml
end
end end
def settings def settings
@ -160,15 +178,26 @@ class ProjectsController < ApplicationController
# Edit @project # Edit @project
def edit def edit
if request.post? if request.get?
else
@project.attributes = params[:project] @project.attributes = params[:project]
if validate_parent_id && @project.save if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update) flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project redirect_to :action => 'settings', :id => @project
}
format.xml { head :ok }
end
else else
respond_to do |format|
format.html {
settings settings
render :action => 'settings' render :action => 'settings'
}
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
end
end end
end end
end end
@ -195,9 +224,16 @@ class ProjectsController < ApplicationController
# Delete @project # Delete @project
def destroy def destroy
@project_to_destroy = @project @project_to_destroy = @project
if request.post? and params[:confirm] if request.get?
# display confirmation view
else
if params[:format] == 'xml' || params[:confirm]
@project_to_destroy.destroy @project_to_destroy.destroy
redirect_to :controller => 'admin', :action => 'projects' respond_to do |format|
format.html { redirect_to :controller => 'admin', :action => 'projects' }
format.xml { head :ok }
end
end
end end
# hide project in layout # hide project in layout
@project = nil @project = nil

View File

@ -0,0 +1,18 @@
xml.instruct!
xml.projects :type => 'array' do
@projects.each do |project|
xml.project :id => project.id do
xml.name project.name
xml.identifier project.identifier
xml.description project.description
xml.parent(:id => project.parent_id, :name => project.parent.name) unless project.parent.nil?
xml.custom_fields do
project.custom_field_values.each do |custom_value|
xml.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
end
end unless project.custom_field_values.empty?
xml.created_on project.created_on
xml.updated_on project.updated_on
end
end
end

View File

@ -0,0 +1,16 @@
xml.instruct!
xml.project :id => @project.id do
xml.name @project.name
xml.identifier @project.identifier
xml.description @project.description
xml.homepage @project.homepage
xml.custom_fields do
@project.custom_field_values.each do |custom_value|
xml.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
end
end unless @project.custom_field_values.empty?
xml.created_on @project.created_on
xml.updated_on @project.updated_on
end

View File

@ -186,6 +186,7 @@ ActionController::Routing::Routes.draw do |map|
project_views.connect 'projects.:format', :action => 'index' project_views.connect 'projects.:format', :action => 'index'
project_views.connect 'projects/new', :action => 'add' project_views.connect 'projects/new', :action => 'add'
project_views.connect 'projects/:id', :action => 'show' project_views.connect 'projects/:id', :action => 'show'
project_views.connect 'projects/:id.:format', :action => 'show'
project_views.connect 'projects/:id/:action', :action => /roadmap|destroy|settings/ project_views.connect 'projects/:id/:action', :action => /roadmap|destroy|settings/
project_views.connect 'projects/:id/files', :action => 'list_files' project_views.connect 'projects/:id/files', :action => 'list_files'
project_views.connect 'projects/:id/files/new', :action => 'add_file' project_views.connect 'projects/:id/files/new', :action => 'add_file'
@ -204,6 +205,7 @@ ActionController::Routing::Routes.draw do |map|
projects.with_options :conditions => {:method => :post} do |project_actions| projects.with_options :conditions => {:method => :post} do |project_actions|
project_actions.connect 'projects/new', :action => 'add' project_actions.connect 'projects/new', :action => 'add'
project_actions.connect 'projects', :action => 'add' project_actions.connect 'projects', :action => 'add'
project_actions.connect 'projects.:format', :action => 'add', :format => /xml/
project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/ project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
project_actions.connect 'projects/:id/files/new', :action => 'add_file' project_actions.connect 'projects/:id/files/new', :action => 'add_file'
project_actions.connect 'projects/:id/versions/new', :action => 'add_version' project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
@ -211,7 +213,12 @@ ActionController::Routing::Routes.draw do |map|
project_actions.connect 'projects/:id/activities/save', :action => 'save_activities' project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
end end
projects.with_options :conditions => {:method => :put} do |project_actions|
project_actions.conditions 'projects/:id.:format', :action => 'edit', :format => /xml/
end
projects.with_options :conditions => {:method => :delete} do |project_actions| projects.with_options :conditions => {:method => :delete} do |project_actions|
project_actions.conditions 'projects/:id.:format', :action => 'destroy', :format => /xml/
project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities' project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
end end
end end

View File

@ -44,7 +44,7 @@ custom_fields_003:
- Alpha - Alpha
- Planning - Planning
id: 3 id: 3
is_required: true is_required: false
field_format: list field_format: list
default_value: "" default_value: ""
editable: true editable: true

View File

@ -0,0 +1,134 @@
# Redmine - project management software
# Copyright (C) 2006-2010 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.dirname(__FILE__)}/../test_helper"
class ProjectsApiTest < ActionController::IntegrationTest
fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
:trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
:attachments, :custom_fields, :custom_values, :time_entries
def setup
Setting.rest_api_enabled = '1'
end
def test_index_routing
assert_routing(
{:method => :get, :path => '/projects.xml'},
:controller => 'projects', :action => 'index', :format => 'xml'
)
end
def test_index
get '/projects.xml'
assert_response :success
assert_equal 'application/xml', @response.content_type
end
def test_show_routing
assert_routing(
{:method => :get, :path => '/projects/1.xml'},
:controller => 'projects', :action => 'show', :id => '1', :format => 'xml'
)
end
def test_show
get '/projects/1.xml'
assert_response :success
assert_equal 'application/xml', @response.content_type
end
def test_create_routing
assert_routing(
{:method => :post, :path => '/projects.xml'},
:controller => 'projects', :action => 'add', :format => 'xml'
)
end
def test_create
attributes = {:name => 'API test', :identifier => 'api-test'}
assert_difference 'Project.count' do
post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
end
assert_response :created
assert_equal 'application/xml', @response.content_type
project = Project.first(:order => 'id DESC')
attributes.each do |attribute, value|
assert_equal value, project.send(attribute)
end
end
def test_create_failure
attributes = {:name => 'API test'}
assert_no_difference 'Project.count' do
post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
end
assert_response :unprocessable_entity
assert_equal 'application/xml', @response.content_type
assert_tag :errors, :child => {:tag => 'error', :content => "Identifier can't be blank"}
end
def test_update_routing
assert_routing(
{:method => :put, :path => '/projects/1.xml'},
:controller => 'projects', :action => 'edit', :id => '1', :format => 'xml'
)
end
def test_update
attributes = {:name => 'API update'}
assert_no_difference 'Project.count' do
put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
end
assert_response :ok
assert_equal 'application/xml', @response.content_type
project = Project.find(1)
attributes.each do |attribute, value|
assert_equal value, project.send(attribute)
end
end
def test_update_failure
attributes = {:name => ''}
assert_no_difference 'Project.count' do
put '/projects/1.xml', {:project => attributes}, :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
def test_destroy_routing
assert_routing(
{:method => :delete, :path => '/projects/1.xml'},
:controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml'
)
end
def test_destroy
assert_difference 'Project.count', -1 do
delete '/projects/2.xml', {}, :authorization => credentials('admin')
end
assert_response :ok
assert_equal 'application/xml', @response.content_type
assert_nil Project.find_by_id(2)
end
def credentials(user, password=nil)
ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
end
end