def delete_images(request, image_id): image = get_object_or_404(Image, id=image_id) if image.image_set.has_perm( 'delete_images', request.user) and not image.image_set.image_lock: root().remove(image.path()) image.delete() next_image = request.POST.get('next-image-id', '') if next_image == '': return redirect( reverse('images:view_imageset', args=(image.image_set.id, ))) else: return redirect( reverse('annotations:annotate', args=(next_image, )))
def delete_imageset(request, imageset_id): imageset = get_object_or_404(ImageSet, id=imageset_id) if not imageset.has_perm('delete_set', request.user): messages.warning( request, _('You do not have permission to delete this imageset.')) return redirect(reverse('images:imageset', args=(imageset.pk, ))) if request.method == 'POST': root().removetree(imageset.root_path()) imageset.delete() return redirect(reverse('users:team', args=(imageset.team.id, ))) return render(request, 'images/delete_imageset.html', { 'imageset': imageset, })
def create_tool(request): if request.method == 'POST': form = ToolUploadForm(request.POST, request.FILES) if form.is_valid(): tool = form.instance # TODO: use team permissions if tool.team and request.user not in tool.team.members.all(): messages.error( request, 'You are not permitted to create tools for the team "{}".'. format(tool.team.name)) return redirect(reverse('tools:overview')) with transaction.atomic(): tool.creator = request.user tool.save() tool.filename = '{}_{}'.format(tool.id, request.FILES['file'].name) tools_dir = root().makedirs(settings.TOOLS_PATH, recreate=True) with tools_dir.open(tool.filename, 'wb+') as f: for chunk in request.FILES['file']: f.write(chunk) messages.success(request, 'The tool was successfully uploaded') tool.save() return redirect(reverse('tools:overview')) messages.error( request, 'There was an error with your upload. You can only upload files up to 2 MiB' ) return redirect(reverse('tools:overview'))
def create_imageset(request): team = get_object_or_404(Team, id=request.POST['team']) if not team.has_perm('create_set', request.user): messages.warning( request, _('You do not have permission to create image sets in the team {}.' ).format(team.name)) return redirect(reverse('users:team', args=(team.id, ))) form = ImageSetCreationForm() if request.method == 'POST': form = ImageSetCreationForm(request.POST) if form.is_valid(): if team.image_sets\ .filter(name=form.cleaned_data.get('name')).exists(): form.add_error( 'name', _('The name is already in use by an imageset of this team.' )) else: with transaction.atomic(): form.instance.team = team form.instance.creator = request.user form.instance.save() form.instance.path = '{}_{}'.format( team.id, form.instance.id) form.instance.save() # create a folder to store the images of the set folder_path = form.instance.root_path() root().makedirs(folder_path, recreate=True) # TODO rfrigg: Check if necessary # shutil.chown(folder_path, group=settings.UPLOAD_FS_GROUP) messages.success(request, _('The image set was created successfully.')) return redirect( reverse('images:view_imageset', args=(form.instance.id, ))) return render(request, 'images/create_imageset.html', { 'team': team, 'form': form, })
def delete_tool(request, tool_id): if request.method == 'POST': tool = get_object_or_404(Tool, id=tool_id) if tool.has_perm('delete_tool', request.user): tools_dir = root().opendir(settings.TOOLS_PATH) try: tools_dir.remove(tool.filename) except ResourceNotFound: pass tool.delete() else: messages.error( request, 'You do not have the permission to delete the tool!') return redirect(reverse('tools:overview'))
def download_imageset_zip(request, image_set_id): """ Get a zip archive containing the images of the imageset with id image_set_id. If the zip file generation is still in progress, a HTTP status 202 (ACCEPTED) is returned. For empty image sets, status 204 (NO CONTENT) is returned instead of an empty zip file. """ image_set = get_object_or_404(ImageSet, id=image_set_id) if not settings.ENABLE_ZIP_DOWNLOAD: return HttpResponse(status=HTTP_404_NOT_FOUND) if not image_set.has_perm('read', request.user): return HttpResponseForbidden() if image_set.image_count == 0: # It should not be possible to download empty image sets. This # is already blocked in the UI, but it should also be checked # on the server side. return HttpResponse(status=HTTP_204_NO_CONTENT) if image_set.zip_state != ImageSet.ZipState.READY: return HttpResponse(content=b'Imageset is currently processed', status=HTTP_202_ACCEPTED) if settings.USE_NGINX_IMAGE_PROVISION: response = HttpResponse(content_type='application/zip') response['X-Accel-Redirect'] = "/ngx_static_dn/{0}".format( image_set.relative_zip_path()) else: response = FileResponse(root().open(image_set.zip_path(), 'rb'), content_type='application/zip') response['Content-Length'] = root().getsize(image_set.zip_path()) response['Content-Disposition'] = "attachment; filename={}".format( image_set.zip_name()) return response
def view_image(request, image_id): """ This view is to authenticate direct access to the images via nginx auth_request directive it will return forbidden on if the user is not authenticated """ image = get_object_or_404(Image, id=image_id) if not image.image_set.has_perm('read', request.user): return HttpResponseForbidden() if settings.USE_NGINX_IMAGE_PROVISION: response = HttpResponse() # Let nginx determine the Content-Type del response['Content-Type'] response['X-Accel-Redirect'] = "/ngx_static_dn/{}".format( image.relative_path()) response["Content-Length"] = root().getsize(image.path()) else: bytes_io = BytesIO() root().download(image.path(), bytes_io) bytes_io.seek(0) response = FileResponse(bytes_io, content_type="image") response["Content-Length"] = bytes_io.getbuffer().nbytes return response
def _regenerate_zip(self, imageset): updated = ImageSet.objects.filter(pk=imageset.pk) \ .filter(~Q(zip_state=ImageSet.ZipState.READY)) \ .update(zip_state=ImageSet.ZipState.PROCESSING) if not updated: self.stderr.write( 'skipping regeneration of ready imageset {}'.format( imageset.name)) return tmp_zip_path = imageset.tmp_zip_path() tmp().makedirs(path.dirname(tmp_zip_path), recreate=True) with tmp().open(tmp_zip_path, 'wb') as zip_file: with WriteZipFS(zip_file) as zip_fs: for image in imageset.images.all(): root().download(image.path(), zip_fs.openbin(image.name, 'w')) with tmp().open(tmp_zip_path, 'rb') as zip_file: root().upload(imageset.zip_path(), zip_file) tmp().remove(tmp_zip_path) # Set state to ready if image set has not been set to invalid during regeneration ImageSet.objects.filter(pk=imageset.pk, zip_state=ImageSet.ZipState.PROCESSING) \ .update(zip_state=ImageSet.ZipState.READY)
def download_tool(request, tool_id): tool = get_object_or_404(Tool, id=tool_id) if tool.has_perm('download_tool', request.user): tools_dir = root().opendir(settings.TOOLS_PATH) if tool.filename and tools_dir.isfile(tool.filename): with tools_dir.open(tool.filename, 'rb') as fh: response = HttpResponse(fh.read(), content_type="application") response[ 'Content-Disposition'] = 'attachment; filename=' + tool.filename return response else: messages.error(request, 'There was an error accessing the tool') else: messages.error(request, 'You do not have the permission to download the tool!') return redirect(reverse('tools:overview'))
def edit_tool(request, tool_id): if request.method == 'POST': tool = get_object_or_404(Tool, id=tool_id) changed_name = False changed_description = False changed_public = False changed_file = False public = 'public' in request.POST.keys() if tool.name != request.POST['name']: tool.name = request.POST['name'] changed_name = True if tool.description != request.POST['description']: tool.description = request.POST['description'] changed_description = True if tool.public != public: tool.public = public changed_public = True file_form = FileUploadForm(request.POST, request.FILES) if file_form.is_valid() and 'file' in request.FILES.keys(): changed_file = True tools_dir = root().opendir(settings.TOOLS_PATH) old_name = tool.filename tool.filename = request.FILES['file'].name tools_dir.remove(old_name) with tools_dir.open(tool.filename, 'wb+') as f: for chunk in request.FILES['file']: f.write(chunk) tool.save() msg = 'changed' if changed_name: msg += ' name' if changed_description: msg += ' description' if changed_public: msg += ' public status' if changed_file: msg += ' file' if not (changed_public or changed_description or changed_file or changed_name): msg += ' nothing' msg += ' in the tool' messages.warning(request, msg) return redirect(reverse('tools:overview'))
def upload_image(request, imageset_id): imageset = get_object_or_404(ImageSet, id=imageset_id) imageset_dir = root().makedirs(imageset.root_path(), recreate=True) if request.method == 'POST' \ and imageset.has_perm('edit_set', request.user) \ and not imageset.image_lock: if request.FILES is None: return HttpResponseBadRequest('Must have files attached!') json_files = [] for uploaded_file in request.FILES.getlist('files[]'): error = { 'duplicates': 0, 'damaged': False, 'directories': False, 'exists': False, 'unsupported': False, 'zip': False, } magic_number = uploaded_file.read(4) uploaded_file.seek( 0) # reset file cursor to the beginning of the file if magic_number == b'PK\x03\x04': # ZIP file magic number error['zip'] = True with ReadZipFS(uploaded_file) as zip_fs: try: # Deal with weird structure of zip files created with macOS Finder. subfolder = zip_fs.listdir("__MACOSX")[0] zip_fs = zip_fs.opendir(subfolder) except ResourceNotFound: pass filenames = sorted( f for f in zip_fs.walk.files(path="", max_depth=0)) duplicate_count = 0 for filename in filenames: image_file = zip_fs.open(filename, 'rb') try: if imghdr.what( image_file) in settings.IMAGE_EXTENSION: image_file.seek(0) # creates a checksum for image fchecksum = hashlib.sha512() while True: buf = image_file.read(10000) if not buf: break fchecksum.update(buf) image_file.seek(0) fchecksum = fchecksum.digest() # Tests for duplicates in imageset if Image.objects.filter( checksum=fchecksum, image_set=imageset).count() == 0: (shortname, extension) = splitext(filename) img_fname = ( ''.join(shortname) + '_' + ''.join( random.choice( string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(6)) + extension) try: with PIL_Image.open( image_file) as image: width, height = image.size image_file.seek(0) imageset_dir.upload( img_fname, image_file) # TODO rfrigg: Check if necessary # shutil.chown(file_new_path, group=settings.UPLOAD_FS_GROUP) new_image = Image(name=filename, image_set=imageset, filename=img_fname, checksum=fchecksum, width=width, height=height) new_image.save() except (OSError, IOError): error['damaged'] = True else: duplicate_count = duplicate_count + 1 else: error['unsupported'] = True except IsADirectoryError: error['directories'] = True finally: image_file.close() if duplicate_count > 0: error['duplicates'] = duplicate_count else: # creates a checksum for image fchecksum = hashlib.sha512() for chunk in uploaded_file.chunks(): fchecksum.update(chunk) fchecksum = fchecksum.digest() # tests for duplicats in imageset if Image.objects.filter(checksum=fchecksum, image_set=imageset)\ .count() == 0: fname = uploaded_file.name.split('.') fname = ('_'.join(fname[:-1]) + '_' + ''.join( random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(6)) + '.' + fname[-1]) image = Image(name=uploaded_file.name, image_set=imageset, filename=fname, checksum=fchecksum) buffer = BytesIO() for chunk in uploaded_file.chunks(): buffer.write(chunk) buffer.seek(0) # TODO rfrigg: Check if necessary # shutil.chown(image.path(), group=settings.UPLOAD_FS_GROUP) if imghdr.what(buffer) in settings.IMAGE_EXTENSION: try: buffer.seek(0) with PIL_Image.open(buffer) as pil_image: width, height = pil_image.size image.height = height image.width = width image.save() buffer.seek(0) imageset_dir.upload(fname, buffer) except (OSError, IOError): error['damaged'] = True else: error['unsupported'] = True else: error['exists'] = True errormessage = '' if error['zip']: errors = list() if error['directories']: errors.append('directories') if error['unsupported']: errors.append('unsupported files') if error['duplicates'] > 0: errors.append(str(error['duplicates']) + ' duplicates') if error['damaged']: errors.append('damaged files') if len(errors) > 0: # Build beautiful error message errormessage += ', '.join( errors) + ' in the archive have been skipped!' p = errormessage.rfind(',') if p != -1: errormessage = errormessage[:p].capitalize( ) + ' and' + errormessage[p + 1:] else: errormessage = errormessage.capitalize() else: if error['unsupported']: errormessage = 'This file type is unsupported!' elif error['damaged']: errormessage = 'This file seems to be damaged!' elif error['exists']: errormessage = 'This image already exists in the imageset!' if errormessage == '': json_files.append({ 'name': uploaded_file.name, 'size': uploaded_file.size, # 'url': reverse('images_imageview', args=(image.id, )), # 'thumbnailUrl': reverse('images_imageview', args=(image.id, )), # 'deleteUrl': reverse('images_imagedeleteview', args=(image.id, )), # 'deleteType': "DELETE", }) else: json_files.append({ 'name': uploaded_file.name, 'size': uploaded_file.size, 'error': errormessage, }) return JsonResponse({'files': json_files})