def test_versioning_insert_draft_child(db, version_pids, build_pid): """Test the insert_draft_child method of PIDNodeVersioning.""" parent_pid = build_pid(version_pids[0]['parent']) h1 = PIDNodeVersioning(parent_pid) # assert that there is a draft_child present assert h1.draft_child == version_pids[0]['children'][-1] draft2 = PersistentIdentifier.create('recid', 'foobar.draft2', object_type='rec', status=PIDStatus.RESERVED) with pytest.raises(PIDRelationConsistencyError): # try to add a second draft_child h1.insert_draft_child(draft2)
def test_update_redirect(db, version_pids, build_pid): """Test PIDNodeVersioning.update_redirect().""" # Test update_redirect on a PID without any child parent_pids = create_pids(1, prefix='parent', status=PIDStatus.RESERVED) draft_pids = create_pids(2, prefix='draft', status=PIDStatus.RESERVED) parent = PIDNodeVersioning(build_pid(parent_pids[0])) parent.update_redirect() assert parent_pids[0].status == PIDStatus.RESERVED # Test that update_redirect remains reserved once it has a draft child parent.insert_draft_child(draft_pids[0]) assert parent_pids[0].status == PIDStatus.RESERVED h1 = PIDNodeVersioning(build_pid(version_pids[0]['parent'])) def test_redirect(expected_length, expected_redirect): filtered = filter_pids(version_pids[0]['children'], status=PIDStatus.REGISTERED) assert len(filtered) == expected_length assert h1.children.ordered('asc').all() == filtered assert h1._resolved_pid.get_redirect() == expected_redirect # Test update_redirect when it already points to the last version last = h1.last_child draft = h1.draft_child h1.update_redirect() test_redirect(3, last) # Test update_redirect after publishing the draft h1.draft_child.register() h1.update_redirect() test_redirect(4, draft) # Test update_redirect after deleting the last version h1.last_child.delete() h1.update_redirect() test_redirect(3, last) # Test that if every version is deleted the HEAD pid is also deleted for pid in filter_pids(version_pids[0]['children'], status=PIDStatus.REGISTERED): pid.delete() h1.update_redirect() test_redirect(0, last) # Test that an exception is raised if unsupported PIDStatus are used. version_pids[0]['children'][0].status = PIDStatus.NEW with pytest.raises(PIDRelationConsistencyError): h1.update_redirect()
def create(cls, data, id_=None, version_of=None): """Create a deposit with the optional id. :params version_of: PID of an existing record. If set, the new record will be marked as a new version of this referenced record. If no data is provided the new record will be a copy of this record. Note: this PID must reference the current last version of a record. """ # check that the status field is not set if 'publication_state' in data: raise InvalidDepositError( 'Field "publication_state" cannot be set.') data['publication_state'] = PublicationStates.draft.name # Set record's schema if '$schema' in data: raise InvalidDepositError('"$schema" field should not be set.') # Retrieve reserved record PID which should have already been created # by the deposit minter (The record PID value is the same # as the one of the deposit) rec_pid = RecordUUIDProvider.get(data['_deposit']['id']).pid version_master, prev_version = None, None # if this is a new version of an existing record, add the future # record pid in the chain of versions. if version_of: version_master, prev_version = \ find_version_master_and_previous_record(version_of) # The new version must be in the same community if data['community'] != prev_version['community']: raise ValidationError( 'The community field cannot change between versions.') try: version_master.insert_draft_child(rec_pid) except Exception as exc: # Only one draft is allowed per version chain. if 'Draft child already exists for this relation' in \ exc.args[0]: raise DraftExistsVersioningError( version_master.draft_child ) raise exc else: # create parent PID parent_pid = RecordUUIDProvider.create().pid version_master = PIDNodeVersioning(parent=parent_pid) version_master.insert_draft_child(child=rec_pid) # Mint the deposit with the parent PID data['_pid'] = [{ 'value': version_master.parent.pid_value, 'type': RecordUUIDProvider.parent_pid_type, }] if 'community' not in data or not data['community']: raise ValidationError( 'Record\s metadata has no community field.') try: community_id = uuid.UUID(data['community']) except ValueError as e: raise InvalidDepositError( 'Community ID is not a valid UUID.') from e try: schema = CommunitySchema.get_community_schema(community_id) except CommunitySchemaDoesNotExistError as e: raise InvalidDepositError( 'No schema for community {}.'.format(community_id)) from e if version_of: data['$schema'] = Deposit._build_deposit_schema(prev_version) else: from b2share.modules.schemas.serializers import \ community_schema_draft_json_schema_link data['$schema'] = community_schema_draft_json_schema_link( schema, _external=True ) # create file bucket if prev_version and prev_version.files: # Clone the bucket from the previous version. This doesn't # duplicate files. bucket = prev_version.files.bucket.snapshot(lock=False) bucket.locked = False else: bucket = Bucket.create(storage_class=current_app.config[ 'DEPOSIT_DEFAULT_STORAGE_CLASS' ]) if 'external_pids' in data: create_b2safe_file(data['external_pids'], bucket) del data['external_pids'] deposit = super(Deposit, cls).create(data, id_=id_) db.session.add(bucket) db.session.add(RecordsBuckets( record_id=deposit.id, bucket_id=bucket.id )) return deposit
def alembic_upgrade_database_data(alembic, verbose): """Migrate the database data from v2.0.0 to 2.1.0.""" ### Add versioning PIDs ### # Reserve the record PID and versioning PID for unpublished deposits # Hack: disable record indexing during record migration from invenio_indexer.api import RecordIndexer old_index_fn = RecordIndexer.index RecordIndexer.index = lambda s, record: None if verbose: click.secho('migrating deposits and records...') with db.session.begin_nested(): # Migrate published records records_pids = PersistentIdentifier.query.filter( PersistentIdentifier.pid_type == RecordUUIDProvider.pid_type, PersistentIdentifier.status == PIDStatus.REGISTERED, ).all() for rec_pid in records_pids: if verbose: click.secho(' record {}'.format(rec_pid.pid_value)) try: record = Record.get_record(rec_pid.object_uuid) except NoResultFound: # The record is deleted but not the PID. Fix it. rec_pid.status = PIDStatus.DELETED continue # Create parent version PID parent_pid = RecordUUIDProvider.create().pid version_master = PIDNodeVersioning(parent=parent_pid) version_master.insert_draft_child(child=rec_pid) version_master.update_redirect() migrate_record_metadata( Record.get_record(rec_pid.object_uuid), parent_pid ) # Migrate deposits deposit_pids = PersistentIdentifier.query.filter( PersistentIdentifier.pid_type == DepositUUIDProvider.pid_type, PersistentIdentifier.status == PIDStatus.REGISTERED, ).all() for dep_pid in deposit_pids: if verbose: click.secho(' deposit {}'.format(dep_pid.pid_value)) try: deposit = Deposit.get_record(dep_pid.object_uuid) if deposit['publication_state'] != \ PublicationStates.published.name: # The record is not published yet. Reserve the PID. rec_pid = RecordUUIDProvider.create( object_type='rec', pid_value=dep_pid.pid_value, ).pid # Create parent version PID parent_pid = RecordUUIDProvider.create().pid assert parent_pid version_master = PIDNodeVersioning(parent=parent_pid) version_master.insert_draft_child(child=rec_pid) else: # Retrieve previously created version PID rec_pid = RecordUUIDProvider.get(dep_pid.pid_value).pid version_master = PIDNodeVersioning(child=rec_pid) parent_pid = version_master.parent if not parent_pid: click.secho(' record {} was deleted, but the deposit has not been removed'.format(rec_pid.pid_value), fg='red') if parent_pid: migrate_record_metadata( Deposit.get_record(dep_pid.object_uuid), parent_pid ) except NoResultFound: # The deposit is deleted but not the PID. Fix it. dep_pid.status = PIDStatus.DELETED if verbose: click.secho('done migrating deposits.') RecordIndexer.index = old_index_fn