class ThroughAwards(ShareObject): funder = ShareForeignKey(AbstractAgentWorkRelation) award = ShareForeignKey(Award) class Meta(ShareObject.Meta): unique_together = ('funder', 'award') verbose_name_plural = 'through awards' matching_criteria = MatchByManyToOne('funder', 'award')
class ThroughTags(ShareObject): tag = ShareForeignKey(Tag, related_name='work_relations') creative_work = ShareForeignKey('AbstractCreativeWork', related_name='tag_relations') class Meta: unique_together = ('tag', 'creative_work') class Disambiguation: all = ('tag', 'creative_work')
class Association(ShareObject): entity = ShareForeignKey('Entity') creative_work = ShareForeignKey(AbstractCreativeWork) class Meta: unique_together = ('entity', 'creative_work') def __str__(self): return str(self.entity)
class Subject(ShareObject): name = models.TextField() is_deleted = models.BooleanField(default=False) uri = ShareURLField(null=True, blank=True) taxonomy = models.ForeignKey(SubjectTaxonomy, editable=False, on_delete=models.CASCADE) parent = ShareForeignKey('Subject', blank=True, null=True, related_name='children') central_synonym = ShareForeignKey('Subject', blank=True, null=True, related_name='custom_synonyms') @classmethod def normalize(cls, node, graph): edge = node.related('central_synonym') if edge and edge.related and edge.related.id == node.id: graph.remove_edge(edge) def save(self, *args, **kwargs): if self.id is not None and self.parent is not None: new_lineage = self.parent.lineage() if self in new_lineage: raise CyclicalTaxonomyError( 'Making {} a child of {} would cause a cycle!'.format( self, self.parent)) return super().save(*args, **kwargs) def lineage(self): query = ''' WITH RECURSIVE lineage_chain(id, parent, depth, path, cycle) AS ( SELECT id, parent_id, 1, ARRAY[id], false FROM {table} WHERE id = %(id)s UNION SELECT {table}.id, {table}.parent_id, lineage_chain.depth + 1, path || {table}.id, {table}.id = ANY(path) FROM lineage_chain JOIN {table} ON lineage_chain.parent = {table}.id WHERE NOT cycle ) SELECT {table}.* FROM {table} INNER JOIN lineage_chain ON {table}.id = lineage_chain.id ORDER BY lineage_chain.depth DESC '''.format(table=self._meta.db_table) lineage = list( self._meta.model.objects.raw(query, params={'id': self.id})) if lineage[0].parent is not None: raise CyclicalTaxonomyError( 'Subject taxonomy cycle! {}'.format(lineage)) return lineage def __str__(self): return self.name class Meta: unique_together = (('name', 'taxonomy'), ('uri', 'taxonomy')) class Disambiguation: all = ('name', 'central_synonym')
class ThroughTags(ShareObject): tag = ShareForeignKey(Tag, related_name='work_relations') creative_work = ShareForeignKey('AbstractCreativeWork', related_name='tag_relations') class Meta(ShareObject.Meta): unique_together = ('tag', 'creative_work') verbose_name_plural = 'through tags' matching_criteria = MatchByManyToOne('tag', 'creative_work')
class ThroughAwards(ShareObject): funder = ShareForeignKey(AbstractAgentWorkRelation) award = ShareForeignKey(Award) class Meta: unique_together = ('funder', 'award') verbose_name_plural = 'through awards' class Disambiguation: all = ('funder', 'award')
class ThroughSubjects(ShareObject): subject = ShareForeignKey('Subject', related_name='work_relations') creative_work = ShareForeignKey('AbstractCreativeWork', related_name='subject_relations') is_deleted = models.BooleanField(default=False) class Meta(ShareObject.Meta): unique_together = ('subject', 'creative_work') verbose_name_plural = 'through subjects' matching_criteria = MatchByManyToOne('subject', 'creative_work')
class Affiliation(ShareObject): # start_date = models.DateField() # end_date = models.DateField() person = ShareForeignKey(Person) entity = ShareForeignKey('Entity') class Meta: unique_together = ('person', 'entity') def __str__(self): return '{} ({})'.format(self.person, self.entity)
class AbstractWorkRelation(ShareObject, metaclass=TypedShareObjectMeta): subject = ShareForeignKey('AbstractCreativeWork', related_name='outgoing_creative_work_relations') related = ShareForeignKey('AbstractCreativeWork', related_name='incoming_creative_work_relations') matching_criteria = MatchByManyToOne('subject', 'related', constrain_types=True) class Meta(ShareObject.Meta): db_table = 'share_workrelation' unique_together = ('subject', 'related', 'type')
class Contributor(ShareObject): cited_name = models.TextField(blank=True) bibliographic = models.BooleanField(default=True) order_cited = models.PositiveIntegerField(null=True) person = ShareForeignKey(Person) creative_work = ShareForeignKey('AbstractCreativeWork') def __str__(self): return '{} -> {}'.format(self.person, self.creative_work) class Meta: unique_together = ('person', 'creative_work')
class AbstractAgentWorkRelation(ShareObject, metaclass=TypedShareObjectMeta): creative_work = ShareForeignKey('AbstractCreativeWork', related_name='agent_relations') agent = ShareForeignKey('AbstractAgent', related_name='work_relations') cited_as = models.TextField(blank=True) matching_criteria = [ MatchByManyToOne('creative_work', 'agent', constrain_types=True), MatchAgentWorkRelations(), ] class Meta(ShareObject.Meta): db_table = 'share_agentworkrelation' unique_together = ('agent', 'creative_work', 'type')
class Contributor(ShareObject): cited_name = models.TextField(blank=True) order_cited = models.PositiveIntegerField(null=True) person = ShareForeignKey(Person) creative_work = ShareForeignKey('AbstractCreativeWork') def __str__(self): return '{} -> {}'.format(self.person, self.creative_work) class Meta: index_together = (( 'cited_name', 'order_cited', ))
class AgentIdentifier(ShareObject): """Unique identifier (in IRI form) for an agent.""" uri = ShareURLField(unique=True) host = models.TextField(editable=False) scheme = models.TextField(editable=False) agent = ShareForeignKey('AbstractAgent', related_name='identifiers') # objects = FilteredEmailsManager() # objects_unfiltered = models.Manager() @classmethod def normalize(self, node, graph): try: ret = IRILink().execute(node.attrs['uri']) except InvalidIRI as e: logger.warning('Discarding invalid identifier %s with error %s', node.attrs['uri'], e) graph.remove(node) return if node.attrs['uri'] != ret['IRI']: logger.debug('Normalized %s to %s', node.attrs['uri'], ret['IRI']) node.attrs = { 'uri': ret['IRI'], 'host': ret['authority'], 'scheme': ret['scheme'], } def __repr__(self): return '<{}({}, {})>'.format(self.__class__.__name__, self.uri, self.agent_id) class Disambiguation: all = ('uri', )
class AbstractCreativeWork(ShareObject, metaclass=TypedShareObjectMeta): title = models.TextField(blank=True) description = models.TextField(blank=True) contributors = ShareManyToManyField(Person, through='Contributor') awards = ShareManyToManyField(Award, through='ThroughAwards') venues = ShareManyToManyField(Venue, through='ThroughVenues') links = ShareManyToManyField('Link', through='ThroughLinks') funders = ShareManyToManyField('Funder', through='Association') publishers = ShareManyToManyField('Publisher', through='Association') institutions = ShareManyToManyField('Institution', through='Association') organizations = ShareManyToManyField('Organization', through='Association') subject = ShareForeignKey(Tag, related_name='subjected_%(class)s', null=True) # Note: Null allows inserting of None but returns it as an empty string tags = ShareManyToManyField(Tag, related_name='tagged_%(class)s', through='ThroughTags') date_created = models.DateTimeField(null=True, db_index=True) date_published = models.DateTimeField(null=True, db_index=True) date_updated = models.DateTimeField(null=True, db_index=True) free_to_read_type = ShareURLField(blank=True, db_index=True) free_to_read_date = models.DateTimeField(null=True, db_index=True) rights = models.TextField(blank=True, null=True, db_index=True) language = models.TextField(blank=True, null=True, db_index=True) def __str__(self): return self.title
class AbstractAgentRelation(ShareObject, metaclass=TypedShareObjectMeta): subject = ShareForeignKey('AbstractAgent', related_name='outgoing_agent_relations') related = ShareForeignKey('AbstractAgent', related_name='incoming_agent_relations') class Disambiguation: all = ('subject', 'related') constrain_types = True class Meta(ShareObject.Meta): db_table = 'share_agentrelation' unique_together = ('subject', 'related', 'type') @classmethod def normalize(self, node, graph): if len(node.related()) < 2: logger.warning('Removing incomplete or circular relation %s, %s', node, node.related()) graph.remove(node)
class ThroughSubjects(ShareObject): subject = models.ForeignKey('Subject', related_name='work_relations') creative_work = ShareForeignKey('AbstractCreativeWork', related_name='subject_relations') class Meta: unique_together = ('subject', 'creative_work') class Disambiguation: all = ('subject', 'creative_work')
class ThroughContributor(ShareObject): subject = ShareForeignKey(AbstractAgentWorkRelation, related_name='+') related = ShareForeignKey(AbstractAgentWorkRelation, related_name='+') def clean(self): if self.subject.creative_work != self.related.creative_work: raise ValidationError(_('ThroughContributors must contribute to the same AbstractCreativeWork')) if self.subject.agent == self.related.agent: raise ValidationError(_('A contributor may not contribute through itself')) def save(self, *args, **kwargs): self.clean() return super().save(*args, **kwargs) matching_criteria = MatchByManyToOne('subject', 'related') class Meta(ShareObject.Meta): unique_together = ('subject', 'related')
class AbstractAgentWorkRelation(ShareObject, metaclass=TypedShareObjectMeta): creative_work = ShareForeignKey('AbstractCreativeWork', related_name='agent_relations') agent = ShareForeignKey('AbstractAgent', related_name='work_relations') cited_as = models.TextField(blank=True) @classmethod def normalize(self, node, graph): for k, v in tuple(node.attrs.items()): if isinstance(v, str): node.attrs[k] = strip_whitespace(v) class Disambiguation: all = ('creative_work',) any = ('agent', 'cited_as') # TODO could be multiple people with the same cited_as on a work... could use order_cited for Creators, but what to do for other contributors? constrain_types = True class Meta: db_table = 'share_agentworkrelation' unique_together = ('agent', 'creative_work', 'type')
class AgentIdentifier(ShareObject): """Unique identifier (in IRI form) for an agent.""" uri = ShareURLField(unique=True) host = models.TextField(blank=True) scheme = models.TextField(blank=True) agent = ShareForeignKey('AbstractAgent', related_name='identifiers') # objects = FilteredEmailsManager() # objects_unfiltered = models.Manager() def __repr__(self): return '<{}({}, {})>'.format(self.__class__.__name__, self.uri, self.agent_id) matching_criteria = MatchByAttrs('uri')
class WorkIdentifier(ShareObject): """ Unique identifier (in IRI form) for a creative work. """ uri = ShareURLField(unique=True) host = models.TextField(editable=False) scheme = models.TextField( editable=False, help_text= _('A prefix to URI indicating how the following data should be interpreted.' )) creative_work = ShareForeignKey('AbstractCreativeWork', related_name='identifiers') # objects = FilteredEmailsManager() # objects_unfiltered = models.Manager() @classmethod def normalize(self, node, graph): try: ret = IRILink().execute(node.attrs['uri']) except InvalidIRI as e: logger.warning('Discarding invalid identifier %s with error %s', node.attrs['uri'], e) graph.remove(node) return if ret['authority'] in {'issn', 'orcid.org' } or ret['scheme'] in {'mailto'}: logger.warning( 'Discarding %s %s as an invalid identifier for works', ret['authority'], ret['IRI']) graph.remove(node) return if node.attrs['uri'] != ret['IRI']: logger.debug('Normalized %s to %s', node.attrs['uri'], ret['IRI']) node.attrs = { 'uri': ret['IRI'], 'host': ret['authority'], 'scheme': ret['scheme'], } def __repr__(self): return '<{}({}, {})>'.format(self.__class__.__name__, self.uri, self.creative_work_id) class Disambiguation: all = ('uri', )
class WorkIdentifier(ShareObject): """ Unique identifier (in IRI form) for a creative work. """ uri = ShareURLField(unique=True) host = models.TextField(blank=True) scheme = models.TextField(blank=True, help_text=_('A prefix to URI indicating how the following data should be interpreted.')) creative_work = ShareForeignKey('AbstractCreativeWork', related_name='identifiers') # objects = FilteredEmailsManager() # objects_unfiltered = models.Manager() def __repr__(self): return '<{}({}, {})>'.format(self.__class__.__name__, self.uri, self.creative_work_id) matching_criteria = MatchByAttrs('uri')
class ThroughLinks(ShareObject): link = ShareForeignKey(Link) creative_work = ShareForeignKey('AbstractCreativeWork') class Meta: unique_together = ('link', 'creative_work')
class ThroughIdentifiers(ShareObject): person = ShareForeignKey(Person) identifier = ShareForeignKey(Identifier) class Meta: unique_together = ('person', 'identifier')
class PersonEmail(ShareObject): email = ShareForeignKey(Email) person = ShareForeignKey(Person) class Meta: unique_together = ('email', 'person')
class ThroughAwardEntities(ShareObject): award = ShareForeignKey('Award') entity = ShareForeignKey('Entity') class Meta: unique_together = ('award', 'entity')
class ThroughSubjects(ShareObject): subject = models.ForeignKey('Subject') creative_work = ShareForeignKey('AbstractCreativeWork') class Meta: unique_together = ('subject', 'creative_work')
class ThroughVenues(ShareObject): venue = ShareForeignKey(Venue) creative_work = ShareForeignKey('AbstractCreativeWork') class Meta: unique_together = ('venue', 'creative_work')
class ThroughTags(ShareObject): tag = ShareForeignKey(Tag) creative_work = ShareForeignKey('AbstractCreativeWork') class Meta: unique_together = ('tag', 'creative_work')
class ThroughAwards(ShareObject): award = ShareForeignKey(Award) creative_work = ShareForeignKey('AbstractCreativeWork') class Meta: unique_together = ('award', 'creative_work')