コード例 #1
0
def deposit_state(request, deposit):
    """
    Deposit state endpoint: return status of this deposit

    Example GET of state:
      curl -v http://localhost:8000/api/v1/location/96606387-cc70-4b09-b422-a7220606488d/sword/state/
    """
    if isinstance(deposit, basestring):
        try:
            deposit = models.Package.objects.get(uuid=deposit)
        except models.Package.DoesNotExist:
            return helpers.sword_error_response(
                request, 404,
                'Deposit location {uuid} does not exist.'.format(uuid=deposit))

    if request.method == 'GET':
        status = helpers.deposit_downloading_status(deposit)
        state_term = status
        state_description = 'Deposit initiation: ' + status

        # if deposit hasn't been finalized and last finalization attempt
        # failed, note failed finalization

        if deposit.misc_attributes.get(
                'finalization_attempt_failed',
                None) and deposit.misc_attributes.get(
                    'deposit_completion_time', None) is None:
            state_description += ' (last finalization attempt failed)'

        # get status of download tasks, if any
        tasks = helpers.deposit_download_tasks(deposit)

        # create atom representation of download tasks
        entries = []

        for task in tasks:
            task_files = models.PackageDownloadTaskFile.objects.filter(
                task=task)
            for task_file in task_files:
                entries.append({
                    'title': task_file.filename,
                    'url': task_file.url,
                    'summary': task_file.downloading_status()
                })

        response = HttpResponse(
            render_to_string('locations/api/sword/state.xml', locals()))
        response['Content-Type'] = 'application/atom+xml;type=feed'
        return response
    else:
        return helpers.sword_error_response(
            request, 405,
            'This endpoint only responds to the GET HTTP method.')
コード例 #2
0
def _handle_upload_request_with_potential_md5_checksum(request, file_path,
                                                       success_status_code):
    """
    Write the HTTP request body to a file and, if the HTTP Content-MD5 header is
    set, make sure the destination file has the expected checkcum

    Returns a response (with a specified HTTP status code) or an error response
    if a checksum has been provided but the destination file's is different
    """
    temp_filepath = helpers.write_request_body_to_temp_file(request)
    if 'HTTP_CONTENT_MD5' in request.META:
        md5sum = helpers.get_file_md5_checksum(temp_filepath)
        if request.META['HTTP_CONTENT_MD5'] != md5sum:
            os.remove(temp_filepath)
            return helpers.sword_error_response(
                request, 400,
                'MD5 checksum of uploaded file ({uploaded_md5sum}) does not match checksum provided in header ({header_md5sum}).'
                .format(uploaded_md5sum=md5sum,
                        header_md5sum=request.META['HTTP_CONTENT_MD5']))
        else:
            shutil.copyfile(temp_filepath, file_path)
            os.remove(temp_filepath)
            return HttpResponse(status=success_status_code)
    else:
        shutil.copyfile(temp_filepath, file_path)
        os.remove(temp_filepath)
        return HttpResponse(status=success_status_code)
コード例 #3
0
def _handle_adding_to_or_replacing_file_in_deposit(request,
                                                   deposit,
                                                   replace_file=False):
    """
    Parse a destination filename from an HTTP Content-Disposition header and
    either add or replace it

    Returns a response with an HTTP status code indicating whether creation (201)
    or updating (204) has occurred or an error response
    """
    if 'HTTP_CONTENT_DISPOSITION' in request.META:
        filename = helpers.parse_filename_from_content_disposition(
            request.META['HTTP_CONTENT_DISPOSITION'])

        if filename != '':
            file_path = os.path.join(deposit.full_path, filename)

            if replace_file:
                # if doing a file replace, the file being replaced must exist
                if os.path.exists(file_path):
                    return _handle_upload_request_with_potential_md5_checksum(
                        request, file_path, 204)
                else:
                    return helpers.sword_error_response(
                        request, 400, 'File does not exist.')
            else:
                # if adding a file, the file must not already exist
                if os.path.exists(file_path):
                    return helpers.sword_error_response(
                        request, 400, 'File already exists.')
                else:
                    return _handle_upload_request_with_potential_md5_checksum(
                        request, file_path, 201)
        else:
            return helpers.sword_error_response(
                request, 400,
                'No filename found in Content-disposition header.')
    else:
        return helpers.sword_error_response(
            request, 400, 'Content-disposition must be set in request header.')
コード例 #4
0
def _finalize_or_mark_for_finalization(request, deposit):
    """
    If a request specifies the deposit should be finalized, synchronously finalize
    or, if downloading is incomplete, mark for finalization.

    Returns deposit receipt response or error response
    """
    if 'HTTP_IN_PROGRESS' in request.META and request.META[
            'HTTP_IN_PROGRESS'] == 'false':
        if helpers.deposit_downloading_status(
                deposit) == models.PackageDownloadTask.COMPLETE:
            helpers.spawn_finalization(deposit.uuid)
            return _deposit_receipt_response(request, deposit, 200)
        else:
            return helpers.sword_error_response(
                request, 400,
                'Downloading not yet complete or errors were encountered.')
    else:
        return helpers.sword_error_response(
            request, 400,
            'The In-Progress header must be set to false when starting deposit processing.'
        )
コード例 #5
0
def collection(request, location):
    """
    Collection endpoint: accepts deposits, and returns current deposits.

    Example GET of collection deposit list:
      curl -v http://localhost:8000/api/v1/location/96606387-cc70-4b09-b422-a7220606488d/sword/collection/

    Example POST creation of deposit, allowing asynchronous downloading of object content URLs:
      curl -v -H "In-Progress: true" --data-binary @mets.xml --request POST http://localhost:8000/api/v1/location/96606387-cc70-4b09-b422-a7220606488d/sword/collection/

    Example POST creation of deposit, finalizing the deposit and auto-approving it:
      curl -v -H "In-Progress: false" --data-binary @mets.xml --request POST http://localhost:8000/api/v1/location/c0bee7c8-3e9b-41e3-8600-ee9b2c475da2/sword/collection/

    Example POST creation of deposit from another location:
      curl -v -H "In-Progress: true" --request POST http://localhost:8000/api/v1/location/96606387-cc70-4b09-b422-a7220606488d/sword/collection/?source_location=aab142a9-018f-4452-8f93-67c1bf7fd486&relative_path_to_files=archivematica-sampledata/SampleTransfers/Images
    """
    if isinstance(location, basestring):
        try:
            location = models.Location.active.get(uuid=location)
        except models.Location.DoesNotExist:
            return helpers.sword_error_response(
                request, 404,
                'Collection {uuid} does not exist.'.format(uuid=location))

    if request.method == 'GET':
        # return list of deposits as ATOM feed
        col_iri = request.build_absolute_uri(
            reverse('sword_collection',
                    kwargs={
                        'api_name': 'v1',
                        'resource_name': 'location',
                        'uuid': location.uuid
                    }))
        feed = {'title': 'Deposits', 'url': col_iri}

        entries = []
        for deposit in helpers.deposit_list(location.uuid):
            edit_iri = request.build_absolute_uri(
                reverse('sword_deposit',
                        kwargs={
                            'api_name': 'v1',
                            'resource_name': 'file',
                            'uuid': deposit.uuid
                        }))

            entries.append({
                'title':
                deposit.description or 'Deposit ' + deposit.uuid,
                'url':
                edit_iri,
            })

        # feed and entries passed via locals() to the template
        collection_xml = render_to_string('locations/api/sword/collection.xml',
                                          locals())
        response = HttpResponse(collection_xml)
        response['Content-Type'] = 'application/atom+xml;type=feed'
        return response

    elif request.method == 'POST':
        # has the In-Progress header been set?
        if 'HTTP_IN_PROGRESS' in request.META:
            # process creation request, if criteria met
            source_location = request.GET.get('source_location', '')
            relative_path_to_files = request.GET.get('relative_path_to_files',
                                                     '')

            if request.body != '':
                try:
                    temp_filepath = helpers.write_request_body_to_temp_file(
                        request)

                    # parse name and content URLs out of XML
                    try:
                        mets_data = _parse_name_and_content_urls_from_mets_file(
                            temp_filepath)
                    except etree.XMLSyntaxError as e:
                        os.unlink(temp_filepath)
                        mets_data = None

                    if mets_data != None:
                        if mets_data['deposit_name'] == None:
                            return helpers.sword_error_response(
                                request, 400, 'No deposit name found in XML.')
                        if not os.path.isdir(location.full_path):
                            return helpers.sword_error_response(
                                request, 500,
                                'Collection path (%s) does not exist: contact an administrator.'
                                % (location.full_path))

                        # TODO: should get this from author header or provided XML metadata
                        sourceofacquisition = request.META[
                            'HTTP_ON_BEHALF_OF'] if 'HTTP_ON_BEHALF_OF' in request.META else None
                        deposit = _create_deposit_directory_and_db_entry(
                            location=location,
                            deposit_name=mets_data['deposit_name'],
                            sourceofacquisition=sourceofacquisition)
                        if deposit is None:
                            return helpers.sword_error_response(
                                request, 500,
                                'Could not create deposit: contact an administrator.'
                            )

                        # move METS file to submission documentation directory
                        object_id = mets_data.get('object_id', 'fedora')
                        helpers.store_mets_data(temp_filepath, deposit,
                                                object_id)

                        _spawn_batch_download_and_flag_finalization_if_requested(
                            deposit, request, mets_data)

                        if request.META['HTTP_IN_PROGRESS'] == 'true':
                            return _deposit_receipt_response(
                                request, deposit, 201)
                        else:
                            return _deposit_receipt_response(
                                request, deposit, 200)
                    else:
                        return helpers.sword_error_response(
                            request, 412, 'Error parsing XML')
                except Exception as e:
                    return helpers.sword_error_response(
                        request, 400, traceback.format_exc())
            elif source_location or relative_path_to_files:
                if not source_location or not relative_path_to_files:
                    if not source_location:
                        return helpers.sword_error_response(
                            request, 400,
                            'relative_path_to_files is set, but source_location is not.'
                        )
                    else:
                        return helpers.sword_error_response(
                            request, 400,
                            'source_location is set, but relative_path_to_files is not.'
                        )
                else:
                    result = deposit_from_location_relative_path(
                        source_location, relative_path_to_files, location)
                    if result.get('error', False):
                        return helpers.sword_error_response(
                            request, 500, result['message'])
                    else:
                        return _deposit_receipt_response(
                            request, result['deposit_uuid'], 200)
            else:
                return helpers.sword_error_response(
                    request, 412,
                    'A request body must be sent when creating a deposit.')
        else:
            return helpers.sword_error_response(
                request, 412,
                'The In-Progress header must be set to either true or false when creating a deposit.'
            )
    else:
        return helpers.sword_error_response(
            request, 405,
            'This endpoint only responds to the GET and POST HTTP methods.')
コード例 #6
0
def deposit_media(request, deposit):
    """
    Deposit media endpoint: list, create, update or delete files

    Example GET of files list:
      curl -v http://127.0.0.1:8000/api/v1/file/149cc29d-6472-4bcf-bee8-f8223bf60580/sword/media/

    Example PUT of file:
      curl -v -H "Content-Disposition: attachment; filename=joke.jpg" --data-binary "@joke.jpg" --request PUT http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/

    Example POST of file:
      curl -v -H "Content-Disposition: attachment; filename=joke.jpg" --data-binary "@joke.jpg" --request POST http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/

    Example POST of METS with file info:
      curl -v -H "Packaging: METS" -H "In-Progress: true" --data-binary @mets.xml --request POST http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/

    Example DELETE of all files:
      curl -v -XDELETE http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/

    Example DELETE of file:
      curl -v -XDELETE http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/?filename=joke.jpg
    """

    if isinstance(deposit, basestring):
        try:
            deposit = models.Package.objects.get(uuid=deposit)
        except models.Package.DoesNotExist:
            return helpers.sword_error_response(
                request, 404,
                'Deposit location {uuid} does not exist.'.format(uuid=deposit))

    if deposit.has_been_submitted_for_processing():
        return helpers.sword_error_response(
            request, 400,
            'This deposit has already been submitted for processing.')

    if request.method == 'GET':
        # TODO should this be returned in SWORD XML?
        return HttpResponse(str(os.listdir(deposit.full_path)))
    elif request.method == 'PUT':
        # replace a file in the deposit
        return _handle_adding_to_or_replacing_file_in_deposit(
            request, deposit, replace_file=True)
    elif request.method == 'POST':
        # Allow async batch upload via METS XML body content
        if 'HTTP_PACKAGING' in request.META and request.META[
                'HTTP_PACKAGING'] == 'METS':
            # If METS XML has been sent to indicate a list of files needing downloading, handle it
            if request.body != '':
                temp_filepath = helpers.write_request_body_to_temp_file(
                    request)
                try:
                    mets_data = _parse_name_and_content_urls_from_mets_file(
                        temp_filepath)
                except etree.XMLSyntaxError as e:
                    os.unlink(temp_filepath)
                    mets_data = None

                if mets_data != None:
                    # move METS file to submission documentation directory
                    object_id = mets_data.get('object_id', 'fedora')
                    helpers.store_mets_data(temp_filepath, deposit, object_id)

                    _spawn_batch_download_and_flag_finalization_if_requested(
                        deposit, request, mets_data)
                    return _deposit_receipt_response(request, deposit, 201)
                else:
                    return helpers.sword_error_response(
                        request, 412, 'Error parsing XML.')
            else:
                return helpers.sword_error_response(
                    request, 400, 'No METS body content sent.')
        else:
            # add a file to the deposit
            return _handle_adding_to_or_replacing_file_in_deposit(
                request, deposit)
    elif request.method == 'DELETE':
        filename = request.GET.get('filename', '')
        if filename != '':
            # Delete PackageDownloadTaskFile for this filename
            models.PackageDownloadTaskFile.objects.filter(
                task__package=deposit).filter(filename=filename).delete()
            # Delete empty PackageDownloadTasks for this deposit
            models.PackageDownloadTask.objects.filter(package=deposit).filter(
                download_file_set=None).delete()
            # Delete file
            file_path = os.path.join(deposit.full_path, filename)
            if os.path.exists(file_path):
                os.remove(file_path)
                return HttpResponse(status=204)  # No content
            else:
                return helpers.sword_error_response(
                    request, 404,
                    'The path to this file (%s) does not exist.' % (file_path))
        else:
            # Delete all PackageDownloadTaskFile and PackageDownloadTask for this deposit
            models.PackageDownloadTaskFile.objects.filter(
                task__package=deposit).delete()
            models.PackageDownloadTask.objects.filter(package=deposit).delete()
            # Delete all files
            for filename in os.listdir(deposit.full_path):
                filepath = os.path.join(deposit.full_path, filename)
                if os.path.isfile(filepath):
                    os.remove(filepath)
                elif os.path.isdir(filepath):
                    shutil.rmtree(filepath)

            return HttpResponse(status=204)  # No content
    else:
        return helpers.sword_error_response(
            request, 405,
            'This endpoint only responds to the GET, POST, PUT, and DELETE HTTP methods.'
        )
コード例 #7
0
def deposit_edit(request, deposit):
    """
    Deposit endpoint: list info, accept new files, finalize or delete deposit.

    Example POST adding files to the deposit:
      curl -v -H "In-Progress: true" --data-binary @mets.xml --request POST http://127.0.0.1:8000/api/v1/file/149cc29d-6472-4bcf-bee8-f8223bf60580/sword/

    Example POST finalization of deposit:
      curl -v -H "In-Progress: false" --request POST http://127.0.0.1:8000/api/v1/file/149cc29d-6472-4bcf-bee8-f8223bf60580/sword/

    Example DELETE of deposit:
      curl -v -XDELETE http://127.0.0.1:8000/api/v1/file/149cc29d-6472-4bcf-bee8-f8223bf60580/sword/
    """
    if isinstance(deposit, basestring):
        try:
            deposit = models.Package.objects.get(uuid=deposit)
        except models.Package.DoesNotExist:
            return helpers.sword_error_response(
                request, 404,
                'Deposit location {uuid} does not exist.'.format(uuid=deposit))

    if deposit.has_been_submitted_for_processing():
        return helpers.sword_error_response(
            request, 400,
            'This deposit has already been submitted for processing.')

    if request.method == 'GET':
        edit_iri = request.build_absolute_uri(
            reverse('sword_deposit',
                    kwargs={
                        'api_name': 'v1',
                        'resource_name': 'file',
                        'uuid': deposit.uuid
                    }))
        entry = {'title': deposit.description, 'url': edit_iri}
        response = HttpResponse(
            render_to_string('locations/api/sword/entry.xml', locals()))
        response['Content-Type'] = 'application/atom+xml'
        return response
    elif request.method == 'POST':
        # If METS XML has been sent to indicate a list of files needing downloading, handle it
        if request.body != '':
            temp_filepath = helpers.write_request_body_to_temp_file(request)
            try:
                mets_data = _parse_name_and_content_urls_from_mets_file(
                    temp_filepath)
            except etree.XMLSyntaxError as e:
                os.unlink(temp_filepath)
                mets_data = None

            if mets_data is not None:
                # move METS file to submission documentation directory
                object_id = mets_data.get('object_id', 'fedora')
                helpers.store_mets_data(temp_filepath, deposit, object_id)

                _spawn_batch_download_and_flag_finalization_if_requested(
                    deposit, request, mets_data)
                return _deposit_receipt_response(request, deposit, 200)
            else:
                return helpers.sword_error_response(request, 412,
                                                    'Error parsing XML.')
        else:
            # Attempt to finalize (if requested), otherwise just return deposit receipt
            if 'HTTP_IN_PROGRESS' in request.META and request.META[
                    'HTTP_IN_PROGRESS'] == 'false':
                return _finalize_or_mark_for_finalization(request, deposit)
            else:
                return _deposit_receipt_response(request, deposit, 200)
    elif request.method == 'PUT':
        # TODO: implement update deposit
        return HttpResponse(status=204)  # No content
    elif request.method == 'DELETE':
        # Delete files
        shutil.rmtree(deposit.full_path)
        # Delete all PackageDownloadTaskFile and PackageDownloadTask for this deposit
        models.PackageDownloadTaskFile.objects.filter(
            task__package=deposit).delete()
        models.PackageDownloadTask.objects.filter(package=deposit).delete()
        # TODO should this actually delete the Package entry?
        deposit.status = models.Package.DELETED
        deposit.save()
        return HttpResponse(status=204)  # No content
    else:
        return helpers.sword_error_response(
            request, 405,
            'This endpoint only responds to the GET, POST, PUT, and DELETE HTTP methods.'
        )