class AffiliationSchema(Schema): """Affiliation of a creator/contributor.""" name = SanitizedUnicode(required=True) identifiers = IdentifierSet( fields.Nested(IdentifierSchema, allowed_schemes=RDM_RECORDS_IDENTIFIERS_SCHEMES), )
class PersonOrOrganizationSchema(Schema): """Person or Organization schema.""" NAMES = ["organizational", "personal"] type = SanitizedUnicode( required=True, validate=validate.OneOf( choices=NAMES, error=_('Invalid value. Choose one of {NAMES}.').format( NAMES=NAMES)), error_messages={ # [] needed to mirror error message above "required": [_('Invalid value. Choose one of {NAMES}.').format(NAMES=NAMES)] }) name = SanitizedUnicode() given_name = SanitizedUnicode() family_name = SanitizedUnicode() identifiers = IdentifierSet( fields.Nested( partial( IdentifierSchema, # It is intended to allow org schemes to be sent as personal # and viceversa. This is a trade off learnt from running # Zenodo in production. allowed_schemes=record_personorg_schemes))) @validates_schema def validate_names(self, data, **kwargs): """Validate names based on type.""" if data['type'] == "personal": if not data.get('family_name'): messages = [_("Family name must be filled.")] raise ValidationError({"family_name": messages}) elif data['type'] == "organizational": if not data.get('name'): messages = [_('Name cannot be blank.')] raise ValidationError({"name": messages}) @post_load def update_names(self, data, **kwargs): """Update names for organization / person. Fill name from given_name and family_name if person. Remove given_name and family_name if organization. """ if data["type"] == "personal": names = [data.get("family_name"), data.get("given_name")] data["name"] = ", ".join([n for n in names if n]) elif data['type'] == "organizational": if 'family_name' in data: del data['family_name'] if 'given_name' in data: del data['given_name'] return data
class AffiliationSchema(BaseVocabularySchema): """Service schema for affiliations.""" acronym = SanitizedUnicode() identifiers = IdentifierSet( fields.Nested( partial(IdentifierSchema, allowed_schemes=affiliation_schemes, identifier_required=False))) name = SanitizedUnicode(required=True)
class TestSchema(Schema): allowed_schemes = { "orcid": { "label": "ORCID", "validator": idutils.is_orcid }, "doi": { "label": "DOI", "validator": idutils.is_doi } } identifiers = IdentifierSet( Nested(partial(IdentifierSchema, allowed_schemes=allowed_schemes)))
class MetadataSchema(Schema): """Schema for the record metadata.""" field_load_permissions = { # TODO: define "can_admin" action } field_dump_permissions = { # TODO: define "can_admin" action } class Meta: """Meta class to accept unknwon fields.""" unknown = INCLUDE # Metadata fields resource_type = ResourceType(required=True) creators = fields.List(fields.Nested(CreatorSchema), required=True, validate=validate.Length( min=1, error=_("Missing data for required field."))) title = SanitizedUnicode(required=True, validate=validate.Length(min=3)) additional_titles = fields.List(fields.Nested(TitleSchema)) publisher = SanitizedUnicode() publication_date = EDTFDateString(required=True) subjects = fields.List(fields.Nested(SubjectSchema)) contributors = fields.List(fields.Nested(ContributorSchema)) dates = fields.List(fields.Nested(DateSchema)) languages = fields.List(fields.Nested(LanguageSchema)) # alternate identifiers identifiers = IdentifierSet( fields.Nested(partial(IdentifierSchema, allow_all=True))) related_identifiers = fields.List(fields.Nested(RelatedIdentifierSchema)) sizes = fields.List( SanitizedUnicode( validate=_not_blank(_('Size cannot be a blank string.')))) formats = fields.List( SanitizedUnicode( validate=_not_blank(_('Format cannot be a blank string.')))) version = SanitizedUnicode() rights = fields.List(fields.Nested(RightsSchema)) description = SanitizedHTML(validate=validate.Length(min=3)) additional_descriptions = fields.List(fields.Nested(DescriptionSchema)) locations = fields.List(fields.Nested(LocationSchema)) funding = fields.List(fields.Nested(FundingSchema)) references = fields.List(fields.Nested(ReferenceSchema))
class AwardRelationSchema(Schema): """Award relation schema.""" id = SanitizedUnicode() number = SanitizedUnicode() title = i18n_strings identifiers = IdentifierSet( fields.Nested( partial(IdentifierSchema, allowed_schemes=award_schemes, identifier_required=False))) @validates_schema def validate_data(self, data, **kwargs): """Validate either id or number/title are present.""" id_ = data.get("id") number = data.get("number") title = data.get("title") if not id_ and not (number and title): raise ValidationError( _("An existing id or number/title must be present."), "award")
class AwardSchema(BaseVocabularySchema): """Award schema.""" identifiers = IdentifierSet( fields.Nested( partial(IdentifierSchema, allowed_schemes=award_schemes, identifier_required=False))) number = SanitizedUnicode(required=True, validate=validate.Length( min=1, error=_('Number cannot be blank.'))) funder = fields.Nested(FunderRelationSchema) acronym = SanitizedUnicode() id = SanitizedUnicode( validate=validate.Length(min=1, error=_('Pid cannot be blank.'))) @validates_schema def validate_id(self, data, **kwargs): """Validates ID.""" is_create = "record" not in self.context if is_create and "id" not in data: raise ValidationError(_("Missing PID."), "id") if not is_create: data.pop("id", None) @post_load(pass_many=False) def move_id(self, data, **kwargs): """Moves id to pid.""" if "id" in data: data["pid"] = data.pop("id") return data @pre_dump(pass_many=False) def extract_pid_value(self, data, **kwargs): """Extracts the PID value.""" data['id'] = data.pid.pid_value return data
class MetadataSchema(Schema): """Schema for the record metadata.""" # Metadata fields resource_type = fields.Nested(VocabularySchema, required=True) creators = fields.List(fields.Nested(CreatorSchema), required=True, validate=validate.Length( min=1, error=_("Missing data for required field."))) title = SanitizedUnicode(required=True, validate=validate.Length(min=3)) additional_titles = fields.List(fields.Nested(TitleSchema)) publisher = SanitizedUnicode() publication_date = EDTFDateString(required=True) subjects = fields.List(fields.Nested(SubjectSchema)) contributors = fields.List(fields.Nested(ContributorSchema)) dates = fields.List(fields.Nested(DateSchema)) languages = fields.List(fields.Nested(VocabularySchema)) # alternate identifiers identifiers = IdentifierSet( fields.Nested( partial(IdentifierSchema, allowed_schemes=record_identifiers_schemes))) related_identifiers = fields.List(fields.Nested(RelatedIdentifierSchema)) sizes = fields.List( SanitizedUnicode( validate=_not_blank(_('Size cannot be a blank string.')))) formats = fields.List( SanitizedUnicode( validate=_not_blank(_('Format cannot be a blank string.')))) version = SanitizedUnicode() rights = fields.List(fields.Nested(RightsSchema)) description = SanitizedHTML(validate=validate.Length(min=3)) additional_descriptions = fields.List(fields.Nested(DescriptionSchema)) locations = fields.Nested(FeatureSchema) funding = fields.List(fields.Nested(FundingSchema)) references = fields.List(fields.Nested(ReferenceSchema))
class AffiliationSchema(Schema): """Affiliation of a creator/contributor.""" name = SanitizedUnicode(required=True) identifiers = IdentifierSet(fields.Nested(IdentifierSchema), )
class TestSchema(Schema): identifiers = IdentifierSet( Nested(partial(IdentifierSchema, allowed_schemes=["doi", "orcid"])))
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 PersonOrOrganizationSchema(Schema): """Person or Organization schema.""" NAMES = ["organizational", "personal"] type = SanitizedUnicode( required=True, validate=validate.OneOf( choices=NAMES, error=_(f'Invalid value. Choose one of {NAMES}.')), error_messages={ # NOTE: [] needed to mirror above error message "required": [_(f'Invalid value. Choose one of {NAMES}.')] }) name = SanitizedUnicode() given_name = SanitizedUnicode() family_name = SanitizedUnicode() identifiers = IdentifierSet( fields.Nested( partial( IdentifierSchema, # It is intendedly allowing org schemes to be sent as personal # and viceversa. This is a trade off learnt from running # Zenodo in production. allowed_schemes={ "orcid": { "label": "ORCID", "validator": idutils.is_orcid }, "isni": { "label": "ISNI", "validator": idutils.is_isni }, "gnd": { "label": "GND", "validator": idutils.is_gnd }, "ror": { "label": "ROR", "validator": idutils.is_ror } }))) @validates_schema def validate_names(self, data, **kwargs): """Validate names based on type.""" if data['type'] == "personal": if not (data.get('given_name') or data.get('family_name')): messages = [_("Family name or given name must be filled.")] raise ValidationError({ "given_name": messages, "family_name": messages }) elif data['type'] == "organizational": if not data.get('name'): messages = [_('Name cannot be blank.')] raise ValidationError({"name": messages}) @post_load def update_names(self, data, **kwargs): """Update names for organization / person. Fill name from given_name and family_name if person. Remove given_name and family_name if organization. """ if data["type"] == "personal": names = [data.get("family_name"), data.get("given_name")] data["name"] = ", ".join([n for n in names if n]) elif data['type'] == "organizational": if 'family_name' in data: del data['family_name'] if 'given_name' in data: del data['given_name'] return data
class AffiliationSchema(BaseVocabularySchema): """Service schema for affiliations.""" acronym = SanitizedUnicode() identifiers = IdentifierSet(fields.Nested(IdentifierSchema)) name = SanitizedUnicode(required=True)