From 97bba2498bc65ab01c9102acd634c45b4f0dbd1c Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Mon, 23 Jan 2023 11:50:12 +0100 Subject: [PATCH] Add m2m corpus-follower table --- app/models.py | 164 ++++++++++++++------------- migrations/versions/4aa88f253dab_.py | 31 ----- migrations/versions/5fe6a6c7870c_.py | 36 ++++++ migrations/versions/7d51bc4b6079_.py | 31 ----- nopaque.py | 2 + 5 files changed, 123 insertions(+), 141 deletions(-) delete mode 100644 migrations/versions/4aa88f253dab_.py create mode 100644 migrations/versions/5fe6a6c7870c_.py delete mode 100644 migrations/versions/7d51bc4b6079_.py diff --git a/app/models.py b/app/models.py index 4281fc55..af3d1cfc 100644 --- a/app/models.py +++ b/app/models.py @@ -3,6 +3,7 @@ from enum import Enum, IntEnum from flask import current_app, url_for from flask_hashids import HashidMixin from flask_login import UserMixin +from sqlalchemy.ext.associationproxy import association_proxy from time import sleep from tqdm import tqdm 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) permissions = db.Column(db.Integer, default=0) # Relationships - users = db.relationship('User', backref='role', lazy='dynamic') + users = db.relationship('User', back_populates='role', lazy='dynamic') def __repr__(self): return f'' @@ -239,7 +240,8 @@ class Token(db.Model): access_expiration = db.Column(db.DateTime) refresh_token = db.Column(db.String(64), index=True) refresh_expiration = db.Column(db.DateTime) - # Backrefs: user: User + # Relationships + user = db.relationship('User', back_populates='tokens') def expire(self): self.access_expiration = datetime.utcnow() @@ -258,7 +260,9 @@ class Avatar(HashidMixin, FileMixin, db.Model): id = db.Column(db.Integer, primary_key=True) # Foreign keys user_id = db.Column(db.Integer, db.ForeignKey('users.id')) - + # Relationships + user = db.relationship('User', back_populates='avatar') + @property def path(self): return os.path.join(self.user.path, 'avatar') @@ -278,19 +282,22 @@ class Avatar(HashidMixin, FileMixin, db.Model): return json_serializeable -corpus_followers = db.Table( - 'corpus_followers', - db.Model.metadata, - db.Column('user_id', db.ForeignKey('users.id'), primary_key=True), - db.Column('corpus_id', db.ForeignKey('corpora.id'), primary_key=True) -) +class CorpusFollowerAssociation(db.Model): + __tablename__ = 'corpus_follower_associations' + # Primary key + id = db.Column(db.Integer, 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'' -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): __tablename__ = 'users' @@ -316,56 +323,53 @@ class User(HashidMixin, UserMixin, db.Model): organization = db.Column(db.String(128)) is_public = db.Column(db.Boolean, default=False) profile_privacy_settings = db.Column(db.Integer(), default=0) - # Backrefs: role: Role # Relationships avatar = db.relationship( 'Avatar', - backref='user', + back_populates='user', cascade='all, delete-orphan', 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( 'Corpus', - backref='user', + back_populates='user', cascade='all, delete-orphan', lazy='dynamic' ) - followed_corpora = db.relationship( - 'Corpus', - secondary=corpus_followers, - primaryjoin=(corpus_followers.c.user_id == id), - backref=db.backref('followers', lazy='dynamic'), - lazy='dynamic' + followed_corpus_associations = db.relationship( + 'CorpusFollowerAssociation', + back_populates='following_user' ) - followed_users = db.relationship( - 'User', - secondary=user_followers, - primaryjoin=(user_followers.c.follower_user_id == id), - secondaryjoin=(user_followers.c.followed_user_id == id), - backref=db.backref('followers', lazy='dynamic'), - lazy='dynamic' + followed_corpora = association_proxy( + 'followed_corpus_associations', + 'followed_corpus', + creator=lambda c: CorpusFollowerAssociation(followed_corpus=c) ) jobs = db.relationship( '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', lazy='dynamic' ) tokens = db.relationship( 'Token', - backref='user', + back_populates='user', cascade='all, delete-orphan', lazy='dynamic' ) @@ -572,30 +576,6 @@ class User(HashidMixin, UserMixin, db.Model): self.profile_privacy_settings = 0 #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): json_serializeable = { 'id': self.hashid, @@ -670,7 +650,8 @@ class TesseractOCRPipelineModel(FileMixin, HashidMixin, db.Model): publishing_url = db.Column(db.String(512)) publishing_year = db.Column(db.Integer) is_public = db.Column(db.Boolean, default=False) - # Backrefs: user: User + # Relationships + user = db.relationship('User', back_populates='tesseract_ocr_pipeline_models') @property def path(self): @@ -794,7 +775,8 @@ class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model): publishing_year = db.Column(db.Integer) pipeline_name = db.Column(db.String(64)) is_public = db.Column(db.Boolean, default=False) - # Backrefs: user: User + # Relationships + user = db.relationship('User', back_populates='spacy_nlp_pipeline_models') @property def path(self): @@ -909,7 +891,11 @@ class JobInput(FileMixin, HashidMixin, db.Model): id = db.Column(db.Integer, primary_key=True) # Foreign keys job_id = db.Column(db.Integer, db.ForeignKey('jobs.id')) - # Backrefs: job: Job + # Relationships + job = db.relationship( + 'Job', + back_populates='inputs' + ) def __repr__(self): return f'' @@ -965,7 +951,11 @@ class JobResult(FileMixin, HashidMixin, db.Model): job_id = db.Column(db.Integer, db.ForeignKey('jobs.id')) # Fields description = db.Column(db.String(255)) - # Backrefs: job: Job + # Relationships + job = db.relationship( + 'Job', + back_populates='results' + ) def __repr__(self): return f'' @@ -1039,20 +1029,23 @@ class Job(HashidMixin, db.Model): default=JobStatus.INITIALIZING ) title = db.Column(db.String(32)) - # Backrefs: user: User # Relationships inputs = db.relationship( 'JobInput', - backref='job', + back_populates='job', cascade='all, delete-orphan', lazy='dynamic' ) results = db.relationship( 'JobResult', - backref='job', + back_populates='job', cascade='all, delete-orphan', lazy='dynamic' ) + user = db.relationship( + 'User', + back_populates='jobs' + ) def __repr__(self): return f'' @@ -1173,7 +1166,11 @@ class CorpusFile(FileMixin, HashidMixin, db.Model): pages = db.Column(db.String(255)) publisher = db.Column(db.String(255)) school = db.Column(db.String(255)) - # Backrefs: corpus: Corpus + # Relationships + corpus = db.relationship( + 'Corpus', + back_populates='files' + ) @property def download_url(self): @@ -1262,14 +1259,23 @@ class Corpus(HashidMixin, db.Model): num_analysis_sessions = db.Column(db.Integer, default=0) num_tokens = db.Column(db.Integer, default=0) is_public = db.Column(db.Boolean, default=False) - # Backrefs: user: User # Relationships files = db.relationship( 'CorpusFile', - backref='corpus', + back_populates='corpus', lazy='dynamic', 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 max_num_tokens = 2_147_483_647 diff --git a/migrations/versions/4aa88f253dab_.py b/migrations/versions/4aa88f253dab_.py deleted file mode 100644 index 2351532c..00000000 --- a/migrations/versions/4aa88f253dab_.py +++ /dev/null @@ -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') diff --git a/migrations/versions/5fe6a6c7870c_.py b/migrations/versions/5fe6a6c7870c_.py new file mode 100644 index 00000000..2bc02206 --- /dev/null +++ b/migrations/versions/5fe6a6c7870c_.py @@ -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 ### diff --git a/migrations/versions/7d51bc4b6079_.py b/migrations/versions/7d51bc4b6079_.py deleted file mode 100644 index df825ad1..00000000 --- a/migrations/versions/7d51bc4b6079_.py +++ /dev/null @@ -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') diff --git a/nopaque.py b/nopaque.py index 6ecb2cf0..602954cc 100644 --- a/nopaque.py +++ b/nopaque.py @@ -8,6 +8,7 @@ from app.models import ( Avatar, Corpus, CorpusFile, + CorpusFollowerAssociation, Job, JobInput, JobResult, @@ -38,6 +39,7 @@ def make_shell_context() -> Dict[str, Any]: 'Avatar': Avatar, 'Corpus': Corpus, 'CorpusFile': CorpusFile, + 'CorpusFollowerAssociation': CorpusFollowerAssociation, 'db': db, 'Job': Job, 'JobInput': JobInput,