Ability to delete a version from a wiki page history (#10852).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@10705 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2012-10-23 18:45:14 +00:00
parent 9e7f71080f
commit 6cccdce06e
10 changed files with 135 additions and 6 deletions

View File

@ -35,7 +35,7 @@ class WikiController < ApplicationController
default_search_scope :wiki_pages default_search_scope :wiki_pages
before_filter :find_wiki, :authorize before_filter :find_wiki, :authorize
before_filter :find_existing_or_new_page, :only => [:show, :edit, :update] before_filter :find_existing_or_new_page, :only => [:show, :edit, :update]
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy] before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :destroy, :destroy_version]
helper :attachments helper :attachments
include AttachmentsHelper include AttachmentsHelper
@ -237,6 +237,14 @@ class WikiController < ApplicationController
redirect_to :action => 'index', :project_id => @project redirect_to :action => 'index', :project_id => @project
end end
def destroy_version
return render_403 unless editable?
@content = @page.content_for_version(params[:version])
@content.destroy
redirect_to_referer_or :action => 'history', :id => @page.title, :project_id => @project
end
# Export wiki to a single pdf or html file # Export wiki to a single pdf or html file
def export def export
@pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75) @pages = @wiki.pages.all(:order => 'title', :include => [:content, :attachments], :limit => 75)

View File

@ -147,6 +147,10 @@ module ApplicationHelper
end end
end end
def wiki_page_path(page, options={})
url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
end
def thumbnail_tag(attachment) def thumbnail_tag(attachment)
link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)), link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
{:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename}, {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},

View File

@ -73,6 +73,8 @@ class WikiContent < ActiveRecord::Base
"LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
"LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"} "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
after_destroy :page_update_after_destroy
def text=(plain) def text=(plain)
case Setting.wiki_compression case Setting.wiki_compression
when 'gzip' when 'gzip'
@ -128,5 +130,18 @@ class WikiContent < ActiveRecord::Base
includes(:author). includes(:author).
where("wiki_content_id = ? AND version > ?", wiki_content_id, version).first where("wiki_content_id = ? AND version > ?", wiki_content_id, version).first
end end
private
# Updates page's content if the latest version is removed
# or destroys the page if it was the only version
def page_update_after_destroy
latest = page.content.versions.reorder("#{self.class.table_name}.version DESC").first
if latest && page.content.version != latest.version
raise ActiveRecord::Rollback unless page.content.revert_to!(latest)
elsif latest.nil?
raise ActiveRecord::Rollback unless page.destroy
end
end
end end
end end

View File

@ -28,7 +28,10 @@
<td class="updated_on"><%= format_time(ver.updated_on) %></td> <td class="updated_on"><%= format_time(ver.updated_on) %></td>
<td class="author"><%= link_to_user ver.author %></td> <td class="author"><%= link_to_user ver.author %></td>
<td class="comments"><%=h ver.comments %></td> <td class="comments"><%=h ver.comments %></td>
<td class="buttons"><%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %></td> <td class="buttons">
<%= link_to l(:button_annotate), :action => 'annotate', :id => @page.title, :version => ver.version %>
<%= delete_link wiki_page_path(@page, :version => ver.version) if User.current.allowed_to?(:delete_wiki_pages, @page.project) && @version_count > 1 %>
</td>
</tr> </tr>
<% line_num += 1 %> <% line_num += 1 %>
<% end %> <% end %>

View File

@ -160,6 +160,7 @@ RedmineApp::Application.routes.draw do
end end
match 'wiki', :controller => 'wiki', :action => 'show', :via => :get match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
get 'wiki/:id/:version', :to => 'wiki#show' get 'wiki/:id/:version', :to => 'wiki#show'
delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
get 'wiki/:id/:version/annotate', :to => 'wiki#annotate' get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
get 'wiki/:id/:version/diff', :to => 'wiki#diff' get 'wiki/:id/:version/diff', :to => 'wiki#diff'
end end

View File

@ -374,7 +374,7 @@ module ActiveRecord #:nodoc:
# Clones a model. Used when saving a new version or reverting a model's version. # Clones a model. Used when saving a new version or reverting a model's version.
def clone_versioned_model(orig_model, new_model) def clone_versioned_model(orig_model, new_model)
self.versioned_attributes.each do |key| self.versioned_attributes.each do |key|
new_model.send("#{key}=", orig_model.send(key)) if orig_model.has_attribute?(key) new_model.send("#{key}=", orig_model.send(key)) if orig_model.respond_to?(key)
end end
if self.class.columns_hash.include?(self.class.inheritance_column) if self.class.columns_hash.include?(self.class.inheritance_column)

View File

@ -118,7 +118,7 @@ Redmine::AccessControl.map do |map|
map.project_module :wiki do |map| map.project_module :wiki do |map|
map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member map.permission :delete_wiki_pages, {:wiki => [:destroy, :destroy_version]}, :require => :member
map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true map.permission :view_wiki_pages, {:wiki => [:index, :show, :special, :date_index]}, :read => true
map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true map.permission :export_wiki_pages, {:wiki => [:export]}, :read => true
map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true map.permission :view_wiki_edits, {:wiki => [:history, :diff, :annotate]}, :read => true

View File

@ -499,6 +499,7 @@ class WikiControllerTest < ActionController::TestCase
end end
def test_history def test_history
@request.session[:user_id] = 2
get :history, :project_id => 'ecookbook', :id => 'CookBook_documentation' get :history, :project_id => 'ecookbook', :id => 'CookBook_documentation'
assert_response :success assert_response :success
assert_template 'history' assert_template 'history'
@ -508,17 +509,24 @@ class WikiControllerTest < ActionController::TestCase
assert_select "input[type=submit][name=commit]" assert_select "input[type=submit][name=commit]"
assert_select 'td' do assert_select 'td' do
assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => '2' assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => '2'
assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/annotate' assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2/annotate', :text => 'Annotate'
assert_select 'a[href=?]', '/projects/ecookbook/wiki/CookBook_documentation/2', :text => 'Delete'
end end
end end
def test_history_with_one_version def test_history_with_one_version
get :history, :project_id => 1, :id => 'Another_page' @request.session[:user_id] = 2
get :history, :project_id => 'ecookbook', :id => 'Another_page'
assert_response :success assert_response :success
assert_template 'history' assert_template 'history'
assert_not_nil assigns(:versions) assert_not_nil assigns(:versions)
assert_equal 1, assigns(:versions).size assert_equal 1, assigns(:versions).size
assert_select "input[type=submit][name=commit]", false assert_select "input[type=submit][name=commit]", false
assert_select 'td' do
assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1', :text => '1'
assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1/annotate', :text => 'Annotate'
assert_select 'a[href=?]', '/projects/ecookbook/wiki/Another_page/1', :text => 'Delete', :count => 0
end
end end
def test_diff def test_diff
@ -681,6 +689,18 @@ class WikiControllerTest < ActionController::TestCase
assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent assert_equal WikiPage.find(1), WikiPage.find_by_id(5).parent
end end
def test_destroy_version
@request.session[:user_id] = 2
assert_difference 'WikiContent::Version.count', -1 do
assert_no_difference 'WikiContent.count' do
assert_no_difference 'WikiPage.count' do
delete :destroy_version, :project_id => 'ecookbook', :id => 'CookBook_documentation', :version => 2
assert_redirected_to '/projects/ecookbook/wiki/CookBook_documentation/history'
end
end
end
end
def test_index def test_index
get :index, :project_id => 'ecookbook' get :index, :project_id => 'ecookbook'
assert_response :success assert_response :success

View File

@ -38,6 +38,11 @@ class RoutingWikiTest < ActionController::IntegrationTest
{ :controller => 'wiki', :action => 'diff', :project_id => '1', { :controller => 'wiki', :action => 'diff', :project_id => '1',
:id => 'CookBook_documentation' } :id => 'CookBook_documentation' }
) )
assert_routing(
{ :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2" },
{ :controller => 'wiki', :action => 'show', :project_id => '1',
:id => 'CookBook_documentation', :version => '2' }
)
assert_routing( assert_routing(
{ :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/diff" }, { :method => 'get', :path => "/projects/1/wiki/CookBook_documentation/2/diff" },
{ :controller => 'wiki', :action => 'diff', :project_id => '1', { :controller => 'wiki', :action => 'diff', :project_id => '1',
@ -117,5 +122,10 @@ class RoutingWikiTest < ActionController::IntegrationTest
{ :controller => 'wiki', :action => 'destroy', :project_id => '22', { :controller => 'wiki', :action => 'destroy', :project_id => '22',
:id => 'ladida' } :id => 'ladida' }
) )
assert_routing(
{ :method => 'delete', :path => "/projects/22/wiki/ladida/3" },
{ :controller => 'wiki', :action => 'destroy_version', :project_id => '22',
:id => 'ladida', :version => '3' }
)
end end
end end

View File

@ -0,0 +1,68 @@
# Redmine - project management software
# Copyright (C) 2006-2012 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 WikiContentTest < ActiveSupport::TestCase
fixtures :projects, :users, :wikis, :wiki_pages, :wiki_contents, :wiki_content_versions
def setup
end
def test_destroy
v = WikiContent::Version.find(2)
assert_difference 'WikiContent::Version.count', -1 do
v.destroy
end
end
def test_destroy_last_version_should_revert_content
v = WikiContent::Version.find(3)
assert_no_difference 'WikiPage.count' do
assert_no_difference 'WikiContent.count' do
assert_difference 'WikiContent::Version.count', -1 do
assert v.destroy
end
end
end
c = WikiContent.find(1)
v = c.versions.last
assert_equal 2, c.version
assert_equal v.version, c.version
assert_equal v.comments, c.comments
assert_equal v.text, c.text
assert_equal v.author, c.author
assert_equal v.updated_on, c.updated_on
end
def test_destroy_all_versions_should_delete_page
WikiContent::Version.find(1).destroy
WikiContent::Version.find(2).destroy
v = WikiContent::Version.find(3)
assert_difference 'WikiPage.count', -1 do
assert_difference 'WikiContent.count', -1 do
assert_difference 'WikiContent::Version.count', -1 do
assert v.destroy
end
end
end
assert_nil WikiPage.find_by_id(1)
end
end