Ticket #2827: thumbailPreviewOfAttachImages.r6174.patch
| File thumbailPreviewOfAttachImages.r6174.patch, 14.7 KB (added by Slava Zanko <slavaz>, 12 months ago) |
|---|
-
trac/attachment.py
20 20 import os 21 21 import re 22 22 import shutil 23 import Image 23 24 import time 24 25 import unicodedata 25 26 … … 113 114 self.env = env 114 115 self.parent_realm = self.resource.parent.realm 115 116 self.parent_id = unicode(self.resource.parent.id) 117 self.first1000bytes = None 116 118 if self.resource.id: 117 119 self._fetch(self.resource.id, db) 118 120 else: … … 122 124 self.date = None 123 125 self.author = None 124 126 self.ipnr = None 127 self.mimetype = None 128 self.charset = None 125 129 126 130 def _set_filename(self, val): 127 131 self.resource.id = val 128 132 129 133 filename = property(lambda self: self.resource.id, _set_filename) 130 131 134 def _fetch(self, filename, db=None): 132 135 if not db: 133 136 db = self.env.get_db_cnx() … … 149 152 self.date = datetime.fromtimestamp(time, utc) 150 153 self.author = row[4] 151 154 self.ipnr = row[5] 155 self._get_mimetype() 156 self._get_charset() 152 157 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))) 153 197 def _get_path(self): 154 198 path = os.path.join(self.env.path, 'attachments', self.parent_realm, 155 199 unicode_quote(self.parent_id)) … … 260 304 attachment.date = datetime.fromtimestamp(time, utc) 261 305 attachment.author = author 262 306 attachment.ipnr = ipnr 307 attachment._get_mimetype() 308 attachment._get_charset() 263 309 yield attachment 264 310 265 311 def delete_all(cls, env, parent_realm, parent_id, db): … … 342 388 # IRequestHandler methods 343 389 344 390 def match_request(self, req): 345 match = re.match(r'^/(raw- )?attachment/([^/]+)(?:[/:](.*))?$',391 match = re.match(r'^/(raw-|thumb-)?attachment/([^/]+)(?:[/:](.*))?$', 346 392 req.path_info) 347 393 if match: 348 394 raw, realm, path = match.groups() 349 if raw :395 if raw == 'raw-': 350 396 req.args['format'] = 'raw' 397 elif raw == 'thumb-': 398 req.args['format'] = 'thumb' 351 399 req.args['realm'] = realm 352 400 if path: 353 401 req.args['path'] = path.replace(':', '/') … … 401 449 402 450 def get_link_resolvers(self): 403 451 yield ('raw-attachment', self._format_link) 452 yield ('thumb-attachment', self._format_link) 404 453 yield ('attachment', self._format_link) 405 454 406 455 # Public methods … … 483 532 if format == 'raw': 484 533 kwargs.pop('format') 485 534 prefix = 'raw-attachment' 535 elif format == 'thumb': 536 kwargs.pop('format') 537 prefix = 'thumb-attachment' 486 538 parent_href = get_resource_url(self.env, resource.parent, Href('')) 487 539 if not resource.id: 488 540 return href(prefix, parent_href) + '/' … … 505 557 506 558 # Internal methods 507 559 508 def _do_save (self, req, attachment):560 def _do_save_one(self, req, attachment, upload, description): 509 561 req.perm(attachment.resource).require('ATTACHMENT_CREATE') 510 562 511 if 'cancel' in req.args:512 req.redirect(get_resource_url(self.env, attachment.resource.parent,513 req.href))514 563 515 upload = req.args['attachment']516 564 if not hasattr(upload, 'filename') or not upload.filename: 517 565 raise TracError(_('No file uploaded')) 518 566 if hasattr(upload.file, 'fileno'): … … 540 588 raise TracError(_('No file uploaded')) 541 589 # Now the filename is known, update the attachment resource 542 590 # attachment.filename = filename 543 attachment.description = req.args.get('description', '')591 attachment.description = description 544 592 attachment.author = get_reporter_id(req, 'author') 545 593 attachment.ipnr = req.remote_addr 546 594 … … 569 617 attachment.filename = None 570 618 attachment.insert(filename, upload.file, size) 571 619 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), 573 639 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)) 574 645 575 646 def _do_delete(self, req, attachment): 576 647 req.perm(attachment.resource).require('ATTACHMENT_DELETE') … … 618 689 'title': get_resource_name(self.env, attachment.resource), 619 690 'attachment': attachment} 620 691 692 mime_type = attachment.mimetype 621 693 fd = attachment.open() 622 694 try: 623 695 mimeview = Mimeview(self.env) 624 696 625 # MIME type detection626 str_data = fd.read(1000)627 fd.seek(0)628 629 mime_type = mimeview.get_mimetype(attachment.filename, str_data)630 631 697 # Eventually send the file directly 632 698 format = req.args.get('format') 633 if format in ('raw', 'txt'):699 if format == 'thumb': 634 700 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: 635 706 # Force browser to download files instead of rendering 636 707 # them, since they might contain malicious code enabling 637 708 # XSS attacks … … 693 764 format = None 694 765 if ns.startswith('raw'): 695 766 format = 'raw' 767 elif ns.startswith('thumb'): 768 format = 'thumb' 696 769 href = get_resource_url(self.env, attachment, formatter.href, 697 770 format=format) 698 771 return tag.a(label, class_='attachment', href=href + params, -
trac/htdocs/js/trac.js
69 69 function getAncestorByTagName(elem, tagName) { 70 70 return $(elem).parents(tagName).get(0); 71 71 } 72 var ATTACHFILE_COUNTER=0; 73 function 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
179 179 </ul> 180 180 </py:when> 181 181 <py:when test="not compact"> 182 <h2>Attachments</h2>183 182 <div py:if="alist.attachments or alist.can_create" id="attachments"> 183 <h2>Attachments (Files)</h2> 184 184 <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') ]"> 186 186 <dt>${show_one_attachment(attachment)}</dt> 187 187 <dd py:if="attachment.description"> 188 188 ${wiki_to_oneliner(context, attachment.description)} 189 189 </dd> 190 190 </py:for> 191 191 </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 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> 192 221 ${attach_file_form(alist)} 193 222 </div> 194 223 </py:when> -
trac/templates/attachment.html
13 13 <body py:with="parent = attachments and attachments.parent or 14 14 attachment.resource.parent"> 15 15 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()"> 29 17 <div class="field"> 30 18 <label>File:<br /><input type="file" name="attachment" /></label> 31 19 </div> … … 49 37 </div> 50 38 <br /> 51 39 </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()} 52 87 <div class="buttons"> 53 88 <input type="hidden" name="action" value="new" /> 54 89 <input type="hidden" name="realm" value="$parent.realm" />
