""" 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()