class RecordSchema(RecordSchemaBase): """Schema for records in JSON.""" parent = NestedAttribute(ParentSchema, dump_only=True) versions = NestedAttribute(VersionsSchema, dump_only=True) is_published = fields.Boolean(dump_only=True) expires_at = fields.Str(dump_only=True)
class AccessSchema(Schema): """Access schema.""" record = SanitizedUnicode(required=True) files = SanitizedUnicode(required=True) embargo = NestedAttribute(EmbargoSchema) owned_by = fields.List(fields.Nested(Agent)) def validate_protection_value(self, value, field_name): """Check that the protection value is valid.""" if value not in ["public", "restricted"]: raise ValidationError( _(f"'{field_name}' must be either 'public' or 'restricted'"), "record") def get_attribute(self, obj, key, default): """Override from where we get attributes when serializing.""" if key in ["record", "files"]: return getattr(obj.protection, key, default) elif key == "status": return obj.status.value return getattr(obj, key, default) @validates("record") def validate_record_protection(self, value): """Validate the record protection value.""" self.validate_protection_value(value, "record") @validates("files") def validate_files_protection(self, value): """Validate the files protection value.""" self.validate_protection_value(value, "files")
class ParentAccessSchema(Schema): """Access schema.""" metadata = SanitizedUnicode(required=True) files = SanitizedUnicode(required=True) status = SanitizedUnicode(dump_only=False) embargo = NestedAttribute(EmbargoSchema) owned_by = List(fields.Nested(Agent)) @validates_schema def validate_embargo(self, data, **kwargs): """Validate that the properties are consistent with each other.""" metadata = data.get("metadata", "") embargo = data.get("embargo", None) if AccessStatusEnum.EMBARGOED.value == metadata and not embargo: raise ValidationError( _("Embargo schema must be set if metadata is Embargoed"), field_name="embargo", )
class AccessSchema(Schema): """Access schema.""" metadata = SanitizedUnicode(required=True) files = SanitizedUnicode(required=True) embargo = NestedAttribute(EmbargoSchema) status = SanitizedUnicode(dump_only=False) owned_by = List(Nested(Agent)) def validate_protection_value(self, value, field_name): """Check that the protection value is valid.""" if value not in AccessStatusEnum.list(): raise ValidationError( _("'{}' must be either '{}', '{}' or '{}'").format( field_name, *AccessStatusEnum.list(), ), "record", ) @validates("metadata") def validate_record_protection(self, value): """Validate the record protection value.""" self.validate_protection_value(value, "metadata") @validates_schema def validate_embargo(self, data, **kwargs): """Validate that the properties are consistent with each other.""" metadata = data.get("metadata", "") embargo = data.get("embargo", "") if AccessStatusEnum.EMBARGOED.value == metadata and not embargo: raise ValidationError( _("Embargo must be set if metadata is Embargoed"), field_name="embargo", ) @validates("files") def validate_files_protection(self, value): """Validate the files protection value.""" self.validate_protection_value(value, "files")
class Marc21RecordSchema(BaseRecordSchema): """Record schema.""" id = fields.Str() # pid pids = fields.List(NestedAttribute(PIDSchema)) parent = NestedAttribute(Marc21ParentSchema, dump_only=True) metadata = NestedAttribute(MetadataSchema) access = NestedAttribute(AccessSchema) files = NestedAttribute(FilesSchema, dump_only=True) created = fields.Str(dump_only=True) updated = fields.Str(dump_only=True) revision = fields.Integer(dump_only=True) versions = NestedAttribute(VersionsSchema, dump_only=True) is_published = fields.Boolean(dump_only=True) # Add version to record schema # versions = NestedAttribute(VersionsSchema, dump_only=True) @post_dump def default_nested(self, data, many, **kwargs): """Serialize metadata as empty dict for partial drafts. Cannot use marshmallow for Nested fields due to issue: https://github.com/marshmallow-code/marshmallow/issues/1566 https://github.com/marshmallow-code/marshmallow/issues/41 and more. """ if not data.get("metadata"): data["metadata"] = {} return data
class RDMRecordSchema(RecordSchema, FieldPermissionsMixin): """Record schema.""" class Meta: """Meta class.""" # TODO: RAISE instead! unknown = EXCLUDE field_load_permissions = { 'access': 'manage', 'files': 'update', } field_dump_permissions = { 'files': 'read_files', } pids = fields.List(NestedAttribute(PIDSchema)) metadata = NestedAttribute(MetadataSchema) # ext = fields.Method('dump_extensions', 'load_extensions') # tombstone # provenance access = fields.Nested(AccessSchema) # files = NestedAttribute(FilesSchema, dump_only=True) # notes = fields.List(fields.Nested(InternalNoteSchema)) revision = fields.Integer(dump_only=True) versions = NestedAttribute(VersionsSchema, dump_only=True) parent = NestedAttribute(RDMParentSchema, dump_only=True) is_published = fields.Boolean(dump_only=True) # communities = NestedAttribute(CommunitiesSchema) # stats = NestedAttribute(StatsSchema, dump_only=True) # relations = NestedAttribute(RelationsSchema, dump_only=True) # schema_version = fields.Interger(dump_only=True) # def dump_extensions(self, obj): # """Dumps the extensions value. # :params obj: invenio_records_files.api.Record instance # """ # current_app_metadata_extensions = ( # current_app.extensions['invenio-rdm-records'].metadata_extensions # ) # ExtensionSchema = current_app_metadata_extensions.to_schema() # return ExtensionSchema().dump(obj.get('extensions', {})) # def load_extensions(self, value): # """Loads the 'extensions' field. # :params value: content of the input's 'extensions' field # """ # current_app_metadata_extensions = ( # current_app.extensions['invenio-rdm-records'].metadata_extensions # ) # ExtensionSchema = current_app_metadata_extensions.to_schema() # return ExtensionSchema().load(value) @post_dump def default_nested(self, data, many, **kwargs): """Serialize metadata as empty dict for partial drafts. Cannot use marshmallow for Nested fields due to issue: https://github.com/marshmallow-code/marshmallow/issues/1566 https://github.com/marshmallow-code/marshmallow/issues/41 and more. """ if not data.get("metadata"): data["metadata"] = {} return data
class RDMRecordSchema(RecordSchema, FieldPermissionsMixin): """Record schema.""" field_load_permissions = { 'access': 'manage', 'files': 'update_draft', } # ATTENTION: In this schema you should be using the ``NestedAttribute`` # instead of Marshmallow's ``fields.Nested``. Using NestedAttribute # ensures that the nested schema will receive the system field instead of # the record dict (i.e. record.myattr instead of record['myattr']). pids = fields.Dict(keys=fields.String(), values=fields.Nested(PIDSchema)) metadata = NestedAttribute(MetadataSchema) # ext = fields.Method('dump_extensions', 'load_extensions') # tombstone # provenance access = NestedAttribute(AccessSchema) files = NestedAttribute(FilesSchema) # notes = fields.List(fields.Nested(InternalNoteSchema)) revision = fields.Integer(dump_only=True) versions = NestedAttribute(VersionsSchema, dump_only=True) parent = NestedAttribute(RDMParentSchema, dump_only=True) is_published = fields.Boolean(dump_only=True) # communities = NestedAttribute(CommunitiesSchema) # stats = NestedAttribute(StatsSchema, dump_only=True) # relations = NestedAttribute(RelationsSchema, dump_only=True) # schema_version = fields.Interger(dump_only=True) # def dump_extensions(self, obj): # """Dumps the extensions value. # :params obj: invenio_records_files.api.Record instance # """ # current_app_metadata_extensions = ( # current_app.extensions['invenio-rdm-records'].metadata_extensions # ) # ExtensionSchema = current_app_metadata_extensions.to_schema() # return ExtensionSchema().dump(obj.get('extensions', {})) # def load_extensions(self, value): # """Loads the 'extensions' field. # :params value: content of the input's 'extensions' field # """ # current_app_metadata_extensions = ( # current_app.extensions['invenio-rdm-records'].metadata_extensions # ) # ExtensionSchema = current_app_metadata_extensions.to_schema() # return ExtensionSchema().load(value) @validates("pids") def validate_pids(self, value): """Validates the keys of the pids are supported providers.""" for scheme, pid_attrs in value.items(): # The required flag applies to the identifier value # It won't fail for empty allowing the components to reserve one id_schema = IdentifierSchema( fail_on_unknown=False, identifier_required=True) id_schema.load({ "scheme": scheme, "identifier": pid_attrs.get("identifier") }) @post_dump def default_nested(self, data, many, **kwargs): """Serialize metadata as empty dict for partial drafts. Cannot use marshmallow for Nested fields due to issue: https://github.com/marshmallow-code/marshmallow/issues/1566 https://github.com/marshmallow-code/marshmallow/issues/41 and more. """ if not data.get("metadata"): data["metadata"] = {} return data
class DataSetMetadataSchemaV2(InvenioRecordMetadataFilesMixin, InvenioRecordMetadataSchemaV1Mixin, StrictKeysMixin): """DataSet metaddata schema.""" resource_type = ResourceType(required=True) creators = fields.List(fields.Nested(CreatorSchema), required=True, validate=validate.Length( min=1, error=_("Missing data for required field."))) creator = SanitizedUnicode() title = MultilingualStringV2(required=True) additional_titles = List(MultilingualStringV2()) publisher = SanitizedUnicode() publication_date = EDTFDateString(required=True) subjects = List(fields.Nested(SubjectSchema)) contributors = List(fields.Nested(ContributorSchema)) dates = List(fields.Nested(DateSchema)) languages = TaxonomyField(mixins=[TitledMixin], many=True) # alternate identifiers identifiers = IdentifierSet( fields.Nested( partial(IdentifierSchema, allowed_schemes=RDM_RECORDS_IDENTIFIERS_SCHEMES))) related_identifiers = List(fields.Nested(RelatedIdentifierSchema)) version = SanitizedUnicode() rights = TaxonomyField(mixins=[TitledMixin, RightsMixin], many=True) abstract = MultilingualStringV2( required=True) # WARNING: May contain user-input HTML additional_descriptions = fields.List(MultilingualStringV2()) references = fields.List(fields.Nested(ReferenceSchema)) pids = fields.Dict(keys=fields.String(), values=fields.Nested(PIDSchema)) access = NestedAttribute(AccessSchema) keywords = List(SanitizedUnicode()) @pre_load def sanitize_html_fields(self, data, **kwargs): """Sanitize fields that may contain user-input HTML strings.""" if 'abstract' in data: for lang, val in data.get('abstract').items(): raw = data['abstract'][lang] data['abstract'][lang] = SanitizedHTML()._deserialize( raw, 'abstract', data) return data @pre_load def set_created(self, data, **kwargs): """Set created timestamp if not already set.""" dates = data.get('dates') or [] created = None for dat in dates: if dat.get('type', '') == 'created': created = dat.get('date') if not created: dates.append({ 'date': datetime.today().strftime('%Y-%m-%d'), 'type': 'created' }) data['dates'] = dates return data @pre_load def set_creator(self, data, **kwargs): """Set creator to record metadata if not known.""" if not data.get('creator'): if current_user and current_user.is_authenticated: data['creator'] = current_user.email else: data['creator'] = 'anonymous' return data @validates('pids') def validate_pids(self, value): """Validate the keys of the pids are supported providers.""" for scheme, pid_attrs in value.items(): # The required flag applies to the identifier value # It won't fail for empty allowing the components to reserve one id_schema = IdentifierSchema( identifier_required=True, allowed_schemes=RDM_RECORDS_IDENTIFIERS_SCHEMES) id_schema.load({ "scheme": scheme, "identifier": pid_attrs.get("identifier") })
class RDMRecordSchema(RecordSchema, FieldPermissionsMixin): """Record schema.""" field_load_permissions = { 'access': 'manage', 'files': 'update', } field_dump_permissions = { 'files': 'read_files', } # PIDS-FIXME: Re-enable when pids implementation is ready # pids = fields.Dict(keys=fields.String(), values=fields.Nested(PIDSchema)) metadata = NestedAttribute(MetadataSchema) # ext = fields.Method('dump_extensions', 'load_extensions') # tombstone # provenance access = fields.Nested(AccessSchema) # files = NestedAttribute(FilesSchema, dump_only=True) # notes = fields.List(fields.Nested(InternalNoteSchema)) revision = fields.Integer(dump_only=True) versions = NestedAttribute(VersionsSchema, dump_only=True) parent = NestedAttribute(RDMParentSchema, dump_only=True) is_published = fields.Boolean(dump_only=True) # communities = NestedAttribute(CommunitiesSchema) # stats = NestedAttribute(StatsSchema, dump_only=True) # relations = NestedAttribute(RelationsSchema, dump_only=True) # schema_version = fields.Interger(dump_only=True) # def dump_extensions(self, obj): # """Dumps the extensions value. # :params obj: invenio_records_files.api.Record instance # """ # current_app_metadata_extensions = ( # current_app.extensions['invenio-rdm-records'].metadata_extensions # ) # ExtensionSchema = current_app_metadata_extensions.to_schema() # return ExtensionSchema().dump(obj.get('extensions', {})) # def load_extensions(self, value): # """Loads the 'extensions' field. # :params value: content of the input's 'extensions' field # """ # current_app_metadata_extensions = ( # current_app.extensions['invenio-rdm-records'].metadata_extensions # ) # ExtensionSchema = current_app_metadata_extensions.to_schema() # return ExtensionSchema().load(value) # @validates("pids") # def validate_pids(self, value): # """Validates the keys of the pids are supported providers.""" # for scheme, pid_attrs in value.items(): # # The required flag applies to the identifier value # # It won't fail for empty allowing the components to reserve one # id_schema = IdentifierSchema(allow_all=True, required=True) # id_schema.load({ # "scheme": scheme, # "identifier": pid_attrs.get("identifier") # }) @post_dump def default_nested(self, data, many, **kwargs): """Serialize metadata as empty dict for partial drafts. Cannot use marshmallow for Nested fields due to issue: https://github.com/marshmallow-code/marshmallow/issues/1566 https://github.com/marshmallow-code/marshmallow/issues/41 and more. """ if not data.get("metadata"): data["metadata"] = {} return data
class CommunitySchema(BaseRecordSchema): """Schema for the community metadata.""" id = SanitizedUnicode(validate=_not_blank(max=100)) metadata = NestedAttribute(CommunityMetadataSchema, required=True) access = NestedAttribute(CommunityAccessSchema, required=True)
class RDMRecordSchema(RecordSchema, FieldPermissionsMixin): """Record schema.""" field_load_permissions = { 'files': 'update_draft', } # ATTENTION: In this schema you should be using the ``NestedAttribute`` # instead of Marshmallow's ``fields.Nested``. Using NestedAttribute # ensures that the nested schema will receive the system field instead of # the record dict (i.e. record.myattr instead of record['myattr']). pids = fields.Dict( keys=SanitizedUnicode(validate=validate_scheme), values=fields.Nested(PIDSchema), ) metadata = NestedAttribute(MetadataSchema) # ext = fields.Method('dump_extensions', 'load_extensions') # tombstone # provenance access = NestedAttribute(AccessSchema) files = NestedAttribute(FilesSchema) # notes = fields.List(fields.Nested(InternalNoteSchema)) revision = fields.Integer(dump_only=True) versions = NestedAttribute(VersionsSchema, dump_only=True) parent = NestedAttribute(RDMParentSchema) is_published = fields.Boolean(dump_only=True) status = fields.String(dump_only=True) # stats = NestedAttribute(StatsSchema, dump_only=True) # relations = NestedAttribute(RelationsSchema, dump_only=True) # schema_version = fields.Interger(dump_only=True) # def dump_extensions(self, obj): # """Dumps the extensions value. # :params obj: invenio_records_files.api.Record instance # """ # current_app_metadata_extensions = ( # current_app.extensions['invenio-rdm-records'].metadata_extensions # ) # ExtensionSchema = current_app_metadata_extensions.to_schema() # return ExtensionSchema().dump(obj.get('extensions', {})) # def load_extensions(self, value): # """Loads the 'extensions' field. # :params value: content of the input's 'extensions' field # """ # current_app_metadata_extensions = ( # current_app.extensions['invenio-rdm-records'].metadata_extensions # ) # ExtensionSchema = current_app_metadata_extensions.to_schema() # return ExtensionSchema().load(value) @post_dump def default_nested(self, data, many, **kwargs): """Serialize fields as empty dict for partial drafts. Cannot use marshmallow for Nested fields due to issue: https://github.com/marshmallow-code/marshmallow/issues/1566 https://github.com/marshmallow-code/marshmallow/issues/41 and more. """ if not data.get("metadata"): data["metadata"] = {} if not data.get("pids"): data["pids"] = {} return data