Ticket #2827: thumbailPreviewOfAttachImages.r6306.patch
| File thumbailPreviewOfAttachImages.r6306.patch, 14.6 KB (added by Slava Zanko <slavaz>, 4 years 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 … … 149 153 self.date = datetime.fromtimestamp(time, utc) 150 154 self.author = row[4] 151 155 self.ipnr = row[5] 156 self._get_mimetype() 157 self._get_charset() 152 158 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))) 153 198 def _get_path(self): 154 199 path = os.path.join(self.env.path, 'attachments', self.parent_realm, 155 200 unicode_quote(self.parent_id)) … … 260 305 attachment.date = datetime.fromtimestamp(time, utc) 261 306 attachment.author = author 262 307 attachment.ipnr = ipnr 308 attachment._get_mimetype() 309 attachment._get_charset() 263 310 yield attachment 264 311 265 312 def delete_all(cls, env, parent_realm, parent_id, db): … … 342 389 # IRequestHandler methods 343 390 344 391 def match_request(self, req): 345 match = re.match(r'^/(raw- )?attachment/([^/]+)(?:[/:](.*))?$',392 match = re.match(r'^/(raw-|thumb-)?attachment/([^/]+)(?:[/:](.*))?$', 346 393 req.path_info) 347 394 if match: 348 395 raw, realm, path = match.groups() 349 if raw :396 if raw == 'raw-': 350 397 req.args['format'] = 'raw' 398 elif raw == 'thumb-': 399 req.args['format'] = 'thumb' 351 400 req.args['realm'] = realm 352 401 if path: 353 402 req.args['path'] = path.replace(':', '/') … … 401 450 402 451 def get_link_resolvers(self): 403 452 yield ('raw-attachment', self._format_link) 453 yield ('thumb-attachment', self._format_link) 404 454 yield ('attachment', self._format_link) 405 455 406 456 # Public methods … … 483 533 if format == 'raw': 484 534 kwargs.pop('format') 485 535 prefix = 'raw-attachment' 536 elif format == 'thumb': 537 kwargs.pop('format') 538 prefix = 'thumb-attachment' 486 539 parent_href = unicode_unquote(get_resource_url(self.env, 487 540 resource.parent(version=None), Href(''))) 488 541 if not resource.id: … … 506 559 507 560 # Internal methods 508 561 509 def _do_save (self, req, attachment):562 def _do_save_one(self, req, attachment, upload, description): 510 563 req.perm(attachment.resource).require('ATTACHMENT_CREATE') 511 564 512 if 'cancel' in req.args:513 req.redirect(get_resource_url(self.env, attachment.resource.parent,514 req.href))515 565 516 upload = req.args['attachment']517 566 if not hasattr(upload, 'filename') or not upload.filename: 518 567 raise TracError(_('No file uploaded')) 519 568 if hasattr(upload.file, 'fileno'): … … 541 590 raise TracError(_('No file uploaded')) 542 591 # Now the filename is known, update the attachment resource 543 592 # attachment.filename = filename 544 attachment.description = req.args.get('description', '')593 attachment.description = description 545 594 attachment.author = get_reporter_id(req, 'author') 546 595 attachment.ipnr = req.remote_addr 547 596 … … 570 619 attachment.filename = None 571 620 attachment.insert(filename, upload.file, size) 572 621 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), 574 641 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)) 575 647 576 648 def _do_delete(self, req, attachment): 577 649 req.perm(attachment.resource).require('ATTACHMENT_DELETE') … … 619 691 'title': get_resource_name(self.env, attachment.resource), 620 692 'attachment': attachment} 621 693 694 mime_type = attachment.mimetype 622 695 fd = attachment.open() 623 696 try: 624 697 mimeview = Mimeview(self.env) 625 698 626 # MIME type detection627 str_data = fd.read(1000)628 fd.seek(0)629 630 mime_type = mimeview.get_mimetype(attachment.filename, str_data)631 632 699 # Eventually send the file directly 633 700 format = req.args.get('format') 634 if format in ('raw', 'txt'):701 if format == 'thumb': 635 702 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: 636 708 # Force browser to download files instead of rendering 637 709 # them, since they might contain malicious code enabling 638 710 # XSS attacks … … 694 766 format = None 695 767 if ns.startswith('raw'): 696 768 format = 'raw' 769 elif ns.startswith('thumb'): 770 format = 'thumb' 697 771 href = get_resource_url(self.env, attachment, formatter.href, 698 772 format=format) 699 773 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
187 187 </ul> 188 188 </py:when> 189 189 <py:when test="not compact"> 190 <h2>Attachments</h2>191 190 <div py:if="alist.attachments or alist.can_create" id="attachments"> 191 <h2>Attachments (Files)</h2> 192 192 <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') ]"> 194 194 <dt>${show_one_attachment(attachment)}</dt> 195 195 <dd py:if="attachment.description"> 196 196 ${wiki_to_oneliner(context, attachment.description)} 197 197 </dd> 198 198 </py:for> 199 199 </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 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> 200 229 ${attach_file_form(alist)} 201 230 </div> 202 231 </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<py:if test="max_size"> (size limit 31 19 ${pretty_size(max_size, format='%d')})</py:if>:<br /> … … 51 39 </div> 52 40 <br /> 53 41 </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()} 54 89 <div class="buttons"> 55 90 <input type="hidden" name="action" value="new" /> 56 91 <input type="hidden" name="realm" value="$parent.realm" />
