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.
261 lines
8.9 KiB
Python
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
|