Merge pull request #3 from Simple2B/svyat/db_models

Svyat/db models
This commit is contained in:
Svyatoslav Artymovych 2023-04-21 17:28:02 +03:00 committed by GitHub
commit 87a2670c82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1085 additions and 119 deletions

View File

@ -1,4 +1,3 @@
import click
from flask import Flask from flask import Flask
from app import models as m from app import models as m
from app import db, forms from app import db, forms
@ -16,23 +15,23 @@ def init(app: Flask):
if app.config["ENV"] != "production": if app.config["ENV"] != "production":
@app.cli.command() @app.cli.command()
@click.option("--count", default=100, type=int) def db_populate():
def db_populate(count: int):
"""Fill DB by dummy data.""" """Fill DB by dummy data."""
from tests.db import populate from tests.db.create_dummy_data import create_dummy_data
populate(count) create_dummy_data()
print(f"DB populated by {count} instancies") print("Dummy data added")
@app.cli.command("create-admin") @app.cli.command("create-admin")
def create_admin(): def create_admin():
"""Create super admin account""" """Create super admin account"""
if m.User.query.filter_by(email=app.config["ADMIN_EMAIL"]).first(): if m.User.query.filter_by(username=app.config["ADMIN_USERNAME"]).first():
print(f"User with e-mail: [{app.config['ADMIN_EMAIL']}] already exists") print(
f"User with username: [{app.config['ADMIN_USERNAME']}] already exists"
)
return return
m.User( m.User(
username=app.config["ADMIN_USERNAME"], username=app.config["ADMIN_USERNAME"],
email=app.config["ADMIN_EMAIL"],
password=app.config["ADMIN_PASSWORD"], password=app.config["ADMIN_PASSWORD"],
).save() ).save()
print("admin created") print("admin created")

View File

@ -6,7 +6,7 @@ from wtforms import (
ValidationError, ValidationError,
BooleanField, BooleanField,
) )
from wtforms.validators import DataRequired, Email, Length, EqualTo from wtforms.validators import DataRequired, Length, EqualTo
from app import models as m from app import models as m
@ -14,7 +14,6 @@ from app import models as m
class UserForm(FlaskForm): class UserForm(FlaskForm):
next_url = StringField("next_url") next_url = StringField("next_url")
user_id = StringField("user_id", [DataRequired()]) user_id = StringField("user_id", [DataRequired()])
email = StringField("email", [DataRequired(), Email()])
activated = BooleanField("activated") activated = BooleanField("activated")
username = StringField("Username", [DataRequired()]) username = StringField("Username", [DataRequired()])
password = PasswordField("Password", validators=[DataRequired(), Length(6, 30)]) password = PasswordField("Password", validators=[DataRequired(), Length(6, 30)])
@ -36,18 +35,8 @@ class UserForm(FlaskForm):
): ):
raise ValidationError("This username is taken.") raise ValidationError("This username is taken.")
def validate_email(self, field):
if (
m.User.query.filter_by(email=field.data)
.filter(m.User.id != int(self.user_id.data))
.first()
is not None
):
raise ValidationError("This email is already registered.")
class NewUserForm(FlaskForm): class NewUserForm(FlaskForm):
email = StringField("email", [DataRequired(), Email()])
activated = BooleanField("activated") activated = BooleanField("activated")
username = StringField("Username", [DataRequired()]) username = StringField("Username", [DataRequired()])
password = PasswordField("Password", validators=[DataRequired(), Length(6, 30)]) password = PasswordField("Password", validators=[DataRequired(), Length(6, 30)])
@ -63,7 +52,3 @@ class NewUserForm(FlaskForm):
def validate_username(self, field): def validate_username(self, field):
if m.User.query.filter_by(username=field.data).first() is not None: if m.User.query.filter_by(username=field.data).first() is not None:
raise ValidationError("This username is taken.") raise ValidationError("This username is taken.")
def validate_email(self, field):
if m.User.query.filter_by(email=field.data).first() is not None:
raise ValidationError("This email is already registered.")

View File

@ -1,2 +1,15 @@
# flake8: noqa F401 # flake8: noqa F401
from .user import User, AnonymousUser, gen_password_reset_id from .user import User, AnonymousUser, gen_password_reset_id
from .book import Book
from .books_stars import BookStar
from .book_contributor import BookContributor
from .book_version import BookVersion
from .collection import Collection
from .section import Section
from .interpretation import Interpretation
from .comment import Comment
from .comment_vote import CommentVote
from .interpretation_vote import InterpretationVote
from .tag import Tag
from .interpretation_tag import InterpretationTag
from .comment_tag import CommentTags

20
app/models/book.py Normal file
View File

@ -0,0 +1,20 @@
from app import db
from app.models.utils import BaseModel
class Book(BaseModel):
__tablename__ = "books"
label = db.Column(db.String(1024), unique=False, nullable=False)
# Foreign keys
user_id = db.Column(db.ForeignKey("users.id"))
# Relationships
owner = db.relationship("User", viewonly=True)
stars = db.relationship("User", secondary="books_stars", back_populates="stars")
contributors = db.relationship("BookContributor")
versions = db.relationship("BookVersion")
def __repr__(self):
return f"<{self.id}: {self.label}>"

View File

@ -0,0 +1,26 @@
from enum import IntEnum
from app import db
from app.models.utils import BaseModel
class BookContributor(BaseModel):
__tablename__ = "book_contributors"
class Roles(IntEnum):
UNKNOWN = 10
MODERATOR = 1
EDITOR = 2
role = db.Column(db.Enum(Roles), default=Roles.MODERATOR)
# Foreign keys
user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
book_id = db.Column(db.Integer, db.ForeignKey("books.id"))
# Relationships
user = db.relationship("User", viewonly=True)
book = db.relationship("Book", viewonly=True)
def __repr__(self):
return f"<{self.id}: {self.label}>"

View File

@ -0,0 +1,26 @@
from datetime import datetime
from app import db
from app.models.utils import BaseModel
class BookVersion(BaseModel):
__tablename__ = "book_versions"
# need to redeclare id to use it in the derivative relationship
id = db.Column(db.Integer, primary_key=True)
semver = db.Column(db.String(1024), unique=False, nullable=False)
exported = db.Column(db.Boolean, default=False)
updated_at = db.Column(db.DateTime, default=datetime.now)
# Foreign keys
derivative_id = db.Column(db.Integer, db.ForeignKey("book_versions.id"))
book_id = db.Column(db.Integer, db.ForeignKey("books.id"))
# Relationships
book = db.relationship("Book", viewonly=True)
derivative = db.relationship("BookVersion", remote_side=[id])
sections = db.relationship("Section", viewonly=True)
def __repr__(self):
return f"<{self.id}: {self.semver}>"

17
app/models/books_stars.py Normal file
View File

@ -0,0 +1,17 @@
from app import db
from app.models.utils import BaseModel
class BookStar(BaseModel):
__tablename__ = "books_stars"
# Foreign keys
user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
book_id = db.Column(db.Integer, db.ForeignKey("books.id"))
# Relationships
user = db.relationship("User", viewonly=True)
book = db.relationship("Book", viewonly=True)
def __repr__(self):
return f"<{self.user} to {self.book}>"

28
app/models/collection.py Normal file
View File

@ -0,0 +1,28 @@
from app import db
from app.models.utils import BaseModel
class Collection(BaseModel):
__tablename__ = "collections"
# need to redeclare id to use it in the parrent relationship
id = db.Column(db.Integer, primary_key=True)
label = db.Column(db.String(1024), unique=False, nullable=False)
about = db.Column(db.Text, unique=False, nullable=False)
is_root = db.Column(db.Boolean, default=False)
is_leaf = db.Column(db.Boolean, default=False)
# Foreign keys
version_id = db.Column(db.ForeignKey("book_versions.id"))
parrent_id = db.Column(db.ForeignKey("collections.id"))
# Relationships
version = db.relationship("BookVersion")
parrent = db.relationship("Collection", remote_side=[id])
children = db.relationship(
"Collection", backref=db.backref("parent", remote_side=[id]), viewonly=True
)
sections = db.relationship("Section")
def __repr__(self):
return f"<{self.id}: {self.label}>"

34
app/models/comment.py Normal file
View File

@ -0,0 +1,34 @@
from app import db
from app.models.utils import BaseModel
class Comment(BaseModel):
__tablename__ = "comments"
# need to redeclare id to use it in the parrent relationship
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.Text, unique=False, nullable=False)
marked = db.Column(db.Boolean, default=False)
included_with_interpreation = db.Column(db.Boolean, default=False)
# Foreign keys
user_id = db.Column(db.ForeignKey("users.id"))
parrent_id = db.Column(db.ForeignKey("comments.id"))
interpretation_id = db.Column(db.ForeignKey("interpretations.id"))
# Relationships
user = db.relationship("User")
parrent = db.relationship("Comment", remote_side=[id])
children = db.relationship(
"Comment", backref=db.backref("parent", remote_side=[id]), viewonly=True
)
interpretation = db.relationship("Interpretation")
votes = db.relationship("CommentVote")
tags = db.relationship(
"Tag",
secondary="comment_tags",
back_populates="comments",
)
def __repr__(self):
return f"<{self.id}: {self.text[:20]}>"

13
app/models/comment_tag.py Normal file
View File

@ -0,0 +1,13 @@
from app import db
from app.models.utils import BaseModel
class CommentTags(BaseModel):
__tablename__ = "comment_tags"
# Foreign keys
tag_id = db.Column(db.Integer, db.ForeignKey("tags.id"))
comment_id = db.Column(db.Integer, db.ForeignKey("comments.id"))
def __repr__(self):
return f"<t:{self.tag} to c:{self.comment}"

View File

@ -0,0 +1,18 @@
from app import db
from app.models.utils import BaseModel
class CommentVote(BaseModel):
__tablename__ = "comment_votes"
# Foreign keys
user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
comment_id = db.Column(db.Integer, db.ForeignKey("comments.id"))
possitive = db.Column(db.Boolean, default=True)
# Relationships
user = db.relationship("User", viewonly=True)
comment = db.relationship("Comment", viewonly=True)
def __repr__(self):
return f"<{self.user} to {self.comment} Positive:{self.possitive}>"

View File

@ -0,0 +1,28 @@
from app import db
from app.models.utils import BaseModel
class Interpretation(BaseModel):
__tablename__ = "interpretations"
label = db.Column(db.String(1024), unique=False, nullable=False)
text = db.Column(db.Text, unique=False, nullable=False)
marked = db.Column(db.Boolean, default=False)
# Foreign keys
user_id = db.Column(db.ForeignKey("users.id"))
section_id = db.Column(db.ForeignKey("sections.id"))
# Relationships
user = db.relationship("User")
section = db.relationship("Section")
comments = db.relationship("Comment", viewonly=True)
votes = db.relationship("InterpretationVote", viewonly=True)
tags = db.relationship(
"Tag",
secondary="interpretation_tags",
back_populates="interpretations",
)
def __repr__(self):
return f"<{self.id}: {self.label}>"

View File

@ -0,0 +1,13 @@
from app import db
from app.models.utils import BaseModel
class InterpretationTag(BaseModel):
__tablename__ = "interpretation_tags"
# Foreign keys
tag_id = db.Column(db.Integer, db.ForeignKey("tags.id"))
interpretation_id = db.Column(db.Integer, db.ForeignKey("interpretations.id"))
def __repr__(self):
return f"<t:{self.tag_id} to i:{self.interpretation_id}"

View File

@ -0,0 +1,18 @@
from app import db
from app.models.utils import BaseModel
class InterpretationVote(BaseModel):
__tablename__ = "interpretation_votes"
# Foreign keys
user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
interpretation_id = db.Column(db.Integer, db.ForeignKey("interpretations.id"))
possitive = db.Column(db.Boolean, default=True)
# Relationships
user = db.relationship("User", viewonly=True)
interpretation = db.relationship("Interpretation", viewonly=True)
def __repr__(self):
return f"<{self.user} to {self.interpretation} Positive:{self.possitive}>"

24
app/models/section.py Normal file
View File

@ -0,0 +1,24 @@
from app import db
from app.models.utils import BaseModel
class Section(BaseModel):
__tablename__ = "sections"
label = db.Column(db.String(1024), unique=False, nullable=False)
about = db.Column(db.Text, unique=False, nullable=True)
# Foreign keys
collection_id = db.Column(db.ForeignKey("collections.id"))
user_id = db.Column(db.ForeignKey("users.id"))
version_id = db.Column(db.ForeignKey("book_versions.id"))
selected_interpretation_id = db.Column(db.Integer, nullable=True)
# Relationships
collection = db.relationship("Collection", viewonly=True)
user = db.relationship("User", viewonly=True)
version = db.relationship("BookVersion", viewonly=True)
interpretations = db.relationship("Interpretation", viewonly=True)
def __repr__(self):
return f"<{self.id}: {self.label}>"

19
app/models/tag.py Normal file
View File

@ -0,0 +1,19 @@
from app import db
from app.models.utils import BaseModel
class Tag(BaseModel):
__tablename__ = "tags"
name = db.Column(db.String(32), unique=True, nullable=False)
# Relationships
interpretations = db.relationship(
"Interpretation", secondary="interpretation_tags", back_populates="tags"
)
comments = db.relationship(
"Comment", secondary="comment_tags", back_populates="tags"
)
def __repr__(self):
return f"<{self.id}: {self.name}>"

View File

@ -1,4 +1,3 @@
from datetime import datetime
from uuid import uuid4 from uuid import uuid4
from flask_login import UserMixin, AnonymousUserMixin from flask_login import UserMixin, AnonymousUserMixin
@ -7,7 +6,7 @@ from sqlalchemy.ext.hybrid import hybrid_property
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from app import db from app import db
from app.models.utils import ModelMixin from app.models.utils import BaseModel
from app.logger import log from app.logger import log
from app import schema as s from app import schema as s
@ -16,18 +15,15 @@ def gen_password_reset_id() -> str:
return str(uuid4()) return str(uuid4())
class User(db.Model, UserMixin, ModelMixin): class User(BaseModel, UserMixin):
__tablename__ = "users" __tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(60), unique=True, nullable=False) username = db.Column(db.String(60), unique=True, nullable=False)
email = db.Column(db.String(255), unique=True, nullable=False)
password_hash = db.Column(db.String(255), default="") password_hash = db.Column(db.String(255), default="")
activated = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.now) # Relationships
unique_id = db.Column(db.String(36), default=gen_password_reset_id) stars = db.relationship("Book", secondary="books_stars", back_populates="stars")
reset_password_uid = db.Column(db.String(64), default=gen_password_reset_id) books = db.relationship("Book")
@hybrid_property @hybrid_property
def password(self): def password(self):
@ -40,10 +36,7 @@ class User(db.Model, UserMixin, ModelMixin):
@classmethod @classmethod
def authenticate(cls, user_id, password): def authenticate(cls, user_id, password):
user = cls.query.filter( user = cls.query.filter(
db.or_(
func.lower(cls.username) == func.lower(user_id), func.lower(cls.username) == func.lower(user_id),
func.lower(cls.email) == func.lower(user_id),
)
).first() ).first()
if not user: if not user:
log(log.WARNING, "user:[%s] not found", user_id) log(log.WARNING, "user:[%s] not found", user_id)
@ -51,13 +44,8 @@ class User(db.Model, UserMixin, ModelMixin):
if user is not None and check_password_hash(user.password, password): if user is not None and check_password_hash(user.password, password):
return user return user
def reset_password(self):
self.password_hash = ""
self.reset_password_uid = gen_password_reset_id()
self.save()
def __repr__(self): def __repr__(self):
return f"<{self.id}: {self.username},{self.email}>" return f"<{self.id}: {self.username}>"
@property @property
def json(self): def json(self):

View File

@ -1,3 +1,5 @@
from datetime import datetime
from app import db from app import db
@ -10,4 +12,12 @@ class ModelMixin(object):
return self return self
class BaseModel(db.Model, ModelMixin):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True)
created_at = db.Column(db.DateTime, default=datetime.now)
is_deleted = db.Column(db.Boolean, default=False)
# Add your own utility classes and functions here. # Add your own utility classes and functions here.

File diff suppressed because one or more lines are too long

View File

@ -27,7 +27,6 @@
> >
<div class="pl-3"> <div class="pl-3">
<div class="text-base font-semibold">{{ user.username }}</div> <div class="text-base font-semibold">{{ user.username }}</div>
<div class="font-normal text-gray-500"><a class="hover:text-blue-500" href="mailto:{{ user.email }}">{{ user.email }}</a></div>
</div> </div>
</td> </td>
<td class="p-4 text-base font-normal text-gray-900 whitespace-nowrap dark:text-white"> <td class="p-4 text-base font-normal text-gray-900 whitespace-nowrap dark:text-white">

View File

@ -23,7 +23,7 @@ def get_all():
q = request.args.get("q", type=str, default=None) q = request.args.get("q", type=str, default=None)
users = m.User.query.order_by(m.User.id) users = m.User.query.order_by(m.User.id)
if q: if q:
users = users.filter(m.User.username.like(f"{q}%") | m.User.email.like(f"{q}%")) users = users.filter(m.User.username.like(f"{q}%"))
pagination = create_pagination(total=users.count()) pagination = create_pagination(total=users.count())
@ -45,7 +45,6 @@ def save():
log(log.ERROR, "Not found user by id : [%s]", form.user_id.data) log(log.ERROR, "Not found user by id : [%s]", form.user_id.data)
flash("Cannot save user data", "danger") flash("Cannot save user data", "danger")
u.username = form.username.data u.username = form.username.data
u.email = form.email.data
u.activated = form.activated.data u.activated = form.activated.data
if form.password.data.strip("*\n "): if form.password.data.strip("*\n "):
u.password = form.password.data u.password = form.password.data
@ -67,7 +66,6 @@ def create():
if form.validate_on_submit(): if form.validate_on_submit():
user = m.User( user = m.User(
username=form.username.data, username=form.username.data,
email=form.email.data,
password=form.password.data, password=form.password.data,
activated=form.activated.data, activated=form.activated.data,
) )

View File

@ -18,7 +18,6 @@ class BaseConfig(BaseSettings):
# Super admin # Super admin
ADMIN_USERNAME: str ADMIN_USERNAME: str
ADMIN_EMAIL: str
ADMIN_PASSWORD: str ADMIN_PASSWORD: str
# Pagination # Pagination

View File

@ -0,0 +1,199 @@
"""empty message
Revision ID: 02f6f2ebad1b
Revises:
Create Date: 2023-04-21 17:26:06.003994
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '02f6f2ebad1b'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tags',
sa.Column('name', sa.String(length=32), nullable=False),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('users',
sa.Column('username', sa.String(length=60), nullable=False),
sa.Column('password_hash', sa.String(length=255), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('username')
)
op.create_table('books',
sa.Column('label', sa.String(length=1024), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('book_contributors',
sa.Column('role', sa.Enum('UNKNOWN', 'MODERATOR', 'EDITOR', name='roles'), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('book_id', sa.Integer(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['book_id'], ['books.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('book_versions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('semver', sa.String(length=1024), nullable=False),
sa.Column('exported', sa.Boolean(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('derivative_id', sa.Integer(), nullable=True),
sa.Column('book_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['book_id'], ['books.id'], ),
sa.ForeignKeyConstraint(['derivative_id'], ['book_versions.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('books_stars',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('book_id', sa.Integer(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['book_id'], ['books.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('collections',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('label', sa.String(length=1024), nullable=False),
sa.Column('about', sa.Text(), nullable=False),
sa.Column('is_root', sa.Boolean(), nullable=True),
sa.Column('is_leaf', sa.Boolean(), nullable=True),
sa.Column('version_id', sa.Integer(), nullable=True),
sa.Column('parrent_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['parrent_id'], ['collections.id'], ),
sa.ForeignKeyConstraint(['version_id'], ['book_versions.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('sections',
sa.Column('label', sa.String(length=1024), nullable=False),
sa.Column('about', sa.Text(), nullable=True),
sa.Column('collection_id', sa.Integer(), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('version_id', sa.Integer(), nullable=True),
sa.Column('selected_interpretation_id', sa.Integer(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['collection_id'], ['collections.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.ForeignKeyConstraint(['version_id'], ['book_versions.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('interpretations',
sa.Column('label', sa.String(length=1024), nullable=False),
sa.Column('text', sa.Text(), nullable=False),
sa.Column('marked', sa.Boolean(), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('section_id', sa.Integer(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['section_id'], ['sections.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('comments',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('text', sa.Text(), nullable=False),
sa.Column('marked', sa.Boolean(), nullable=True),
sa.Column('included_with_interpreation', sa.Boolean(), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('parrent_id', sa.Integer(), nullable=True),
sa.Column('interpretation_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['interpretation_id'], ['interpretations.id'], ),
sa.ForeignKeyConstraint(['parrent_id'], ['comments.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('interpretation_tags',
sa.Column('tag_id', sa.Integer(), nullable=True),
sa.Column('interpretation_id', sa.Integer(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['interpretation_id'], ['interpretations.id'], ),
sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('interpretation_votes',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('interpretation_id', sa.Integer(), nullable=True),
sa.Column('possitive', sa.Boolean(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['interpretation_id'], ['interpretations.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('comment_tags',
sa.Column('tag_id', sa.Integer(), nullable=True),
sa.Column('comment_id', sa.Integer(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['comment_id'], ['comments.id'], ),
sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('comment_votes',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('comment_id', sa.Integer(), nullable=True),
sa.Column('possitive', sa.Boolean(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['comment_id'], ['comments.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('comment_votes')
op.drop_table('comment_tags')
op.drop_table('interpretation_votes')
op.drop_table('interpretation_tags')
op.drop_table('comments')
op.drop_table('interpretations')
op.drop_table('sections')
op.drop_table('collections')
op.drop_table('books_stars')
op.drop_table('book_versions')
op.drop_table('book_contributors')
op.drop_table('books')
op.drop_table('users')
op.drop_table('tags')
# ### end Alembic commands ###

View File

@ -1,40 +0,0 @@
"""init
Revision ID: 766427519c34
Revises:
Create Date: 2023-04-07 19:36:58.671750
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '766427519c34'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=60), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('password_hash', sa.String(length=255), nullable=True),
sa.Column('activated', sa.Boolean(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('unique_id', sa.String(length=36), nullable=True),
sa.Column('reset_password_uid', sa.String(length=64), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('username')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('users')
# ### end Alembic commands ###

View File

@ -21,7 +21,6 @@ LOCAL_WEB_PORT=8080
# Super admin # Super admin
ADMIN_USERNAME=admin ADMIN_USERNAME=admin
ADMIN_EMAIL=simple2b.info@gmail.com
ADMIN_PASSWORD=admin ADMIN_PASSWORD=admin
# Pagination # Pagination

View File

@ -97,8 +97,6 @@ function editUser(user: IUser) {
input.value = user.username; input.value = user.username;
input = document.querySelector('#user-edit-id'); input = document.querySelector('#user-edit-id');
input.value = user.id.toString(); input.value = user.id.toString();
input = document.querySelector('#user-edit-email');
input.value = user.email;
input = document.querySelector('#user-edit-password'); input = document.querySelector('#user-edit-password');
input.value = '*******'; input.value = '*******';
input = document.querySelector('#user-edit-password_confirmation'); input = document.querySelector('#user-edit-password_confirmation');

View File

@ -50,7 +50,6 @@ def populate(client: FlaskClient):
for i in range(NUM_TEST_USERS): for i in range(NUM_TEST_USERS):
m.User( m.User(
username=f"user{i+1}", username=f"user{i+1}",
email=f"user{i+1}@mail.com",
password="password", password="password",
).save(False) ).save(False)
db.session.commit() db.session.commit()

View File

@ -4,7 +4,6 @@ from sqlalchemy import func
from app import db from app import db
from app import models as m from app import models as m
faker = Faker() faker = Faker()
NUM_TEST_USERS = 100 NUM_TEST_USERS = 100
@ -34,13 +33,3 @@ def gen_test_items(num_objects: int) -> Generator[str, None, None]:
# email formatting # email formatting
yield f"{first_name}{i}".lower(), f"{first_name}.{last_name}{i}@{company}.{dns_org}".lower() yield f"{first_name}{i}".lower(), f"{first_name}.{last_name}{i}@{company}.{dns_org}".lower()
def populate(count: int = NUM_TEST_USERS):
for username, email in gen_test_items(count):
m.User(
username=username,
email=email,
).save(False)
db.session.commit()

View File

@ -0,0 +1,272 @@
from app import models as m
def create_dummy_data():
user = m.User(username="Dummy User 1", password="Dummy Password").save()
user_2 = m.User(username="Dummy User 2", password="Dummy Password").save()
user_3 = m.User(username="Dummy User 3", password="Dummy Password").save()
user_4 = m.User(username="Dummy User 4", password="Dummy Password").save()
book = m.Book(label="Dummy Book", user_id=user.id).save()
m.BookStar(user_id=user.id, book_id=book.id).save()
moderator = m.User(username="Dummy Moderator", password="Dummy Password").save()
m.BookContributor(
user_id=moderator.id, book_id=book.id, role=m.BookContributor.Roles.MODERATOR
).save()
editor = m.User(username="Dummy Editor", password="Dummy Password").save()
m.BookContributor(
user_id=editor.id, book_id=book.id, role=m.BookContributor.Roles.EDITOR
).save()
exported_version = m.BookVersion(
semver="1.0.0", book_id=book.id, exported=True
).save()
unexported_version = m.BookVersion(
semver="1.0.1", book_id=book.id, exported=False, derivative=exported_version
).save()
# collections
# root
# - collection 1 (leaf)
# - collection 2
# - subcollection 2.1 (leaf)
# root
rool_collection = m.Collection(
label="Dummy Root Collection Label",
about="Dummy Root Collection About",
is_root=True,
version_id=unexported_version.id,
).save()
collection_1 = m.Collection(
label="Dummy Collection 1 Label",
about="Dummy Collection 1 About",
version_id=unexported_version.id,
parrent_id=rool_collection.id,
is_leaf=True,
).save()
collection_2 = m.Collection(
label="Dummy Collection 2 Label",
about="Dummy Collection 2 About",
version_id=unexported_version.id,
parrent_id=rool_collection.id,
).save()
subcollection_2_1 = m.Collection(
label="Dummy SubCollection 2.1 Label",
about="Dummy SubCollection 2.1 About",
is_leaf=True,
version_id=unexported_version.id,
parrent_id=collection_2.id,
).save()
# sections
# root
# - collection 1 (leaf)
# - section 1.1
# - collection 2
# - subcollection 2.1 (leaf)
# - section 2.1.1
# - section 2.1.2
section_1_1 = m.Section(
label="Dummy Section 1.1 Label",
about="Dummy Section 1.1 About",
collection_id=collection_1.id,
version_id=collection_1.version_id,
user_id=user.id,
).save()
section_2_1_1 = m.Section(
label="Dummy Section 2.1.1 Label",
about="Dummy Section 2.1.1 About",
collection_id=subcollection_2_1.id,
version_id=unexported_version.id,
user_id=user.id,
).save()
section_2_1_2 = m.Section(
label="Dummy Section 2.1.2 Label",
about="Dummy Section 2.1.2 About",
collection_id=subcollection_2_1.id,
version_id=unexported_version.id,
user_id=user.id,
).save()
# root
# - collection 1 (leaf)
# - section 1.1
# - interpretation 1
# - collection 2
# - subcollection 2.1 (leaf)
# - section 2.1.1
# - interpretation 2 (marked)
# - section 2.1.2
# - interpretation 3 (marked)
# - interpretation 4
interpretation_1 = m.Interpretation(
label="Dummy Interpretation 1 Label",
text="Dummy Interpretation 1 About",
section_id=section_1_1.id,
user_id=user.id,
).save()
interpretation_2 = m.Interpretation(
label="Dummy Interpretation 2 Label",
text="Dummy Interpretation 2 About",
section_id=section_2_1_1.id,
user_id=user.id,
marked=True,
).save()
interpretation_3 = m.Interpretation(
label="Dummy Interpretation 3 Label",
text="Dummy Interpretation 3 About",
section_id=section_2_1_2.id,
user_id=user.id,
marked=True,
).save()
m.Interpretation(
label="Dummy Interpretation 4 Label",
text="Dummy Interpretation 4 About",
section_id=section_2_1_2.id,
user_id=user.id,
).save()
# comments
# - interpretation 2
# - comment 1
# - comment 1.1
# - comment 1.2 (marked)
# - comment 2
# - comment 3
# - comment 3.1 (marked)
# - comment 3.2 (included_with_interpreation)
# - comment 3.3
comment_1 = m.Comment(
text="Dummy Comment 1 Text",
user_id=user_2.id,
interpretation_id=interpretation_2.id,
).save()
m.Comment(
text="Dummy Comment 1.1 Text",
user_id=user_3.id,
parrent_id=comment_1.id,
interpretation_id=interpretation_2.id,
).save()
m.Comment(
text="Dummy Comment 1.2 Text",
user_id=user_2.id,
parrent_id=comment_1.id,
marked=True,
interpretation_id=interpretation_2.id,
).save()
comment_2 = m.Comment(
text="Dummy Comment 2 Text",
user_id=user_4.id,
interpretation_id=interpretation_2.id,
).save()
comment_3 = m.Comment(
text="Dummy Comment 3 Text",
user_id=user.id,
interpretation_id=interpretation_2.id,
).save()
comment_3_1 = m.Comment(
text="Dummy Comment 3.1 Text",
user_id=user.id,
marked=True,
parrent_id=comment_3.id,
interpretation_id=interpretation_2.id,
).save()
comment_3_2 = m.Comment(
text="Dummy Comment 3.2 Text",
user_id=user.id,
included_with_interpreation=True,
parrent_id=comment_3.id,
interpretation_id=interpretation_2.id,
).save()
comment_3_3 = m.Comment(
text="Dummy Comment 3.3 Text",
user_id=user.id,
parrent_id=comment_3.id,
interpretation_id=interpretation_2.id,
).save()
# - comment 3.1 (2 possitive, 2 negative)
# - comment 3.2 (1 negative)
# - comment 3.3 (1 possitive)
m.CommentVote(comment_id=comment_3_1.id, user_id=user.id, possitive=True).save()
m.CommentVote(comment_id=comment_3_1.id, user_id=user_2.id, possitive=True).save()
m.CommentVote(comment_id=comment_3_1.id, user_id=user_3.id, possitive=False).save()
m.CommentVote(comment_id=comment_3_1.id, user_id=user_4.id, possitive=False).save()
m.CommentVote(comment_id=comment_3_2.id, user_id=user_2.id, possitive=False).save()
m.CommentVote(comment_id=comment_3_3.id, user_id=user_3.id, possitive=True).save()
# - interpretation 1 (2 possitive, 1 negative)
# - interpretation 2 (1 negative)
# - interpretation 3 (1 possitive)
m.InterpretationVote(
interpretation_id=interpretation_1.id, user_id=user.id, possitive=True
).save()
m.InterpretationVote(
interpretation_id=interpretation_1.id, user_id=user_2.id, possitive=True
).save()
m.InterpretationVote(
interpretation_id=interpretation_1.id, user_id=user_3.id, possitive=False
).save()
m.InterpretationVote(
interpretation_id=interpretation_2.id, user_id=user_2.id, possitive=False
).save()
m.InterpretationVote(
interpretation_id=interpretation_3.id, user_id=user_3.id, possitive=True
).save()
# tags
tag_1 = m.Tag(name="Dummy Tag 1").save()
tag_2 = m.Tag(name="Dummy Tag 2").save()
tag_3 = m.Tag(name="Dummy Tag 3").save()
# interpretation tags
# - interpretation 1
# - tags: 1,
# - interpretation 2
# - tags: 2, 3
# - interpretation 3
# - tags: 1, 3
m.InterpretationTag(interpretation_id=interpretation_1.id, tag_id=tag_1.id).save()
m.InterpretationTag(interpretation_id=interpretation_2.id, tag_id=tag_2.id).save()
m.InterpretationTag(interpretation_id=interpretation_2.id, tag_id=tag_3.id).save()
m.InterpretationTag(interpretation_id=interpretation_3.id, tag_id=tag_1.id).save()
m.InterpretationTag(interpretation_id=interpretation_3.id, tag_id=tag_3.id).save()
# commen tags
# - comment 1
# - tags: 1,
# - comment 2
# - tags: 2, 3
# - comment 3
# - tags: 1, 3
m.CommentTags(comment_id=comment_1.id, tag_id=tag_1.id).save()
m.CommentTags(comment_id=comment_2.id, tag_id=tag_2.id).save()
m.CommentTags(comment_id=comment_2.id, tag_id=tag_3.id).save()
m.CommentTags(comment_id=comment_3.id, tag_id=tag_1.id).save()
m.CommentTags(comment_id=comment_3.id, tag_id=tag_3.id).save()

View File

@ -0,0 +1,285 @@
from flask.testing import FlaskCliRunner
from click.testing import Result
from app import models as m
def test_dummy_data(runner: FlaskCliRunner):
res: Result = runner.invoke(args=["db-populate"])
assert "Dummy data added" in res.output, res.stderr
user: m.User = m.User.query.filter_by(username="Dummy User 1").first()
book: m.Book = m.Book.query.filter_by(label="Dummy Book").first()
assert user
assert user.books
assert book in user.books
# stars
book_star: m.BookStar = m.BookStar.query.first()
assert book.stars
assert user.stars
assert book_star
assert book_star.user_id == user.id
assert book_star.book_id == book.id
# contributors
assert book.contributors
assert len(book.contributors) == 2
moderator: m.User = m.User.query.filter_by(username="Dummy Moderator").first()
book_moderator: m.BookContributor = m.BookContributor.query.filter_by(
role=m.BookContributor.Roles.MODERATOR
).first()
assert book_moderator
assert book_moderator.book_id == book.id
assert book_moderator.user_id == moderator.id
editor: m.User = m.User.query.filter_by(username="Dummy Editor").first()
book_editor: m.BookContributor = m.BookContributor.query.filter_by(
role=m.BookContributor.Roles.EDITOR
).first()
assert book_editor
assert book_editor.book_id == book.id
assert book_editor.user_id == editor.id
# versions
assert book.versions
exported_version: m.BookVersion = m.BookVersion.query.filter_by(
book=book, exported=True
).first()
assert exported_version.exported
assert exported_version.book_id == book.id
assert not exported_version.derivative_id
unexported_version: m.BookVersion = m.BookVersion.query.filter_by(
book=book, exported=False
).first()
assert not unexported_version.exported
assert unexported_version.derivative == exported_version
assert unexported_version.book_id == book.id
# collections
# root
# - collection 1 (leaf)
# - collection 2
# - subcollection 2.1 (leaf)
root_collection: m.Collection = m.Collection.query.filter_by(
label="Dummy Root Collection Label"
).first()
assert root_collection
assert root_collection.is_root
assert not root_collection.is_leaf
assert root_collection.version == unexported_version
collection_1: m.Collection = m.Collection.query.filter_by(
label="Dummy Collection 1 Label"
).first()
assert collection_1
assert not collection_1.is_root
assert collection_1.is_leaf
assert collection_1.version == unexported_version
collection_2: m.Collection = m.Collection.query.filter_by(
label="Dummy Collection 2 Label"
).first()
assert collection_2
assert not collection_2.is_leaf
assert not collection_2.is_root
assert collection_2.version == unexported_version
subcollection_2_1: m.Collection = m.Collection.query.filter_by(
label="Dummy SubCollection 2.1 Label"
).first()
assert subcollection_2_1
assert subcollection_2_1.is_leaf
assert not subcollection_2_1.is_root
# root
# - collection 1 (leaf)
# - section 1.1
# - collection 2
# - subcollection 2.1 (leaf)
# - section 2.1.1
# - section 2.1.2
section_1_1: m.Section = m.Section.query.filter_by(
label="Dummy Section 1.1 Label"
).first()
assert section_1_1
assert section_1_1.user == user
assert section_1_1.collection == collection_1
section_2_1_1: m.Section = m.Section.query.filter_by(
label="Dummy Section 2.1.1 Label"
).first()
assert section_2_1_1
assert section_2_1_1.user == user
assert section_2_1_1.collection == subcollection_2_1
section_2_1_2: m.Section = m.Section.query.filter_by(
label="Dummy Section 2.1.2 Label"
).first()
assert section_2_1_2
assert section_2_1_2.user == user
assert section_2_1_2.collection == subcollection_2_1
# interpretations
# root
# - collection 1 (leaf)
# - section 1.1
# - interpretation 1
# - collection 2
# - subcollection 2.1 (leaf)
# - section 2.1.1
# - interpretation 2 (marked)
# - section 2.1.2
# - interpretation 3 (marked)
# - interpretation 4
interpretation_1: m.Interpretation = m.Interpretation.query.filter_by(
label="Dummy Interpretation 1 Label"
).first()
assert interpretation_1
assert interpretation_1.user == user
assert interpretation_1.section == section_1_1
interpretation_2: m.Interpretation = m.Interpretation.query.filter_by(
label="Dummy Interpretation 2 Label"
).first()
assert interpretation_2
assert interpretation_2.marked
assert interpretation_2.user == user
assert interpretation_2.section == section_2_1_1
interpretation_3: m.Interpretation = m.Interpretation.query.filter_by(
label="Dummy Interpretation 3 Label"
).first()
assert interpretation_3
assert interpretation_3.marked
assert interpretation_3.user == user
assert interpretation_3.section == section_2_1_2
interpretation_4: m.Interpretation = m.Interpretation.query.filter_by(
label="Dummy Interpretation 3 Label"
).first()
assert interpretation_4
assert interpretation_4.user == user
assert interpretation_4.section == section_2_1_2
# comments
# - interpretation 2
# - comment 1
# - comment 1.1
# - comment 1.2 (marked)
# - comment 2
# - comment 3
# - comment 3.1 (marked)
# - comment 3.2 (included_with_interpreation)
# - comment 3.3
comment_1: m.Comment = m.Comment.query.filter_by(
text="Dummy Comment 1 Text"
).first()
assert not comment_1.parrent
comment_1_1: m.Comment = m.Comment.query.filter_by(
text="Dummy Comment 1.1 Text"
).first()
comment_1_2: m.Comment = m.Comment.query.filter_by(
text="Dummy Comment 1.2 Text"
).first()
assert comment_1_1 in comment_1.children
assert comment_1_1 in comment_1.children
assert comment_1_2.parrent == comment_1
assert comment_1_2.parrent == comment_1
assert comment_1_2.marked
comment_2: m.Comment = m.Comment.query.filter_by(
text="Dummy Comment 2 Text"
).first()
assert not comment_2.parrent
assert not comment_2.children
comment_3: m.Comment = m.Comment.query.filter_by(
text="Dummy Comment 3 Text"
).first()
assert not comment_3.parrent
assert comment_3.children
comment_3_1: m.Comment = m.Comment.query.filter_by(
text="Dummy Comment 3.1 Text"
).first()
comment_3_2: m.Comment = m.Comment.query.filter_by(
text="Dummy Comment 3.2 Text"
).first()
comment_3_3: m.Comment = m.Comment.query.filter_by(
text="Dummy Comment 3.3 Text"
).first()
assert comment_3_1 in comment_3.children
assert comment_3_2 in comment_3.children
assert comment_3_3 in comment_3.children
assert comment_3_1.marked
assert comment_3_2.included_with_interpreation
assert comment_1 in interpretation_2.comments
assert comment_2 in interpretation_2.comments
assert comment_3 in interpretation_2.comments
# - comment 3.1 (2 possitive, 2 negative)
# - comment 3.2 (1 negative)
# - comment 3.3 (1 possitive)
assert len(comment_3_1.votes) == 4
assert len(comment_3_2.votes) == 1
assert len(comment_3_3.votes) == 1
# - interpretation 1 (2 possitive, 1 negative)
# - interpretation 2 (1 negative)
# - interpretation 3 (1 possitive)
assert len(interpretation_1.votes) == 3
assert len(interpretation_2.votes) == 1
assert len(interpretation_3.votes) == 1
# interpretation tags
# - tags: 1,
# - interpretation 2
# - tags: 2, 3
# - interpretation 3
# - tags: 1, 3
assert len(interpretation_1.tags) == 1
assert len(interpretation_2.tags) == 2
assert len(interpretation_3.tags) == 2
# # - comment 1
# # - tags: 1,
# # - comment 2
# # - tags: 2, 3
# # - comment 3
# # - tags: 1, 3
# assert len(comment_1.tags) == 1
# assert len(comment_2.tags) == 2
# assert len(comment_3.tags) == 2

View File

@ -36,14 +36,6 @@ def test_create_admin(runner: FlaskCliRunner):
assert m.User.query.filter_by(username=app.config["ADMIN_USERNAME"]).first() assert m.User.query.filter_by(username=app.config["ADMIN_USERNAME"]).first()
def test_populate_db(runner: FlaskCliRunner):
TEST_COUNT = 56
count_before = m.User.query.count()
res: Result = runner.invoke(args=["db-populate", "--count", f"{TEST_COUNT}"])
assert f"populated by {TEST_COUNT}" in res.stdout
assert (m.User.query.count() - count_before) == TEST_COUNT
def test_delete_user(populate: FlaskClient): def test_delete_user(populate: FlaskClient):
login(populate) login(populate)
users = m.User.query.all() users = m.User.query.all()

View File

@ -5,10 +5,8 @@ TEST_ADMIN_EMAIL = "bob@test.com"
TEST_ADMIN_PASSWORD = "password" TEST_ADMIN_PASSWORD = "password"
def create( def create(username=TEST_ADMIN_NAME, password=TEST_ADMIN_PASSWORD):
username=TEST_ADMIN_NAME, email=TEST_ADMIN_EMAIL, password=TEST_ADMIN_PASSWORD user = User(username=username)
):
user = User(username=username, email=email)
user.password = password user.password = password
user.save() user.save()
return user.id return user.id