aboutsummaryrefslogtreecommitdiffstats
path: root/python/fatcat/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/fatcat/models.py')
-rw-r--r--python/fatcat/models.py429
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()