TracDev/ContextRefactoring: work_in_progress_snapshot.diff
| File work_in_progress_snapshot.diff, 40.8 KB (added by cboos, 14 months ago) |
|---|
-
trac/attachment.py
diff -r e0c9d8655e31 -r 263dd7d3d361 trac/attachment.py
a b 27 27 28 28 from trac import perm, util 29 29 from trac.config import BoolOption, IntOption 30 from trac.context import IContextProvider, Context, ResourceSystem, \ 31 ResourceNotFound 30 from trac.context import IResourceManager, ResourceSystem, ResourceNotFound 32 31 from trac.core import * 33 32 from trac.env import IEnvironmentSetupParticipant 34 33 from trac.perm import PermissionError, PermissionSystem, IPermissionPolicy … … 80 79 OK.""" 81 80 82 81 83 class AttachmentContext(Context):84 """Context for attachment resources."""85 86 realm = 'attachment'87 88 # methods reimplemented from Context89 90 def get_resource(self):91 return Attachment(self.env, self.parent.realm, self.parent.id,92 filename=self.id, db=self._db)93 94 def get_href(self, href, path=None, **kwargs):95 """Return an URL to the attachment itself.96 97 A `format` keyword argument equal to `'raw'` will be converted98 to the raw-attachment prefix.99 """100 format = kwargs.get('format')101 prefix = 'attachment'102 if format == 'raw':103 kwargs.pop('format')104 prefix = 'raw-attachment'105 path = [unicode(p) for p in [prefix, self.parent.realm, self.parent.id,106 self.id, path] if p]107 return Context.get_href(self, href, '/' + '/'.join(path), **kwargs)108 109 def permid(self):110 return self.parent.permid() + (self.realm, self.id)111 112 def name(self):113 if self.id:114 return _("Attachment '%(id)s' in %(parent)s", id=self.id,115 parent=self.parent.name())116 else:117 return _("Attachments of %(parent)s", parent=self.parent.name())118 119 def shortname(self):120 return '%s:%s' % (self.parent.shortname(), self.filename)121 122 def summary(self):123 return self.resource.description124 125 82 126 83 class AttachmentList(object): 127 84 """Helper class for template data, representing a list of attachments.""" 128 85 129 86 def __init__(self, parent, attachments): 130 self. req = parent.req131 self.parent = parent132 self.attachments = [parent('attachment', a.filename, resource=a)133 for a in attachments]134 135 def attach_href(self ):136 return self. parent('attachment').get_href(self.req.href, action='new')87 self.attachments = filter(None, [parent.child('attachment', a.filename, 88 model=a) 89 for a in attachments]) 90 self.new_attachment = parent.child('attachment') 91 92 def attach_href(self, href): 93 return self.new_attachment.url(href, action='new') 137 94 138 95 def can_create(self): 139 return 'ATTACHMENT_CREATE' in self. req.perm(self.parent('attachment'))96 return 'ATTACHMENT_CREATE' in self.new_attachment.perm 140 97 141 98 142 99 class Attachment(object): … … 322 279 class AttachmentModule(Component): 323 280 324 281 implements(IEnvironmentSetupParticipant, IRequestHandler, 325 INavigationContributor, IWikiSyntaxProvider, 326 IContextProvider) 282 INavigationContributor, IWikiSyntaxProvider, IResourceManager) 327 283 328 284 change_listeners = ExtensionPoint(IAttachmentChangeListener) 329 285 manipulators = ExtensionPoint(IAttachmentManipulator) … … 381 337 return True 382 338 383 339 def process_request(self, req): 340 parent_id = None 384 341 parent_realm = req.args.get('realm') 385 342 path = req.args.get('path') 343 filename = None 386 344 387 345 if not parent_realm or not path: 388 346 raise HTTPBadRequest(_('Bad request')) 389 347 390 context = Context(self.env, req) 391 348 parent_realm = req.perm(parent_realm) 392 349 action = req.args.get('action', 'view') 393 350 if action == 'new': 394 attachment = Attachment(self.env, parent_realm, path)351 parent_id = path 395 352 else: 396 353 segments = path.split('/') 397 354 parent_id = '/'.join(segments[:-1]) 398 355 filename = len(segments) > 1 and segments[-1] 399 356 if not filename: # if there's a trailing '/', show the list 400 return self._render_list( req, context(parent_realm, parent_id))401 attachment = Attachment(self.env, parent_realm, parent_id,402 filename) 403 404 ctx = context(attachment.parent_realm, attachment.parent_id) \405 ('attachment', attachment.filename, resource=attachment)406 add_link(req, 'up', ctx.parent.resource_href(), ctx.parent.name())357 return self._render_list( 358 req, parent_realm.assert_copy(id=parent_id)) 359 360 parent = parent_realm.assert_copy(id=parent_id) 361 attachment = parent.child('attachment', filename) 362 363 add_link(req, 'up', parent.url(req.href), parent.name) 407 364 408 365 if req.method == 'POST': 409 366 if action == 'new': 410 self._do_save( ctx)367 self._do_save(req, attachment) 411 368 elif action == 'delete': 412 self._do_delete( ctx)369 self._do_delete(req, attachment) 413 370 elif action == 'delete': 414 data = self._render_confirm_delete( ctx)371 data = self._render_confirm_delete(req, attachment) 415 372 elif action == 'new': 416 data = self._render_form( ctx)417 else: 418 data = self._render_view( ctx)419 420 data[' context'] = ctx373 data = self._render_form(req, attachment) 374 else: 375 data = self._render_view(req, attachment) 376 377 data['resource'] = attachment 421 378 422 379 add_stylesheet(req, 'common/css/code.css') 423 380 return 'attachment.html', data, None … … 437 394 """Return list of attachments allowed in the request.""" 438 395 return AttachmentList(parent, 439 396 [a for a in Attachment.select(self.env, parent.realm, parent.id) 440 if 'ATTACHMENT_VIEW' in req.perm(parent('attachment', a.filename))])397 if parent.child('attachment', a.filename)]) 441 398 442 399 def get_history(self, start, stop, realm): 443 400 """Return an iterable of tuples describing changes to attachments on … … 458 415 time = datetime.fromtimestamp(ts, utc) 459 416 yield ('created', realm, id, filename, time, description, author) 460 417 461 def get_timeline_events(self, context, start, stop): 462 """Return an iterable of events suitable for ITimelineEventProvider. 463 464 `context` specifies the realm. 418 def get_timeline_events(self, req, resource_realm, start, stop): 419 """Return an event generator suitable for ITimelineEventProvider. 420 421 Events are changes to attachments on resources of the given 422 `resource_realm.realm`. 465 423 """ 466 req = context.req467 424 for change, realm, id, filename, time, descr, author in \ 468 self.get_history(start, stop, context.realm): 469 ctx = context(realm=realm, id=id)('attachment', filename) 470 if 'ATTACHMENT_VIEW' not in req.perm(ctx): 471 continue 472 title = tag(tag.em(os.path.basename(filename)), ' attached to ', 473 tag.em(ctx.parent.name(), title=ctx.parent.summary())) 474 event = TimelineEvent(self, 'attachment') 475 event.set_changeinfo(time, author) 476 event.add_markup(title=title) 477 event.add_wiki(ctx, body=descr) 478 yield event 425 self.get_history(start, stop, resource_realm.realm): 426 parent = resource_realm(id=id) 427 if parent: 428 attachment = parent.child('attachment', filename) 429 if attachment: 430 title = tag(tag.em(os.path.basename(filename)), 431 _(" attached to "), 432 tag.em(parent.name, title=parent.summary)) 433 event = TimelineEvent(self, 'attachment') 434 event.set_changeinfo(time, author) 435 event.add_markup(title=title) 436 event.add_wiki(parent, body=descr) 437 yield event 479 438 480 439 def event_formatter(self, event, key): 481 440 return None 482 441 483 # IContextProvider methods 484 485 def get_context_classes(self): 486 yield AttachmentContext 442 # IResourceManager methods 443 444 def get_resource_realms(self): 445 yield 'attachment' 446 447 def get_model(self, resource): 448 return Attachment(resource.env, resource.parent.realm, 449 resource.parent.id, filename=resource.id) 450 451 def get_resource_url(self, resource, href, path=None, **kwargs): 452 """Return an URL to the attachment itself. 453 454 A `format` keyword argument equal to `'raw'` will be converted 455 to the raw-attachment prefix. 456 """ 457 format = kwargs.get('format') 458 prefix = 'attachment' 459 if format == 'raw': 460 kwargs.pop('format') 461 prefix = 'raw-attachment' 462 path = [unicode(p) for p in 463 [prefix, resource.parent.realm, resource.parent.id, 464 resource.id, path] if p] 465 return ResourceSystem(self.env).get_resource_url( 466 resource, href, '/' + '/'.join(path), **kwargs) 467 468 def get_resource_description(self, resource, format=None): 469 if format == 'compact': 470 return '%s:%s' % (resource.parent.shortname, resource.filename) 471 elif format == 'summary': 472 return resource.model.description 473 if resource.id: 474 return _("Attachment '%(id)s' in %(parent)s", id=resource.id, 475 parent=resource.parent.name) 476 else: 477 return _("Attachments of %(parent)s", parent=resource.parent.name) 487 478 488 479 # Internal methods 489 480 490 def _do_save(self, context): 491 req, attachment = context.req, context.resource 492 req.perm.require('ATTACHMENT_CREATE', context) 481 def _do_save(self, req, attachment): 482 attachment.perm.require('ATTACHMENT_CREATE') 493 483 494 484 if 'cancel' in req.args: 495 req.redirect( context.parent.resource_href())485 req.redirect(attachment.parent.url(req.href)) 496 486 497 487 upload = req.args['attachment'] 498 488 if not hasattr(upload, 'filename') or not upload.filename: … … 520 510 filename = os.path.basename(filename) 521 511 if not filename: 522 512 raise TracError(_('No file uploaded')) 523 # Now the filename is known, update the attachment context 524 context.id = filename 525 526 attachment.description = req.args.get('description', '') 527 attachment.author = get_reporter_id(req, 'author') 528 attachment.ipnr = req.remote_addr 513 # Now the filename is known, update the attachment resource 514 attachment.id = filename 515 516 model = Attachment(self.env, parent_realm, path) 517 model.description = req.args.get('description', '') 518 model.author = get_reporter_id(req, 'author') 519 model.ipnr = req.remote_addr 529 520 530 521 # Validate attachment 531 522 for manipulator in self.manipulators: 532 for field, message in manipulator.validate_attachment(req, 533 attachment): 523 for field, message in manipulator.validate_attachment(req, model): 534 524 if field: 535 525 raise InvalidAttachment(_('Attachment field %(field)s is ' 536 526 'invalid: %(message)s', … … 541 531 542 532 if req.args.get('replace'): 543 533 try: 544 old_attachment = Attachment(self.env, attachment.parent _realm,545 attachment.parent _id, filename)534 old_attachment = Attachment(self.env, attachment.parent.realm, 535 attachment.parent.id, filename) 546 536 if not (old_attachment.author and req.authname \ 547 537 and old_attachment.author == req.authname): 548 req.perm.require('ATTACHMENT_DELETE', context)538 attachment.perm.require('ATTACHMENT_DELETE') 549 539 old_attachment.delete() 550 540 except TracError: 551 541 pass # don't worry if there's nothing to replace 552 attachment.filename = None553 attachment.insert(filename, upload.file, size)542 model.filename = None 543 model.insert(filename, upload.file, size) 554 544 555 545 # Redirect the user to list of attachments (must add a trailing '/') 556 req.redirect(context.resource_href('..') + '/') 557 558 def _do_delete(self, context): 559 req, attachment = context.req, context.resource 560 req.perm.require('ATTACHMENT_DELETE', context) 561 562 parent_href = context.parent.resource_href() 546 req.redirect(attachment.url(req.href, '..') + '/') 547 548 def _do_delete(self, req, attachment): 549 resource.perm.require('ATTACHMENT_DELETE') 550 551 parent_href = attachment.parent.url(req.href) 563 552 if 'cancel' in req.args: 564 553 req.redirect(parent_href) 565 554 566 context.resource.delete() 567 568 # Redirect the user to the attachment parent page 555 attachment.model.delete() 569 556 req.redirect(parent_href) 570 557 571 def _render_confirm_delete(self, context): 572 req, attachment = context.req, context.resource 573 req.perm.require('ATTACHMENT_DELETE', context) 574 575 attachment = context.resource 576 return {'mode': 'delete', 577 'title': _('%(context)s (delete)', context=context.name()), 558 def _render_confirm_delete(self, req, attachment): 559 attachment.perm.require('ATTACHMENT_DELETE') 560 return {'mode': 'delete', 'title': _('%(attachment)s (delete)', 561 attachment=attachment.name), 562 'attachment': attachment.model} # FIXME 563 564 def _render_form(self, req, attachment): 565 attachment.perm.require('ATTACHMENT_CREATE') 566 return {'mode': 'new', 'author': get_reporter_id(req), 578 567 'attachment': attachment} 579 568 580 def _render_form(self, context): 581 req, attachment = context.req, context.resource 582 req.perm.require('ATTACHMENT_CREATE', context) 583 584 return {'mode': 'new', 'author': get_reporter_id(context.req)} 585 586 def _render_list(self, req, context): 587 context.req.perm.require('ATTACHMENT_VIEW', context('attachment')) 588 569 def _render_list(self, req, parent): 570 attachment = parent.assert_child('attachment') 589 571 data = { 590 'mode': 'list', ' context': None, # no specific attachment591 'attachments': self.permitted_attachment_list(req, context)}592 593 add_link( context.req, 'up', context.resource_href(), context.name())572 'mode': 'list', 'attachment': None, # no specific attachment 573 'attachments': self.permitted_attachment_list(req, parent)} 574 575 add_link(req, 'up', parent.url(req.href), parent.name) 594 576 595 577 return 'attachment.html', data, None 596 578 597 def _render_view(self, context): 598 req, attachment = context.req, context.resource 599 req.perm.require('ATTACHMENT_VIEW', context) 600 601 req.check_modified(attachment.date) 602 603 data = {'mode': 'view', 'title': context.name(), 604 'attachment': attachment} 605 606 fd = attachment.open() 579 def _render_view(self, req, attachment): 580 req.check_modified(attachment.model.date) 581 582 data = {'mode': 'view', 'title': attachment.name, 583 'attachment': attachment} 584 585 fd = attachment.model.open() 607 586 try: 608 587 mimeview = Mimeview(self.env) 609 588 … … 611 590 str_data = fd.read(1000) 612 591 fd.seek(0) 613 592 614 mime_type = mimeview.get_mimetype(attachment.filename, str_data) 593 mime_type = mimeview.get_mimetype(attachment.model.filename, 594 str_data) 615 595 616 596 # Eventually send the file directly 617 597 format = req.args.get('format') … … 628 608 if 'charset=' not in mime_type: 629 609 charset = mimeview.get_charset(str_data, mime_type) 630 610 mime_type = mime_type + '; charset=' + charset 631 req.send_file(attachment. path, mime_type)611 req.send_file(attachment.model.path, mime_type) 632 612 633 613 # add ''Plain Text'' alternate link if needed 634 614 if (self.render_unsafe_content and 635 615 mime_type and not mime_type.startswith('text/plain')): 636 plaintext_href = context.resource_href(format='txt')616 plaintext_href = attachment.url(req.href, format='txt') 637 617 add_link(req, 'alternate', plaintext_href, _('Plain Text'), 638 618 mime_type) 639 619 640 620 # add ''Original Format'' alternate link (always) 641 raw_href = context.resource_href(format='raw')621 raw_href = attachment.url(req.href, format='raw') 642 622 add_link(req, 'alternate', raw_href, _('Original Format'), 643 623 mime_type) 644 624 645 625 self.log.debug("Rendering preview of file %s with mime-type %s" 646 % (attachment.filename, mime_type)) 647 626 % (attachment.model.filename, mime_type)) 627 628 context = RenderingContext(req.href, attachment) 648 629 data['preview'] = mimeview.preview_data( 649 630 context, fd, os.fstat(fd.fileno()).st_size, mime_type, 650 attachment. filename, raw_href, annotations=['lineno'])631 attachment.model.filename, raw_href, annotations=['lineno']) 651 632 return data 652 633 finally: 653 634 fd.close() … … 655 636 def _format_link(self, formatter, ns, target, label): 656 637 link, params, fragment = formatter.split_link(target) 657 638 ids = link.split(':', 2) 658 context = None639 attachment = None 659 640 if len(ids) == 3: 660 641 known_realms = ResourceSystem(self.env).get_known_realms() 661 642 # new-style attachment: TracLinks (filename:realm:id) 662 643 if ids[1] in known_realms: 663 context = formatter.context(ids[1], ids[2]) \ 664 ('attachment', ids[0]) 644 parent = formatter.perm(ids[1], ids[2]) 645 if parent: 646 attachment = parent.child('attachment', ids[0]) 665 647 else: # try old-style attachment: TracLinks (realm:id:filename) 666 648 if ids[0] in known_realms: 667 context = formatter.context(ids[0], ids[1]) \ 668 ('attachment', ids[2]) 649 parent = formatter.perm(ids[0], ids[1]) 650 if parent: 651 attachment = parent.child('attachment', ids[2]) 669 652 else: # local attachment: TracLinks (filename) 670 context = formatter.context('attachment', link)671 if context:653 attachment = formatter.resource.child('attachment', link) 654 if attachment: 672 655 try: 673 attachment = context.resource656 check_existing = attachment.model # TODO: verify 674 657 format = None 675 658 if ns.startswith('raw'): 676 659 format = 'raw' 677 660 return tag.a(label, class_='attachment', 678 href=context.resource_href(format=format) + params, 679 title=context.name()) 661 href=(attachment.url(formatter.href, 662 format=format) + params), 663 title=attachment.name) 680 664 except TracError, e: 681 665 pass 682 666 return tag.a(label, class_='missing attachment', rel='nofollow') … … 697 681 'milestone': 'MILESTONE_DELETE'}, 698 682 } 699 683 700 def check_permission(self, username, action, context):684 def check_permission(self, username, action, resource): 701 685 perm_map = self._perm_maps.get(action) 702 686 if perm_map: 703 legacy_action = perm_map.get( context.resource.parent_realm)687 legacy_action = perm_map.get(resource.parent.realm) 704 688 if legacy_action: 705 689 decision = PermissionSystem(self.env) \ 706 .check_permission(legacy_action, username, context)690 .check_permission(legacy_action, username, resource) 707 691 if not decision: 708 692 self.env.log.debug('LegacyAttachmentPolicy denied %s ' 709 693 'access to %s. User needs %s' % 710 (username, context, legacy_action))694 (username, resource, legacy_action)) 711 695 return decision -
trac/mimeview/api.py
diff -r e0c9d8655e31 -r 263dd7d3d361 trac/mimeview/api.py
a b 71 71 72 72 73 73 __all__ = ['get_mimetype', 'is_binary', 'detect_unicode', 'Mimeview', 74 'content_to_unicode' ]74 'content_to_unicode', 'RenderingContext'] 75 75 76 76 77 77 # Some common MIME types and their associated keywords and/or file extensions … … 215 215 return mimeview.to_unicode(content, mimetype) 216 216 217 217 218 class RenderingContext(object): 219 """Rendering contexts. 220 221 This specifies ''how'' a rendering should be done, with various 222 options that might be relevant to some or all the renderers. 223 224 A context stores the `href` used as a base for building URLs. 225 If not given, this reverts to the value of `env.abs_href`. 226 227 A resource can also be associated to a rendering context, and that resource 228 will be used as the base content when rendering relative TracLinks. 229 230 Another aspect related to the access context consists of the scope or 231 context trail by which the information belonging to a context is 232 presented. It is quite usual that contexts are embedded in other 233 contexts. This can be known by querying the `parent` context, which 234 is automatically set when creating a subcontext from another context. XXX 235 236 For example, when rendering a ticket description from within a 237 Custom Query rendered by the TicketQuery macro inside a wiki page, 238 the context ''path'' will be: 239 240 context.resource.(realm, id) = ('ticket', '12') 241 context.parent.resource.(realm, id) = ('wiki', 'CurrentStatus') 242 243 Finally, the context could also know about the expected output MIME type 244 which should be used to present the information to the user (TODO) 245 """ 246 247 def __init__(self, req, resource, parent=None, href=None, **kwargs): 248 self.req = req 249 self.resource = resource or req.perm.toplevel() 250 self.env = self.resource.env 251 self.perm = self.resource.perm 252 self.parent = parent 253 self.href = href or self.env.abs_href 254 self.properties = kwargs 255 256 def __repr__(self): 257 path = [] 258 context = self 259 while context: 260 if context.resource.realm: # skip toplevel resource 261 path.append(unicode(context.resource)) 262 context = context.parent 263 return '<RenderingContext %s>' % (' - '.join(reversed(path))) 264 265 def __call__(self, resource, href=None, **kwargs): 266 """Return a sub-`RenderingContext`. 267 268 'href' can be modified on the fly, which is usefull to e.g. turn URL 269 generation to absolute mode. 270 271 'resource' can be modified this way as well. 272 Any remaining keyword argument will be treated as a new property. 273 """ 274 return RenderingContext(self.req, resource, self, href or self.href, 275 **kwargs) 276 277 def __contains__(self, resource): 278 """Check whether a given resource is in the rendering path. 279 280 This is useful for avoiding to render resources recursively. 281 """ 282 context = self 283 while context: 284 if context.resource == resource: 285 return True 286 context = context.parent 287 288 218 289 class IHTMLPreviewRenderer(Interface): 219 290 """Extension point interface for components that add HTML renderers of 220 291 specific content types to the `Mimeview` component. … … 248 319 """ 249 320 250 321 def render(context, mimetype, content, filename=None, url=None): 251 """Render an XHTML preview of the raw `content` within a Context. 322 """Render an XHTML preview of the raw `content` in context. 323 324 `context` is a `RenderingContext`. 252 325 253 326 The `content` might be: 254 327 * a `str` object … … 292 365 """Return the XHTML markup for the table cell that contains the 293 366 annotation data. 294 367 295 `context` is the context corresponding to the content being annotated, 368 `context` is the `RenderingContext` corresponding to the content being 369 annotated, 296 370 `row` is the tr Element being built, `number` is the line number being 297 371 processed and `line` is the line's actual content. 298 372 `annotations` is whatever additional data the `get_annotation_data` -
trac/perm.py
diff -r e0c9d8655e31 -r 263dd7d3d361 trac/perm.py
a b 19 19 """Management of permissions.""" 20 20 21 21 from trac.config import ExtensionOption, OrderedExtensionsOption 22 from trac.context import Resource 22 23 from trac.core import * 23 24 from trac.util.compat import set 24 25 from trac.util.translation import _ … … 30 31 class PermissionError(StandardError): 31 32 """Insufficient permissions to complete the operation""" 32 33 33 def __init__ (self, action=None ):34 def __init__ (self, action=None, resource=None): 34 35 StandardError.__init__(self) 35 36 self.action = action 37 self.resource = resource 36 38 37 39 def __str__ (self): 38 40 if self.action: 39 return _('%(perm)s privileges are required to perform this operation.', 40 perm=self.action)
