Edgewall Software

Ticket #3068: attachment-api.diff

File attachment-api.diff, 9.5 kB (added by athomas, 2 years ago)

Attachment interface API

  • trac/attachment.py

     
    3333from trac.wiki import IWikiSyntaxProvider 
    3434 
    3535 
     36class IAttachmentPointProvider(Interface): 
     37    def get_attachment_points(): 
     38        """Provide details on file attachment points provided by this 
     39        component.  Returns an iterable of tuples in the form `(type, 
     40        description, title, (view_perm, write_perm, delete_perm))`. 
     41        `description` and `title` will be used as the formatting string for the 
     42        attachment point identifier.""" 
     43 
     44 
    3645class Attachment(object): 
    3746 
    3847    def __init__(self, env, parent_type, parent_id, filename=None, db=None): 
     
    8695        return req.href(self.parent_type, self.parent_id) 
    8796 
    8897    def _get_title(self): 
    89         return '%s%s: %s' % (self.parent_type == 'ticket' and '#' or '', 
    90                              self.parent_id, self.filename) 
     98        title = AttachmentModule(self.env)._get_attachment_point(self.parent_type)[2] 
     99        title = title % self.parent_id 
     100        return '%s: %s' % (title, self.filename) 
    91101    title = property(_get_title) 
    92102 
    93103    def delete(self, db=None): 
     
    215225    implements(IEnvironmentSetupParticipant, IRequestHandler, 
    216226               INavigationContributor, IWikiSyntaxProvider) 
    217227 
     228    attachment_points = ExtensionPoint(IAttachmentPointProvider) 
     229 
    218230    CHUNK_SIZE = 4096 
    219231 
    220232    max_size = IntOption('attachment', 'max_size', 262144, 
    221         """Maximum allowed file size for ticket and wiki attachments.""") 
     233        """Maximum allowed file size for attachments.""") 
    222234 
    223235    render_unsafe_content = BoolOption('attachment', 'render_unsafe_content', 
    224236                                       'false', 
     
    256268    # IRequestHandler methods 
    257269 
    258270    def match_request(self, req): 
    259         match = re.match(r'^/attachment/(ticket|wiki)(?:[/:](.*))?$', 
     271        match = re.match(r'^/attachment/([^/]+)(?:[/:](.*))?$', 
    260272                         req.path_info) 
    261273        if match: 
    262274            req.args['type'] = match.group(1) 
     
    268280        path = req.args.get('path') 
    269281        if not parent_type or not path: 
    270282            raise HTTPBadRequest('Bad request') 
    271         if not parent_type in ['ticket', 'wiki']: 
    272             raise HTTPBadRequest('Unknown attachment type') 
     283        for point in self.attachment_points: 
     284            for descriptor in point.get_attachment_points(): 
     285                if descriptor[0] == parent_type: 
     286                    break 
     287            else: 
     288                continue 
     289            break 
     290        else: 
     291            raise HTTPBadRequest('Unknown attachment type "%s"' % parent_type) 
    273292 
    274293        action = req.args.get('action', 'view') 
    275294        if action == 'new': 
     
    306325    def _parent_to_hdf(self, req, parent_type, parent_id): 
    307326        # Populate attachment.parent: 
    308327        parent_link = req.href(parent_type, parent_id) 
    309         if parent_type == 'ticket': 
    310             parent_text = 'Ticket #' + parent_id 
    311         else: # 'wiki' 
    312             parent_text = parent_id 
     328        type, description, title, perms = self._get_attachment_point(parent_type) 
     329        parent_text = description % parent_id 
    313330        req.hdf['attachment.parent'] = { 
    314331            'type': parent_type, 'id': parent_id, 
    315332            'name': parent_text, 'href': parent_link 
     
    326343 
    327344    # Internal methods 
    328345 
     346    def _get_attachment_point(self, point_type): 
     347        for point in self.attachment_points: 
     348            for descriptor in point.get_attachment_points(): 
     349                if descriptor[0] == point_type: 
     350                    return descriptor 
     351        raise TracError('No attachment point found for %s' % 
     352                        attachment.parent_type) 
     353 
     354    def _get_permission(self, operation, attachment): 
     355        """Find the actual permission required to perform `operation`, which can 
     356        be one of `view`, `write`, `delete`.""" 
     357        operation = {'view': 0, 'modify': 1, 'delete': 2}[operation] 
     358        return self._get_attachment_point(attachment.parent_type)[-1][operation] 
     359 
    329360    def _do_save(self, req, attachment): 
    330         perm_map = {'ticket': 'TICKET_APPEND', 'wiki': 'WIKI_MODIFY'} 
    331         req.perm.assert_permission(perm_map[attachment.parent_type]) 
     361        req.perm.assert_permission(self._get_permission('modify', attachment)) 
    332362 
    333363        if req.args.has_key('cancel'): 
    334364            req.redirect(attachment.parent_href(req)) 
     
    367397                                            attachment.parent_id, filename) 
    368398                if not (old_attachment.author and req.authname \ 
    369399                        and old_attachment.author == req.authname): 
    370                     perm_map = {'ticket': 'TICKET_ADMIN', 'wiki': 'WIKI_DELETE'} 
    371                     req.perm.assert_permission(perm_map[old_attachment.parent_type]) 
     400                    req.perm.assert_permission(self._get_permission('delete', 
     401                                               old_attachment)) 
    372402                old_attachment.delete() 
    373403            except TracError: 
    374404                pass # don't worry if there's nothing to replace 
     
    379409        req.redirect(attachment.href(req)) 
    380410 
    381411    def _do_delete(self, req, attachment): 
    382         perm_map = {'ticket': 'TICKET_ADMIN', 'wiki': 'WIKI_DELETE'} 
    383         req.perm.assert_permission(perm_map[attachment.parent_type]) 
     412        req.perm.assert_permission(self._get_permission('delete', attachment)) 
    384413 
    385414        if req.args.has_key('cancel'): 
    386415            req.redirect(attachment.href(req)) 
     
    391420        req.redirect(attachment.parent_href(req)) 
    392421 
    393422    def _render_confirm(self, req, attachment): 
    394         perm_map = {'ticket': 'TICKET_ADMIN', 'wiki': 'WIKI_DELETE'} 
    395         req.perm.assert_permission(perm_map[attachment.parent_type]) 
     423        req.perm.assert_permission(self._get_permission('delete', attachment)) 
    396424 
    397425        req.hdf['title'] = '%s (delete)' % attachment.title 
    398426        req.hdf['attachment'] = {'filename': attachment.filename, 
    399427                                 'mode': 'delete'} 
    400428 
    401429    def _render_form(self, req, attachment): 
    402         perm_map = {'ticket': 'TICKET_APPEND', 'wiki': 'WIKI_MODIFY'} 
    403         req.perm.assert_permission(perm_map[attachment.parent_type]) 
     430        req.perm.assert_permission(self._get_permission('modify', attachment)) 
    404431 
    405432        req.hdf['attachment'] = {'mode': 'new', 
    406433                                 'author': util.get_reporter_id(req)} 
    407434 
    408435    def _render_view(self, req, attachment): 
    409         perm_map = {'ticket': 'TICKET_VIEW', 'wiki': 'WIKI_VIEW'} 
    410         req.perm.assert_permission(perm_map[attachment.parent_type]) 
     436        req.perm.assert_permission(self._get_permission('view', attachment)) 
    411437 
    412438        req.check_modified(attachment.time) 
    413439 
     
    416442        req.hdf['attachment'] = attachment_to_hdf(self.env, req, None, 
    417443                                                  attachment) 
    418444         
    419         perm_map = {'ticket': 'TICKET_ADMIN', 'wiki': 'WIKI_DELETE'} 
    420         if req.perm.has_permission(perm_map[attachment.parent_type]): 
     445        if req.perm.has_permission(self._get_permission('delete', attachment)): 
    421446            req.hdf['attachment.can_delete'] = 1 
    422447 
    423448        fd = attachment.open() 
  • trac/ticket/web_ui.py

     
    1818import re 
    1919import time 
    2020 
    21 from trac.attachment import attachments_to_hdf, Attachment 
     21from trac.attachment import attachments_to_hdf, Attachment, \ 
     22                            IAttachmentPointProvider 
    2223from trac.config import BoolOption, Option 
    2324from trac.core import * 
    2425from trac.env import IEnvironmentSetupParticipant 
     
    179180 
    180181class TicketModule(TicketModuleBase): 
    181182 
    182     implements(INavigationContributor, IRequestHandler, ITimelineEventProvider) 
     183    implements(INavigationContributor, IRequestHandler, ITimelineEventProvider, 
     184               IAttachmentPointProvider) 
    183185 
    184186    default_version = Option('ticket', 'default_version', '', 
    185187        """Default version for newly created tickets.""") 
     
    200202        """Enable the display of all ticket changes in the timeline 
    201203        (''since 0.9'').""") 
    202204 
     205    # IAttachmentPointProvider methods 
     206 
     207    def get_attachment_points(self): 
     208        yield ('ticket', 'Ticket #%s', '#%s', 
     209               ('TICKET_VIEW', 'TICKET_APPEND', 'TICKET_ADMIN')) 
     210 
    203211    # INavigationContributor methods 
    204212 
    205213    def get_active_navigation_item(self, req): 
  • trac/wiki/web_ui.py

     
    1919import re 
    2020import StringIO 
    2121 
    22 from trac.attachment import attachments_to_hdf, Attachment 
     22from trac.attachment import attachments_to_hdf, Attachment, \ 
     23                            IAttachmentPointProvider 
    2324from trac.core import * 
    2425from trac.perm import IPermissionRequestor 
    2526from trac.Search import ISearchSource, search_to_sql, shorten_result 
     
    3839class WikiModule(Component): 
    3940 
    4041    implements(INavigationContributor, IPermissionRequestor, IRequestHandler, 
    41                ITimelineEventProvider, ISearchSource) 
     42               ITimelineEventProvider, ISearchSource, IAttachmentPointProvider) 
    4243 
    4344    page_manipulators = ExtensionPoint(IWikiPageManipulator) 
    4445 
     46    # IAttachmentPointProvider methods 
     47 
     48    def get_attachment_points(self): 
     49        yield ('wiki', '%s', '%s', ('WIKI_VIEW', 'WIKI_MODIFY', 'WIKI_DELETE')) 
     50 
    4551    # INavigationContributor methods 
    4652 
    4753    def get_active_navigation_item(self, req):