def _prep_image_object(image, can_download=None, **url_params): """ Modifies an Image object to add calculated fields. This provides the common data dictionary for the file admin, image admin, image details, upload, and directory listing (with detail) APIs. If the download permission is None, it is calculated for the image's folder and the current user. The permissions engine returns this from cache when possible, but it is more efficient to pass in the permission if it is already known, or when handling many images in the same folder. If any url_params are provided (as kwargs), these are included in the generated image 'url' attribute. """ if can_download is None: can_download = permissions_engine.is_folder_permitted( image.folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user()) image.url = external_url_for('image', src=image.src, **url_params) image.download = can_download image.filename = filepath_filename(image.src) # Unsupported files shouldn't be in the database but it can happen if # support is removed for a file type that was once enabled image.supported = (get_file_extension(image.filename) in image_engine.get_image_formats(supported_only=True)) return image
def _image_dict(db_image, can_download=None): """ Returns the common data dictionary for the imagedetails and upload APIs. Provide an Image data model object and optionally the pre-calculated boolean download permission. If the download permission is None, it is calculated for the image's folder and the current user. """ if can_download is None: can_download = permissions_engine.is_folder_permitted( db_image.folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user() ) return { 'src': db_image.src, 'url': external_url_for('image', src=db_image.src), 'id': db_image.id, 'folder_id': db_image.folder_id, 'title': db_image.title, 'description': db_image.description, 'width': db_image.width, 'height': db_image.height, 'download': can_download }
def upload(): # Get URL parameters for the upload file_list = request.files.getlist('files') path_index = request.form.get('path_index', '-1') # Index into IMAGE_UPLOAD_DIRS or -1 path = request.form.get('path', '') # Manual path when path_index is -1 overwrite = request.form.get('overwrite') ret_dict = {} try: path_index = parse_int(path_index) overwrite = parse_boolean(overwrite) validate_string(path, 0, 1024) current_user = get_session_user() assert current_user is not None put_image_exception = None can_download = None for wkfile in file_list: original_filename = wkfile.filename if original_filename: db_image = None try: # Save (also checks user-folder permissions) _, db_image = image_engine.put_image( current_user, wkfile, secure_filename( original_filename, app.config['ALLOW_UNICODE_FILENAMES'] ), path_index, path, overwrite ) except Exception as e: # Save the error to use as our overall return value if put_image_exception is None: put_image_exception = e # This loop failure, add the error info to our return data ret_dict[original_filename] = {'error': create_api_error_dict(e)} if db_image: # Calculate download permission once (all files are going to same folder) if can_download is None: can_download = permissions_engine.is_folder_permitted( db_image.folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user() ) # This loop success ret_dict[original_filename] = _image_dict(db_image, can_download) # Loop complete. If we had an exception, raise it now. if put_image_exception is not None: raise put_image_exception except Exception as e: # put_image returns ValueError for parameter errors if type(e) is ValueError: e = ParameterError(unicode(e)) # Attach whatever data we have to return with the error # Caller can then decide whether to continue if some files worked e.api_data = ret_dict raise e finally: # Store the result for the upload_complete page cache_engine.raw_put( 'UPLOAD_API:' + str(current_user.id), ret_dict, expiry_secs=(60 * 60 * 24 * 7), integrity_check=True ) # If here, all files were uploaded successfully return make_api_success_response(ret_dict)
def portfolio_view(human_id): try: # Find the portfolio folio = data_engine.get_portfolio(human_id=human_id, load_images=True) if not folio: raise DoesNotExistError('Portfolio \'%s\' does not exist' % human_id) # Ensure that the user has permission to view the portfolio user = get_session_user() permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_VIEW, user ) # Filter out images that the user can't view # so that we don't get broken images in the UI checked_folders = {} # cache folders already checked folio_images_1 = folio.images folio_images_2 = [] for fol_img in folio_images_1: folder_path = fol_img.image.folder.path if folder_path in checked_folders: folio_images_2.append(fol_img) elif permissions_engine.is_folder_permitted( folder_path, FolderPermission.ACCESS_VIEW, user, folder_must_exist=False # though it should exist! ): checked_folders[folder_path] = True folio_images_2.append(fol_img) # Replace the original image list with the filtered one folio.images = folio_images_2 # Generate the image viewing URLs, including any portfolio-specific changes web_view_params = { 'format': 'jpg', 'colorspace': 'srgb' } sizing_view_params = { 'width': 800, 'height': 800, 'size_fit': True } pre_sized_images = [ fol_img for fol_img in folio.images if fol_img.parameters and ( ('width' in fol_img.parameters and fol_img.parameters['width']['value']) or ('height' in fol_img.parameters and fol_img.parameters['height']['value']) ) ] for fol_img in folio.images: image_attrs = get_portfolio_image_attrs(fol_img, False, False, False) image_attrs.apply_dict(web_view_params, True, False, False) if len(pre_sized_images) == 0: image_attrs.apply_dict(sizing_view_params, True, False, False) # Here we normalise the attrs only after everything has been applied image_attrs.normalise_values() fol_img.url = url_for_image_attrs(image_attrs) return render_template( 'portfolio_view.html', title=folio.name, folio=folio, removed_count=(len(folio_images_1) - len(folio_images_2)) ) except Exception as e: # Although this isn't a JSON API, we're still using it like a viewing API, # so get the correct HTTP status code to return. create_api_error_dict() also # logs security errors so we don't need to do that separately here. error_dict = create_api_error_dict(e, logger) if app.config['DEBUG']: raise return render_template( 'portfolio_view.html', title='Portfolio', err_msg='This portfolio cannot be viewed: ' + safe_error_str(e) ), error_dict['status']
def upload(): # Get URL parameters for the upload file_list = request.files.getlist('files') path_index = request.form.get('path_index', '-1') # Index into IMAGE_UPLOAD_DIRS or -1 path = request.form.get('path', '') # Manual path when path_index is -1 overwrite = request.form.get('overwrite') ret_dict = {} try: current_user = get_session_user() assert current_user is not None # Check params path_index = parse_int(path_index) if overwrite != 'rename': overwrite = parse_boolean(overwrite) validate_string(path, 0, 1024) if not path and path_index < 0: raise ValueError('Either path or path_index is required') if len(file_list) < 1: raise ValueError('No files have been attached') if path_index >= 0: # Get a "trusted" pre-defined upload folder # image_engine.put_image() will create it if it doesn't exist _, path = get_upload_directory(path_index) else: # A manually specified folder is "untrusted" and has to exist already if not path_exists(path): raise DoesNotExistError('Path \'' + path + '\' does not exist') # Loop over the upload files put_image_exception = None can_download = None saved_files = [] for wkfile in file_list: original_filepath = wkfile.filename original_filename = filepath_filename(original_filepath) # v2.7.1 added if original_filename: db_image = None try: # Don't allow filenames like "../../../etc/passwd" safe_filename = secure_filename( original_filename, app.config['ALLOW_UNICODE_FILENAMES'] ) # v2.7.1 If we already saved a file as safe_filename during this upload, # override this one to have overwrite=rename overwrite_flag = 'rename' if safe_filename in saved_files else overwrite # Save (this also checks user-folder permissions) _, db_image = image_engine.put_image( current_user, wkfile, path, safe_filename, overwrite_flag ) # v2.7.1 Keep a record of what filenames we used during this upload saved_files.append(safe_filename) except Exception as e: # Save the error to use as our overall return value if put_image_exception is None: put_image_exception = e # This loop failure, add the error info to our return data ret_dict[original_filepath] = { 'error': create_api_error_dict(e, logger) } if db_image: # Calculate download permission once (all files are going to same folder) if can_download is None: can_download = permissions_engine.is_folder_permitted( db_image.folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user() ) # This loop success ret_dict[original_filepath] = object_to_dict( _prep_image_object(db_image, can_download), _omit_fields ) else: logger.warning('Upload received blank filename, ignoring file') # Loop complete. If we had an exception, raise it now. if put_image_exception is not None: raise put_image_exception except Exception as e: # put_image returns ValueError for parameter errors if type(e) is ValueError: e = ParameterError(str(e)) # Attach whatever data we have to return with the error # Caller can then decide whether to continue if some files worked e.api_data = ret_dict raise e finally: # Store the result for the upload_complete page cache_engine.raw_put( 'UPLOAD_API:' + str(current_user.id), ret_dict, expiry_secs=(60 * 60 * 24 * 7) ) # If here, all files were uploaded successfully return make_api_success_response(ret_dict)
def imagelist(): # Check parameters try: from_path = request.args.get('path', '') want_info = parse_boolean(request.args.get('attributes', '')) start = parse_int(request.args.get('start', '0')) limit = parse_int(request.args.get('limit', '1000')) validate_string(from_path, 1, 1024) validate_number(start, 0, 999999999) validate_number(limit, 1, 1000) except ValueError as e: raise ParameterError(e) # Get extra parameters for image URL construction, remove API parameters image_params = request.args.to_dict() image_params.pop('path', None) image_params.pop('attributes', None) image_params.pop('start', None) image_params.pop('limit', None) # Get directory listing directory_info = get_directory_listing(from_path, False, 2, start, limit) if not directory_info.exists(): raise DoesNotExistError('Invalid path') ret_list = [] db_session = data_engine.db_get_session() db_commit = False try: # Auto-populate the folders database db_folder = auto_sync_folder( from_path, data_engine, task_engine, _db_session=db_session ) db_session.commit() # Require view permission or file admin permissions_engine.ensure_folder_permitted( db_folder, FolderPermission.ACCESS_VIEW, get_session_user() ) # Get download permission in case we need to return it later can_download = permissions_engine.is_folder_permitted( db_folder, FolderPermission.ACCESS_DOWNLOAD, get_session_user() ) # Create the response file_list = directory_info.contents() supported_img_types = image_engine.get_image_formats(supported_only=True) base_folder = add_sep(directory_info.name()) for f in file_list: # v2.6.4 Return unsupported files too. If you want to reverse this change, # the filtering needs to be elsewhere for 'start' and 'limit' to work properly supported_file = get_file_extension(f['filename']) in supported_img_types file_path = base_folder + f['filename'] if want_info: # Need to return the database fields too if supported_file: db_entry = auto_sync_existing_file( file_path, data_engine, task_engine, burst_pdf=False, # Don't burst a PDF just by finding it here _db_session=db_session ) db_entry = _prep_image_object(db_entry, can_download, **image_params) else: db_entry = _prep_blank_image_object() db_entry.filename = f['filename'] db_entry.supported = False # Return images in full (standard) image dict format entry = object_to_dict(db_entry, _omit_fields) else: # Return images in short dict format entry = { 'filename': f['filename'], 'supported': supported_file, 'url': (external_url_for('image', src=file_path, **image_params) if supported_file else '') } ret_list.append(entry) db_commit = True finally: try: if db_commit: db_session.commit() else: db_session.rollback() finally: db_session.close() return make_api_success_response(ret_list)