Ticket #1106: rename_simple.diff
| File rename_simple.diff, 22.3 KB (added by shookie@…, 2 years ago) |
|---|
-
trac/admin/tests/console-tests.txt
diff --git a/trac/admin/tests/console-tests.txt b/trac/admin/tests/console-tests.txt index e9c5f15..6c9c997 100644
a b Available actions: 120 120 TICKET_EDIT_CC, TICKET_EDIT_COMMENT, TICKET_EDIT_DESCRIPTION, 121 121 TICKET_MODIFY, TICKET_VIEW, TIMELINE_VIEW, TRAC_ADMIN, 122 122 VERSIONCONTROL_ADMIN, WIKI_ADMIN, WIKI_CREATE, WIKI_DELETE, WIKI_MODIFY, 123 WIKI_ VIEW123 WIKI_RENAME, WIKI_VIEW 124 124 125 125 ===== test_permission_add_one_action_ok ===== 126 126 … … Available actions: 155 155 TICKET_EDIT_CC, TICKET_EDIT_COMMENT, TICKET_EDIT_DESCRIPTION, 156 156 TICKET_MODIFY, TICKET_VIEW, TIMELINE_VIEW, TRAC_ADMIN, 157 157 VERSIONCONTROL_ADMIN, WIKI_ADMIN, WIKI_CREATE, WIKI_DELETE, WIKI_MODIFY, 158 WIKI_ VIEW158 WIKI_RENAME, WIKI_VIEW 159 159 160 160 ===== test_permission_add_multiple_actions_ok ===== 161 161 … … Available actions: 191 191 TICKET_EDIT_CC, TICKET_EDIT_COMMENT, TICKET_EDIT_DESCRIPTION, 192 192 TICKET_MODIFY, TICKET_VIEW, TIMELINE_VIEW, TRAC_ADMIN, 193 193 VERSIONCONTROL_ADMIN, WIKI_ADMIN, WIKI_CREATE, WIKI_DELETE, WIKI_MODIFY, 194 WIKI_ VIEW194 WIKI_RENAME, WIKI_VIEW 195 195 196 196 ===== test_permission_remove_one_action_ok ===== 197 197 … … Available actions: 225 225 TICKET_EDIT_CC, TICKET_EDIT_COMMENT, TICKET_EDIT_DESCRIPTION, 226 226 TICKET_MODIFY, TICKET_VIEW, TIMELINE_VIEW, TRAC_ADMIN, 227 227 VERSIONCONTROL_ADMIN, WIKI_ADMIN, WIKI_CREATE, WIKI_DELETE, WIKI_MODIFY, 228 WIKI_ VIEW228 WIKI_RENAME, WIKI_VIEW 229 229 230 230 ===== test_permission_remove_multiple_actions_ok ===== 231 231 … … Available actions: 259 259 TICKET_EDIT_CC, TICKET_EDIT_COMMENT, TICKET_EDIT_DESCRIPTION, 260 260 TICKET_MODIFY, TICKET_VIEW, TIMELINE_VIEW, TRAC_ADMIN, 261 261 VERSIONCONTROL_ADMIN, WIKI_ADMIN, WIKI_CREATE, WIKI_DELETE, WIKI_MODIFY, 262 WIKI_ VIEW262 WIKI_RENAME, WIKI_VIEW 263 263 264 264 ===== test_component_list_ok ===== 265 265 -
trac/attachment.py
diff --git a/trac/attachment.py b/trac/attachment.py index 77bd09f..3de5389 100644
a b class Attachment(object): 274 274 except OSError, e: 275 275 env.log.error("Can't delete attachment directory %s: %s", 276 276 attachment_dir, exception_to_unicode(e, traceback=True)) 277 278 @classmethod 279 def reparent_all(cls, env, old_realm, old_id, new_realm, new_id, db=None): 280 """Reparent all attachments of a given resource to another resource. 281 282 :param old_realm: old parent's realm 283 :param old_id: old parent's id 284 :param new_realm: new parent's realm 285 :param new_id: new parent's id 286 :param env: the environent 287 :param db: the database connection 288 """ 289 old_dir = os.path.join(os.path.normpath(env.path), 'attachments', 290 old_realm, old_id) 291 if not os.path.exists(old_dir): 292 return 293 294 new_dir = os.path.join(os.path.normpath(env.path), 'attachments', 295 new_realm, new_id) 296 297 @with_transaction(env, db) 298 def do_reparent(db): 299 cursor = db.cursor() 300 cursor.execute(""" 301 SELECT filename FROM attachment 302 WHERE type=%s AND id=%s 303 """, (old_realm, old_id)) 304 305 renames = [] 306 for filename, in cursor: 307 old_filename = os.path.join(old_dir, filename) 308 new_filename = os.path.join(new_dir, filename) 309 if os.path.exists(new_filename): 310 raise TracError(_("Can't reparent attachment '%(att)s' as " 311 "it already exists in %(realm)s:%(id)s", 312 att=new_filename, 313 realm=new_realm, id=new_id)) 314 renames.append((old_filename, new_filename)) 315 316 try: 317 if not os.path.exists(new_dir): 318 os.makedirs(new_dir) 319 except OSError, e: 320 env.log.error("Can't create attachment directory '%s'", 321 new_dir, exc_info=True) 322 raise TracError(_("Can't create attachment folder for " 323 "%(realm)s:%(id)s: %(err)", 324 realm=new_realm, id=new_id, err=e)) 325 326 for old_filename, new_filename in renames: 327 try: 328 os.rename(old_filename, new_filename) 329 except OSError, e: 330 env.log.error("Can't move attachment from '%s' to '%s'", 331 old_filename, new_filename, exc_info=True) 332 raise TracError(_("Can't move attachment '%(att)s'", 333 att=os.path.basename(new_filename))) 334 cursor.execute(""" 335 UPDATE attachment SET type=%s, id=%s 336 WHERE type=%s AND id=%s 337 """, (new_realm, new_id, old_realm, old_id)) 338 339 env.log.info('Attachments reparented to: %s:%s', new_realm, new_id) 340 341 342 return 277 343 278 344 def open(self): 279 345 self.env.log.debug('Trying to open attachment at %s', self.path) -
trac/htdocs/css/wiki.css
diff --git a/trac/htdocs/css/wiki.css b/trac/htdocs/css/wiki.css index 091f2f6..f109783 100644
a b 42 42 #changeinfo br { clear: left } 43 43 #changeinfo .options { padding: 0 0 1em 1em } 44 44 #changeinfo .options, #changeinfo .buttons { clear: left } 45 #delete, # save { margin-left: 6em }45 #delete, #rename, #save { margin-left: 6em } 46 46 #preview { 47 47 background: #f4f4f4 url(../draft.png); 48 48 margin: 1em 0 2em; -
trac/tests/functional/tester.py
diff --git a/trac/tests/functional/tester.py b/trac/tests/functional/tester.py index b983e1b..24a06e5 100755
a b class FunctionalTester(object): 176 176 tc.formvalue('attachment', 'replace', True) 177 177 tc.submit() 178 178 tc.url(self.url + '/attachment/ticket/%s/$' % ticketid) 179 return tempfilename 179 180 180 181 def clone_ticket(self, ticketid): 181 182 """Create a clone of the given ticket id using the clone button.""" … … class FunctionalTester(object): 232 233 tc.formvalue('attachment', 'description', random_sentence()) 233 234 tc.submit() 234 235 tc.url(self.url + '/attachment/wiki/%s/$' % name) 236 return tempfilename 235 237 236 238 def create_milestone(self, name=None, due=None): 237 239 """Creates the specified milestone, with a random name if none is -
trac/wiki/api.py
diff --git a/trac/wiki/api.py b/trac/wiki/api.py index f0ea7e9..c226599 100644
a b class IWikiChangeListener(Interface): 42 42 def wiki_page_deleted(page): 43 43 """Called when a page has been deleted.""" 44 44 45 def wiki_page_renamed(page,old_page_name): 46 """Called when a page has been renamed in-place.""" 47 45 48 def wiki_page_version_deleted(page): 46 49 """Called when a version of a page has been deleted.""" 47 50 -
trac/wiki/model.py
diff --git a/trac/wiki/model.py b/trac/wiki/model.py index 6dc95b5..74b94f2 100644
a b class WikiPage(object): 158 158 self.old_readonly = self.readonly 159 159 self.old_text = self.text 160 160 161 def rename(self, new_name, db=None): 162 """Rename wiki page in-place, keeping the history intact. 163 Renaming a page this way will eventually leave dangling references 164 to the old page - which litterally doesn't exist anymore. 165 """ 166 assert self.exists, 'Cannot rename non-existent page' 167 168 old_name = self.name 169 170 @with_transaction(self.env, db) 171 def do_rename(db): 172 cursor = db.cursor() 173 new_page = WikiPage(self.env, new_name, version=None, db=db) 174 if new_page.exists: 175 raise TracError(_("Can't rename to existing %(nn)s page.", 176 nn=new_name)) 177 178 cursor.execute("UPDATE wiki SET name=%s WHERE name=%s", 179 (new_name, old_name)) 180 self.name = new_name 181 self._fetch(self.name, None, db) 182 183 from trac.attachment import Attachment 184 Attachment.reparent_all(self.env, 'wiki', old_name, 185 'wiki', self.name, db) 186 187 self.env.log.info('Renamed page %s in-place to %s', old_name, new_name) 188 189 for listener in WikiSystem(self.env).change_listeners: 190 if hasattr(listener, 'wiki_page_renamed'): 191 listener.wiki_page_renamed(self, old_name) 192 193 161 194 def get_history(self, db=None): 162 195 if not db: 163 196 db = self.env.get_db_cnx() -
new file trac/wiki/templates/wiki_rename.html
diff --git a/trac/wiki/templates/wiki_rename.html b/trac/wiki/templates/wiki_rename.html new file mode 100644 index 0000000..f00b807
- + 1 <!DOCTYPE html 2 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 3 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 4 <html xmlns="http://www.w3.org/1999/xhtml" 5 xmlns:py="http://genshi.edgewall.org/" 6 xmlns:xi="http://www.w3.org/2001/XInclude"> 7 <xi:include href="layout.html" /> 8 <head> 9 <title>$title</title> 10 </head> 11 12 <body> 13 <div id="content" class="wiki" 14 py:with=" 15 current_href = href.wiki(page.name); 16 "> 17 <h1>${_('Rename')} <a href="$current_href">$page.name</a></h1> 18 <form action="$current_href" method="post"> 19 <p> 20 <input type="hidden" name="action" value="rename" /> 21 <strong>Renaming the page will rename all existing versions 22 of the page in place. <br /> 23 The history of the renamed page will be exactly the same as the one 24 of the original page.</strong> 25 <br /> 26 <p> 27 <label for="new_name">New name:</label> 28 <input type="text" id="new_name" name="new_name" size="40" value="$page.name" /> 29 </p> 30 <br /> 31 <fieldset id="rename_options" class="group"> 32 <legend>${_('Rename')} ${_('Options')}</legend> 33 <input type="checkbox" id="leave_redirection" name="leave_redirection"/> 34 <label for="leave_redirection">Leave a redirection to the new page.</label> 35 <br /> 36 If you choose to leave a redirection, this will 37 re-create the $page.name page with a link pointing to the new 38 page. 39 </fieldset> 40 </p> 41 <div class="buttons"> 42 <input type="submit" name="cancel" value="${_('Cancel')}" /> 43 <input type="submit" value="${_('Rename')} $page.name" /> 44 </div> 45 </form> 46 </div> 47 </body> 48 </html> -
trac/wiki/templates/wiki_view.html
diff --git a/trac/wiki/templates/wiki_view.html b/trac/wiki/templates/wiki_view.html index 1929bab..508f1f0 100644
a b 71 71 72 72 <py:with vars="modify_perm = 'WIKI_MODIFY' in perm(page.resource); 73 73 delete_perm = 'WIKI_DELETE' in perm(page.resource); 74 admin_perm = 'WIKI_ADMIN' in perm(page.resource)"> 74 admin_perm = 'WIKI_ADMIN' in perm(page.resource); 75 rename_perm = 'WIKI_RENAME' in perm(page.resource)"> 75 76 <py:if test="admin_perm or (not page.readonly and (modify_perm or delete_perm))"> 76 77 <div class="buttons"> 77 78 <py:if test="modify_perm"> … … 101 102 <xi:include href="attach_file_form.html" py:with="alist = attachments"/> 102 103 </py:if> 103 104 </py:if> 105 <py:if test="page.exists and rename_perm"> 106 <form method="get" action="${href.wiki(page.name)}" id="rename"> 107 <div> 108 <input type="hidden" name="action" value="rename" /> 109 <input type="submit" value="${_('Rename page')}" /> 110 </div> 111 </form> 112 </py:if> 104 113 <py:if test="page.exists and delete_perm"> 105 114 <form method="get" action="${href.wiki(page.name)}"> 106 115 <div id="delete"> -
trac/wiki/tests/functional.py
diff --git a/trac/wiki/tests/functional.py b/trac/wiki/tests/functional.py index bd85ec8..07a290e 100755
a b class TestWiki(FunctionalTwillTestCaseSetup): 12 12 self._tester.attach_file_to_wiki(pagename) 13 13 14 14 15 class TestWikiRename(FunctionalTwillTestCaseSetup): 16 def runTest(self): 17 """Test for simple wiki rename""" 18 pagename = random_unique_camel() 19 self._tester.create_wiki_page(pagename) 20 self._tester.rename_wiki_page(pagename) 21 attachment = self._tester.attach_file_to_wiki(pagename) 22 base_url = self._tester.url 23 page_url = base_url + "/wiki/" + pagename 24 25 def click_rename(): 26 tc.formvalue('rename', 'action', 'rename') 27 tc.submit() 28 tc.url(page_url+r'\?action=rename') 29 tc.find("New name:") 30 31 tc.go(page_url) 32 tc.find("Rename page") 33 click_rename() 34 # attempt to rename the page to the current page name ... 35 tc.formvalue(page_url, 'new_name', 'rename') 36 tc.submit('rename') 37 tc.url(page_url) 38 tc.find("New name must be different from old name.") 39 # attempt to rename the page to an existing page name ... 40 tc.formvalue(page_url, 'new_name', 'WikiStart') 41 tc.submit('rename') 42 tc.url(page_url) 43 tc.find("Trac Error") 44 tc.find("Can't rename to existing WikiStart page") 45 # correct rename to new page name (old page replaced by a redirection) 46 tc.go(page_url) 47 click_rename() 48 newpagename = pagename+'Renamed' 49 tc.formvalue(page_url, 'new_name', newpagename) 50 tc.formvalue(page_url, 'leave_redirection', True) # the default 51 tc.submit('rename') 52 # check redirection page 53 tc.url(page_url) 54 tc.find("See.*/wiki/"+newpagename) 55 # check whether attachment exists on the new page but not on old page 56 tc.go(base_url+'/attachment/wiki/'+newpagename+'/'+attachment) 57 tc.notfind("Error: Invalid Attachment") 58 tc.go(base_url+'/attachment/wiki/'+pagename+'/'+attachment) 59 tc.find("Error: Invalid Attachment") 60 # rename again to another new page name (this time, no redirection) 61 tc.go(page_url) 62 click_rename() 63 newpagename = pagename+'RenamedAgain' 64 tc.formvalue(page_url, 'new_name', newpagename) 65 tc.formvalue(page_url, 'leave_redirection', False) 66 tc.submit('rename') 67 tc.url(base_url + "/wiki/" + newpagename) 68 # this time, the original page is gone 69 tc.go(page_url) 70 tc.url(page_url) 71 tc.find("Describe %s here" % pagename) 72 73 74 15 75 class RegressionTestTicket4812(FunctionalTwillTestCaseSetup): 16 76 def runTest(self): 17 77 """Test for regression of http://trac.edgewall.org/ticket/4812""" -
trac/wiki/tests/model.py
diff --git a/trac/wiki/tests/model.py b/trac/wiki/tests/model.py index e4fde47..a5ea1cd 100644
a b 1 1 from datetime import datetime 2 import tempfile 2 3 import unittest 3 4 5 from trac.attachment import Attachment 4 6 from trac.core import * 5 7 from trac.test import EnvironmentStub 6 8 from trac.util.datefmt import utc, to_utimestamp … … class TestWikiChangeListener(Component): 14 16 self.changed = [] 15 17 self.deleted = [] 16 18 self.deleted_version = [] 19 self.renamed = {} 17 20 18 21 def wiki_page_added(self, page): 19 22 self.added.append(page) … … class TestWikiChangeListener(Component): 27 30 def wiki_page_version_deleted(self, page): 28 31 self.deleted_version.append(page) 29 32 33 def wiki_page_renamed(self, page, old_page_name): 34 self.renamed[old_page_name] = page 35 30 36 31 37 class WikiPageTestCase(unittest.TestCase): 32 38 … … class WikiPageTestCase(unittest.TestCase): 194 200 listener = TestWikiChangeListener(self.env) 195 201 self.assertEqual(page, listener.deleted[0]) 196 202 203 def test_rename_page(self): 204 cursor = self.db.cursor() 205 data = (1, 42, 'joe', '::1', 'Bla bla', 'Testing', 0) 206 cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)", 207 ('TestPage',) + data) 208 209 page = WikiPage(self.env, 'TestPage') 210 211 attachment = Attachment(self.env, 'wiki', 'TestPage') 212 attachment.insert('foo.txt', tempfile.TemporaryFile(), 0, 1) 213 214 page.rename('PageRenamed') 215 216 cursor.execute("SELECT version,time,author,ipnr,text,comment," 217 "readonly FROM wiki WHERE name=%s", ('PageRenamed',)) 218 self.assertEqual(data, cursor.fetchone()) 219 self.assertEqual(None, cursor.fetchone()) 220 221 attachments = Attachment.select(self.env, 'wiki', 'PageRenamed') 222 self.assertEqual('foo.txt', attachments.next().filename) 223 self.assertRaises(StopIteration, attachments.next) 224 Attachment.delete_all(self.env, 'wiki', 'PageRenamed', self.db) 225 226 227 old_page = WikiPage(self.env, 'TestPage') 228 229 cursor.execute("SELECT version,time,author,ipnr,text,comment," 230 "readonly FROM wiki WHERE name=%s", ('TestPage',)) 231 self.assertEqual(None, cursor.fetchone()) 232 233 listener = TestWikiChangeListener(self.env) 234 self.assertEqual({'TestPage': page}, listener.renamed) 235 236 197 237 198 238 def suite(): 199 239 return unittest.makeSuite(WikiPageTestCase, 'test') -
trac/wiki/web_ui.py
diff --git a/trac/wiki/web_ui.py b/trac/wiki/web_ui.py index 097c34d..92b6788 100644
a b class WikiModule(Component): 93 93 # IPermissionRequestor methods 94 94 95 95 def get_permission_actions(self): 96 actions = ['WIKI_CREATE', 'WIKI_DELETE', 'WIKI_MODIFY', 'WIKI_ VIEW']96 actions = ['WIKI_CREATE', 'WIKI_DELETE', 'WIKI_MODIFY', 'WIKI_RENAME', 'WIKI_VIEW'] 97 97 return actions + [('WIKI_ADMIN', actions)] 98 98 99 99 # IRequestHandler methods … … class WikiModule(Component): 145 145 return self._render_editor(req, page, action, has_collision) 146 146 elif action == 'delete': 147 147 self._do_delete(req, versioned_page) 148 elif action == 'rename': 149 return self._do_rename(req, page) 148 150 elif action == 'diff': 149 151 get_diff_options(req) 150 152 req.redirect(req.href.wiki(versioned_page.name, action='diff', 151 153 old_version=old_version, 152 154 version=version)) 153 155 elif action == 'delete': 154 return self._render_confirm(req, versioned_page) 156 return self._render_confirm_delete(req, versioned_page) 157 elif action == 'rename': 158 return self._render_confirm_rename(req, page) 155 159 elif action == 'edit': 156 160 return self._render_editor(req, versioned_page) 157 161 elif action == 'diff': … … class WikiModule(Component): 246 250 old_version = int(req.args.get('old_version', 0)) or version 247 251 248 252 @with_transaction(self.env) 249 def do_ transaction(db):253 def do_delete(db): 250 254 if version and old_version and version > old_version: 251 255 # delete from `old_version` exclusive to `version` inclusive: 252 256 for v in range(old_version, version): … … class WikiModule(Component): 270 274 version=version, name=page.name)) 271 275 req.redirect(req.href.wiki(page.name)) 272 276 277 def _do_rename(self, req, page): 278 if page.readonly: 279 req.perm(page.resource).require('WIKI_ADMIN') 280 else: 281 req.perm(page.resource).require('WIKI_RENAME') 282 283 if 'cancel' in req.args: 284 req.redirect(get_resource_url(self.env, page.resource, req.href)) 285 286 new_name = req.args.get('new_name', '').rstrip('/') 287 old_name = page.name 288 old_version = page.version 289 leave_redirection = req.args.get('leave_redirection') 290 291 # verify input parameters 292 warn = None 293 if not new_name: 294 warn = _("New name is mandatory for a rename.") 295 elif new_name == old_name: 296 warn = _("New name must be different from old name.") 297 if warn: 298 add_warning(req, warn) 299 return self._render_rename_form(req, page) 300 301 302 @with_transaction(self.env) 303 def do_rename(db): 304 page.rename(new_name, db) 305 if leave_redirection: 306 redirection_page = WikiPage(self.env, old_name) 307 redirection_page.text = _("See [wiki:%(page)s].", 308 page=new_name) 309 author = get_reporter_id(req) 310 comment = _("[wiki:'%(new_name)s@%(old_version)d' " 311 "%(old_name)s] was renamed to %(new_name)s", 312 old_name=old_name, old_version=old_version, 313 new_name=new_name) 314 redirection_page.save(author, comment, req.remote_addr, 315 None, db) 316 317 req.redirect(req.href.wiki(leave_redirection and old_name or new_name)) 318 319 320 273 321 def _do_save(self, req, page): 274 322 if page.readonly: 275 323 req.perm(page.resource).require('WIKI_ADMIN') … … class WikiModule(Component): 294 342 add_warning(req, _("Page not modified, showing latest version.")) 295 343 return self._render_view(req, page) 296 344 297 def _render_confirm (self, req, page):345 def _render_confirm_delete(self, req, page): 298 346 if page.readonly: 299 347 req.perm(page.resource).require('WIKI_ADMIN') 300 348 else: … … class WikiModule(Component): 319 367 self._wiki_ctxtnav(req, page) 320 368 return 'wiki_delete.html', data, None 321 369 370 def _render_confirm_rename(self, req, page): 371 if page.readonly: 372 req.perm(page.resource).require('WIKI_ADMIN') 373 else: 374 req.perm(page.resource).require('WIKI_RENAME') 375 376 data = self._page_data(req, page, 'rename') 377 self._wiki_ctxtnav(req, page) 378 return 'wiki_rename.html', data, None 379 380 322 381 def _render_diff(self, req, page): 323 382 if not page.exists: 324 383 raise TracError(_('Version %(num)s of page "%(name)s" does not '
