1
1
Fork 0
You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yadc/yadc/models.py

261 lines
8.9 KiB
Python

import enum
import hashlib
import humanize
import os
from datetime import datetime, timezone
from flask import current_app, url_for
from flask_login import UserMixin, login_user, logout_user, current_user
from PIL import Image
from sqlalchemy_utc import UtcDateTime, utcnow
from werkzeug.security import check_password_hash, generate_password_hash
from werkzeug.utils import cached_property
from yadc import db, login, utils
class OP_LEVEL(enum.Enum):
user = 0
creator = 1
moderator = 5
admin = 9
class USER_STATUS(enum.Enum):
active = 0
inactive = 1
banned = 5
class FILETYPE(enum.Enum):
png = 0
jpeg = 1
class RATING(enum.Enum):
safe = 0
questionable = 1
explicit = 2
@staticmethod
def matched(rating=None):
rat = {r.name : r for r in RATING}.get(rating, RATING.safe)
return [r for r in RATING if r.value<=rat.value]
class POST_STATUS(enum.Enum):
pending = 0
active = 1
deleted = 2
class TAG_CATEGORY(enum.Enum):
general = 0
style = 1
circle = 2
artist = 3
character = 4
copyright = 5
# class COMMENT_STATUS(enum.Enum):
# visible = 0
# deleted = 2
class TimestampMixin(object):
created = db.Column(UtcDateTime, nullable=False, default=utcnow())
updated = db.Column(UtcDateTime, nullable=False, onupdate=utcnow(), default=utcnow())
def natural_created(self):
return humanize.naturaltime(datetime.now().astimezone(self.created.tzinfo) - self.created)
def natural_updated(self):
return humanize.naturaltime(datetime.now().astimezone(self.updated.tzinfo) - self.updated)
class User(UserMixin, TimestampMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(128), unique=True, nullable=False)
email = db.Column(db.String(256), unique=True)
pass_hash = db.Column(db.String(128))
op_level = db.Column(db.Enum(OP_LEVEL), default=OP_LEVEL.user, nullable=False)
user_status = db.Column(db.Enum(USER_STATUS), default=USER_STATUS.active, nullable=False)
last_login = db.Column(UtcDateTime)
ban_status = None
ban_until = None
ban_reason = None
#authored_posts = db.relationship('Post', back_populates='author')
#approved_posts = db.relationship('Post', back_populates='approver')
def __repr__(self):
return '<User {}>'.format(self.username)
def create_password(self, password):
self.pass_hash = generate_password_hash(password, salt_length=16)
def check_password(self, password):
if self.pass_hash is None:
return True
return check_password_hash(self.pass_hash, password)
def login(self, remember):
login_user(self, remember=remember)
self.last_login = utcnow()
def logout(self):
logout_user()
@property
def is_moderator(self):
# return self.op_level in [OP_LEVEL.moderator, OP_LEVEL.admin]
return self.op_level.value >= OP_LEVEL.moderator.value
@property
def is_admin(self):
return self.op_level is OP_LEVEL.admin
@login.user_loader
def load_user(id):
return User.query.get(int(id))
# class UserPreferences(db.Model):
# user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
# user = db.relationship('User', backref=db.backref('profile', uselist=False))
# rating = db.Column(db.Enum(RATING), default=RATING.safe, nullable=False)
# tag_blacklist = None
post_tags = db.Table('post_tags', db.metadata,
db.Column('post_id', db.Integer, db.ForeignKey('post.id')),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
)
class Post(TimestampMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
md5 = db.Column(db.String(32), unique=True, nullable=False)
filetype = db.Column(db.Enum(FILETYPE), nullable=False)
rating = db.Column(db.Enum(RATING), nullable=False)
status = db.Column(db.Enum(POST_STATUS), default=POST_STATUS.pending, nullable=False)
width = db.Column(db.Integer, default=0)
height = db.Column(db.Integer, default=0)
filesize = db.Column(db.Integer, default=0)
source = db.Column(db.String(2048))
origin_filename = db.Column(db.String(255))
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
author = db.relationship('User', backref=db.backref('authored_posts', lazy=True), foreign_keys=[author_id])
approver_id = db.Column(db.Integer, db.ForeignKey('user.id'))
approver = db.relationship('User', backref=db.backref('approved_posts', lazy=True), foreign_keys=[approver_id])
#tags = db.relationship('Tag', secondary=post_tags, back_populates='posts')
tags = db.relationship('Tag', secondary=post_tags, backref=db.backref('posts'))
def __init__(self, file, **kwargs):
super().__init__(**kwargs)
self.md5 = hashlib.md5(file.data.getbuffer()).hexdigest()
self.filetype = FILETYPE[file.mimetype.split('/')[1]]
with Image.open(file.data) as im:
self.width, self.height = im.width, im.height
self.filesize = file.data.getbuffer().nbytes
self.origin_filename = file.filename
def __repr__(self):
return('<Post #{} by {}, {} of {} bytes>'.format(self.id, self.author, self.filetype.name, self.filesize))
@cached_property
def flex(self):
return "flex: {0:.2f} 1 {0:.2f}px;".format(self.width/self.height*current_app.config.get('POST_LIST_THUMB_HEIGHT', 240))
@property
def image_url(self):
# filename = "{}.{}".format('maybe_later_generated_cute_filename', 'jpg' if self.filetype is FILETYPE.jpeg else 'png')
# filename = 'maybe_later_generated_cute_filename'
filename = "{} - {} {}".format(current_app.config.get('INSTANCE_NAME'), self.id, " ".join(tag.content.replace(' ', '_') for tag in self.tags))
return os.path.join(self.md5, filename)
def url(self, path, endpoint='img'):
if endpoint == 'img':
return url_for('main.uploaded_img', path="{}.{}".format(path,'jpg' if self.filetype is FILETYPE.jpeg else 'png'))
elif endpoint == 'jpeg':
return url_for('main.uploaded_jpeg' if not self.filetype is FILETYPE.jpeg else 'main.uploaded_img', path="{}.{}".format(path,'jpg'))
elif endpoint == 'sample':
return url_for('main.uploaded_sample', path="{}.{}".format(path,'jpg'))
elif endpoint == 'thumb':
return url_for('main.uploaded_thumb', path="{}.{}".format(path,'jpg'))
@property
def image_path(self):
filename = "{}.{}".format(self.md5, 'jpg' if self.filetype is FILETYPE.jpeg else 'png')
return os.path.join(current_app.config.get('POST_UPLOADS'), 'img', filename)
@property
def jpeg_path(self):
if self.filetype is FILETYPE.jpeg:
return None
jpeg_filename = "{}.{}".format(self.md5, 'jpg')
return os.path.join(current_app.config.get('POST_UPLOADS'), 'jpeg', jpeg_filename)
@property
def sample_path(self):
jpeg_filename = "{}.{}".format(self.md5, 'jpg')
return os.path.join(current_app.config.get('POST_UPLOADS'), 'sample', jpeg_filename)
@property
def thumb_path(self):
jpeg_filename = "{}.{}".format(self.md5, 'jpg')
return os.path.join(current_app.config.get('POST_UPLOADS'), 'thumb', jpeg_filename)
@cached_property
def image_resolution(self):
return "{}x{}".format(self.width, self.height)
@cached_property
def filesize_human(self):
return utils.sizeof_fmt(self.filesize)
@property
def is_approved(self):
return self.status is POST_STATUS.active
@property
def can_edit(self):
author = current_user == self.author
moderator = current_user.is_moderator
return author or moderator
class Tag(TimestampMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(128), unique=True, nullable=False)
category = db.Column(db.Enum(TAG_CATEGORY), default=TAG_CATEGORY.general)
#posts = db.relationship('Post', secondary=post_tags, back_populates='tags')
@property
def content_deser(self):
return self.content.replace('_',' ')
def __repr__(self):
return '<Tag {}, {}>'.format(self.content, self.category.name)
class Comment(TimestampMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(512), nullable=False)
# status = db.Column(db.Enum(COMMENT_STATUS), default=COMMENT_STATUS.visible)
deleted = db.Column(db.Boolean, default=False)
delete_reason = db.Column(db.String(512))
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
post = db.relationship('Post', backref=db.backref('comments', lazy=True))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User', backref=db.backref('comments', lazy=True))
@property
def can_edit(self):
author = current_user == self.user
moderator = current_user.is_moderator
return author or moderator
# THINK ABOUT LAZY LOADING