Skip to content

Commit

Permalink
Use type annotated model class
Browse files Browse the repository at this point in the history
  • Loading branch information
greyli committed Jun 25, 2024
1 parent 40bad4a commit 9a8f459
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 58 deletions.
17 changes: 16 additions & 1 deletion greybook/core/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,24 @@
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import CSRFProtect
from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
metadata = MetaData(
naming_convention={
'ix': 'ix_%(column_0_label)s',
'uq': 'uq_%(table_name)s_%(column_0_name)s',
'ck': 'ck_%(table_name)s_%(constraint_name)s',
'fk': 'fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s',
'pk': 'pk_%(table_name)s',
}
)


bootstrap = Bootstrap5()
db = SQLAlchemy()
db = SQLAlchemy(model_class=Base)
login_manager = LoginManager()
csrf = CSRFProtect()
ckeditor = CKEditor()
Expand Down
9 changes: 4 additions & 5 deletions greybook/fakes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ def fake_admin():
blog_title='Greybook',
blog_sub_title='Just some random thoughts',
name='Grey Li',
about='Hello, I am Grey Li. This is an example '
'Flask project for <a href="https://helloflask.com/book/4/">my book</a>.',
about='This is an example Flask project for <a href="https://helloflask.com/book/4/">this book</a>.',
)
db.session.add(admin)
db.session.commit()
Expand All @@ -42,7 +41,7 @@ def fake_categories(count=10):

def fake_posts(count=50):
for _ in range(count):
category_count = db.session.execute(select(func.count(Category.id))).scalars().one()
category_count = db.session.scalars(select(func.count(Category.id))).one()
created_date = fake.date_time_between_dates(
datetime_start=datetime(2010, 1, 1), datetime_end=datetime(2020, 1, 1)
)
Expand All @@ -62,7 +61,7 @@ def fake_posts(count=50):

def fake_comments(count=500):
for _ in range(count):
post_count = db.session.execute(select(func.count(Post.id))).scalars().one()
post_count = db.session.scalars(select(func.count(Post.id))).one()
comment = Comment(
author=fake.name(),
email=fake.email(),
Expand All @@ -84,7 +83,7 @@ def fake_comments(count=500):

def fake_replies(count=50):
for _ in range(count):
comment_count = db.session.execute(select(func.count(Comment.id))).scalars().one()
comment_count = db.session.scalars(select(func.count(Comment.id))).one()
replied = db.session.get(Comment, random.randint(1, comment_count))
comment = Comment(
author=fake.name(),
Expand Down
115 changes: 69 additions & 46 deletions greybook/models.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import os
import re
from datetime import datetime
from datetime import datetime, timezone
from typing import Optional

from flask import current_app, url_for
from flask_login import UserMixin
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.orm import relationship
from sqlalchemy import ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from werkzeug.security import check_password_hash, generate_password_hash

from greybook.core.extensions import db


class Admin(db.Model, UserMixin):
id = Column(Integer, primary_key=True)
username = Column(String(20))
password_hash = Column(String(128))
blog_title = Column(String(60))
blog_sub_title = Column(String(100))
name = Column(String(30))
about = Column(Text)
custom_footer = Column(Text)
custom_css = Column(Text)
custom_js = Column(Text)
__tablename__ = 'admin'

id: Mapped[int] = mapped_column(primary_key=True)
username: Mapped[str] = mapped_column(String(20))
password_hash: Mapped[str] = mapped_column(String(128))
blog_title: Mapped[str] = mapped_column(String(60))
blog_sub_title: Mapped[str] = mapped_column(String(100))
name: Mapped[str] = mapped_column(String(30))
about: Mapped[str] = mapped_column(Text)
custom_footer: Mapped[Optional[str]] = mapped_column(Text)
custom_css: Mapped[Optional[str]] = mapped_column(Text)
custom_js: Mapped[Optional[str]] = mapped_column(Text)

def __repr__(self):
return f'<Admin: {self.username}>'

@property
def password(self):
Expand All @@ -36,10 +42,15 @@ def validate_password(self, password):


class Category(db.Model):
id = Column(Integer, primary_key=True)
name = Column(String(30), unique=True)
__tablename__ = 'category'

posts = relationship('Post', back_populates='category')
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30), unique=True)

posts: Mapped[list['Post']] = relationship(back_populates='category')

def __repr__(self):
return f'<Category {self.id}: {self.name}>'

def delete(self):
default_category = db.session.get(Category, 1)
Expand All @@ -51,17 +62,22 @@ def delete(self):


class Post(db.Model):
id = Column(Integer, primary_key=True)
title = Column(String(60))
body = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow, index=True)
updated_at = Column(DateTime, onupdate=datetime.utcnow)
can_comment = Column(Boolean, default=True)
__tablename__ = 'post'

id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String(60))
body: Mapped[str] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc), index=True)
updated_at: Mapped[Optional[datetime]] = mapped_column(onupdate=lambda: datetime.now(timezone.utc))
can_comment: Mapped[bool] = mapped_column(default=True)

category_id = Column(Integer, ForeignKey('category.id'))
category_id: Mapped[int] = mapped_column(ForeignKey('category.id'))

category = relationship('Category', back_populates='posts')
comments = relationship('Comment', back_populates='post', cascade='all, delete-orphan')
category: Mapped['Category'] = relationship(back_populates='posts')
comments: Mapped[list['Comment']] = relationship(back_populates='post', cascade='all, delete-orphan')

def __repr__(self):
return f'<Post {self.id}: {self.title}>'

@property
def reviewed_comments_count(self):
Expand All @@ -80,27 +96,34 @@ def delete(self):


class Comment(db.Model):
id = Column(Integer, primary_key=True)
author = Column(String(30))
email = Column(String(254))
site = Column(String(255))
body = Column(Text)
from_admin = Column(Boolean, default=False)
reviewed = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow, index=True)

replied_id = Column(Integer, ForeignKey('comment.id'))
post_id = Column(Integer, ForeignKey('post.id'))

post = relationship('Post', back_populates='comments')
replies = relationship('Comment', back_populates='replied', cascade='all, delete-orphan')
replied = relationship('Comment', back_populates='replies', remote_side=[id])
# Same with:
# replies = relationship('Comment', backref=backref('replied', remote_side=[id]),
# cascade='all,delete-orphan')
__tablename__ = 'comment'

id: Mapped[int] = mapped_column(primary_key=True)
author: Mapped[str] = mapped_column(String(30))
email: Mapped[str] = mapped_column(String(255))
site: Mapped[str] = mapped_column(String(255))
body: Mapped[str] = mapped_column(Text)
from_admin: Mapped[bool] = mapped_column(default=False)
reviewed: Mapped[bool] = mapped_column(default=False)
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(timezone.utc), index=True)

replied_id: Mapped[Optional[int]] = mapped_column(ForeignKey('comment.id'))
post_id: Mapped[int] = mapped_column(ForeignKey('post.id'))

post: Mapped['Post'] = relationship(back_populates='comments')
replies: Mapped[list['Comment']] = relationship(back_populates='replied', cascade='all, delete-orphan')
replied: Mapped['Comment'] = relationship(back_populates='replies', remote_side=[id])

def __repr__(self):
return f'<Comment {self.id}: {self.author}>'


class Link(db.Model):
id = Column(Integer, primary_key=True)
name = Column(String(30))
url = Column(String(255))
__tablename__ = 'link'

id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30))
url: Mapped[str] = mapped_column(String(255))

def __repr__(self):
return f'<Link {self.id}: {self.name}>'
12 changes: 6 additions & 6 deletions greybook/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import sys
from pathlib import Path

basedir = Path(__file__).resolve().parent.parent
BASE_DIR = Path(__file__).resolve().parent.parent

# SQLite URI compatible
prefix = 'sqlite:///' if sys.platform.startswith('win') else 'sqlite:////'
SQLITE_PREFIX = 'sqlite:///' if sys.platform.startswith('win') else 'sqlite:////'


class BaseConfig:
Expand Down Expand Up @@ -34,14 +34,14 @@ class BaseConfig:
GREYBOOK_THEMES = {'default': 'Default', 'perfect_blue': 'Perfect Blue'}
GREYBOOK_SLOW_QUERY_THRESHOLD = 1

GREYBOOK_UPLOAD_PATH = os.getenv('GREYBOOK_UPLOAD_PATH', basedir / 'uploads')
GREYBOOK_UPLOAD_PATH = os.getenv('GREYBOOK_UPLOAD_PATH', BASE_DIR / 'uploads')
GREYBOOK_ALLOWED_IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif']
GREYBOOK_LOGGING_PATH = os.getenv('GREYBOOK_LOGGING_PATH', basedir / 'logs/greybook.log')
GREYBOOK_LOGGING_PATH = os.getenv('GREYBOOK_LOGGING_PATH', BASE_DIR / 'logs/greybook.log')
GREYBOOK_ERROR_EMAIL_SUBJECT = '[Greybook] Application Error'


class DevelopmentConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = prefix + str(basedir / 'data-dev.db')
SQLALCHEMY_DATABASE_URI = SQLITE_PREFIX + str(BASE_DIR / 'data-dev.db')


class TestingConfig(BaseConfig):
Expand All @@ -51,7 +51,7 @@ class TestingConfig(BaseConfig):


class ProductionConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL', prefix + str(basedir / 'data.db'))
SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL', SQLITE_PREFIX + str(BASE_DIR / 'data.db'))


config = {'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig}

0 comments on commit 9a8f459

Please sign in to comment.