def portfolio_download(human_id, filename): logger.debug('GET ' + request.url) try: # Find the portfolio folio = data_engine.get_portfolio(human_id=human_id) if not folio: raise DoesNotExistError('Portfolio \'%s\' does not exist' % human_id) # Ensure that the user has permission to download the portfolio user = get_session_user() permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_DOWNLOAD, user) # Check that the filename is valid (note: assumes folio.downloads is eager loaded) if not filename: raise DoesNotExistError('No filename specified') folio_exports = [ dl for dl in folio.downloads if dl.filename == filename ] if not folio_exports: raise DoesNotExistError('Download \'%s\' is not available' % filename) folio_export = folio_exports[0] # The physical file should always exist when the data+filename exists # This also checks that the file path lies inside IMAGES_BASE_DIR zip_path = get_portfolio_export_file_path(folio_export) ensure_path_exists(zip_path, require_file=True) # Prepare to serve the file response = send_file( get_abs_path(zip_path), mimetype='application/zip', as_attachment=True, conditional=True, cache_timeout=31536000 # zips never change once created ) # Lastly write an audit record data_engine.add_portfolio_history(folio, user, FolioHistory.ACTION_DOWNLOADED, folio_export.filename) return response except httpexc.HTTPException: # Pass through HTTP 4xx and 5xx raise except SecurityError as e: if app.config['DEBUG']: raise log_security_error(e, request) raise httpexc.Forbidden() except DoesNotExistError as e: logger.warning('404 Not found: ' + str(e)) raise httpexc.NotFound(safe_error_str(e)) except Exception as e: if app.config['DEBUG']: raise logger.error('500 Error for ' + request.url + '\n' + str(e)) raise httpexc.InternalServerError(safe_error_str(e))
def delete(self, folio_id, image_id): db_session = data_engine.db_get_session() try: folio_image = data_engine.get_portfolio_image( AttrObject(id=folio_id), AttrObject(id=image_id), _db_session=db_session) if folio_image is None: raise DoesNotExistError(str(folio_id) + '/' + str(image_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio_image.portfolio, FolioPermission.ACCESS_EDIT, get_session_user()) # Add history first so that we only commit once at the end folio = folio_image.portfolio data_engine.add_portfolio_history(folio, get_session_user(), FolioHistory.ACTION_IMAGE_CHANGE, '%s removed' % folio_image.image.src, _db_session=db_session, _commit=False) # Flag that exported zips will be out of date folio.last_updated = datetime.utcnow() # Delete the image from the portfolio and commit changes data_engine.delete_object(folio_image, _db_session=db_session, _commit=True) return make_api_success_response() finally: db_session.close()
def delete_portfolio_export(folio_export, history_user, history_info, _db_session=None): """ Deletes a portfolio export record and the associated zip file (if it exists), and adds an audit trail entry for the parent portfolio. If you supply a database session it will be committed before the zip file is deleted, so that files are only deleted once the database operations are known to have worked. Raises a ServerTooBusyError if the export is still in progress. Raises an OSError if the zip file or directory cannot be deleted. """ db_session = _db_session or data_engine.db_get_session() try: # Ensure we can access folio_export.portfolio if not data_engine.object_in_session(folio_export, db_session): folio_export = data_engine.get_object(FolioExport, folio_export.id, _db_session=db_session) # Check whether the export task is running if folio_export.task_id: task = task_engine.get_task(folio_export.task_id, _db_session=db_session) if (task and task.status == Task.STATUS_ACTIVE) or ( task and task.status == Task.STATUS_PENDING and not task_engine.cancel_task(task)): raise ServerTooBusyError( 'this export is currently in progress, wait a while then try again' ) # Delete and add history in one commit data_engine.add_portfolio_history(folio_export.portfolio, history_user, FolioHistory.ACTION_UNPUBLISHED, history_info, _db_session=db_session, _commit=False) data_engine.delete_object(folio_export, _db_session=db_session, _commit=True) # If we got this far the database delete worked and we now need to # delete the exported zip file zip_rel_path = get_portfolio_export_file_path(folio_export) if folio_export.filename: delete_file(zip_rel_path) # And if the zip directory is now empty, delete the directory too zip_rel_dir = get_portfolio_directory(folio_export.portfolio) if path_exists(zip_rel_dir, require_directory=True): zips_count = count_files(zip_rel_dir, recurse=False) if zips_count[0] == 0: delete_dir(zip_rel_dir) finally: if not _db_session: db_session.close()
def post(self, folio_id): db_session = data_engine.db_get_session() try: # Get the portfolio folio = data_engine.get_portfolio(folio_id, _db_session=db_session) if folio is None: raise DoesNotExistError(str(folio_id)) # Check portfolio permissions permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_EDIT, get_session_user()) # Get the image by either ID or src params = self._get_validated_object_parameters(request.form, True) if 'image_id' in params: image = data_engine.get_image(params['image_id'], _db_session=db_session) if image is None: raise DoesNotExistError(str(params['image_id'])) else: image = auto_sync_file(params['image_src'], data_engine, task_engine, anon_history=True, burst_pdf=False, _db_session=db_session) if image is None or image.status == Image.STATUS_DELETED: raise DoesNotExistError(params['image_src']) # Check image permissions permissions_engine.ensure_folder_permitted( image.folder, FolderPermission.ACCESS_VIEW, get_session_user(), False) # Add history first so that we only commit once at the end data_engine.add_portfolio_history(folio, get_session_user(), FolioHistory.ACTION_IMAGE_CHANGE, '%s added' % image.src, _db_session=db_session, _commit=False) # Flag that exported zips are now out of date folio.last_updated = datetime.utcnow() # Add the image and commit changes db_folio_image = data_engine.save_object(FolioImage( folio, image, params['image_parameters'], params['filename'], params['index']), refresh=True, _db_session=db_session, _commit=True) return make_api_success_response( object_to_dict(_prep_folioimage_object(db_folio_image), _omit_fields + ['portfolio'])) finally: db_session.close()
def put(self, folio_id): db_session = data_engine.db_get_session() try: # Get portfolio folio = data_engine.get_portfolio(folio_id, _db_session=db_session) if folio is None: raise DoesNotExistError(str(folio_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_EDIT, get_session_user()) # Update the object params = self._get_validated_object_parameters(request.form) permissions_changed = self._set_permissions( folio, params, db_session) changes = [] if params['human_id'] != folio.human_id: changes.append('short URL changed') if params['name'] != folio.name: changes.append('name changed') if params['description'] != folio.description: changes.append('description changed') if permissions_changed: changes.append('permissions changed') folio.human_id = params['human_id'] or Folio.create_human_id() folio.name = params['name'] folio.description = params['description'] # Note: folio.last_updated is only for image changes # (to know when to invalidate the exported zips) data_engine.add_portfolio_history(folio, get_session_user(), FolioHistory.ACTION_EDITED, ', '.join(changes).capitalize(), _db_session=db_session, _commit=False) data_engine.save_object( folio, _db_session=db_session, _commit=True # fail here if human_id not unique ) if permissions_changed: permissions_engine.reset_portfolio_permissions() # Return a clean object the same as for get(id) folio = data_engine.get_portfolio(folio.id, load_images=True, load_history=True) folio = _prep_folio_object(folio) return make_api_success_response( object_to_dict(folio, _omit_fields)) finally: db_session.close()
def put(self, folio_id, image_id): db_session = data_engine.db_get_session() try: folio_image = data_engine.get_portfolio_image( AttrObject(id=folio_id), AttrObject(id=image_id), _db_session=db_session) if folio_image is None: raise DoesNotExistError(str(folio_id) + '/' + str(image_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio_image.portfolio, FolioPermission.ACCESS_EDIT, get_session_user()) # Update the object with any/all parameters that were passed in params = self._get_validated_object_parameters(request.form, False) changes = [] affects_zips = False if (params['image_parameters'] is not None and params['image_parameters'] != folio_image.parameters): folio_image.parameters = params['image_parameters'] changes.append('image attributes changed') affects_zips = True if (params['filename'] is not None and params['filename'] != folio_image.filename): folio_image.filename = params['filename'] changes.append('filename changed') affects_zips = True if (params['index'] is not None and params['index'] != folio_image.order_num): folio_image.order_num = params['index'] changes.append('set as position %d' % (params['index'] + 1)) if changes: # Flag if exported zips will be out of date if affects_zips: folio_image.portfolio.last_updated = datetime.utcnow() # Add history and commit changes data_engine.add_portfolio_history( folio_image.portfolio, get_session_user(), FolioHistory.ACTION_IMAGE_CHANGE, '%s updated: %s' % (folio_image.image.src, ', '.join(changes)), _db_session=db_session, _commit=True) return make_api_success_response( object_to_dict(_prep_folioimage_object(folio_image), _omit_fields + ['portfolio'])) finally: db_session.close()
def post(self, folio_id): db_session = data_engine.db_get_session() try: # Get the portfolio folio = data_engine.get_portfolio(folio_id, _db_session=db_session) if folio is None: raise DoesNotExistError(str(folio_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio, FolioPermission.ACCESS_EDIT, get_session_user()) # Block the export now if it would create an empty zip file if len(folio.images) == 0: raise ParameterError( 'this portfolio is empty and cannot be published') # Create a folio-export record and start the export as a background task params = self._get_validated_object_parameters(request.form) folio_export = FolioExport(folio, params['description'], params['originals'], params['image_parameters'], params['expiry_time']) data_engine.add_portfolio_history(folio, get_session_user(), FolioHistory.ACTION_PUBLISHED, folio_export.describe(True), _db_session=db_session, _commit=False) folio_export = data_engine.save_object(folio_export, refresh=True, _db_session=db_session, _commit=True) export_task = task_engine.add_task( get_session_user(), 'Export portfolio %d / export %d' % (folio.id, folio_export.id), 'export_portfolio', { 'export_id': folio_export.id, 'ignore_errors': False }, Task.PRIORITY_NORMAL, 'info', 'error', 60) # Update and return the folio-export record with the task ID folio_export.task_id = export_task.id data_engine.save_object(folio_export, _db_session=db_session, _commit=True) return make_api_success_response(object_to_dict( _prep_folioexport_object(folio, folio_export), _omit_fields + ['portfolio']), task_accepted=True) finally: db_session.close()
def put(self, folio_id, image_id): db_session = data_engine.db_get_session() try: # Get data folio_image = data_engine.get_portfolio_image( AttrObject(id=folio_id), AttrObject(id=image_id), _db_session=db_session) if folio_image is None: raise DoesNotExistError(str(folio_id) + '/' + str(image_id)) # Check permissions permissions_engine.ensure_portfolio_permitted( folio_image.portfolio, FolioPermission.ACCESS_EDIT, get_session_user()) # Update the portfolio params = self._get_validated_object_parameters(request.form) chd_folio_image = data_engine.reorder_portfolio( folio_image, params['index']) data_engine.add_portfolio_history( folio_image.portfolio, get_session_user(), FolioHistory.ACTION_IMAGE_CHANGE, '%s moved to position %d' % (folio_image.image.src, chd_folio_image.order_num + 1), _db_session=db_session, _commit=True) # Return the updated image list db_session.expire(folio_image) folio = data_engine.get_portfolio(folio_id, load_images=True, _db_session=db_session) image_list = [_prep_folioimage_object(fi) for fi in folio.images] return make_api_success_response( object_to_dict_list(image_list, _omit_fields + ['portfolio'])) finally: db_session.close()