def _validate_sequence_creation_payload(self, record, payload): """Validate the payload when creating a new sequence relation.""" try: next_pid_value = payload.pop("next_pid_value") next_pid_type = payload.pop("next_pid_type") previous_pid_value = payload.pop("previous_pid_value") previous_pid_type = payload.pop("previous_pid_type") except KeyError as key: raise RecordRelationsError( "The `{}` is a required field".format(key)) if (record["pid"] != next_pid_value and record["pid"] != previous_pid_value): raise RecordRelationsError( "Cannot create a relation for other record than one with PID " "`{}`".format(record["pid"])) if next_pid_value == previous_pid_value: raise RecordRelationsError( "Cannot create a sequence with the same next PID `{}`" "and previous PID `{}`".format(next_pid_value, previous_pid_value)) return ( next_pid_value, next_pid_type, previous_pid_value, previous_pid_type, payload, )
def _validate_relation_between_records(self, first, second, relation_name): """Validate relation between type of records.""" from invenio_app_ils.documents.api import Document from invenio_app_ils.series.api import Series # records must be of the same type same_document = isinstance(first, Document) and isinstance( second, Document) same_series = (isinstance(first, Series) and isinstance(second, Series) and first["mode_of_issuance"] == second["mode_of_issuance"]) valid_edition_relation = relation_name == "edition" and ( (isinstance(first, Document) and isinstance(second, Series) and second["mode_of_issuance"] == "MULTIPART_MONOGRAPH") or (isinstance(second, Document) and isinstance(first, Series) and first["mode_of_issuance"] == "MULTIPART_MONOGRAPH")) valid_edition_fields = relation_name == "edition" and \ first.get('edition', False) and second.get('edition', False) valid_language_fields = relation_name == 'language' and \ first.get('languages', False) and second.get('languages', False) valid_other_fields = relation_name == "other" equal_editions = relation_name == 'edition' and first.get('edition') \ == second.get('edition') equal_languages = relation_name == 'language' and \ first.get('languages') == second.get('languages') if not (same_document or same_series or valid_edition_relation): raise RecordRelationsError( "Cannot create relation `{}` between PID `{}` and PID `{}`," " they are different record types".format( relation_name, first.pid.pid_value, second.pid.pid_value)) if not (valid_edition_fields or valid_language_fields or valid_other_fields): raise RecordRelationsError( "Cannot create relation `{}` " "between PID `{}` and PID `{}`," " one of the records is missing {} fields".format( relation_name, first.pid.pid_value, second.pid.pid_value, relation_name)) if equal_editions or equal_languages: raise RecordRelationsError("Cannot create relation `{}` " "between PID `{}` and PID `{}`," " records have equal {} fields".format( relation_name, first.pid.pid_value, second.pid.pid_value, relation_name)) return True
def _validate_relation_between_records(self, parent, child, relation_name): """Validate relation between type of records.""" from invenio_app_ils.documents.api import Document from invenio_app_ils.series.api import Series # when child is Document, parent is any type of Series is_series_doc = isinstance(child, Document) and isinstance( parent, Series ) # when child is Multipart Monograph, parent is only Serials is_serial_mm = ( isinstance(child, Series) and isinstance(parent, Series) and child["mode_of_issuance"] == "MULTIPART_MONOGRAPH" and parent["mode_of_issuance"] == "SERIAL" ) if not (is_series_doc or is_serial_mm): raise RecordRelationsError( "Cannot create a relation `{}` between PID `{}` as parent and " "PID `{}` as child.".format( relation_name, parent.pid.pid_value, child.pid.pid_value ) ) return True
def get_relation_by_name(name): """Get the relation_type by name.""" for relation in current_app.config["ILS_PIDRELATIONS_TYPES"]: if relation.name == name: return relation raise RecordRelationsError( "A relation type with name `{}` does not exist".format(name))
def _create_sibling_relation(self, record, relation_type, payload): """Create a Siblings relation from current record to the given PID. Expected payload: { pid_value: <pid_value>, pid_type: <pid_type>, relation_type: "<relation name>", [note: "<note>"] } """ pid_value, pid_type, metadata = self._validate_siblings_creation_payload( payload ) if pid_value == record["pid"] and pid_type == record._pid_type: raise RecordRelationsError( "Cannot create a relation for PID `{}` with itself".format( pid_value ) ) second = IlsRecord.get_record_by_pid(pid_value, pid_type=pid_type) rr = RecordRelationsSiblings() modified_record = rr.add( first=record, second=second, relation_type=relation_type, **metadata ) return modified_record, record, second
def _validate_relation_between_records(self, first, second, relation_name): """Validate relation between type of records.""" from invenio_app_ils.documents.api import Document from invenio_app_ils.series.api import Series # records must be of the same type same_document = isinstance(first, Document) and isinstance( second, Document ) same_series = ( isinstance(first, Series) and isinstance(second, Series) and first["mode_of_issuance"] == second["mode_of_issuance"] ) valid_edition = relation_name == "edition" and ( ( isinstance(first, Document) and isinstance(second, Series) and second["mode_of_issuance"] == "MULTIPART_MONOGRAPH" ) or ( isinstance(second, Document) and isinstance(first, Series) and first["mode_of_issuance"] == "MULTIPART_MONOGRAPH" ) ) if not (same_document or same_series or valid_edition): raise RecordRelationsError( "Cannot create a relation `{}` between PID `{}` and PID `{}`," " they are different record types".format( relation_name, first.pid.pid_value, second.pid.pid_value ) ) return True
def _validate_relation_between_records( self, previous_rec, next_rec, relation_name ): """Validate relation between type of records.""" from invenio_app_ils.series.api import Series # records must be of the same type, Sequences support only Series allowed_types = [Series] for record_type in allowed_types: if isinstance(previous_rec, record_type) and isinstance( next_rec, record_type ): return True raise RecordRelationsError( "Cannot create a relation `{}` between PID `{}` with type {} " " and PID `{}` with type {}.".format( relation_name, previous_rec.pid.pid_value, previous_rec.pid.pid_type, next_rec.pid.pid_value, next_rec.pid.pid_type, ) )
def _validate_relation_type(self, relation_type): """Validate the given relation type.""" if relation_type not in self.relation_types: rel_names = ",".join([rt.name for rt in self.relation_types]) raise RecordRelationsError( "Relation type must be one of `{}`".format(rel_names) )
def delete(payload): try: relation_type = payload.pop("relation_type") except KeyError as key: return abort(400, "The `{}` is a required field".format(key)) rt = Relation.get_relation_by_name(relation_type) if rt in current_app.config["PARENT_CHILD_RELATION_TYPES"]: modified, first, second = self._delete_parent_child_relation( record, rt, payload) elif rt in current_app.config["SIBLINGS_RELATION_TYPES"]: modified, first, second = self._delete_sibling_relation( record, rt, payload) else: raise RecordRelationsError("Invalid relation type `{}`".format( rt.name)) db.session.commit() records_to_index.append(first) records_to_index.append(second) # if the record is the modified, return the modified version if (modified.pid == record.pid and modified._pid_type == record._pid_type): return modified return record
def remove(self, pid): """Remove the only possible relation of this given PID. If the given PID is a child, or only one relation exists, the relation involving PID is simply removed. When the given PID is a parent of many relations, then it is deleted and a new parent is chosen as when adding new relations. Example: 1 -> 2 <rel-type-1> 1 -> 3 <rel-type-1> 1 -> 4 <rel-type-1> 1 -> 5 <rel-type-1> remove(1) 2 -> 3 <rel-type-1> 2 -> 4 <rel-type-1> 2 -> 5 <rel-type-1> """ # get all relations where first is a parent or a child, or second is # a parent or a child (any relation where first or second are involved) all_relations = self.get_any_relation_of(pid) if not all_relations: return # if there is only one relation involving the given PID, or it is a # parent with only one relation or it is a child if len(all_relations) == 1: # only of relation exists for the given PID, simply delete it with db.session.begin_nested(): db.session.delete(all_relations[0]) else: # given PID is a parent and there are multiple relations with this # parent. Elect a new parent and adjust all relations attached to # it pids_to_relate = set() for rel in all_relations: pids_to_relate.add(rel.parent) pids_to_relate.add(rel.child) try: # remove the PID for the relation to delete pids_to_relate.remove(pid) except KeyError: raise RecordRelationsError( "Error deleting relation_type `{}` for PID `{}`: the PID " "is not found in all relations of this type.".format( self.relation_type.name, pid.pid_value ) ) self._recreate_relations_with_random_parent( all_relations, pids_to_relate )
def _validate_siblings_creation_payload(self, payload): """Validate the payload when creating a new siblings relation.""" try: pid = payload.pop("pid") pid_type = payload.pop("pid_type") except KeyError as key: raise RecordRelationsError( "The `{}` is a required field".format(key)) return pid, pid_type, payload
def _validate_parent_child_creation_payload(self, payload): """Validate the payload when creating a new parent-child relation.""" try: parent_pid = payload.pop("parent_pid") parent_pid_type = payload.pop("parent_pid_type") child_pid = payload.pop("child_pid") child_pid_type = payload.pop("child_pid_type") except KeyError as key: raise RecordRelationsError( "The `{}` is a required field".format(key)) return parent_pid, parent_pid_type, child_pid, child_pid_type, payload
def add(self, parent_pid, child_pid): """Add a new relation between parent and child.""" if self.relation_exists(parent_pid, child_pid): raise RecordRelationsError( "The relation `{}` between PID `{}` and PID `{}` already " "exists".format( self.relation_type.name, parent_pid.pid_value, child_pid.pid_value, )) with db.session.begin_nested(): return PIDRelation.create(parent_pid, child_pid, self.relation_type.id)
def add(self, previous_pid, next_pid): """Add a new relation between previous and next pid.""" if self.relation_exists(previous_pid, next_pid): raise RecordRelationsError( "The relation `{}` between parent PID `{}` and child PID `{}` " "already exists".format( self.relation_type.name, previous_pid.pid_value, next_pid.pid_value, )) with db.session.begin_nested(): return PIDRelation.create(previous_pid, next_pid, self.relation_type.id)
def _validate_relation_between_records(self, parent, child, relation_type): """Validate relation between type of records.""" from invenio_app_ils.documents.api import Document from invenio_app_ils.series.api import Series if relation_type.name == "multipart_monograph": pcr = ParentChildRelation(relation_type) relations = pcr.get_relations_by_child(child.pid) if len(relations) > 0: raise RecordRelationsError( "Cannot create a relation `{}` between PID `{}` as parent" " and PID `{}` as child. Record `{}` has already a" " multipart monograph.".format( relation_type.name, parent.pid.pid_value, child.pid.pid_value, child.pid.pid_value, )) # when child is Document, parent is any type of Series is_series_doc = isinstance(child, Document) and isinstance( parent, Series) # when child is Multipart Monograph, parent is only Serials is_serial_mm = (isinstance(child, Series) and isinstance(parent, Series) and child["mode_of_issuance"] == "MULTIPART_MONOGRAPH" and parent["mode_of_issuance"] == "SERIAL") if not (is_series_doc or is_serial_mm): raise RecordRelationsError( "Cannot create a relation `{}` between PID `{}` as parent and " "PID `{}` as child.".format( relation_type.name, parent.pid.pid_value, child.pid.pid_value, )) return True
def add_extra_metadata_to(cls, record, relation_name, pid_value, pid_type, **kwargs): """Add a new extra metadata dict for the given PID and type.""" metadata = record.setdefault(cls.field_name(), {}) relation_metadata = metadata.setdefault(relation_name, []) for m in relation_metadata: if (m.get("pid_value", "") == pid_value and m.get("pid_type", "") == pid_type): raise RecordRelationsError( "The record PID `{}` has already metadata for the relation" " `{}` and record PID `{}`".format(record.pid.pid_value, relation_name, pid_value)) obj = RecordRelationsExtraMetadata.build_metadata_object( pid_value, pid_type, **kwargs) relation_metadata.append(obj) record.commit()
def remove(self, previous_pid, next_pid): """Delete the relation for the given PIDs.""" relation = PIDRelation.query.filter_by( parent_id=previous_pid.id, child_id=next_pid.id, relation_type=self.relation_type.id, ).one_or_none() if not relation: raise RecordRelationsError( "The relation `{}` between PID `{}` and PID `{}` does not " "exists".format( self.relation_type.name, previous_pid.pid_value, next_pid.pid_value, )) with db.session.begin_nested(): db.session.delete(relation)
def create(payload): try: relation_type = payload.pop("relation_type") except KeyError as key: return abort(400, "The `{}` is a required field".format(key)) rt = Relation.get_relation_by_name(relation_type) if rt in PARENT_CHILD_RELATION_TYPES: modified, first, second = self._create_parent_child_relation( record, rt, payload ) elif rt in SIBLINGS_RELATION_TYPES: modified, first, second = self._create_sibling_relation( record, rt, payload ) elif rt in SEQUENCE_RELATION_TYPES: first, second = self._create_sequence_relation( record, rt, payload ) modified = second else: raise RecordRelationsError( "Invalid relation type `{}`".format(rt.name) ) db.session.commit() records_to_index.append(first) records_to_index.append(second) def is_modified(x, r): return x.pid == r.pid and x._pid_type == r._pid_type # NOTE: modified can be a record or a list of records, if one # matches our record return the modified one. _modified = modified if isinstance(modified, list) else [modified] for mod_record in _modified: if is_modified(mod_record, record): return mod_record return record