Add m2m corpus-follower table

This commit is contained in:
Patrick Jentsch 2023-01-23 11:50:12 +01:00
parent 62bd21ad84
commit 97bba2498b
5 changed files with 123 additions and 141 deletions

View File

@ -3,6 +3,7 @@ from enum import Enum, IntEnum
from flask import current_app, url_for from flask import current_app, url_for
from flask_hashids import HashidMixin from flask_hashids import HashidMixin
from flask_login import UserMixin from flask_login import UserMixin
from sqlalchemy.ext.associationproxy import association_proxy
from time import sleep from time import sleep
from tqdm import tqdm from tqdm import tqdm
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
@ -169,7 +170,7 @@ class Role(HashidMixin, db.Model):
default = db.Column(db.Boolean, default=False, index=True) default = db.Column(db.Boolean, default=False, index=True)
permissions = db.Column(db.Integer, default=0) permissions = db.Column(db.Integer, default=0)
# Relationships # Relationships
users = db.relationship('User', backref='role', lazy='dynamic') users = db.relationship('User', back_populates='role', lazy='dynamic')
def __repr__(self): def __repr__(self):
return f'<Role {self.name}>' return f'<Role {self.name}>'
@ -239,7 +240,8 @@ class Token(db.Model):
access_expiration = db.Column(db.DateTime) access_expiration = db.Column(db.DateTime)
refresh_token = db.Column(db.String(64), index=True) refresh_token = db.Column(db.String(64), index=True)
refresh_expiration = db.Column(db.DateTime) refresh_expiration = db.Column(db.DateTime)
# Backrefs: user: User # Relationships
user = db.relationship('User', back_populates='tokens')
def expire(self): def expire(self):
self.access_expiration = datetime.utcnow() self.access_expiration = datetime.utcnow()
@ -258,7 +260,9 @@ class Avatar(HashidMixin, FileMixin, db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
# Foreign keys # Foreign keys
user_id = db.Column(db.Integer, db.ForeignKey('users.id')) user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
# Relationships
user = db.relationship('User', back_populates='avatar')
@property @property
def path(self): def path(self):
return os.path.join(self.user.path, 'avatar') return os.path.join(self.user.path, 'avatar')
@ -278,19 +282,22 @@ class Avatar(HashidMixin, FileMixin, db.Model):
return json_serializeable return json_serializeable
corpus_followers = db.Table( class CorpusFollowerAssociation(db.Model):
'corpus_followers', __tablename__ = 'corpus_follower_associations'
db.Model.metadata, # Primary key
db.Column('user_id', db.ForeignKey('users.id'), primary_key=True), id = db.Column(db.Integer, primary_key=True)
db.Column('corpus_id', db.ForeignKey('corpora.id'), primary_key=True) # Foreign keys
) following_user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
followed_corpus_id = db.Column(db.Integer, db.ForeignKey('corpora.id'))
# Fields
permissions = db.Column(db.Integer, default=0, nullable=False)
# Relationships
followed_corpus = db.relationship('Corpus', back_populates='following_user_associations')
following_user = db.relationship('User', back_populates='followed_corpus_associations')
def __repr__(self):
return f'<CorpusFollowerAssociation {self.following_user.__repr__()} ~ {self.followed_corpus.__repr__()}>'
user_followers = db.Table(
'user_followers',
db.Model.metadata,
db.Column('follower_user_id', db.ForeignKey('users.id'), primary_key=True),
db.Column('followed_user_id', db.ForeignKey('users.id'), primary_key=True)
)
class User(HashidMixin, UserMixin, db.Model): class User(HashidMixin, UserMixin, db.Model):
__tablename__ = 'users' __tablename__ = 'users'
@ -316,56 +323,53 @@ class User(HashidMixin, UserMixin, db.Model):
organization = db.Column(db.String(128)) organization = db.Column(db.String(128))
is_public = db.Column(db.Boolean, default=False) is_public = db.Column(db.Boolean, default=False)
profile_privacy_settings = db.Column(db.Integer(), default=0) profile_privacy_settings = db.Column(db.Integer(), default=0)
# Backrefs: role: Role
# Relationships # Relationships
avatar = db.relationship( avatar = db.relationship(
'Avatar', 'Avatar',
backref='user', back_populates='user',
cascade='all, delete-orphan', cascade='all, delete-orphan',
uselist=False uselist=False
) )
tesseract_ocr_pipeline_models = db.relationship(
'TesseractOCRPipelineModel',
backref='user',
cascade='all, delete-orphan',
lazy='dynamic'
)
spacy_nlp_pipeline_models = db.relationship(
'SpaCyNLPPipelineModel',
backref='user',
cascade='all, delete-orphan',
lazy='dynamic'
)
corpora = db.relationship( corpora = db.relationship(
'Corpus', 'Corpus',
backref='user', back_populates='user',
cascade='all, delete-orphan', cascade='all, delete-orphan',
lazy='dynamic' lazy='dynamic'
) )
followed_corpora = db.relationship( followed_corpus_associations = db.relationship(
'Corpus', 'CorpusFollowerAssociation',
secondary=corpus_followers, back_populates='following_user'
primaryjoin=(corpus_followers.c.user_id == id),
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
) )
followed_users = db.relationship( followed_corpora = association_proxy(
'User', 'followed_corpus_associations',
secondary=user_followers, 'followed_corpus',
primaryjoin=(user_followers.c.follower_user_id == id), creator=lambda c: CorpusFollowerAssociation(followed_corpus=c)
secondaryjoin=(user_followers.c.followed_user_id == id),
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
) )
jobs = db.relationship( jobs = db.relationship(
'Job', 'Job',
backref='user', back_populates='user',
cascade='all, delete-orphan',
lazy='dynamic'
)
role = db.relationship(
'Role',
back_populates='users'
)
spacy_nlp_pipeline_models = db.relationship(
'SpaCyNLPPipelineModel',
back_populates='user',
cascade='all, delete-orphan',
lazy='dynamic'
)
tesseract_ocr_pipeline_models = db.relationship(
'TesseractOCRPipelineModel',
back_populates='user',
cascade='all, delete-orphan', cascade='all, delete-orphan',
lazy='dynamic' lazy='dynamic'
) )
tokens = db.relationship( tokens = db.relationship(
'Token', 'Token',
backref='user', back_populates='user',
cascade='all, delete-orphan', cascade='all, delete-orphan',
lazy='dynamic' lazy='dynamic'
) )
@ -572,30 +576,6 @@ class User(HashidMixin, UserMixin, db.Model):
self.profile_privacy_settings = 0 self.profile_privacy_settings = 0
#endregion Profile Privacy settings #endregion Profile Privacy settings
def follow_corpus(self, corpus):
if not self.is_following_corpus(corpus):
self.followed_corpora.append(corpus)
def unfollow_corpus(self, corpus):
if self.is_following_corpus(corpus):
self.followed_corpora.remove(corpus)
def is_following_corpus(self, corpus):
return self.followed_corpora.filter(
corpus_followers.c.corpus_id == corpus.id).count() > 0
def follow_user(self, user):
if not self.is_following_user(user):
self.followed_users.append(user)
def unfollow_user(self, user):
if self.is_following_user(user):
self.followed_users.remove(user)
def is_following_user(self, user):
return self.followed_users.filter(
user_followers.c.followed_user_id == user.id).count() > 0
def to_json_serializeable(self, backrefs=False, relationships=False, filter_by_privacy_settings=False): def to_json_serializeable(self, backrefs=False, relationships=False, filter_by_privacy_settings=False):
json_serializeable = { json_serializeable = {
'id': self.hashid, 'id': self.hashid,
@ -670,7 +650,8 @@ class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model):
publishing_url = db.Column(db.String(512)) publishing_url = db.Column(db.String(512))
publishing_year = db.Column(db.Integer) publishing_year = db.Column(db.Integer)
is_public = db.Column(db.Boolean, default=False) is_public = db.Column(db.Boolean, default=False)
# Backrefs: user: User # Relationships
user = db.relationship('User', back_populates='tesseract_ocr_pipeline_models')
@property @property
def path(self): def path(self):
@ -794,7 +775,8 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model):
publishing_year = db.Column(db.Integer) publishing_year = db.Column(db.Integer)
pipeline_name = db.Column(db.String(64)) pipeline_name = db.Column(db.String(64))
is_public = db.Column(db.Boolean, default=False) is_public = db.Column(db.Boolean, default=False)
# Backrefs: user: User # Relationships
user = db.relationship('User', back_populates='spacy_nlp_pipeline_models')
@property @property
def path(self): def path(self):
@ -909,7 +891,11 @@ class JobInput(FileMixin, HashidMixin, db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
# Foreign keys # Foreign keys
job_id = db.Column(db.Integer, db.ForeignKey('jobs.id')) job_id = db.Column(db.Integer, db.ForeignKey('jobs.id'))
# Backrefs: job: Job # Relationships
job = db.relationship(
'Job',
back_populates='inputs'
)
def __repr__(self): def __repr__(self):
return f'<JobInput {self.filename}>' return f'<JobInput {self.filename}>'
@ -965,7 +951,11 @@ class JobResult(FileMixin, HashidMixin, db.Model):
job_id = db.Column(db.Integer, db.ForeignKey('jobs.id')) job_id = db.Column(db.Integer, db.ForeignKey('jobs.id'))
# Fields # Fields
description = db.Column(db.String(255)) description = db.Column(db.String(255))
# Backrefs: job: Job # Relationships
job = db.relationship(
'Job',
back_populates='results'
)
def __repr__(self): def __repr__(self):
return f'<JobResult {self.filename}>' return f'<JobResult {self.filename}>'
@ -1039,20 +1029,23 @@ class Job(HashidMixin, db.Model):
default=JobStatus.INITIALIZING default=JobStatus.INITIALIZING
) )
title = db.Column(db.String(32)) title = db.Column(db.String(32))
# Backrefs: user: User
# Relationships # Relationships
inputs = db.relationship( inputs = db.relationship(
'JobInput', 'JobInput',
backref='job', back_populates='job',
cascade='all, delete-orphan', cascade='all, delete-orphan',
lazy='dynamic' lazy='dynamic'
) )
results = db.relationship( results = db.relationship(
'JobResult', 'JobResult',
backref='job', back_populates='job',
cascade='all, delete-orphan', cascade='all, delete-orphan',
lazy='dynamic' lazy='dynamic'
) )
user = db.relationship(
'User',
back_populates='jobs'
)
def __repr__(self): def __repr__(self):
return f'<Job {self.title}>' return f'<Job {self.title}>'
@ -1173,7 +1166,11 @@ class CorpusFile(FileMixin, HashidMixin, db.Model):
pages = db.Column(db.String(255)) pages = db.Column(db.String(255))
publisher = db.Column(db.String(255)) publisher = db.Column(db.String(255))
school = db.Column(db.String(255)) school = db.Column(db.String(255))
# Backrefs: corpus: Corpus # Relationships
corpus = db.relationship(
'Corpus',
back_populates='files'
)
@property @property
def download_url(self): def download_url(self):
@ -1262,14 +1259,23 @@ class Corpus(HashidMixin, db.Model):
num_analysis_sessions = db.Column(db.Integer, default=0) num_analysis_sessions = db.Column(db.Integer, default=0)
num_tokens = db.Column(db.Integer, default=0) num_tokens = db.Column(db.Integer, default=0)
is_public = db.Column(db.Boolean, default=False) is_public = db.Column(db.Boolean, default=False)
# Backrefs: user: User
# Relationships # Relationships
files = db.relationship( files = db.relationship(
'CorpusFile', 'CorpusFile',
backref='corpus', back_populates='corpus',
lazy='dynamic', lazy='dynamic',
cascade='all, delete-orphan' cascade='all, delete-orphan'
) )
following_user_associations = db.relationship(
'CorpusFollowerAssociation',
back_populates='followed_corpus'
)
following_users = association_proxy(
'following_user_associations',
'following_user',
creator=lambda u: CorpusFollowerAssociation(following_user=u)
)
user = db.relationship('User', back_populates='corpora')
# "static" attributes # "static" attributes
max_num_tokens = 2_147_483_647 max_num_tokens = 2_147_483_647

View File

@ -1,31 +0,0 @@
"""Add corpus_followers table
Revision ID: 4aa88f253dab
Revises: 5b2a6e590166
Create Date: 2023-01-12 14:47:39.492875
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4aa88f253dab'
down_revision = '5b2a6e590166'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'corpus_followers',
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('corpus_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['corpus_id'], ['corpora.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('user_id', 'corpus_id')
)
def downgrade():
op.drop_table('corpus_followers')

View File

@ -0,0 +1,36 @@
"""empty message
Revision ID: 5fe6a6c7870c
Revises: 5b2a6e590166
Create Date: 2023-01-23 08:27:10.169454
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '5fe6a6c7870c'
down_revision = '5b2a6e590166'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('corpus_follower_associations',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('following_user_id', sa.Integer(), nullable=True),
sa.Column('followed_corpus_id', sa.Integer(), nullable=True),
sa.Column('permissions', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['followed_corpus_id'], ['corpora.id'], ),
sa.ForeignKeyConstraint(['following_user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('corpus_follower_associations')
# ### end Alembic commands ###

View File

@ -1,31 +0,0 @@
"""Add user_followers table
Revision ID: 7d51bc4b6079
Revises: 4aa88f253dab
Create Date: 2023-01-17 12:48:33.261942
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7d51bc4b6079'
down_revision = '4aa88f253dab'
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'user_followers',
sa.Column('follower_user_id', sa.Integer(), nullable=False),
sa.Column('followed_user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['followed_user_id'], ['users.id'], ),
sa.ForeignKeyConstraint(['follower_user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('follower_user_id', 'followed_user_id')
)
def downgrade():
op.drop_table('user_followers')

View File

@ -8,6 +8,7 @@ from app.models import (
Avatar, Avatar,
Corpus, Corpus,
CorpusFile, CorpusFile,
CorpusFollowerAssociation,
Job, Job,
JobInput, JobInput,
JobResult, JobResult,
@ -38,6 +39,7 @@ def make_shell_context() -> Dict[str, Any]:
'Avatar': Avatar, 'Avatar': Avatar,
'Corpus': Corpus, 'Corpus': Corpus,
'CorpusFile': CorpusFile, 'CorpusFile': CorpusFile,
'CorpusFollowerAssociation': CorpusFollowerAssociation,
'db': db, 'db': db,
'Job': Job, 'Job': Job,
'JobInput': JobInput, 'JobInput': JobInput,