git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3310 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/1.0.0
@@ -70,8 +70,8 @@ class ApplicationController < ActionController::Base | |||
elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) | |||
# RSS key authentication does not start a session | |||
User.find_by_rss_key(params[:key]) | |||
elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) && accept_key_auth_actions.include?(params[:action]) | |||
if params[:key].present? | |||
elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format]) | |||
if params[:key].present? && accept_key_auth_actions.include?(params[:action]) | |||
# Use API key | |||
User.find_by_api_key(params[:key]) | |||
else | |||
@@ -194,18 +194,35 @@ class ApplicationController < ActionController::Base | |||
def render_403 | |||
@project = nil | |||
render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 | |||
respond_to do |format| | |||
format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 } | |||
format.atom { head 403 } | |||
format.xml { head 403 } | |||
format.json { head 403 } | |||
end | |||
return false | |||
end | |||
def render_404 | |||
render :template => "common/404", :layout => !request.xhr?, :status => 404 | |||
respond_to do |format| | |||
format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 } | |||
format.atom { head 404 } | |||
format.xml { head 404 } | |||
format.json { head 404 } | |||
end | |||
return false | |||
end | |||
def render_error(msg) | |||
flash.now[:error] = msg | |||
render :text => '', :layout => !request.xhr?, :status => 500 | |||
respond_to do |format| | |||
format.html { | |||
flash.now[:error] = msg | |||
render :text => '', :layout => !request.xhr?, :status => 500 | |||
} | |||
format.atom { head 500 } | |||
format.xml { head 500 } | |||
format.json { head 500 } | |||
end | |||
end | |||
def invalid_authenticity_token |
@@ -46,7 +46,7 @@ class IssuesController < ApplicationController | |||
helper :timelog | |||
include Redmine::Export::PDF | |||
verify :method => :post, | |||
verify :method => [:post, :delete], | |||
:only => :destroy, | |||
:render => { :nothing => true, :status => :method_not_allowed } | |||
@@ -59,6 +59,7 @@ class IssuesController < ApplicationController | |||
limit = per_page_option | |||
respond_to do |format| | |||
format.html { } | |||
format.xml { } | |||
format.atom { limit = Setting.feeds_limit.to_i } | |||
format.csv { limit = Setting.issues_export_limit.to_i } | |||
format.pdf { limit = Setting.issues_export_limit.to_i } | |||
@@ -74,6 +75,7 @@ class IssuesController < ApplicationController | |||
respond_to do |format| | |||
format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? } | |||
format.xml { render :layout => false } | |||
format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") } | |||
format.csv { send_data(issues_to_csv(@issues, @project), :type => 'text/csv; header=present', :filename => 'export.csv') } | |||
format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') } | |||
@@ -113,6 +115,7 @@ class IssuesController < ApplicationController | |||
@time_entry = TimeEntry.new | |||
respond_to do |format| | |||
format.html { render :template => 'issues/show.rhtml' } | |||
format.xml { render :layout => false } | |||
format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' } | |||
format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") } | |||
end | |||
@@ -155,10 +158,20 @@ class IssuesController < ApplicationController | |||
attach_files(@issue, params[:attachments]) | |||
flash[:notice] = l(:notice_successful_create) | |||
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) | |||
redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } : | |||
{ :action => 'show', :id => @issue }) | |||
respond_to do |format| | |||
format.html { | |||
redirect_to(params[:continue] ? { :action => 'new', :tracker_id => @issue.tracker } : | |||
{ :action => 'show', :id => @issue }) | |||
} | |||
format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) } | |||
end | |||
return | |||
end | |||
else | |||
respond_to do |format| | |||
format.html { } | |||
format.xml { render(:xml => @issue.errors, :status => :unprocessable_entity); return } | |||
end | |||
end | |||
end | |||
@priorities = IssuePriority.all | |||
render :layout => !request.xhr? | |||
@@ -184,7 +197,9 @@ class IssuesController < ApplicationController | |||
@issue.safe_attributes = attrs | |||
end | |||
if request.post? | |||
if request.get? | |||
# nop | |||
else | |||
@time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today) | |||
@time_entry.attributes = params[:time_entry] | |||
if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.valid? | |||
@@ -201,9 +216,18 @@ class IssuesController < ApplicationController | |||
flash[:notice] = l(:notice_successful_update) | |||
end | |||
call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal}) | |||
redirect_to(params[:back_to] || {:action => 'show', :id => @issue}) | |||
respond_to do |format| | |||
format.html { redirect_to(params[:back_to] || {:action => 'show', :id => @issue}) } | |||
format.xml { head :ok } | |||
end | |||
return | |||
end | |||
end | |||
# failure | |||
respond_to do |format| | |||
format.html { } | |||
format.xml { render :xml => @issue.errors, :status => :unprocessable_entity } | |||
end | |||
end | |||
rescue ActiveRecord::StaleObjectError | |||
# Optimistic locking exception | |||
@@ -346,12 +370,17 @@ class IssuesController < ApplicationController | |||
TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues]) | |||
end | |||
else | |||
# display the destroy form | |||
return | |||
unless params[:format] == 'xml' | |||
# display the destroy form if it's a user request | |||
return | |||
end | |||
end | |||
end | |||
@issues.each(&:destroy) | |||
redirect_to :action => 'index', :project_id => @project | |||
respond_to do |format| | |||
format.html { redirect_to :action => 'index', :project_id => @project } | |||
format.xml { head :ok } | |||
end | |||
end | |||
def gantt | |||
@@ -484,7 +513,8 @@ private | |||
end | |||
def find_project | |||
@project = Project.find(params[:project_id]) | |||
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] | |||
@project = Project.find(project_id) | |||
rescue ActiveRecord::RecordNotFound | |||
render_404 | |||
end |
@@ -0,0 +1,31 @@ | |||
xml.instruct! | |||
xml.issues :type => 'array', :count => @issue_count do | |||
@issues.each do |issue| | |||
xml.issue :id => issue.id do | |||
xml.project(:id => issue.project_id, :name => issue.project.name) unless issue.project.nil? | |||
xml.tracker(:id => issue.tracker_id, :name => issue.tracker.name) unless issue.tracker.nil? | |||
xml.status(:id => issue.status_id, :name => issue.status.name) unless issue.status.nil? | |||
xml.priority(:id => issue.priority_id, :name => issue.priority.name) unless issue.priority.nil? | |||
xml.author(:id => issue.author_id, :name => issue.author.name) unless issue.author.nil? | |||
xml.assigned_to(:id => issue.assigned_to_id, :name => issue.assigned_to.name) unless issue.assigned_to.nil? | |||
xml.category(:id => issue.category_id, :name => issue.category.name) unless issue.category.nil? | |||
xml.fixed_version(:id => issue.fixed_version_id, :name => issue.fixed_version.name) unless issue.fixed_version.nil? | |||
xml.subject issue.subject | |||
xml.description issue.description | |||
xml.start_date issue.start_date | |||
xml.due_date issue.due_date | |||
xml.done_ratio issue.done_ratio | |||
xml.estimated_hours issue.estimated_hours | |||
xml.custom_fields do | |||
issue.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 | |||
xml.created_on issue.created_on | |||
xml.updated_on issue.updated_on | |||
end | |||
end | |||
end |
@@ -0,0 +1,54 @@ | |||
xml.instruct! | |||
xml.issue :id => @issue.id do | |||
xml.project(:id => @issue.project_id, :name => @issue.project.name) unless @issue.project.nil? | |||
xml.tracker(:id => @issue.tracker_id, :name => @issue.tracker.name) unless @issue.tracker.nil? | |||
xml.status(:id => @issue.status_id, :name => @issue.status.name) unless @issue.status.nil? | |||
xml.priority(:id => @issue.priority_id, :name => @issue.priority.name) unless @issue.priority.nil? | |||
xml.author(:id => @issue.author_id, :name => @issue.author.name) unless @issue.author.nil? | |||
xml.assigned_to(:id => @issue.assigned_to_id, :name => @issue.assigned_to.name) unless @issue.assigned_to.nil? | |||
xml.category(:id => @issue.category_id, :name => @issue.category.name) unless @issue.category.nil? | |||
xml.fixed_version(:id => @issue.fixed_version_id, :name => @issue.fixed_version.name) unless @issue.fixed_version.nil? | |||
xml.subject @issue.subject | |||
xml.description @issue.description | |||
xml.start_date @issue.start_date | |||
xml.due_date @issue.due_date | |||
xml.done_ratio @issue.done_ratio | |||
xml.estimated_hours @issue.estimated_hours | |||
if User.current.allowed_to?(:view_time_entries, @project) | |||
xml.spent_hours @issue.spent_hours | |||
end | |||
xml.custom_fields do | |||
@issue.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 @issue.custom_field_values.empty? | |||
xml.created_on @issue.created_on | |||
xml.updated_on @issue.updated_on | |||
xml.changesets do | |||
@issue.changesets.each do |changeset| | |||
xml.changeset :revision => changeset.revision do | |||
xml.user(:id => changeset.user_id, :name => changeset.user.name) unless changeset.user.nil? | |||
xml.comments changeset.comments | |||
xml.committed_on changeset.committed_on | |||
end | |||
end | |||
end if User.current.allowed_to?(:view_changesets, @project) && @issue.changesets.any? | |||
xml.journals do | |||
@issue.journals.each do |journal| | |||
xml.journal :id => journal.id do | |||
xml.user(:id => journal.user_id, :name => journal.user.name) unless journal.user.nil? | |||
xml.notes journal.notes | |||
xml.details do | |||
journal.details.each do |detail| | |||
xml.detail :property => detail.property, :name => detail.prop_key, :old => detail.old_value, :new => detail.value | |||
end | |||
end | |||
end | |||
end | |||
end unless @issue.journals.empty? | |||
end |
@@ -119,9 +119,17 @@ ActionController::Routing::Routes.draw do |map| | |||
issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/ | |||
end | |||
issues_routes.with_options :conditions => {:method => :post} do |issues_actions| | |||
issues_actions.connect 'issues', :action => 'index' | |||
issues_actions.connect 'projects/:project_id/issues', :action => 'new' | |||
issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/ | |||
issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/ | |||
issues_actions.connect 'issues.:format', :action => 'new', :format => /xml/ | |||
end | |||
issues_routes.with_options :conditions => {:method => :put} do |issues_actions| | |||
issues_actions.connect 'issues/:id.:format', :action => 'edit', :id => /\d+/, :format => /xml/ | |||
end | |||
issues_routes.with_options :conditions => {:method => :delete} do |issues_actions| | |||
issues_actions.connect 'issues/:id.:format', :action => 'destroy', :id => /\d+/, :format => /xml/ | |||
end | |||
issues_routes.connect 'issues/:action' | |||
end |
@@ -0,0 +1,158 @@ | |||
# 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 IssuesApiTest < ActionController::IntegrationTest | |||
fixtures :projects, | |||
:users, | |||
:roles, | |||
:members, | |||
:member_roles, | |||
:issues, | |||
:issue_statuses, | |||
:versions, | |||
:trackers, | |||
:projects_trackers, | |||
:issue_categories, | |||
:enabled_modules, | |||
:enumerations, | |||
:attachments, | |||
:workflows, | |||
:custom_fields, | |||
:custom_values, | |||
:custom_fields_projects, | |||
:custom_fields_trackers, | |||
:time_entries, | |||
:journals, | |||
:journal_details, | |||
:queries | |||
def setup | |||
Setting.rest_api_enabled = '1' | |||
end | |||
def test_index_routing | |||
assert_routing( | |||
{:method => :get, :path => '/issues.xml'}, | |||
:controller => 'issues', :action => 'index', :format => 'xml' | |||
) | |||
end | |||
def test_index | |||
get '/issues.xml' | |||
assert_response :success | |||
assert_equal 'application/xml', @response.content_type | |||
end | |||
def test_show_routing | |||
assert_routing( | |||
{:method => :get, :path => '/issues/1.xml'}, | |||
:controller => 'issues', :action => 'show', :id => '1', :format => 'xml' | |||
) | |||
end | |||
def test_show | |||
get '/issues/1.xml' | |||
assert_response :success | |||
assert_equal 'application/xml', @response.content_type | |||
end | |||
def test_create_routing | |||
assert_routing( | |||
{:method => :post, :path => '/issues.xml'}, | |||
:controller => 'issues', :action => 'new', :format => 'xml' | |||
) | |||
end | |||
def test_create | |||
attributes = {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3} | |||
assert_difference 'Issue.count' do | |||
post '/issues.xml', {:issue => attributes}, :authorization => credentials('jsmith') | |||
end | |||
assert_response :created | |||
assert_equal 'application/xml', @response.content_type | |||
issue = Issue.first(:order => 'id DESC') | |||
attributes.each do |attribute, value| | |||
assert_equal value, issue.send(attribute) | |||
end | |||
end | |||
def test_create_failure | |||
attributes = {:project_id => 1} | |||
assert_no_difference 'Issue.count' do | |||
post '/issues.xml', {:issue => attributes}, :authorization => credentials('jsmith') | |||
end | |||
assert_response :unprocessable_entity | |||
assert_equal 'application/xml', @response.content_type | |||
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} | |||
end | |||
def test_update_routing | |||
assert_routing( | |||
{:method => :put, :path => '/issues/1.xml'}, | |||
:controller => 'issues', :action => 'edit', :id => '1', :format => 'xml' | |||
) | |||
end | |||
def test_update | |||
attributes = {:subject => 'API update'} | |||
assert_no_difference 'Issue.count' do | |||
assert_difference 'Journal.count' do | |||
put '/issues/1.xml', {:issue => attributes}, :authorization => credentials('jsmith') | |||
end | |||
end | |||
assert_response :ok | |||
assert_equal 'application/xml', @response.content_type | |||
issue = Issue.find(1) | |||
attributes.each do |attribute, value| | |||
assert_equal value, issue.send(attribute) | |||
end | |||
end | |||
def test_update_failure | |||
attributes = {:subject => ''} | |||
assert_no_difference 'Issue.count' do | |||
assert_no_difference 'Journal.count' do | |||
put '/issues/1.xml', {:issue => attributes}, :authorization => credentials('jsmith') | |||
end | |||
end | |||
assert_response :unprocessable_entity | |||
assert_equal 'application/xml', @response.content_type | |||
assert_tag :errors, :child => {:tag => 'error', :content => "Subject can't be blank"} | |||
end | |||
def test_destroy_routing | |||
assert_routing( | |||
{:method => :delete, :path => '/issues/1.xml'}, | |||
:controller => 'issues', :action => 'destroy', :id => '1', :format => 'xml' | |||
) | |||
end | |||
def test_destroy | |||
assert_difference 'Issue.count', -1 do | |||
delete '/issues/1.xml', {}, :authorization => credentials('jsmith') | |||
end | |||
assert_response :ok | |||
assert_equal 'application/xml', @response.content_type | |||
assert_nil Issue.find_by_id(1) | |||
end | |||
def credentials(user, password=nil) | |||
ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user) | |||
end | |||
end |