Edgewall Software

Ticket #2827: thumbailPreviewOfAttachImages.r6174.patch

File thumbailPreviewOfAttachImages.r6174.patch, 14.7 KB (added by Slava Zanko <slavaz>, 12 months ago)

Preview attached images

  • trac/attachment.py

     
    2020import os 
    2121import re 
    2222import shutil 
     23import Image 
    2324import time 
    2425import unicodedata 
    2526 
     
    113114        self.env = env 
    114115        self.parent_realm = self.resource.parent.realm 
    115116        self.parent_id = unicode(self.resource.parent.id) 
     117        self.first1000bytes = None 
    116118        if self.resource.id: 
    117119            self._fetch(self.resource.id, db) 
    118120        else: 
     
    122124            self.date = None 
    123125            self.author = None 
    124126            self.ipnr = None 
     127            self.mimetype = None 
     128            self.charset = None 
    125129 
    126130    def _set_filename(self, val): 
    127131        self.resource.id = val 
    128132 
    129133    filename = property(lambda self: self.resource.id, _set_filename) 
    130  
    131134    def _fetch(self, filename, db=None): 
    132135        if not db: 
    133136            db = self.env.get_db_cnx() 
     
    149152        self.date = datetime.fromtimestamp(time, utc) 
    150153        self.author = row[4] 
    151154        self.ipnr = row[5] 
     155        self._get_mimetype() 
     156        self._get_charset() 
    152157 
     158    def _get_first_1000_bytes(self): 
     159        if not self.first1000bytes: 
     160            fd = self.open() 
     161            try: 
     162                str_data = fd.read(1000) 
     163                fd.seek(0) 
     164            finally: 
     165                fd.close() 
     166 
     167    def _get_charset(self): 
     168        self._get_first_1000_bytes() 
     169        mimeview = Mimeview(self.env) 
     170        self.charset = mimeview.get_charset(self.first1000bytes, self.mimetype) 
     171 
     172    def _get_mimetype(self): 
     173        self._get_first_1000_bytes() 
     174        mimeview = Mimeview(self.env) 
     175        self.mimetype = mimeview.get_mimetype(self.filename, self.first1000bytes) 
     176        if not self.mimetype: 
     177            self.mimetype = 'application/octet-stream' 
     178 
     179    def getThumbail(self, size = (160,160)): 
     180        image = Image.open(self.path) 
     181        thumbDir=os.path.join(self._get_dirPath(),'.thumb') 
     182        thumbFile=os.path.join(thumbDir,unicode_quote(self.filename)) 
     183        if not os.path.isdir(thumbDir): 
     184            os.mkdir(thumbDir) 
     185        # 200 x 150 
     186        if os.path.exists(thumbFile): 
     187            return thumbFile 
     188        if image.size[0] < size[0] and image.size[0] < size[0]: 
     189            shutil.copyfile(self.path,thumbFile) 
     190            return thumbFile 
     191        image.thumbnail(size) 
     192        image.save(thumbFile) 
     193        image.close() 
     194    def _get_dirPath(self): 
     195        return os.path.normpath(os.path.join(self.env.path, 'attachments', self.parent_realm, 
     196                            unicode_quote(self.parent_id))) 
    153197    def _get_path(self): 
    154198        path = os.path.join(self.env.path, 'attachments', self.parent_realm, 
    155199                            unicode_quote(self.parent_id)) 
     
    260304            attachment.date = datetime.fromtimestamp(time, utc) 
    261305            attachment.author = author 
    262306            attachment.ipnr = ipnr 
     307            attachment._get_mimetype() 
     308            attachment._get_charset() 
    263309            yield attachment 
    264310 
    265311    def delete_all(cls, env, parent_realm, parent_id, db): 
     
    342388    # IRequestHandler methods 
    343389 
    344390    def match_request(self, req): 
    345         match = re.match(r'^/(raw-)?attachment/([^/]+)(?:[/:](.*))?$', 
     391        match = re.match(r'^/(raw-|thumb-)?attachment/([^/]+)(?:[/:](.*))?$', 
    346392                         req.path_info) 
    347393        if match: 
    348394            raw, realm, path = match.groups() 
    349             if raw: 
     395            if raw == 'raw-': 
    350396                req.args['format'] = 'raw' 
     397            elif raw == 'thumb-': 
     398                req.args['format'] = 'thumb' 
    351399            req.args['realm'] = realm 
    352400            if path: 
    353401                req.args['path'] = path.replace(':', '/') 
     
    401449 
    402450    def get_link_resolvers(self): 
    403451        yield ('raw-attachment', self._format_link) 
     452        yield ('thumb-attachment', self._format_link) 
    404453        yield ('attachment', self._format_link) 
    405454 
    406455    # Public methods 
     
    483532        if format == 'raw': 
    484533            kwargs.pop('format') 
    485534            prefix = 'raw-attachment' 
     535        elif format == 'thumb': 
     536            kwargs.pop('format') 
     537            prefix = 'thumb-attachment' 
    486538        parent_href = get_resource_url(self.env, resource.parent, Href('')) 
    487539        if not resource.id: 
    488540            return href(prefix, parent_href) + '/' 
     
    505557 
    506558    # Internal methods 
    507559 
    508     def _do_save(self, req, attachment): 
     560    def _do_save_one(self, req, attachment, upload, description): 
    509561        req.perm(attachment.resource).require('ATTACHMENT_CREATE') 
    510562 
    511         if 'cancel' in req.args: 
    512             req.redirect(get_resource_url(self.env, attachment.resource.parent, 
    513                                           req.href)) 
    514563 
    515         upload = req.args['attachment'] 
    516564        if not hasattr(upload, 'filename') or not upload.filename: 
    517565            raise TracError(_('No file uploaded')) 
    518566        if hasattr(upload.file, 'fileno'): 
     
    540588            raise TracError(_('No file uploaded')) 
    541589        # Now the filename is known, update the attachment resource 
    542590        # attachment.filename = filename 
    543         attachment.description = req.args.get('description', '') 
     591        attachment.description = description 
    544592        attachment.author = get_reporter_id(req, 'author') 
    545593        attachment.ipnr = req.remote_addr 
    546594 
     
    569617            attachment.filename = None 
    570618        attachment.insert(filename, upload.file, size) 
    571619 
    572         req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
     620    def _do_save_many(self,req, attachment): 
     621        curr_attach = 0 
     622        while curr_attach < int(req.args['multiple_attachments']): 
     623            upload=req.args['attachment-%d' % curr_attach] 
     624            if hasattr(upload, 'filename') and upload.filename: 
     625                attachment.filename = None 
     626                self._do_save_one(req, attachment, upload, req.args.get('description-%d' % curr_attach, '')) 
     627            curr_attach=curr_attach+1 
     628 
     629    def _do_save(self, req, attachment): 
     630        req.perm(attachment.resource).require('ATTACHMENT_CREATE') 
     631 
     632        if 'cancel' in req.args: 
     633            req.redirect(get_resource_url(self.env, attachment.resource.parent, 
     634                                          req.href)) 
     635 
     636        if 'multiple_attachments' in req.args: 
     637            self._do_save_many(req, attachment) 
     638            req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
    573639                                      req.href)) 
     640        else: 
     641            self._do_save_one(req, attachment, req.args['attachment'], req.args.get('description', '')) 
     642            # Redirect the user to list of attachments (must add a trailing '/') 
     643            req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
     644                                      req.href)) 
    574645 
    575646    def _do_delete(self, req, attachment): 
    576647        req.perm(attachment.resource).require('ATTACHMENT_DELETE') 
     
    618689                'title': get_resource_name(self.env, attachment.resource), 
    619690                'attachment': attachment} 
    620691 
     692        mime_type = attachment.mimetype 
    621693        fd = attachment.open() 
    622694        try: 
    623695            mimeview = Mimeview(self.env) 
    624696 
    625             # MIME type detection 
    626             str_data = fd.read(1000) 
    627             fd.seek(0) 
    628              
    629             mime_type = mimeview.get_mimetype(attachment.filename, str_data) 
    630  
    631697            # Eventually send the file directly 
    632698            format = req.args.get('format') 
    633             if format in ('raw', 'txt'): 
     699            if format == 'thumb': 
    634700                if not self.render_unsafe_content: 
     701                    req.send_header('Content-Disposition', 'attachment') 
     702                if attachment.mimetype[0:5] in ('image'): 
     703                    req.send_file(attachment.getThumbail(), mime_type) 
     704            elif format in ('raw', 'txt'): 
     705                if not self.render_unsafe_content: 
    635706                    # Force browser to download files instead of rendering 
    636707                    # them, since they might contain malicious code enabling  
    637708                    # XSS attacks 
     
    693764                format = None 
    694765                if ns.startswith('raw'): 
    695766                    format = 'raw' 
     767                elif ns.startswith('thumb'): 
     768                    format = 'thumb' 
    696769                href = get_resource_url(self.env, attachment, formatter.href, 
    697770                                        format=format) 
    698771                return tag.a(label, class_='attachment', href=href + params, 
  • trac/htdocs/js/trac.js

     
    6969function getAncestorByTagName(elem, tagName) { 
    7070  return $(elem).parents(tagName).get(0); 
    7171} 
     72var ATTACHFILE_COUNTER=0; 
     73function manageMultipleAttachFields(_obj){ 
     74    if (_obj.value == '') { 
     75        if($("#multiAttach_tbody").get(0).rows.length == 1) return; 
     76        $("#multiAttach_tbody").get(0).deleteRow($(_obj).attr("attachnum")*1); 
     77        ATTACHFILE_COUNTER=0; 
     78        $("#multiAttach_tbody tr").each(function(index, element){ 
     79            $("input", $("td", element).get(0)) 
     80                .attr("attachnum",ATTACHFILE_COUNTER) 
     81                .get(0).name = "attachment-"+ATTACHFILE_COUNTER; 
     82            $("input", $("td", element).get(1)) 
     83                .get(0).name = "description-"+ATTACHFILE_COUNTER; 
     84            ATTACHFILE_COUNTER++; 
     85        }); 
     86        $("#multiAttach_count").get(0).value = $("#multiAttach_count").val()*1-1; 
     87    } else { 
     88        if ($(_obj).attr("attachnum") != $("#multiAttach_count").val()-1) return; 
     89        var tr = $("#multiAttach_tbody").get(0).insertRow(-1); 
     90        var td = tr.insertCell(-1); 
     91        $('<input type="file" onchange="manageMultipleAttachFields(this)">') 
     92           .attr("attachnum",$("#multiAttach_count").val()) 
     93           .appendTo(td) 
     94           .get(0).name = "attachment-"+$("#multiAttach_count").val(); 
     95 
     96        td = tr.insertCell(-1); 
     97        $('<input type="text">') 
     98            .attr("size",60) 
     99            .appendTo(td) 
     100            .get(0).name = "description-"+$("#multiAttach_count").val(); 
     101        $("#multiAttach_count").get(0).value = $("#multiAttach_count").val()*1+1; 
     102    } 
     103} 
  • trac/templates/macros.html

     
    179179          </ul> 
    180180        </py:when> 
    181181        <py:when test="not compact"> 
    182           <h2>Attachments</h2> 
    183182          <div py:if="alist.attachments or alist.can_create" id="attachments"> 
     183          <h2>Attachments  (Files)</h2> 
    184184            <dl py:if="alist.attachments" class="attachments"> 
    185               <py:for each="attachment in alist.attachments"> 
     185               <py:for each="attachment in  [f for f in alist.attachments if f.mimetype[0:5] not in ('image') ]"> 
    186186                <dt>${show_one_attachment(attachment)}</dt> 
    187187                <dd py:if="attachment.description"> 
    188188                  ${wiki_to_oneliner(context, attachment.description)} 
    189189                </dd> 
    190190              </py:for> 
    191191            </dl> 
     192          <h2>Attachments  (Images)</h2> 
     193             <py:with vars="attachs = [f for f in alist.attachments if f.mimetype[0:5] in ('image') ]; fullrow = len(attachs)"> 
     194                <table align="center"> 
     195                <tr py:for="row in group(attachs, 3)"> 
     196                <py:for each="oneattach in row"> 
     197                <td py:if="not oneattach" width="210"> 
     198                &nbsp; 
     199                </td> 
     200                <td py:if="oneattach" align="center" style="border: 1px solid #f0f0f0; text-align: center;"> 
     201                <div style="width: 200px; height: 200px;"> 
     202                    <a href="${url_of(oneattach.resource)}"> 
     203                        <img src="${url_of(oneattach.resource,format='thumb')}" 
     204                        style="cursor: pointer;" 
     205                        border="0" 
     206                        id="img_${oneattach.filename}" 
     207                        /> 
     208                    </a> 
     209                </div> 
     210                <div style="background-color: #f0f0f0;"> 
     211                    <py:with vars="attach_size = round(float(oneattach.size)/float(1024),3)"> 
     212                    <small>${oneattach.filename} (${attach_size} kb)<br /> 
     213                    <i>${oneattach.description}</i> 
     214                    </small> 
     215                    </py:with> 
     216                </div> 
     217                </td> 
     218                </py:for> 
     219                </tr></table> 
     220             </py:with> 
    192221            ${attach_file_form(alist)} 
    193222          </div> 
    194223        </py:when> 
  • trac/templates/attachment.html

     
    1313  <body py:with="parent = attachments and attachments.parent or 
    1414                          attachment.resource.parent"> 
    1515 
    16     <div id="ctxtnav" class="nav"> 
    17       <h2>Attachment Navigation</h2> 
    18       <ul> 
    19         <li class="last"> 
    20           <a href="${chrome.links.up[0].href}">${_("Back to %(parent)s", parent=name_of(parent))}</a> 
    21         </li> 
    22       </ul> 
    23     </div> 
    24  
    25     <div py:choose="mode" id="content" class="attachment"> 
    26       <py:when test="'new'"> 
    27         <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1> 
    28         <form id="attachment" method="post" enctype="multipart/form-data" action=""> 
     16    <py:def function="attach_oneFile()"> 
    2917          <div class="field"> 
    3018            <label>File:<br /><input type="file" name="attachment" /></label> 
    3119          </div> 
     
    4937            </div> 
    5038            <br /> 
    5139          </fieldset> 
     40     
     41    </py:def> 
     42    <py:def function="attach_multiFile()"> 
     43         <table><thead> 
     44          <tr><th align="left">File</th><th align="left">Description</th></tr> 
     45         </thead> 
     46        <tbody id="multiAttach_tbody"> 
     47        <tr> 
     48            <td><input type="file" attachnum="0" name="attachment-0" onchange="manageMultipleAttachFields(this);"/></td> 
     49            <td><input type="text" name="description-0" size="60" /></td> 
     50        </tr> 
     51        </tbody></table> 
     52        <input type="hidden" id="multiAttach_count" name="multiple_attachments" value="1" /> 
     53 
     54          <fieldset> 
     55            <legend>Attachment Info</legend> 
     56            <py:if test="authname == 'anonymous'"> 
     57              <div class="field"> 
     58                <label>Your email or username:<br /> 
     59                  <input type="text" name="author" size="30" value="$author" /> 
     60                </label> 
     61              </div> 
     62              </py:if> 
     63            <div class="options"> 
     64              <label><input type="checkbox" name="replace" /> 
     65                Replace existing attachments of the same name</label> 
     66            </div> 
     67            <br /> 
     68          </fieldset> 
     69     
     70    </py:def> 
     71 
     72    <div id="ctxtnav" class="nav"> 
     73      <h2>Attachment Navigation</h2> 
     74      <ul> 
     75        <li class="last"> 
     76          <a href="${chrome.links.up[0].href}">${_("Back to %(parent)s", parent=name_of(parent))}</a> 
     77        </li> 
     78      </ul> 
     79    </div> 
     80 
     81    <div py:choose="mode" id="content" class="attachment"> 
     82      <py:when test="'new'"> 
     83        <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1> 
     84        <form id="attachment" method="post" enctype="multipart/form-data" action=""> 
     85         
     86        ${attach_multiFile()} 
    5287          <div class="buttons"> 
    5388            <input type="hidden" name="action" value="new" /> 
    5489            <input type="hidden" name="realm" value="$parent.realm" />