Skip to content

Commit

Permalink
Merge f034df6 into 12f3656
Browse files Browse the repository at this point in the history
  • Loading branch information
remileduc committed Jun 6, 2017
2 parents 12f3656 + f034df6 commit d78ff55
Show file tree
Hide file tree
Showing 10 changed files with 733 additions and 58 deletions.
43 changes: 36 additions & 7 deletions invenio_sipstore/admin.py
Expand Up @@ -21,7 +21,7 @@

from flask_admin.contrib.sqla import ModelView

from .models import SIP, RecordSIP, SIPFile
from .models import SIP, RecordSIP, SIPFile, SIPMetadata


class SIPModelView(ModelView):
Expand All @@ -33,18 +33,17 @@ class SIPModelView(ModelView):
can_view_details = True
column_display_all_relations = True
column_list = (
'sip_format', 'content', 'user_id', 'agent'
'user_id', 'agent', 'archivable', 'archived'
)
column_labels = dict(
sip_format='SIP Format',
content='Content',
user_id='User ID',
agent='Agent'
agent='Agent',
archivable='Archivable',
archived='Archived'
)
column_filters = (
'sip_format', 'content', 'user_id',
'user_id', 'archivable', 'archived'
)
column_searchable_list = ('sip_format', 'content')
page_size = 25


Expand All @@ -58,6 +57,31 @@ class SIPFileModelView(ModelView):
page_size = 25


class SIPMetadataModelView(ModelView):
"""ModelView for the SIPMetadata."""

can_create = False
can_edit = False
can_delete = False
can_view_details = True
column_display_all_relations = True
column_list = (
'format',
'content',
'sip.agent',
'sip.archivable',
'sip.archived'
)
column_labels = {
'format': 'Format',
'content': 'Content',
'sip.agent': 'Agent',
'sip.archivable': 'Archivable',
'sip.archived': 'Archived'
}
page_size = 25


class RecordSIPModelView(ModelView):
"""ModelView for the RecordSIP."""

Expand All @@ -78,6 +102,11 @@ class RecordSIPModelView(ModelView):
model=SIPFile,
name='SIPFile',
category='Records')
sipmetadata_adminview = dict(
modelview=SIPMetadataModelView,
model=SIPMetadata,
name='SIPMetadata',
category='Records')
recordsip_adminview = dict(
modelview=RecordSIPModelView,
model=RecordSIP,
Expand Down
243 changes: 243 additions & 0 deletions invenio_sipstore/api.py
@@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# In applying this license, CERN does not
# waive the privileges and immunities granted to it by virtue of its status
# as an Intergovernmental Organization or submit itself to any jurisdiction.

"""API for Invenio-SIPStore."""

import json

from flask import current_app, has_request_context, request
from flask_login import current_user
from invenio_db import db
from werkzeug.utils import import_string

from invenio_sipstore.models import SIP as SIP_
from invenio_sipstore.models import RecordSIP as RecordSIP_
from invenio_sipstore.models import SIPFile, SIPMetadata
from invenio_sipstore.signals import sipstore_created


class SIP(object):
"""API for managing SIPs."""

def __init__(self, sip):
"""Constructor.
:param sip: the SIP model associated
:type sip: :py:data:`invenio_sipstore.models.SIP`
"""
self.model = sip

@staticmethod
def _build_agent_info():
"""Build the SIP agent info.
This method can be changed in the config to suit your needs, see
:py:data:`invenio_sipstore.config.SIPSTORE_AGENT_FACTORY`
:returns: Agent information regarding the SIP.
:rtype: dict
"""
agent = dict()
if has_request_context() and request.remote_addr:
agent['ip_address'] = request.remote_addr
if current_user.is_authenticated and current_user.email:
agent['email'] = current_user.email
return agent

@property
def id(self):
"""Return the ID of the associated model."""
return self.model.id

# read only as it shouldn't change
@property
def user(self):
"""Return the user of the associated model."""
return self.model.user

# read only as it shouldn't change
@property
def agent(self):
"""Return the agent of the associated model."""
return self.model.agent

# read only as it shouldn't change
@property
def archivable(self):
"""Tell if the SIP should be archived."""
return self.model.archivable

# read only as it shouldn't change
@property
def archived(self):
"""Tell if the SIP has been archived."""
return self.model.archived

@archived.setter
def archived(self, is_archived):
"""Change the archived status of the SIP.
:param bool is_archived: True if the SIP has been archived
"""
self.model.archived = is_archived

@property
def files(self):
"""Return the list of files associated with the SIP.
:rtype: list(:py:data:`invenio_sipstore.models.SIPFile`)
"""
return self.model.sip_files

@property
def metadata(self):
"""Return the list of metadata associated with the SIP.
:rtype: list(:py:data:`invenio_sipstore.models.SIPMetadata`)
"""
return self.model.sip_metadata

def attach_file(self, file):
"""Add a file to the SIP.
:param file: the file to attach. It must at least implement a `key`
and a valid `file_id`. See
:py:class:`invenio-files_rest.models.ObjectVersion`.
:returns: the created SIPFile
:rtype: :py:data:`invenio_sipstore.models.SIPFile`
"""
sf = SIPFile(sip_id=self.id, filepath=file.key, file_id=file.file_id)
db.session.add(sf)
return sf

def attach_metadata(self, metadata, format='json'):
"""Add metadata to the SIP.
:param str metadata: the metadata to attach.
:param str format: the format of metadata (json, marcxml...)
:returns: the created SIPMetadata
:rtype: :py:data:`invenio_sipstore.models.SIPMetadata`
"""
sm = SIPMetadata(sip_id=self.id, format=format, content=metadata)
db.session.add(sm)
return sm

@classmethod
def create(cls, archivable, files=None, metadata=None, user_id=None,
agent=None):
"""Create a SIP, from the PID and the Record.
Apart from the SIP itself, it also creates ``SIPFile`` objects for
each of the files in the record, along with ``SIPMetadata`` for the
metadata.
Those objects are not returned by this function but can be fetched by
the corresponding SIP attributes 'files' and 'metadata'.
The created model is stored in the attribute 'model'.
:param bool archivable: tells if the SIP should be archived or not.
Usefull if ``Invenio-Archivematica`` is installed.
:param list files: The list of files to associate with the SIP. See
:py:func:`invenio_sipstore.api.SIP.attach_file`
:param dict metadata: A dictionary of metadata. The keys are the
format (json, marcxml...) and the values are the content (string)
:param user_id: the ID of the user. If not given, automatically
computed
:param agent: If not given, automatically computed
:returns: API SIP object.
:rtype: :py:class:`invenio_sipstore.api.SIP`
"""
if not user_id:
user_id = (None if current_user.is_anonymous
else current_user.get_id())
if not agent:
agent_factory = import_string(
current_app.config['SIPSTORE_AGENT_FACTORY'])
agent = agent_factory()
files = [] if not files else files
metadata = {} if not metadata else metadata

with db.session.begin_nested():
sip = cls(SIP_.create(user_id=user_id, agent=agent,
archivable=archivable))
for f in files:
sip.attach_file(f)
for format, content in metadata.items():
sip.attach_metadata(content, format)
sipstore_created.send(sip)
return sip


class RecordSIP(object):
"""API for managing SIPRecords."""

def __init__(self, recordsip, sip):
"""Constructor.
:param recordsip: the RecordSIP model to manage
:type recordsip: :py:data:`invenio_sipstore.models.RecordSIP`
:param sip: the SIP associated
:type sip: :py:data:`invenio_sipstore.api.SIP`
"""
self.model = recordsip
self.__sip = sip

# we make it unwritable
@property
def sip(self):
"""Return the SIP corresponding to this record.
:rtype: :py:class:`invenio_sipstore.api.SIP`
"""
return self.__sip

@classmethod
def create(cls, pid, record, archivable, create_sip_files=True,
user_id=None, agent=None):
"""Create a SIP, from the PID and the Record.
Apart from the SIP itself, it also creates ``RecordSIP`` for the
SIP-PID-Record relationship, as well as ``SIPFile`` objects for each
of the files in the record, along with ``SIPMetadata`` for the
metadata.
Those objects are not returned by this function but can be fetched by
the corresponding RecordSIP attributes 'sip', 'sip.files' and
'sip.metadata'.
:param pid: PID of the published record ('recid').
:type pid: `invenio_pidstore.models.PersistentIdentifier`
:param record: Record for which the SIP should be created.
:type record: `invenio_records.api.Record`
:param bool archivable: tells if the record should be archived.
Usefull when ``Invenio-Archivematica`` is installed.
:param bool create_sip_files: If True the SIPFiles will be created.
:returns: RecordSIP object.
:rtype: :py:class:`invenio_sipstore.api.RecordSIP`
"""
files = record.files if create_sip_files else None
metadata = {'json': json.dumps(record.dumps())}
with db.session.begin_nested():
sip = SIP.create(archivable, files=files, metadata=metadata,
user_id=user_id, agent=agent)
model = RecordSIP_(sip_id=sip.id, pid_id=pid.id)
db.session.add(model)
recsip = cls(model, sip)
return recsip
9 changes: 8 additions & 1 deletion invenio_sipstore/config.py
Expand Up @@ -25,10 +25,17 @@
"""Default configuration of Invenio-SIPStore module."""

SIPSTORE_DEFAULT_AGENT_JSONSCHEMA = 'sipstore/agent-v1.0.0.json'
"""Default JSON schema for extra SIP agent information."""
"""Default JSON schema for extra SIP agent information.
For more examples, you can have a look at Zenodo's config:
https://github.com/zenodo/zenodo/tree/master/zenodo/modules/sipstore/jsonschemas/sipstore
"""

SIPSTORE_AGENT_JSONSCHEMA_ENABLED = True
"""Enable SIP agent validation by default."""

SIPSTORE_AGENT_FACTORY = 'invenio_sipstore.api.SIP._build_agent_info'
"""Factory to build the agent, stored for the information about the SIP."""

SIPSTORE_FILEPATH_MAX_LEN = 1024
"""Max filepath length."""

0 comments on commit d78ff55

Please sign in to comment.