Ticket #2827: thumbailPreviewOfAttachImages.r6370.patch
| File thumbailPreviewOfAttachImages.r6370.patch, 14.1 KB (added by Slava Zanko, 11 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 … … 114 115 self.env = env 115 116 self.parent_realm = self.resource.parent.realm 116 117 self.parent_id = unicode(self.resource.parent.id) 118 self.first1000bytes = None 117 119 if self.resource.id: 118 120 self._fetch(self.resource.id, db) 119 121 else: … … 123 125 self.date = None 124 126 self.author = None 125 127 self.ipnr = None 128 self.mimetype = None 129 self.charset = None 126 130 127 131 def _set_filename(self, val): 128 132 self.resource.id = val … … 150 154 self.date = datetime.fromtimestamp(time, utc) 151 155 self.author = row[4] 152 156 self.ipnr = row[5] 157 self._get_mimetype() 158 self._get_charset() 153 159 160 def _get_first_1000_bytes(self): 161 if not self.first1000bytes: 162 fd = self.open() 163 try: 164 str_data = fd.read(1000) 165 fd.seek(0) 166 finally: 167 fd.close() 168 169 def _get_charset(self): 170 self._get_first_1000_bytes() 171 mimeview = Mimeview(self.env) 172 self.charset = mimeview.get_charset(self.first1000bytes, self.mimetype) 173 174 def _get_mimetype(self): 175 self._get_first_1000_bytes() 176 mimeview = Mimeview(self.env) 177 self.mimetype = mimeview.get_mimetype(self.filename, self.first1000bytes) 178 if not self.mimetype: 179 self.mimetype = 'application/octet-stream' 180 181 def getThumbail(self, size = (160,160)): 182 image = Image.open(self.path) 183 thumbDir=os.path.join(self._get_dirPath(),'.thumb') 184 thumbFile=os.path.join(thumbDir,unicode_quote(self.filename)) 185 if not os.path.isdir(thumbDir): 186 os.mkdir(thumbDir) 187 # 200 x 150 188 if os.path.exists(thumbFile): 189 return thumbFile 190 if image.size[0] < size[0] and image.size[0] < size[0]: 191 shutil.copyfile(self.path,thumbFile) 192 return thumbFile 193 image.thumbnail(size) 194 image.save(thumbFile) 195 image.close() 196 def _get_dirPath(self): 197 return os.path.normpath(os.path.join(self.env.path, 'attachments', self.parent_realm, 198 unicode_quote(self.parent_id))) 154 199 def _get_path(self): 155 200 path = os.path.join(self.env.path, 'attachments', self.parent_realm, 156 201 unicode_quote(self.parent_id)) … … 261 306 attachment.date = datetime.fromtimestamp(time, utc) 262 307 attachment.author = author 263 308 attachment.ipnr = ipnr 309 attachment._get_mimetype() 310 attachment._get_charset() 264 311 yield attachment 265 312 266 313 def delete_all(cls, env, parent_realm, parent_id, db): … … 343 390 # IRequestHandler methods 344 391 345 392 def match_request(self, req): 346 match = re.match(r'^/(raw- )?attachment/([^/]+)(?:[/:](.*))?$',393 match = re.match(r'^/(raw-|thumb-)?attachment/([^/]+)(?:[/:](.*))?$', 347 394 req.path_info) 348 395 if match: 349 396 raw, realm, path = match.groups() 350 if raw :397 if raw == 'raw-': 351 398 req.args['format'] = 'raw' 399 elif raw == 'thumb-': 400 req.args['format'] = 'thumb' 352 401 req.args['realm'] = realm 353 402 if path: 354 403 req.args['path'] = path.replace(':', '/') … … 405 454 406 455 def get_link_resolvers(self): 407 456 yield ('raw-attachment', self._format_link) 457 yield ('thumb-attachment', self._format_link) 408 458 yield ('attachment', self._format_link) 409 459 410 460 # Public methods … … 487 537 if format == 'raw': 488 538 kwargs.pop('format') 489 539 prefix = 'raw-attachment' 540 elif format == 'thumb': 541 kwargs.pop('format') 542 prefix = 'thumb-attachment' 490 543 parent_href = unicode_unquote(get_resource_url(self.env, 491 544 resource.parent(version=None), Href(''))) 492 545 if not resource.id: … … 510 563 511 564 # Internal methods 512 565 513 def _do_save (self, req, attachment):566 def _do_save_one(self, req, attachment, upload, description): 514 567 req.perm(attachment.resource).require('ATTACHMENT_CREATE') 515 568 516 if 'cancel' in req.args:517 req.redirect(get_resource_url(self.env, attachment.resource.parent,518 req.href))519 569 520 upload = req.args['attachment']521 570 if not hasattr(upload, 'filename') or not upload.filename: 522 571 raise TracError(_('No file uploaded')) 523 572 if hasattr(upload.file, 'fileno'): … … 545 594 raise TracError(_('No file uploaded')) 546 595 # Now the filename is known, update the attachment resource 547 596 # attachment.filename = filename 548 attachment.description = req.args.get('description', '')597 attachment.description = description 549 598 attachment.author = get_reporter_id(req, 'author') 550 599 attachment.ipnr = req.remote_addr 551 600 … … 574 623 attachment.filename = None 575 624 attachment.insert(filename, upload.file, size) 576 625 577 req.redirect(get_resource_url(self.env, attachment.resource(id=None), 626 def _do_save_many(self,req, attachment): 627 curr_attach = 0 628 while curr_attach < int(req.args['multiple_attachments']): 629 upload=req.args['attachment-%d' % curr_attach] 630 if hasattr(upload, 'filename') and upload.filename: 631 attachment.filename = None 632 self._do_save_one(req, attachment, upload, req.args.get('description-%d' % curr_attach, '')) 633 curr_attach=curr_attach+1 634 635 def _do_save(self, req, attachment): 636 req.perm(attachment.resource).require('ATTACHMENT_CREATE') 637 638 if 'cancel' in req.args: 639 req.redirect(get_resource_url(self.env, attachment.resource.parent, 640 req.href)) 641 642 if 'multiple_attachments' in req.args: 643 self._do_save_many(req, attachment) 644 req.redirect(get_resource_url(self.env, attachment.resource(id=None), 578 645 req.href)) 646 else: 647 self._do_save_one(req, attachment, req.args['attachment'], req.args.get('description', '')) 648 # Redirect the user to list of attachments (must add a trailing '/') 649 req.redirect(get_resource_url(self.env, attachment.resource(id=None), 650 req.href)) 579 651 580 652 def _do_delete(self, req, attachment): 581 653 req.perm(attachment.resource).require('ATTACHMENT_DELETE') … … 623 695 'title': get_resource_name(self.env, attachment.resource), 624 696 'attachment': attachment} 625 697 698 mime_type = attachment.mimetype 626 699 fd = attachment.open() 627 700 try: 628 701 mimeview = Mimeview(self.env) 629 702 630 # MIME type detection631 str_data = fd.read(1000)632 fd.seek(0)633 634 mime_type = mimeview.get_mimetype(attachment.filename, str_data)635 636 703 # Eventually send the file directly 637 704 format = req.args.get('format') 638 if format in ('raw', 'txt'):705 if format == 'thumb': 639 706 if not self.render_unsafe_content: 707 req.send_header('Content-Disposition', 'attachment') 708 if attachment.mimetype[0:5] in ('image'): 709 req.send_file(attachment.getThumbail(), mime_type) 710 elif format in ('raw', 'txt'): 711 if not self.render_unsafe_content: 640 712 # Force browser to download files instead of rendering 641 713 # them, since they might contain malicious code enabling 642 714 # XSS attacks … … 698 770 format = None 699 771 if ns.startswith('raw'): 700 772 format = 'raw' 773 elif ns.startswith('thumb'): 774 format = 'thumb' 701 775 href = get_resource_url(self.env, attachment, formatter.href, 702 776 format=format) 703 777 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
12 12 13 13 <body py:with="parent = attachments and attachments.parent or 14 14 attachment.resource.parent"> 15 <div py:choose="mode" id="content" class="attachment"> 16 <py:when test="'new'"> 17 <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1> 18 <form id="attachment" method="post" enctype="multipart/form-data" action=""> 15 <py:def function="attach_oneFile()"> 19 16 <div class="field"> 20 17 <label>File<py:if test="max_size"> (size limit 21 18 ${pretty_size(max_size, format='%d')})</py:if>:<br /> … … 41 38 </div> 42 39 <br /> 43 40 </fieldset> 41 42 </py:def> 43 <py:def function="attach_multiFile()"> 44 <table><thead> 45 <tr><th align="left">File</th><th align="left">Description</th></tr> 46 </thead> 47 <tbody id="multiAttach_tbody"> 48 <tr> 49 <td><input type="file" attachnum="0" name="attachment-0" onchange="manageMultipleAttachFields(this);"/></td> 50 <td><input type="text" name="description-0" size="60" /></td> 51 </tr> 52 </tbody></table> 53 <input type="hidden" id="multiAttach_count" name="multiple_attachments" value="1" /> 54 55 <fieldset> 56 <legend>Attachment Info</legend> 57 <py:if test="authname == 'anonymous'"> 58 <div class="field"> 59 <label>Your email or username:<br /> 60 <input type="text" name="author" size="30" value="$author" /> 61 </label> 62 </div> 63 </py:if> 64 <div class="options"> 65 <label><input type="checkbox" name="replace" /> 66 Replace existing attachments of the same name</label> 67 </div> 68 <br /> 69 </fieldset> 70 71 </py:def> 72 73 <div py:choose="mode" id="content" class="attachment"> 74 <py:when test="'new'"> 75 <h1>Add Attachment to <a href="${url_of(parent)}">${name_of(parent)}</a></h1> 76 <form id="attachment" method="post" enctype="multipart/form-data" action=""> 77 78 ${attach_multiFile()} 44 79 <div class="buttons"> 45 80 <input type="hidden" name="action" value="new" /> 46 81 <input type="hidden" name="realm" value="$parent.realm" />
