diff options
Diffstat (limited to 'python/fatcat/models.py')
-rw-r--r-- | python/fatcat/models.py | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/python/fatcat/models.py b/python/fatcat/models.py new file mode 100644 index 00000000..c35e541f --- /dev/null +++ b/python/fatcat/models.py @@ -0,0 +1,429 @@ + +""" +states for identifiers: +- pre-live: points to a rev (during edit/accept period) +- live: points to a rev +- redirect: live, points to upstream rev, also points to redirect id + => if live and redirect non-null, all other fields copied from redirect target +- deleted: live, but doesn't point to a rev + +possible refactors: +- '_rev' instead of '_rev' +- use mixins for entities +""" + +import json +import hashlib +from marshmallow import post_dump, pre_load +from fatcat import db, ma + + +### Inter-Entity Relationships ############################################### + +class ReleaseContrib(db.Model): + __tablename__ = "release_contrib" + release_rev = db.Column(db.ForeignKey('release_rev.id'), nullable=False, primary_key=True) + creator_ident_id = db.Column(db.ForeignKey('creator_ident.id'), nullable=False, primary_key=True) + stub = db.Column(db.String, nullable=True) + type = db.Column(db.String, nullable=True) + # TODO: index (int)? + + creator = db.relationship("CreatorIdent") + release = db.relationship("ReleaseRev") + +class ReleaseRef(db.Model): + __tablename__ = "release_ref" + id = db.Column(db.Integer, primary_key=True, nullable=False) + release_rev = db.Column(db.ForeignKey('release_rev.id'), nullable=False) + target_release_ident_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) + index = db.Column(db.Integer, nullable=True) + stub = db.Column(db.String, nullable=True) + doi = db.Column(db.String, nullable=True) + + release = db.relationship("ReleaseRev") + target = db.relationship("ReleaseIdent") + +class FileRelease(db.Model): + __tablename__ = "file_release" + id = db.Column(db.Integer, primary_key=True, nullable=False) + file_rev= db.Column(db.ForeignKey('file_rev.id'), nullable=False) + release_ident_id = db.Column(db.ForeignKey('release_ident.id'), nullable=False) + + release = db.relationship("ReleaseIdent") + file = db.relationship("FileRev") + + +### Entities ################################################################# + +class WorkRev(db.Model): + __tablename__ = 'work_rev' + id = db.Column(db.Integer, primary_key=True) + extra_json = db.Column(db.String, nullable=True) + + title = db.Column(db.String) + work_type = db.Column(db.String) + primary_release_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) + primary_release = db.relationship('ReleaseIdent') + +class WorkIdent(db.Model): + """ + If rev_id is null, this was deleted. + If redirect_id is not null, this has been merged with the given id. In this + case rev_id is a "cached" copy of the redirect's rev_id, as + an optimization. If the merged work is "deleted", rev_id can be + null and redirect_id not-null. + """ + __tablename__ = 'work_ident' + id = db.Column(db.Integer, primary_key=True, nullable=False) + is_live = db.Column(db.Boolean, nullable=False, default=False) + rev_id = db.Column(db.ForeignKey('work_rev.id'), nullable=True) + redirect_id = db.Column(db.ForeignKey('work_ident.id'), nullable=True) + rev = db.relationship("WorkRev") + +class WorkEdit(db.Model): + __tablename__ = 'work_edit' + id = db.Column(db.Integer, primary_key=True) + ident_id = db.Column(db.ForeignKey('work_ident.id'), nullable=True) + rev_id = db.Column(db.ForeignKey('work_rev.id'), nullable=True) + redirect_id = db.Column(db.ForeignKey('work_ident.id'), nullable=True) + editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) + extra_json = db.Column(db.String, nullable=True) + ident = db.relationship("WorkIdent", foreign_keys="WorkEdit.ident_id") + rev = db.relationship("WorkRev") + editgroup = db.relationship("EditGroup") + + +class ReleaseRev(db.Model): + __tablename__ = 'release_rev' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + extra_json = db.Column(db.String, nullable=True) + + work_ident_id = db.Column(db.ForeignKey('work_ident.id', use_alter=True), nullable=True) # XXX: nullable=False + container_ident_id = db.Column(db.ForeignKey('container_ident.id'), nullable=True) + title = db.Column(db.String, nullable=False) + license = db.Column(db.String, nullable=True) # TODO: oa status foreign key + release_type = db.Column(db.String) # TODO: foreign key + date = db.Column(db.String, nullable=True) # TODO: datetime + doi = db.Column(db.String, nullable=True) # TODO: identifier table + volume = db.Column(db.String, nullable=True) + pages = db.Column(db.String, nullable=True) + issue = db.Column(db.String, nullable=True) + + work = db.relationship("WorkIdent", lazy='subquery', foreign_keys="ReleaseRev.work_ident_id") + container = db.relationship("ContainerIdent", lazy='subquery') + creators = db.relationship('ReleaseContrib', lazy='subquery') + refs = db.relationship('ReleaseRef', lazy='subquery') + +class ReleaseIdent(db.Model): + __tablename__ = 'release_ident' + id = db.Column(db.Integer, primary_key=True) + is_live = db.Column(db.Boolean, nullable=False, default=False) + rev_id = db.Column(db.ForeignKey('release_rev.id')) + redirect_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) + rev = db.relationship("ReleaseRev") + +class ReleaseEdit(db.Model): + __tablename__ = 'release_edit' + id = db.Column(db.Integer, primary_key=True) + ident_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) + rev_id = db.Column(db.ForeignKey('release_rev.id'), nullable=True) + redirect_id = db.Column(db.ForeignKey('release_ident.id'), nullable=True) + editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) + extra_json = db.Column(db.String, nullable=True) + ident = db.relationship("ReleaseIdent", foreign_keys="ReleaseEdit.ident_id") + rev = db.relationship("ReleaseRev") + editgroup = db.relationship("EditGroup") + + +class CreatorRev(db.Model): + __tablename__ = 'creator_rev' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + extra_json = db.Column(db.String, nullable=True) + + name = db.Column(db.String) + sortname = db.Column(db.String) + orcid = db.Column(db.String) # TODO: identifier table + +class CreatorIdent(db.Model): + __tablename__ = 'creator_ident' + id = db.Column(db.Integer, primary_key=True) + is_live = db.Column(db.Boolean, nullable=False, default=False) + rev_id = db.Column(db.ForeignKey('creator_rev.id')) + redirect_id = db.Column(db.ForeignKey('creator_ident.id'), nullable=True) + rev = db.relationship("CreatorRev") + +class CreatorEdit(db.Model): + __tablename__ = 'creator_edit' + id = db.Column(db.Integer, primary_key=True) + ident_id = db.Column(db.ForeignKey('creator_ident.id'), nullable=True) + rev_id = db.Column(db.ForeignKey('creator_rev.id'), nullable=True) + redirect_id = db.Column(db.ForeignKey('creator_ident.id'), nullable=True) + editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) + extra_json = db.Column(db.String, nullable=True) + ident = db.relationship("CreatorIdent", foreign_keys="CreatorEdit.ident_id") + rev = db.relationship("CreatorRev") + editgroup = db.relationship("EditGroup") + + +class ContainerRev(db.Model): + __tablename__ = 'container_rev' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + extra_json = db.Column(db.String, nullable=True) + + name = db.Column(db.String) + parent_id = db.Column(db.ForeignKey('container_ident.id', use_alter=True)) + publisher = db.Column(db.String) # TODO: foreign key + sortname = db.Column(db.String) + issn = db.Column(db.String) # TODO: identifier table + parent = db.relationship("ContainerIdent", foreign_keys="ContainerRev.parent_id") + +class ContainerIdent(db.Model): + __tablename__ = 'container_ident' + id = db.Column(db.Integer, primary_key=True) + is_live = db.Column(db.Boolean, nullable=False, default=False) + rev_id = db.Column(db.ForeignKey('container_rev.id')) + redirect_id = db.Column(db.ForeignKey('container_ident.id'), nullable=True) + rev = db.relationship("ContainerRev", foreign_keys="ContainerIdent.rev_id") + +class ContainerEdit(db.Model): + __tablename__ = 'container_edit' + id = db.Column(db.Integer, primary_key=True) + ident_id = db.Column(db.ForeignKey('container_ident.id'), nullable=True) + rev_id = db.Column(db.ForeignKey('container_rev.id'), nullable=True) + redirect_id = db.Column(db.ForeignKey('container_ident.id'), nullable=True) + editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) + extra_json = db.Column(db.String, nullable=True) + ident = db.relationship("ContainerIdent", foreign_keys="ContainerEdit.ident_id") + rev = db.relationship("ContainerRev") + editgroup = db.relationship("EditGroup") + + +class FileRev(db.Model): + __tablename__ = 'file_rev' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + extra_json = db.Column(db.String, nullable=True) + + size = db.Column(db.Integer) + sha1 = db.Column(db.String) # TODO: hash table... only or in addition? + url = db.Column(db.Integer) # TODO: URL table + releases = db.relationship('FileRelease', lazy='subquery') + +class FileIdent(db.Model): + __tablename__ = 'file_ident' + id = db.Column(db.Integer, primary_key=True) + is_live = db.Column(db.Boolean, nullable=False, default=False) + rev_id = db.Column(db.ForeignKey('file_rev.id')) + redirect_id = db.Column(db.ForeignKey('file_ident.id'), nullable=True) + rev = db.relationship("FileRev") + +class FileEdit(db.Model): + __tablename__ = 'file_edit' + id = db.Column(db.Integer, primary_key=True) + ident_id = db.Column(db.ForeignKey('file_ident.id'), nullable=True) + rev_id = db.Column(db.ForeignKey('file_rev.id'), nullable=True) + redirect_id = db.Column(db.ForeignKey('file_ident.id'), nullable=True) + editgroup_id = db.Column(db.ForeignKey('editgroup.id'), nullable=True) + extra_json = db.Column(db.String, nullable=True) + ident = db.relationship("FileIdent", foreign_keys="FileEdit.ident_id") + rev = db.relationship("FileRev") + editgroup = db.relationship("EditGroup") + + +### Editing ################################################################# + +class EditGroup(db.Model): + __tablename__ = 'editgroup' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + editor_id = db.Column(db.ForeignKey('editor.id'), nullable=False) + description = db.Column(db.String) + extra_json = db.Column(db.String, nullable=True) + + editor = db.relationship("Editor", foreign_keys="EditGroup.editor_id") + +class Editor(db.Model): + __tablename__ = 'editor' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + username = db.Column(db.String, nullable=False, unique=True) + is_admin = db.Column(db.Boolean, nullable=False, default=False) + active_editgroup_id = db.Column(db.ForeignKey('editgroup.id', use_alter=True)) + active_editgroup = db.relationship('EditGroup', foreign_keys='Editor.active_editgroup_id') + +class ChangelogEntry(db.Model): + __tablename__= 'changelog' + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + editgroup_id = db.Column(db.ForeignKey('editgroup.id')) + timestamp = db.Column(db.Integer) + editgroup = db.relationship("EditGroup") + + +### Marshmallow Wrappers #################################################### + +class ExtraJsonSchema(ma.ModelSchema): + + @post_dump(pass_many=False) + def json_unflatten(self, data): + extra = data.pop('extra_json', None) + if extra != None: + extra = json.loads(extra) + data['extra'] = extra + + @pre_load(pass_many=False) + def json_flatten(self, data): + extra = data.pop('extra', None) + if extra != None: + extra = json.dumps(extra) + data['extra_json'] = extra + +class EntitySchema(ExtraJsonSchema): + + @post_dump(pass_many=False) + def merge_rev(self, data): + if data.get('rev', None) != None: + rev_id = data['rev'].pop('id') + data.update(data['rev']) + data['rev'] = rev_id + else: + data['rev'] = None + +class ReleaseContribSchema(ma.ModelSchema): + class Meta: + model = ReleaseContrib + creator = db.relationship("CreatorIdent") + release = db.relationship("ReleaseRev") + +class ReleaseRefSchema(ma.ModelSchema): + class Meta: + model = ReleaseRef + release = db.relationship("ReleaseRev") + target = db.relationship("ReleaseIdent") + +class FileReleaseSchema(ma.ModelSchema): + class Meta: + model = FileRelease + release = db.relationship("ReleaseIdent") + file = db.relationship("FileRev") + +class WorkRevSchema(ma.ModelSchema): + class Meta: + model = WorkRev + include_fk = True + +class WorkSchema(EntitySchema): + class Meta: + model = WorkIdent + include_fk = True + rev = ma.Nested(WorkRevSchema) + +class WorkEditSchema(ma.ModelSchema): + class Meta: + model = WorkEdit + +work_rev_schema = WorkRevSchema() +work_schema = WorkSchema() +work_edit_schema = WorkEditSchema() + + +class ReleaseRevSchema(ma.ModelSchema): + class Meta: + model = ReleaseRev + include_fk = True + work = ma.Nested('WorkSchema') + container = ma.Nested('ContainerSchema') + creators = ma.Nested(ReleaseContribSchema, many=True) + refs = ma.Nested(ReleaseRefSchema, many=True) + +class ReleaseSchema(EntitySchema): + class Meta: + model = ReleaseIdent + include_fk = True + rev = ma.Nested(ReleaseRevSchema) + # XXX: files = ma.Nested('FileSchema', many=True) + +class ReleaseEditSchema(ma.ModelSchema): + class Meta: + model = ReleaseEdit + +release_rev_schema = ReleaseRevSchema() +release_schema = ReleaseSchema() +release_edit_schema = ReleaseEditSchema() + + +class CreatorRevSchema(ma.ModelSchema): + class Meta: + model = CreatorRev + include_fk = True + +class CreatorSchema(EntitySchema): + class Meta: + model = CreatorIdent + include_fk = True + rev = ma.Nested(CreatorRevSchema) + +class CreatorEditSchema(ma.ModelSchema): + class Meta: + model = CreatorEdit + +creator_rev_schema = CreatorRevSchema() +creator_schema = CreatorSchema() +creator_edit_schema = CreatorEditSchema() + + +class ContainerRevSchema(ma.ModelSchema): + class Meta: + model = ContainerRev + include_fk = True + +class ContainerSchema(EntitySchema): + class Meta: + model = ContainerIdent + include_fk = True + rev = ma.Nested(ContainerRevSchema) + +class ContainerEditSchema(ma.ModelSchema): + class Meta: + model = ContainerEdit + +container_rev_schema = ContainerRevSchema() +container_schema = ContainerSchema() +container_edit_schema = ContainerEditSchema() + + +class FileRevSchema(ma.ModelSchema): + class Meta: + model = FileRev + include_fk = True + + releases = ma.Nested(FileReleaseSchema, many=True) + +class FileSchema(EntitySchema): + class Meta: + model = FileIdent + include_fk = True + rev = ma.Nested(FileRevSchema) + +class FileEditSchema(ma.ModelSchema): + class Meta: + model = FileEdit + +file_rev_schema = FileRevSchema() +file_schema = FileSchema() +file_edit_schema = FileEditSchema() + + +class EditorSchema(ma.ModelSchema): + class Meta: + model = Editor + +class EditGroupSchema(ma.ModelSchema): + class Meta: + model = EditGroup + editor = ma.Nested(EditorSchema) + +editor_schema = EditorSchema() +editgroup_schema = EditGroupSchema() + +class ChangelogEntrySchema(ma.ModelSchema): + class Meta: + model = ChangelogEntry + +changelogentry_schema = ChangelogEntrySchema() |