Пример #1
0
Файл: views.py Проект: quru/qis
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))
Пример #2
0
 def delete(self, folio_id, export_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())
         # Get the single portfolio-export
         folio_export = data_engine.get_object(FolioExport,
                                               export_id,
                                               _db_session=db_session)
         if folio_export is None:
             raise DoesNotExistError(str(export_id))
         if folio_export.folio_id != folio_id:
             raise ParameterError(
                 'export ID %d does not belong to portfolio ID %d' %
                 (export_id, folio_id))
         # Delete it and the export files
         delete_portfolio_export(folio_export,
                                 get_session_user(),
                                 'Deleted: ' + folio_export.describe(True),
                                 _db_session=db_session)
         return make_api_success_response()
     finally:
         db_session.close()
Пример #3
0
 def get(self, folio_id, export_id=None):
     # Get the portfolio
     folio = data_engine.get_portfolio(folio_id)
     if folio is None:
         raise DoesNotExistError(str(folio_id))
     # Check permissions
     permissions_engine.ensure_portfolio_permitted(
         folio, FolioPermission.ACCESS_VIEW, get_session_user())
     if export_id is None:
         # List portfolio exports
         exports_list = [
             _prep_folioexport_object(folio, fe) for fe in folio.downloads
         ]
         return make_api_success_response(
             object_to_dict_list(exports_list, _omit_fields))
     else:
         # Get a single portfolio-export
         folio_export = data_engine.get_object(FolioExport, export_id)
         if folio_export is None:
             raise DoesNotExistError(str(export_id))
         if folio_export.folio_id != folio_id:
             raise ParameterError(
                 'export ID %d does not belong to portfolio ID %d' %
                 (export_id, folio_id))
         return make_api_success_response(
             object_to_dict(_prep_folioexport_object(folio, folio_export),
                            _omit_fields))
Пример #4
0
 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()
Пример #5
0
 def get(self, folio_id, image_id=None):
     if image_id is None:
         # List images in the portfolio
         folio = data_engine.get_portfolio(folio_id, load_images=True)
         if folio is None:
             raise DoesNotExistError(str(folio_id))
         # Check permissions
         permissions_engine.ensure_portfolio_permitted(
             folio, FolioPermission.ACCESS_VIEW, get_session_user())
         image_list = [_prep_folioimage_object(fi) for fi in folio.images]
         return make_api_success_response(
             object_to_dict_list(image_list, _omit_fields))
     else:
         # Get a single portfolio-image
         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_VIEW,
                 get_session_user())
             return make_api_success_response(
                 object_to_dict(_prep_folioimage_object(folio_image),
                                _omit_fields + ['portfolio']))
         finally:
             db_session.close()
Пример #6
0
 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()
Пример #7
0
 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()
Пример #8
0
 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()
Пример #9
0
 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()
Пример #10
0
 def delete(self, folio_id):
     # Get portfolio
     folio = data_engine.get_portfolio(folio_id)
     if folio is None:
         raise DoesNotExistError(str(folio_id))
     # Check permissions
     permissions_engine.ensure_portfolio_permitted(
         folio, FolioPermission.ACCESS_DELETE, get_session_user())
     # Double check the downloads were eager-loaded before we try to use them below
     if not data_engine.attr_is_loaded(folio, 'downloads'):
         raise ValueError('bug: folio.downloads should be present')
     # Delete - cascades to folio images, permissions, history, and exports
     data_engine.delete_object(folio)
     # If we got this far the database delete worked and we now need to
     # delete the exported zip files (if any)
     delete_dir(get_portfolio_directory(folio), recursive=True)
     return make_api_success_response()
Пример #11
0
 def get(self, folio_id=None):
     # v4.1 Added support for /api/portfolios/?human_id=<human id>
     human_id = request.args.get('human_id', '')
     if not folio_id and not human_id:
         # List portfolios that the user can view
         folio_list = data_engine.list_portfolios(
             get_session_user(), FolioPermission.ACCESS_VIEW)
         folio_list = [_prep_folio_object(f) for f in folio_list]
         return make_api_success_response(
             object_to_dict_list(folio_list, _omit_fields))
     else:
         # Get single portfolio by either ID or v4.1 by human ID
         folio = data_engine.get_portfolio(folio_id,
                                           human_id,
                                           load_images=True,
                                           load_history=True)
         if folio is None:
             raise DoesNotExistError(human_id or str(folio_id))
         # Check permissions
         permissions_engine.ensure_portfolio_permitted(
             folio, FolioPermission.ACCESS_VIEW, get_session_user())
         folio = _prep_folio_object(folio)
         return make_api_success_response(
             object_to_dict(folio, _omit_fields))
Пример #12
0
 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()
Пример #13
0
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']