Edgewall Software

Ticket #2827: thumbailPreviewOfAttachImages.r6306.patch

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

renew patch

  • 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 
     
    149153        self.date = datetime.fromtimestamp(time, utc) 
    150154        self.author = row[4] 
    151155        self.ipnr = row[5] 
     156        self._get_mimetype() 
     157        self._get_charset() 
    152158 
     159    def _get_first_1000_bytes(self): 
     160        if not self.first1000bytes: 
     161            fd = self.open() 
     162            try: 
     163                str_data = fd.read(1000) 
     164                fd.seek(0) 
     165            finally: 
     166                fd.close() 
     167 
     168    def _get_charset(self): 
     169        self._get_first_1000_bytes() 
     170        mimeview = Mimeview(self.env) 
     171        self.charset = mimeview.get_charset(self.first1000bytes, self.mimetype) 
     172 
     173    def _get_mimetype(self): 
     174        self._get_first_1000_bytes() 
     175        mimeview = Mimeview(self.env) 
     176        self.mimetype = mimeview.get_mimetype(self.filename, self.first1000bytes) 
     177        if not self.mimetype: 
     178            self.mimetype = 'application/octet-stream' 
     179 
     180    def getThumbail(self, size = (160,160)): 
     181        image = Image.open(self.path) 
     182        thumbDir=os.path.join(self._get_dirPath(),'.thumb') 
     183        thumbFile=os.path.join(thumbDir,unicode_quote(self.filename)) 
     184        if not os.path.isdir(thumbDir): 
     185            os.mkdir(thumbDir) 
     186        # 200 x 150 
     187        if os.path.exists(thumbFile): 
     188            return thumbFile 
     189        if image.size[0] < size[0] and image.size[0] < size[0]: 
     190            shutil.copyfile(self.path,thumbFile) 
     191            return thumbFile 
     192        image.thumbnail(size) 
     193        image.save(thumbFile) 
     194        image.close() 
     195    def _get_dirPath(self): 
     196        return os.path.normpath(os.path.join(self.env.path, 'attachments', self.parent_realm, 
     197                            unicode_quote(self.parent_id))) 
    153198    def _get_path(self): 
    154199        path = os.path.join(self.env.path, 'attachments', self.parent_realm, 
    155200                            unicode_quote(self.parent_id)) 
     
    260305            attachment.date = datetime.fromtimestamp(time, utc) 
    261306            attachment.author = author 
    262307            attachment.ipnr = ipnr 
     308            attachment._get_mimetype() 
     309            attachment._get_charset() 
    263310            yield attachment 
    264311 
    265312    def delete_all(cls, env, parent_realm, parent_id, db): 
     
    342389    # IRequestHandler methods 
    343390 
    344391    def match_request(self, req): 
    345         match = re.match(r'^/(raw-)?attachment/([^/]+)(?:[/:](.*))?$', 
     392        match = re.match(r'^/(raw-|thumb-)?attachment/([^/]+)(?:[/:](.*))?$', 
    346393                         req.path_info) 
    347394        if match: 
    348395            raw, realm, path = match.groups() 
    349             if raw: 
     396            if raw == 'raw-': 
    350397                req.args['format'] = 'raw' 
     398            elif raw == 'thumb-': 
     399                req.args['format'] = 'thumb' 
    351400            req.args['realm'] = realm 
    352401            if path: 
    353402                req.args['path'] = path.replace(':', '/') 
     
    401450 
    402451    def get_link_resolvers(self): 
    403452        yield ('raw-attachment', self._format_link) 
     453        yield ('thumb-attachment', self._format_link) 
    404454        yield ('attachment', self._format_link) 
    405455 
    406456    # Public methods 
     
    483533        if format == 'raw': 
    484534            kwargs.pop('format') 
    485535            prefix = 'raw-attachment' 
     536        elif format == 'thumb': 
     537            kwargs.pop('format') 
     538            prefix = 'thumb-attachment' 
    486539        parent_href = unicode_unquote(get_resource_url(self.env, 
    487540                            resource.parent(version=None), Href(''))) 
    488541        if not resource.id: 
     
    506559 
    507560    # Internal methods 
    508561 
    509     def _do_save(self, req, attachment): 
     562    def _do_save_one(self, req, attachment, upload, description): 
    510563        req.perm(attachment.resource).require('ATTACHMENT_CREATE') 
    511564 
    512         if 'cancel' in req.args: 
    513             req.redirect(get_resource_url(self.env, attachment.resource.parent, 
    514                                           req.href)) 
    515565 
    516         upload = req.args['attachment'] 
    517566        if not hasattr(upload, 'filename') or not upload.filename: 
    518567            raise TracError(_('No file uploaded')) 
    519568        if hasattr(upload.file, 'fileno'): 
     
    541590            raise TracError(_('No file uploaded')) 
    542591        # Now the filename is known, update the attachment resource 
    543592        # attachment.filename = filename 
    544         attachment.description = req.args.get('description', '') 
     593        attachment.description = description 
    545594        attachment.author = get_reporter_id(req, 'author') 
    546595        attachment.ipnr = req.remote_addr 
    547596 
     
    570619            attachment.filename = None 
    571620        attachment.insert(filename, upload.file, size) 
    572621 
    573         req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
     622    def _do_save_many(self,req, attachment): 
     623        curr_attach = 0 
     624        while curr_attach < int(req.args['multiple_attachments']): 
     625            upload=req.args['attachment-%d' % curr_attach] 
     626            if hasattr(upload, 'filename') and upload.filename: 
     627                attachment.filename = None 
     628                self._do_save_one(req, attachment, upload, req.args.get('description-%d' % curr_attach, '')) 
     629            curr_attach=curr_attach+1 
     630 
     631    def _do_save(self, req, attachment): 
     632        req.perm(attachment.resource).require('ATTACHMENT_CREATE') 
     633 
     634        if 'cancel' in req.args: 
     635            req.redirect(get_resource_url(self.env, attachment.resource.parent, 
     636                                          req.href)) 
     637 
     638        if 'multiple_attachments' in req.args: 
     639            self._do_save_many(req, attachment) 
     640            req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
    574641                                      req.href)) 
     642        else: 
     643            self._do_save_one(req, attachment, req.args['attachment'], req.args.get('description', '')) 
     644            # Redirect the user to list of attachments (must add a trailing '/') 
     645            req.redirect(get_resource_url(self.env, attachment.resource(id=None), 
     646                                      req.href)) 
    575647 
    576648    def _do_delete(self, req, attachment): 
    577649        req.perm(attachment.resource).require('ATTACHMENT_DELETE') 
     
    619691                'title': get_resource_name(self.env, attachment.resource), 
    620692                'attachment': attachment} 
    621693 
     694        mime_type = attachment.mimetype 
    622695        fd = attachment.open() 
    623696        try: 
    624697            mimeview = Mimeview(self.env) 
    625698 
    626             # MIME type detection 
    627             str_data = fd.read(1000) 
    628             fd.seek(0) 
    629              
    630             mime_type = mimeview.get_mimetype(attachment.filename, str_data) 
    631  
    632699            # Eventually send the file directly 
    633700            format = req.args.get('format') 
    634             if format in ('raw', 'txt'): 
     701            if format == 'thumb': 
    635702                if not self.render_unsafe_content: 
     703                    req.send_header('Content-Disposition', 'attachment') 
     704                if attachment.mimetype[0:5] in ('image'): 
     705                    req.send_file(attachment.getThumbail(), mime_type) 
     706            elif format in ('raw', 'txt'): 
     707                if not self.render_unsafe_content: 
    636708                    # Force browser to download files instead of rendering 
    637709                    # them, since they might contain malicious code enabling  
    638710                    # XSS attacks 
     
    694766                format = None 
    695767                if ns.startswith('raw'): 
    696768                    format = 'raw' 
     769                elif ns.startswith('thumb'): 
     770                    format = 'thumb' 
    697771                href = get_resource_url(self.env, attachment, formatter.href, 
    698772                                        format=format) 
    699773                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

     
    187187          </ul> 
    188188        </py:when> 
    189189        <py:when test="not compact"> 
    190           <h2>Attachments</h2> 
    191190          <div py:if="alist.attachments or alist.can_create" id="attachments"> 
     191          <h2>Attachments  (Files)</h2> 
    192192            <dl py:if="alist.attachments" class="attachments"> 
    193               <py:for each="attachment in alist.attachments"> 
     193               <py:for each="attachment in  [f for f in alist.attachments if f.mimetype[0:5] not in ('image') ]"> 
    194194                <dt>${show_one_attachment(attachment)}</dt> 
    195195                <dd py:if="attachment.description"> 
    196196                  ${wiki_to_oneliner(context, attachment.description)} 
    197197                </dd> 
    198198              </py:for> 
    199199            </dl> 
     200          <h2>Attachments  (Images)</h2> 
     201             <py:with vars="attachs = [f for f in alist.attachments if f.mimetype[0:5] in ('image') ]; fullrow = len(attachs)"> 
     202                <table align="center"> 
     203                <tr py:for="row in group(attachs, 3)"> 
     204                <py:for each="oneattach in row"> 
     205                <td py:if="not oneattach" width="210"> 
     206                &nbsp; 
     207                </td> 
     208                <td py:if="oneattach" align="center" style="border: 1px solid #f0f0f0; text-align: center;"> 
     209                <div style="width: 200px; height: 200px;"> 
     210                    <a href="${url_of(oneattach.resource)}"> 
     211                        <img src="${url_of(oneattach.resource,format='thumb')}" 
     212                        style="cursor: pointer;" 
     213                        border="0" 
     214                        id="img_${oneattach.filename}" 
     215                        /> 
     216                    </a> 
     217                </div> 
     218                <div style="background-color: #f0f0f0;"> 
     219                    <py:with vars="attach_size = round(float(oneattach.size)/float(1024),3)"> 
     220                    <small>${oneattach.filename} (${attach_size} kb)<br /> 
     221                    <i>${oneattach.description}</i> 
     222                    </small> 
     223                    </py:with> 
     224                </div> 
     225                </td> 
     226                </py:for> 
     227                </tr></table> 
     228             </py:with> 
    200229            ${attach_file_form(alist)} 
    201230          </div> 
    202231        </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<py:if test="max_size"> (size limit 
    3119                  ${pretty_size(max_size, format='%d')})</py:if>:<br /> 
     
    5139            </div> 
    5240            <br /> 
    5341          </fieldset> 
     42     
     43    </py:def> 
     44    <py:def function="attach_multiFile()"> 
     45         <table><thead> 
     46          <tr><th align="left">File</th><th align="left">Description</th></tr> 
     47         </thead> 
     48        <tbody id="multiAttach_tbody"> 
     49        <tr> 
     50            <td><input type="file" attachnum="0" name="attachment-0" onchange="manageMultipleAttachFields(this);"/></td> 
     51            <td><input type="text" name="description-0" size="60" /></td> 
     52        </tr> 
     53        </tbody></table> 
     54        <input type="hidden" id="multiAttach_count" name="multiple_attachments" value="1" /> 
     55 
     56          <fieldset> 
     57            <legend>Attachment Info</legend> 
     58            <py:if test="authname == 'anonymous'"> 
     59              <div class="field"> 
     60                <label>Your email or username:<br /> 
     61                  <input type="text" name="author" size="30" value="$author" /> 
     62                </label> 
     63              </div> 
     64              </py:if> 
     65            <div class="options"> 
     66              <label><input type="checkbox" name="replace" /> 
     67                Replace existing attachments of the same name</label> 
     68            </div> 
     69            <br /> 
     70          </fieldset> 
     71     
     72    </py:def> 
     73 
     74    <div id="ctxtnav" class="nav"> 
     75      <h2>Attachment Navigation</h2> 
     76      <ul> 
     77        <li class="last"> 
     78          <a href="${chrome.links.up[0].href}">${_("Back to %(parent)s", parent=name_of(parent))}</a> 
     79        </li> 
     80      </ul> 
     81    </div> 
     82 
     83    <div py:choose="mode" id="content" class="attachment"> 
     84      <py:when test="'new'"> 
     85        <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1> 
     86        <form id="attachment" method="post" enctype="multipart/form-data" action=""> 
     87         
     88        ${attach_multiFile()} 
    5489          <div class="buttons"> 
    5590            <input type="hidden" name="action" value="new" /> 
    5691            <input type="hidden" name="realm" value="$parent.realm" />