class Marc21Draft(Draft): """Marc21 draft API.""" model_cls = DraftMetadata index = IndexField("marc21records-drafts-marc21-v1.0.0", search_alias="marc21records-marc21") pid = PIDField( key="id", provider=MarcDraftProvider, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, delete=False, ) conceptpid = PIDField( key="conceptid", provider=MarcDraftProvider, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, delete=False, ) files = FilesField( store=False, file_cls=DraftFile, delete=False, ) bucket_id = ModelField(dump=False) bucket = ModelField(dump=False)
class Marc21Record(Record): """Define API for Marc21 create and manipulate.""" model_cls = RecordMetadata index = IndexField("marc21records-marc21-marc21-v1.0.0", search_alias="marc21records-marc21") pid = PIDField( key="id", provider=MarcRecordProvider, delete=False, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, ) conceptpid = PIDField( key="conceptid", provider=MarcRecordProvider, delete=False, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, ) files = FilesField( store=False, file_cls=RecordFile, create=False, delete=False, ) bucket_id = ModelField(dump=False) bucket = ModelField(dump=False)
class Marc21Draft(Draft): """Marc21 draft API.""" model_cls = DraftMetadata versions_model_cls = VersionsState parent_record_cls = Marc21Parent index = IndexField("marc21records-drafts-marc21-v1.0.0", search_alias="marc21records-marc21") parent = ParentField(Marc21Parent, create=True, soft_delete=False, hard_delete=True) pid = PIDField( key="id", provider=MarcDraftProvider, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, delete=False, ) files = FilesField( store=False, file_cls=DraftFile, delete=False, ) bucket_id = ModelField(dump=False) bucket = ModelField(dump=False)
class Marc21Record(Record): """Define API for Marc21 create and manipulate.""" model_cls = RecordMetadata versions_model_cls = VersionsState parent_record_cls = Marc21Parent index = IndexField("marc21records-marc21-marc21-v1.0.0", search_alias="marc21records-marc21") parent = ParentField(Marc21Parent, create=True, soft_delete=False, hard_delete=True) pid = PIDField( key="id", provider=MarcRecordProvider, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, delete=False, ) files = FilesField( store=False, file_cls=RecordFile, create=False, delete=False, ) bucket_id = ModelField(dump=False) bucket = ModelField(dump=False)
class ParentRecord(RecordBase): """Parent record API.""" # Configuration model_cls = None pid = PIDField('id', provider=DraftRecordIdProviderV2, delete=True)
class Draft(Record): """Draft base API for metadata creation and manipulation.""" # WHY: We want to force the model_cls to be specified by the user # No default one is given, only the base. model_cls = None pid = PIDField('id', provider=RecordIdProviderV2, delete=False) conceptpid = PIDField('conceptid', provider=RecordIdProviderV2, delete=False) expires_at = ModelField() fork_version_id = ModelField()
class LOMDraft(Draft): """For representing entries from the 'lom_drafts_metadata'-SQL-table.""" model_cls = models.LOMDraftMetadata parent_record_cls = LOMParent versions_model_cls = models.LOMVersionsState pid = PIDField( key="id", provider=LOMDraftRecordIdProvider, resolver_cls=LOMResolver, context_cls=LOMPIDFieldContext, # flag for deleting pid from database on post-record-deletion, # delete pid via LOMRecordService instead delete=False, ) files = FilesField( file_cls=LOMFileDraft, delete=False, store=False, ) access = RecordAccessField() bucket_id = ModelField(dump=False) bucket = ModelField(dump=False) index = IndexField("lomrecords-drafts-draft-v1.0.0", search_alias="lomrecords") is_published = PIDStatusCheckField(status=PIDStatus.REGISTERED, dump=True) resource_type = DictField()
class Vocabulary(Record): """A generic vocabulary record.""" # Configuration model_cls = VocabularyMetadata # System fields # TODO: Can schema name be changed (remove localhost) schema = ConstantField( "$schema", "https://localhost/schemas/vocabularies/vocabulary-v1.0.0.json", ) index = IndexField( "vocabularies-vocabulary-v1.0.0", search_alias="vocabularies" ) #: Disable the metadata system field. metadata = None type = RelatedModelField(VocabularyType, required=True) pid = PIDField( 'id', provider=VocabularyIdProvider, context_cls=VocabularyPIDFieldContext, create=False, )
class Vocabulary(RecordBase): """Example record API.""" # Configuration model_cls = VocabularyMetadata dumper = ElasticsearchDumper( extensions=[VocabularyTypeElasticsearchDumperExt()] ) # System fields schema = ConstantField( "$schema", "https://localhost/schemas/vocabularies/vocabulary-v1.0.0.json", ) index = IndexField( "vocabularies-vocabulary-v1.0.0", search_alias="vocabularies" ) # TODO: This should be changed to use something else than the recidv2 pid = PIDField("id", provider=RecordIdProviderV2) vocabulary_type_id = ModelField() vocabulary_type = VocabularyTypeField(dump=False)
class Record(RecordBase): """Record base API. Note: This class is meant to work along a with a draft class. """ # Configuration model_cls = None pid = PIDField('id', provider=RecordIdProviderV2) conceptpid = PIDField('conceptid', provider=RecordIdProviderV2) is_published = PIDStatusCheckField(status=PIDStatus.REGISTERED) @classmethod def create_or_update_from(cls, draft): """Create of update the record based on the draft content.""" try: # New version record = cls.get_record(draft.id) except NoResultFound: # New revision record = cls.create({}, id_=draft.id, pid=draft.pid, conceptpid=draft.conceptpid) # NOTE: Merge pid/conceptpid into the current db session if not already # in the session. cls.pid.session_merge(record) cls.conceptpid.session_merge(record) # Overwrite data # FIXME: Data validation should be done one step up self.schema access # TODO: Does this overwrite the pids/conceptpids? record.update(**draft) return record def register(self): """Register the persistent identifiers associated with teh record.""" if not self.conceptpid.is_registered(): self.conceptpid.register() self.pid.register()
class Record(RecordBase): """Example record API.""" # Configuration model_cls = RecordMetadata # Model fields expires_at = ModelField() # System fields schema = ConstantField( '$schema', 'http://localhost/schemas/records/record-v1.0.0.json') index = IndexField('records-record-v1.0.0', search_alias='records') pid = PIDField('id', provider=RecordIdProviderV2) conceptpid = PIDField('conceptid', provider=RecordIdProviderV2) is_published = PIDStatusCheckField(status=PIDStatus.REGISTERED)
def create_record_class(self): """Create record class.""" record_class_attributes = { "model_cls": self.model_cls, "schema": ConstantField("$schema", self.schema_path), "index": IndexField(self.index_name), "pid": PIDField("id", provider=RecordIdProviderV2), "dumper": self.record_dumper or ElasticsearchDumper(), } self.record_cls = type(self.record_type_name, (Record, ), record_class_attributes)
class Marc21Record(Record): """Define API for Marc21 creation and manipulation.""" model_cls = models.RecordMetadata index = IndexField("marc21records-marc21-marc21-v1.0.0", search_alias="marc21records-marc21") pid = PIDField( key="id", provider=MarcRecordProvider, delete=False, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, ) conceptpid = PIDField( key="conceptid", provider=MarcRecordProvider, delete=False, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, )
class Marc21Draft(Draft): """Marc21 draft API.""" model_cls = models.DraftMetadata index = IndexField("marc21records-drafts-marc21-v1.0.0", search_alias="marc21records-marc21") pid = PIDField( key="id", provider=MarcDraftProvider, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, delete=False, ) conceptpid = PIDField( key="conceptid", provider=MarcDraftProvider, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, delete=False, )
class Marc21Parent(BaseParentRecord): """Parent record.""" versions_model_cls = VersionsState model_cls = ParentMetadata schema = ConstantField("$schema", "local://marc21/parent-v1.0.0.json") pid = PIDField( key="id", provider=MarcDraftProvider, context_cls=MarcPIDFieldContext, resolver_cls=MarcResolver, delete=False, )
class LOMParent(ParentRecord): """For representing entries from the 'lom_parents_metadata'-SQL-table.""" model_cls = models.LOMParentMetadata pid = PIDField( key="id", provider=LOMDraftRecordIdProvider, resolver_cls=LOMResolver, context_cls=LOMPIDFieldContext, # flag for deleting pid from database on post-record-deletion, # pid is deleted via LOMRecordService instead delete=False, ) access = ParentRecordAccessField()
class Record(RecordBase): """Example bibliographic record API.""" model_cls = models.RecordMetadata schema = ConstantField('$schema', 'local://records/record-v1.0.0.json') index = IndexField('records-record-v1.0.0', search_alias='records') pid = PIDField('id', provider=RecordIdProviderV2) # Definitions of relationships from a bibliographic record to the # generic vocabularies. relations = RelationsField(languages=PIDListRelation( 'metadata.languages', keys=['id', 'title'], pid_field=Vocabulary.pid.with_type_ctx('languages')), ) dumper = ElasticsearchDumper(extensions=[ RelationDumperExt('relations'), ])
class Community(Record): """Community API.""" pid = PIDField('id', provider=CommunitiesIdProvider, create=False) schema = ConstantField('$schema', 'local://communities/communities-v1.0.0.json') model_cls = models.CommunityMetadata index = IndexField("communities-communities-v1.0.0", search_alias="communities") access = CommunityAccessField() bucket_id = ModelField(dump=False) bucket = ModelField(dump=False) files = FilesField( store=False, file_cls=CommunityFile, # Don't delete, we'll manage in the service delete=False, )
class Record(BaseRecord): """API for projects resources.""" # Configuration model_cls = models.RecordMetadata # System fields index = IndexField('projects-project-v1.0.0', search_alias='projects') # The `pid_type` must not be filled as argument in this constructor. # Instead it is guessed from RecordIdProvider. pid = PIDField('id', provider=RecordIdProvider) # PID type retrieved from provider pid_type = RecordIdProvider.pid_type dumper = ElasticsearchDumper(extensions=[ElasticsearchDumperObjectsExt()]) _extensions = [ValidationExtension()] @cached_property def schema(self): """Return the schema.""" schema_key = 'projects' if not has_custom_resource( 'projects') else f'{current_organisation["code"]}/projects' schema = f'https://sonar.ch/schemas/{schema_key}/project-v1.0.0.json' return ConstantField('$schema', schema) def __repr__(self): """String representation of object. :returns: A string representing the object. """ return self['metadata']['name']
class Record(RecordBase): model_cls = RecordMetadata pid = PIDField()
class Record(RecordBase): model_cls = RecordMetadata pid = PIDField('pid.id', provider=RecordIdProviderV2)
class Draft(Record): """Draft base API for metadata creation and manipulation.""" #: Class attribute to make it easy to check if record is a draft or not. is_draft = True # # Configuration to be set by a subclass # #: The record's SQLAlchemy model class. Must be set by the subclass. model_cls = None #: The parent state's SQLAlchemy model class. Must be set by the subclass. versions_model_cls = None #: The parent record's API class. Must be set by the subclass. parent_record_cls = None # # System fields # #: The internal persistent identifier. Records and drafts share UUID. pid = PIDField('id', provider=DraftRecordIdProviderV2, delete=False) #: The parent record - the draft is responsible for creating the parent. parent = ParentField(ParentRecord, create=True, soft_delete=False, hard_delete=True) #: Version relationship versions = VersionsField(create=True, set_next=True) #: The expiry date of the draft. expires_at = ModelField() #: Revision id of record from which this draft was created. fork_version_id = ModelField() @classmethod def new_version(cls, record): """Create a draft for a new version of a record. The caller is responsible for: 1) checking if a draft for a new version already exists 2) moving the record data into the draft data. """ return cls.create( {}, # We create a new id, because this is for a new version. id=uuid.uuid4(), # Links the draft with the same parent (i.e. a new version). parent=record.parent, versions=record.versions, # New drafts without a record (i.e. unpublished drafts) must set # the fork version id to None. fork_version_id=None, ) @classmethod def edit(cls, record): """Create a draft for editing an existing version of a record.""" try: # We soft-delete a draft once it has been published, in order to # keep the version_id counter around for optimistic concurrency # control (both for ES indexing and for REST API clients) draft = cls.get_record(record.id, with_deleted=True) if draft.is_deleted: draft.undelete() # Below line is needed to dump PID back into the draft. draft.pid = record.pid # Ensure record is link with the parent draft.parent = record.parent draft.versions = record.versions # Ensure we record the revision id we forked from draft.fork_version_id = record.revision_id # Note, other values like bucket_id values was kept in the # soft-deleted record, so we are not setting them again here. except NoResultFound: # If a draft was ever force deleted, then we will create the draft. # This is a very exceptional case as normally, when we edit a # record then the soft-deleted draft exists and we are in above # case. draft = cls.create( {}, # A draft to edit a record must share the id and uuid. id_=record.id, pid=record.pid, # Link it with the same parent record parent=record.parent, versions=record.versions, # Record which record version we forked from. fork_version_id=record.revision_id, ) return draft
class Record(RecordBase): """Record API.""" #: Class attribute to make it easy to check if record is a draft or not. is_draft = False # # Configuration to be set by a subclass # #: The record's SQLAlchemy model class. Must be set by the subclass. model_cls = None #: The parent state's SQLAlchemy model class. Must be set by the subclass. versions_model_cls = None #: The parent record's API class. Must be set by the subclass. parent_record_cls = None # # System fields # #: The internal persistent identifier. Records and drafts share UUID. pid = PIDField('id', provider=DraftRecordIdProviderV2, delete=True) #: System field to check if a record has been published. is_published = PIDStatusCheckField(status=PIDStatus.REGISTERED) #: The parent record - the draft is responsible for creating the parent. parent = ParentField(ParentRecord, create=False, soft_delete=False, hard_delete=False) #: Version relationship versions = VersionsField(create=True, set_latest=True) @classmethod def get_records_by_parent(cls, parent): """Get all sibling records for the specified parent record.""" versions = cls.model_cls.query.filter_by(parent_id=parent.id).all() return [cls(rec_model.data, model=rec_model) for rec_model in versions] @classmethod def publish(cls, draft): """Publish a draft as a new record. If a record already exists, we simply get the record. If a draft has not yet been published, we create the record. The caller is responsible for registering the internal persistent identifiers (see ``register()``). """ if draft.is_published: record = cls.get_record(draft.id) else: record = cls.create( {}, # A draft and record share UUID, so we reuse the draft's id/pid id_=draft.id, pid=draft.pid, # Link the record with the parent record and set the versioning # relationship. parent=draft.parent, versions=draft.versions, ) # Merge the PIDs into the current db session if not already in the # session. cls.pid.session_merge(record) cls.parent_record_cls.pid.session_merge(record.parent) return record def register(self): """Register the internal persistent identifiers.""" if not self.parent.pid.is_registered(): self.parent.pid.register() self.parent.commit() self.pid.register()