1
1
Fork 0

All the shit and still nobody understands.

Finally a bit cleaner changelist.
Working index page as of today.
dev
Jan Kužílek 5 years ago
parent b5c2f35bac
commit a955ebfcae

3
.gitignore vendored

@ -199,3 +199,6 @@ dmypy.json
.history
# End of https://www.gitignore.io/api/flask,python,visualstudiocode
migrations/*
.vscode/settings.json

@ -20,6 +20,10 @@ flask-assets = "*"
pyscss = "*"
libsass = "*"
cssmin = "*"
python-magic = "*"
flask-mail = "*"
sqlalchemy-utc = "*"
flask-admin = "*"
[requires]
python_version = "3.8"

114
Pipfile.lock generated

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "2b4208c857a995a746d67b2b523ebd5caface2c836881c0b549e9474d200c507"
"sha256": "4aa69f03ba5d5f31ea39beeefc97e9a9b61946c3275fb53e58897aa33c9ffc1c"
},
"pipfile-spec": 6,
"requires": {
@ -18,9 +18,15 @@
"default": {
"alembic": {
"hashes": [
"sha256:49277bb7242192bbb9eac58fed4fe02ec6c3a2a4b4345d2171197459266482b2"
"sha256:3b0cb1948833e062f4048992fbc97ecfaaaac24aaa0d83a1202a99fb58af8c6d"
],
"version": "==1.3.1"
"version": "==1.3.2"
},
"blinker": {
"hashes": [
"sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"
],
"version": "==1.4"
},
"click": {
"hashes": [
@ -44,12 +50,20 @@
"index": "pypi",
"version": "==1.1.1"
},
"flask-admin": {
"hashes": [
"sha256:ed7b256471dba0f3af74f1a315733c3b36244592f2002c3bbdc65fd7c2aa807a"
],
"index": "pypi",
"version": "==1.5.4"
},
"flask-assets": {
"hashes": [
"sha256:6031527b89fb3509d1581d932affa5a79dd348cfffb58d0aef99a43461d47847"
"sha256:1dfdea35e40744d46aada72831f7613d67bf38e8b20ccaaa9e91fdc37aa3b8c2",
"sha256:2845bd3b479be9db8556801e7ebc2746ce2d9edb4e7b64a1c786ecbfc1e5867b"
],
"index": "pypi",
"version": "==0.12"
"version": "==2.0"
},
"flask-login": {
"hashes": [
@ -58,6 +72,13 @@
"index": "pypi",
"version": "==0.4.1"
},
"flask-mail": {
"hashes": [
"sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41"
],
"index": "pypi",
"version": "==0.9.1"
},
"flask-migrate": {
"hashes": [
"sha256:6fb038be63d4c60727d5dfa5f581a6189af5b4e2925bc378697b4f0a40cfb4e1",
@ -159,39 +180,31 @@
},
"pillow": {
"hashes": [
"sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031",
"sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71",
"sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c",
"sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340",
"sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa",
"sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b",
"sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573",
"sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e",
"sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab",
"sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9",
"sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e",
"sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291",
"sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12",
"sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871",
"sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281",
"sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08",
"sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41",
"sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2",
"sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5",
"sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb",
"sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547",
"sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75",
"sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9",
"sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1",
"sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a",
"sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96",
"sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132",
"sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a",
"sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5",
"sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"
"sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be",
"sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946",
"sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837",
"sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f",
"sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00",
"sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d",
"sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533",
"sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a",
"sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358",
"sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda",
"sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435",
"sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2",
"sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313",
"sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff",
"sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317",
"sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2",
"sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614",
"sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0",
"sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386",
"sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9",
"sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636",
"sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865"
],
"index": "pypi",
"version": "==6.2.1"
"version": "==7.0.0"
},
"psycopg2": {
"hashes": [
@ -234,6 +247,14 @@
],
"version": "==1.0.4"
},
"python-magic": {
"hashes": [
"sha256:f2674dcfad52ae6c49d4803fa027809540b130db1dec928cfbb9240316831375",
"sha256:f3765c0f582d2dfc72c15f3b5a82aecfae9498bd29ca840d72f37d7bd38bfcd5"
],
"index": "pypi",
"version": "==0.4.15"
},
"six": {
"hashes": [
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
@ -243,15 +264,24 @@
},
"sqlalchemy": {
"hashes": [
"sha256:afa5541e9dea8ad0014251bc9d56171ca3d8b130c9627c6cb3681cff30be3f8a"
"sha256:bfb8f464a5000b567ac1d350b9090cf081180ec1ab4aa87e7bca12dab25320ec"
],
"version": "==1.3.11"
"version": "==1.3.12"
},
"sqlalchemy-utc": {
"hashes": [
"sha256:ec5395dfa4d237239c162a1b83283d88c8e2a94219512708634c55329c900278",
"sha256:fed53af37d250168b99eba8f9908a50e34e10dab3c32d38df3e65601ac951baf"
],
"index": "pypi",
"version": "==0.10.0"
},
"webassets": {
"hashes": [
"sha256:e7d9c8887343123fd5b32309b33167428cb1318cdda97ece12d0907fd69d38db"
"sha256:167132337677c8cedc9705090f6d48da3fb262c8e0b2773b29f3352f050181cd",
"sha256:a31a55147752ba1b3dc07dee0ad8c8efff274464e08bbdb88c1fd59ffd552724"
],
"version": "==0.12.1"
"version": "==2.0"
},
"werkzeug": {
"hashes": [
@ -420,9 +450,9 @@
},
"sqlalchemy": {
"hashes": [
"sha256:afa5541e9dea8ad0014251bc9d56171ca3d8b130c9627c6cb3681cff30be3f8a"
"sha256:bfb8f464a5000b567ac1d350b9090cf081180ec1ab4aa87e7bca12dab25320ec"
],
"version": "==1.3.11"
"version": "==1.3.12"
},
"werkzeug": {
"hashes": [

@ -6,11 +6,16 @@ from flask_migrate import Migrate
from flask_login import LoginManager
from flask_assets import Environment as AssetsEnvironment, Bundle as AssetsBundle
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
db = SQLAlchemy()
migrate = Migrate()
login = LoginManager()
assets = AssetsEnvironment()
admin = Admin()
def create_app():
app = Flask(__name__)
@ -18,6 +23,11 @@ def create_app():
SECRET_KEY='dev',
#SQLALCHEMY_DATABASE_URI='sqlite:///{}'.format(os.path.join(app.instance_path, 'yadc.db')),
SQLALCHEMY_DATABASE_URI='postgresql://{}:{}@{}:{}/{}'.format('yadc', 'omegalul', 'localhost', 5432, 'yadc'),
MAX_CONTENT_LENGTH=10*1024*1024,
POST_LIST_THUMB_HEIGHT=200,
POST_UPLOADS=os.path.join(app.instance_path, 'post'),
SQLALCHEMY_ECHO=True,
)
try:
@ -30,15 +40,35 @@ def create_app():
login.init_app(app)
assets.init_app(app)
from yadc import main, auth
from yadc import models
admin.init_app(app)
admin.add_view(ModelView(models.User, db.session))
admin.add_view(ModelView(models.Post, db.session))
admin.add_view(ModelView(models.Tag, db.session))
admin.add_view(ModelView(models.Comment, db.session))
from yadc.bp import main, auth
app.register_blueprint(main.bp)
app.register_blueprint(auth.bp)
app.register_blueprint(auth.bp, url_prefix='/user')
login.login_view = 'auth.login'
#assets.url = app.static_url_path
scss = AssetsBundle('default.scss', filters='libsass', output='all.css')
assets.register('scss_all', scss)
from yadc import models
return app
from yadc import models as m
app = create_app()
@app.shell_context_processor
def make_shell_context():
ctxt = {'db': db}
for mdl in [m.User, m.Post, m.Tag, m.Comment]:
ctxt[mdl.__name__] = mdl
return ctxt
# return {'db': db, 'User': models.User, 'Post': models.Post, 'Tag': models.Tag, 'Comment': models.Comment}

@ -1,40 +0,0 @@
from flask import request
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
from flask_login import current_user, login_user, logout_user
from werkzeug.urls import url_parse
from yadc.models import User
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember me')
submit = SubmitField('Log In')
from flask import Blueprint, render_template, flash, redirect, url_for
bp = Blueprint('auth', __name__)
@bp.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
#flash('Login requested for user {}, remember_me={}'.format(form.username.data, form.remember_me.data))
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password.')
return redirect(url_for('auth.login'))
login_user(user, remember=form.remember_me.data)
nextpg = request.args.get('next')
if not nextpg and url_parse(nextpg).netloc != '':
nextpg = url_for('main.index')
#flash('Logged in as {}'.format(user.username))
return redirect(nextpg)
return render_template('login.html', form=form)
@bp.route('/logout')
def logout():
logout_user()
return redirect(url_for('main.index'))

@ -0,0 +1,82 @@
import flask_login as fl
from flask import Blueprint, flash, redirect, render_template, request, url_for
from werkzeug.urls import url_parse
from wtforms import BooleanField, PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired
from yadc import db
from yadc.forms import LoginForm, RegisterForm, ResetPasswordForm
from yadc.models import User
bp = Blueprint('auth', __name__)
def nextpage():
nextpg = request.args.get('next')
if not nextpg or url_parse(nextpg).netloc != '':
nextpg = url_for('main.index')
return nextpg
@bp.route('/login/', methods=['GET', 'POST'])
def login():
if fl.current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password.')
return redirect(url_for('.login'))
user.login(remember=form.remember_me.data)
db.session.commit()
flash('Logged in as {}'.format(user.username))
return redirect(nextpage())
return render_template('login.html', form=form)
@bp.route('/logout/')
def logout():
fl.current_user.logout()
return redirect(nextpage())
@bp.route('/reset_password/', methods=['GET', 'POST'])
def reset_password():
if fl.current_user.is_authenticated:
return redirect(url_for('main.index'))
form = ResetPasswordForm(request.form)
if request.method == 'POST' and form.validate():
user = User.query.filter_by(email=form.email.data).first()
if user:
user.create_password('kuxaman')
db.session.commit()
#do something to reset the password
flash('Password successfully reset. Check your email.')
return redirect(url_for('.login'))
return render_template('reset_password.html', form=form)
@bp.route('/register/', methods=['GET', 'POST'])
def register():
if fl.current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegisterForm(request.form)
if request.method == 'POST' and form.validate():
user = User(username=form.username.data, email=form.email.data)
user.create_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Your account has been successfully registered. You can now login.')
return redirect(url_for('.login'))
return render_template('register.html', form=form)

@ -0,0 +1,123 @@
from flask import Blueprint, render_template, flash, redirect, url_for, request, abort
from flask_login import login_required
bp = Blueprint('main', __name__)
@bp.route('/')
def index():
return posts()
# @bp.route('/')
@bp.route('/post/')
def posts():
posts = Post.query.order_by(Post.created).limit(20).all()
# flash(posts)
tagset = set()
taglist = list()
for post in posts:
for tag in post.tags:
tagset.add(tag)
taglist.append(tag)
for tag in tagset:
tag.count = taglist.count(tag)
tags = list(tagset)
tags.sort(key=lambda x: (x.category.value, x.content) )
# flash([tag.count for tag in tags])
return render_template('index.html', posts=posts, tags=tags)
@bp.route('/post/show/<id>/')
def post_show(id):
post = Post.query.filter_by(id=id)
flash(post)
return render_template('post.html', post=post, tags=tags)
from flask_login import current_user
from yadc import db
from yadc.forms import UploadPostForm
from yadc.models import Post, RATING, FILETYPE
from flask import current_app
from PIL import Image
import io
import os
# @bp.route('/post/new/')
# @bp.route('/post/create/')
@bp.route('/post/upload/', methods=['GET', 'POST'])
@login_required
def post_upload():
form = UploadPostForm(request.form)
if request.method == 'POST' and form.validate():
file = request.files.get(form.post_img.name)
file.data = io.BytesIO(file.read())
# tagy
post = Post(file, source=form.sauce.data, rating=RATING[form.rating.data], author=current_user)
with open(post.image_path, "wb") as f:
f.write(file.data.getbuffer())
# file.seek(0)
# file.save(post.image_path)
with Image.open(file.data) as im:
im = im.convert('RGB')
if post.jpeg_path is not None:
im.save(post.jpeg_path, 'JPEG', quality=80)
im.thumbnail([512,512])
im.save(post.thumb_path, 'JPEG', quality=80)
db.session.add(post)
db.session.commit()
flash('Successfully submitted {}'.format(str(post)))
return redirect(url_for('main.post_upload'))
return render_template('upload.html', form=form)
from flask import send_from_directory
@bp.route('/i/<path:path>')
def uploaded_img(path, store='img', exten=None):
ext = os.path.splitext(path)[1]
if exten is not None and ext.split('.')[1] != exten:
abort(404)
filename = path.split('/')[0].split('.')[0]
# print(os.path.join(current_app.config.get('POST_UPLOADS'), store, "{}{}".format(filename, ext)))
return send_from_directory(os.path.join(current_app.config.get('POST_UPLOADS'), store), "{}{}".format(filename, ext))
@bp.route('/jpeg/<path:path>')
def uploaded_jpeg(*args, **kwargs):
return uploaded_img(*args, **kwargs, store='jpeg', exten='jpg')
@bp.route('/thumb/<path:path>')
def uploaded_thumb(*args, **kwargs):
return uploaded_img(*args, **kwargs, store='thumb', exten='jpg')
@bp.route('/threads/')
def threads():
return render_template('index.html')
@bp.route('/user/show/<username>/')
def user_profile(username):
pass
@bp.route('/user/settings/')
@login_required
def user_settings():
pass

@ -0,0 +1,81 @@
from wtforms import Form
from wtforms import StringField, PasswordField, BooleanField, SubmitField, FileField, MultipleFileField, ValidationError, RadioField
from wtforms.validators import DataRequired, InputRequired, Email, EqualTo, AnyOf
from werkzeug.utils import cached_property
from flask import current_app
from flask_wtf.csrf import _FlaskFormCSRF
class CSRFForm(Form):
class Meta:
csrf = True
csrf_class = _FlaskFormCSRF
@cached_property
def csrf_secret(self):
return current_app.secret_key
csrf_time_limit = 3600
csrf_field_name = 'csrf_token'
class LoginForm(CSRFForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember me')
submit = SubmitField('Log In')
from yadc.models import User
class ResetPasswordForm(CSRFForm):
email = StringField('E-mail', validators=[DataRequired(), Email()])
submit = SubmitField('Reset password')
# def validate_email(form, field):
# email = User.query.filter_by(email=field.data).first()
# if not email:
# raise ValidationError('This')
class RegisterForm(CSRFForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('E-mail', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
password_again = PasswordField('Repeat password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Register')
def validate_username(form, field):
user = User.query.filter_by(username=field.data).first()
if user is not None:
raise ValidationError('Username already taken. Try different username.')
def validate_email(form, field):
email = User.query.filter_by(email=field.data).first()
if email is not None:
raise ValidationError('This email address is already registered. Maybe try logging in instead?')
from flask import request
# from magic import Magic
def validate_file(form, field):
file = request.files.get(field.name)
if not file or file.filename == '':
raise ValidationError('Please select a file')
class UploadPostForm(CSRFForm):
post_img = FileField('Image', validators=[validate_file], render_kw={'required':''})
sauce = StringField('Sauce', validators=[DataRequired()])
tags = StringField('Tags')
rating = RadioField('Rating',
choices=[('safe', 'S'), ('questionable', 'Q'), ('explicit', 'E')],
default='safe',
validators=[DataRequired()])
submit = SubmitField('Upload')
def validate_post_img(form, field):
file = request.files.get(field.name)
client_mimetype = file.mimetype
# Not sure if safe
# real_mimetype = Magic(mime=True).from_buffer(file.stream.read())
if client_mimetype not in ['image/png','image/jpeg']:
raise ValidationError('Please select an image file of PNG or JPEG format')

@ -1,40 +0,0 @@
from flask import Blueprint, render_template, flash, redirect
from flask_login import login_required
bp = Blueprint('main', __name__)
@bp.route('/index')
def index():
return render_template('index.html')
@bp.route('/humm')
@login_required
def humm():
return render_template('index.html')
@bp.route('/')
@bp.route('/post')
def post():
return render_template('index.html')
pass
@bp.route('/post/show/<id>')
def post_show(id):
return render_template('post.html')
pass
@bp.route('/post/new')
@bp.route('/post/create')
@bp.route('/post/upload')
@login_required
def post_upload():
pass
@bp.route('/user/show/<username>')
def user_profile(username):
pass
@bp.route('/user/settings')
@login_required
def user_settings():
pass

@ -1,8 +1,7 @@
import enum
from datetime import datetime
from flask_login import UserMixin
from werkzeug.security import check_password_hash, generate_password_hash
from sqlalchemy_utc import UtcDateTime, utcnow
# from sqlalchemy.sql.functions import now as current_timestamp
from yadc import db, login
@ -13,6 +12,10 @@ class OP_LEVEL(enum.Enum):
moderator = 5
admin = 9
class USER_STATUS(enum.Enum):
active = 0
banned = 1
class FILETYPE(enum.Enum):
png = 0
jpeg = 1
@ -35,15 +38,21 @@ class TAG_CATEGORY(enum.Enum):
character = 4
copyright = 5
class User(UserMixin, db.Model):
class TimestampMixin(object):
created = db.Column(UtcDateTime, nullable=False, default=utcnow())
updated = db.Column(UtcDateTime, nullable=False, onupdate=utcnow(), default=utcnow())
from flask_login import UserMixin, login_user, logout_user
from werkzeug.security import check_password_hash, generate_password_hash
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)
created = db.Column(db.DateTime, default=datetime.now())
updated = db.Column(db.DateTime, default=datetime.now())
last_login = db.Column(db.DateTime)
user_status = db.Column(db.Enum(USER_STATUS), default=USER_STATUS.active, nullable=False)
last_login = db.Column(UtcDateTime)
#authored_posts = db.relationship('Post', back_populates='author')
#approved_posts = db.relationship('Post', back_populates='approver')
@ -57,6 +66,21 @@ class User(UserMixin, db.Model):
def check_password(self, password):
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]
@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))
@ -65,13 +89,18 @@ 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'))
)
from PIL import Image
import hashlib
import os
from werkzeug.utils import cached_property
from flask import current_app, url_for
class Post(db.Model):
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)
created = db.Column(db.DateTime, default=datetime.now())
updated = db.Column(db.DateTime, default=datetime.now())
rating = db.Column(db.Enum(RATING), nullable=False)
status = db.Column(db.Enum(POST_STATUS), default=POST_STATUS.pending, nullable=False)
@ -79,6 +108,9 @@ class Post(db.Model):
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'))
@ -87,18 +119,70 @@ class Post(db.Model):
#tags = db.relationship('Tag', secondary=post_tags, back_populates='posts')
tags = db.relationship('Tag', secondary=post_tags, backref=db.backref('posts'))
class Tag(db.Model):
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'
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', 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.instance_path, 'post', '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.instance_path, 'post', 'jpeg', jpeg_filename)
@property
def thumb_path(self):
jpeg_filename = "{}.{}".format(self.md5, 'jpg')
return os.path.join(current_app.instance_path, 'post', 'thumb', jpeg_filename)
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)
created = db.Column(db.DateTime, default=datetime.now())
updated = db.Column(db.DateTime, default=datetime.now())
#posts = db.relationship('Post', secondary=post_tags, back_populates='tags')
class Comment(db.Model):
def __repr__(self):
return '<Tag {}, {}>'.format(self.content, self.category.name)
class Comment(TimestampMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
created = db.Column(db.DateTime, default=datetime.now())
content = db.Column(db.String(512))
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))

@ -152,11 +152,11 @@ header {
position: relative;
.user_dropdown {
//display: none;
opacity: .1; // BOI DAT HACK
height: 0;
width: 0;
overflow: hidden;
display: none;
// opacity: .1; // BOI DAT HACK
// height: 0;
// width: 0;
// overflow: hidden;
a {
padding: 10px;
@ -174,16 +174,16 @@ header {
position: absolute;
margin: 0;
top: 100%;
left: 0;
// left: 0;
right: 0;
z-index: 10;
background-color: $nav-bg-color;
opacity: 1; // BOI DAT HACK
height: unset;
width: unset;
transition: .2s ease;
// opacity: 1; // BOI DAT HACK
// height: unset;
// width: unset;
// transition: .2s ease;
}
}
}
@ -251,9 +251,10 @@ header {
padding: 10px;
article {
&:not(:last-child) {
margin-bottom: 10px;
}
// &:not(:last-child) {
// margin-bottom: 10px;
// }
margin-bottom: 10px;
}
article.tags {
@ -329,7 +330,7 @@ header {
&::after {
content: "";
flex: /*10000*/350 0 350px;
flex: 10000/*350*/ 0 350px;
}
> figure {

@ -0,0 +1,9 @@
{% macro errors(field) %}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}

@ -13,21 +13,31 @@
</head>
<body>
<header>
<a href="#" class="logo">
<a href="{{ url_for('main.index') }}" class="logo">
<span>YaDc</span>
</a>
<nav id="main-nav">
<a href="#">Posts</a>
<a href="#">Threads</a>
<a href="#">Upload</a>
<a href="{{ url_for('main.posts') }}">Posts</a>
<!-- <a href="{{ url_for('main.threads') }}">Threads</a> -->
<a href="{{ url_for('main.post_upload') }}">Upload</a>
<div class="user">
<a href="#" id="user-menu">KuxaBeast</a>
{% if current_user.is_anonymous %}
<a href="#" id="user-menu">Logged out</a>
{% else %}
<a href="#" id="user-menu">{{ current_user.username }}</a>
{% endif %}
<!--<span class="user fa fa-user"></span>-->
<div class="user_dropdown">
{% if current_user.is_anonymous %}
<a href="{{ url_for('auth.login') }}">Login</a>
<a href="{{ url_for('auth.register') }}">Register</a>
{% else %}
<a href="#">Profile</a>
<a href="#">Settings</a>
<a href="#">Log out</a>
<a href="{{ url_for('auth.logout') }}">Log out</a>
{% endif%}
</div>
</div>
<div class="_overlay"></div>
@ -35,8 +45,18 @@
<span id="nav-menu" class="fa fa-bars"></span>
</header>
{% with msgs = get_flashed_messages() %}
{% if msgs %}
<ul>
{% for msg in msgs %}
<li>{{ msg }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<div class="main_wrap">
{% block content %}{% endblock %}
{% block content %}{% endblock %}
</div>
<footer>

@ -6,172 +6,36 @@
<article class="tags">
<h3>Tags</h3>
<div class="tag_container">
{% for tag in tags %}
<a href="#">
<span class="fa fa-tag"></span>
<span class="name" href="#">neko</span>
<span class="count">10</span>
<span class="name">{{ tag.content }}</span>
<span class="count">{{ tag.count }}</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">works</span>
<span class="count">2</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">anime</span>
<span class="count">4</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">manga</span>
<span class="count">7</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">original</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">azur lane</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">figure</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">touhou</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">photoshop</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">close</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">cropped</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">waifu2x</span>
<span class="count">5</span>
</a>
{% endfor %}
</div>
</article>
</section>
<section class="post_list">
<div class="posts">
<figure style="flex: 307.87 1 307.87px;">
{% for post in posts %}
<figure style="{{ post.flex }}">
<a href="{{ url_for('main.post_show', id=post.id) }}">
<img src="{{ post.url(path=post.image_url, endpoint='thumb') }}" alt="">
</a>
</figure>
{% endfor %}
<!-- <figure style="flex: 307.87 1 307.87px;">
<a href="#">
<img src="static/ryuzu/916993.png" alt="">
<img src="/static/ryuzu/916993.png" alt="">
</a>
<!--<div class="thumb-info">
<div class="thumb-info">
<span>RyuZU²</span>
<span>1920x1080</span>
<span>1.5M</span>
</div>-->
</figure>
<figure style="flex: 309.68 1 309.68px;">
<img src="static/ryuzu/916500.jpg" alt="">
</figure>
<figure style="flex: 157.98 1 157.98px;">
<img src="static/pixiv/illust_76819896_20191017_131929.jpg" alt="">
</figure>
<figure style="flex: 391.11 1 391.11px;">
<img src="static/ryuzu/916132.png" alt="">
</figure>
<figure style="flex: 220.00 1 220.00px;">
<img src="static/pixiv/illust_75885180_20191105_174853.jpg" alt="">
</figure>
<figure style="flex: 334.97 1 334.97px;">
<img src="static/ryuzu/916486.png" alt="">
</figure>
<figure style="flex: 155.53 1 155.53px;">
<img src="static/pixiv/illust_71840908_20191117_162206.png" alt="">
</figure>
<figure style="flex: 391.11 1 391.11px;">
<img src="static/ryuzu/916269.jpg" alt="">
</figure>
<figure style="flex: 131.98 1 131.98px;">
<img src="static/pixiv/illust_72206228_20191026_131238.jpg" alt="">
</figure>
<figure style="flex: 391.11 1 391.11px;">
<img src="static/ryuzu/916234.png" alt="">
</figure>
<figure style="flex: 157.75 1 157.75px;">
<img src="static/pixiv/illust_66441619_20191117_162011.png" alt="">
</figure>
<figure style="flex: 340.92 1 340.92px;">
<img src="static/ryuzu/917345.png" alt="">
</figure>
<figure style="flex: 155.28 1 155.28px;">
<img src="static/pixiv/illust_71851314_20191017_131351.jpg" alt="">
</figure>
<figure style="flex: 356.46 1 356.46px;">
<img src="static/ryuzu/917364.png" alt="">
</figure>
<figure style="flex: 155.55 1 155.55px;">
<img src="static/pixiv/illust_74700365_20191026_131807.jpg" alt="">
</figure>
<figure style="flex: 391.11 1 391.11px;">
<img src="static/ryuzu/916232.png" alt="">
</figure>
<figure style="flex: 229.12 1 229.12px;">
<img src="static/pixiv/illust_73298123_20191017_131918.jpg" alt="">
</figure>
<figure style="flex: 318.79 1 318.79px;">
<img src="static/ryuzu/917571.png" alt="">
</figure>
<figure style="flex: 155.56 1 155.56px;">
<img src="static/pixiv/illust_72068885_20191017_131812.jpg" alt="">
</figure>
<figure style="flex: 361.95 1 361.95px;">
<img src="static/ryuzu/917362.png" alt="">
</figure>
<figure style="flex: 329.28 1 329.28px;">
<img src="static/pixiv/illust_74981463_20191010_180604.png" alt="">
</figure>
<figure style="flex: 201.44 1 201.44px;">
<img src="static/pixiv/illust_73112550_20191017_132201.jpg" alt="">
</figure>
<figure style="flex: 134.56 1 134.56px;">
<img src="static/pixiv/illust_58205189_20191117_161917.jpg" alt="">
</figure>
<figure style="flex: 170.19 1 170.19px;">
<img src="static/pixiv/illust_75746945_20191026_131754.png" alt="">
</figure>
<figure style="flex: 155.54 1 155.54px;">
<img src="static/pixiv/illust_77361639_20191026_131342.png" alt="">
</figure>
<figure style="flex: 220.00 1 220.00px;">
<img src="static/pixiv/illust_76970188_20191105_175035.jpg" alt="">
</figure>
<figure style="flex: 134.89 1 134.89px;">
<img src="static/pixiv/illust_77544221_20191117_162451.png" alt="">
</figure>
<figure style="flex: 171.16 1 171.16px;">
<img src="static/pixiv/illust_76606724_20191027_110006.png" alt="">
</figure>
<figure style="flex: 107.11 1 107.11px;">
<img src="static/pixiv/illust_71850098_20191017_131539.jpg" alt="">
</figure>
<figure style="flex: 220.99 1 220.99px;">
<img src="static/pixiv/illust_76418587_20191027_110252.png" alt="">
</figure>
<figure style="flex: 155.78 1 155.78px;">
<img src="static/pixiv/illust_76629400_20191026_131307.jpg" alt="">
</figure>
</div>
</figure> -->
</div>
<div class="pagin">
<a href="#"><span class="fa fa-chevron-left"></span></a>

@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% from "_formhelpers.html" import errors %}
{% block content %}
<h1>Log In</h1>
<form action="" method="post">
{{ form.csrf_token }}
<div>
{{ form.username.label }} {{ form.username(size=32) }}
{{ errors(form.username) }}
</div>
<div>
{{ form.password.label }} {{ form.password(size=32) }}
{{ errors(form.password) }}
</div>
<div>
{{ form.remember_me() }} {{ form.remember_me.label }}
{{ errors(form.remember_me) }}
</div>
<div>{{ form.submit() }}</div>
</form>
<a href="{{ url_for('auth.reset_password') }}">Forgoten password?</a>
{% endblock content %}

@ -15,66 +15,13 @@
<article class="tags">
<h3>Tags</h3>
<div class="tag_container">
{% for tag in tags %}
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">neko</span>
<span class="name">{{ tag.content }}</span>
<span class="count">10</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">works</span>
<span class="count">2</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">anime</span>
<span class="count">4</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">manga</span>
<span class="count">7</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">original</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">azur lane</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">figure</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">touhou</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">photoshop</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">close</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">cropped</span>
<span class="count">5</span>
</a>
<a href="#">
<span class="fa fa-tag"></span>
<span class="name">waifu2x</span>
<span class="count">5</span>
</a>
{% endfor %}
</div>
</article>
</section>
@ -86,13 +33,11 @@
</section>
</div>
<section class="comments">
{% for comment in comments %}
<article>
<h4>Kuxa</h4>
<p>what the fuck actually happened here?</p>
</article>
<article>
<h4>Glum</h4>
<p>no tea 4 ya</p>
<h4>{{ comment.user.username }}</h4>
<p>{{ comment.content }}</p>
</article>
{% endfor %}
</section>
{% endblock content %}

@ -0,0 +1,26 @@
{% extends 'base.html' %}
{% from "_formhelpers.html" import errors %}
{% block content %}
<h1>Log In</h1>
<form action="" method="post">
{{ form.csrf_token }}
<div>
{{ form.username.label }} {{ form.username(size=32) }}
{{ errors(form.username) }}
</div>
<div>
{{ form.email.label }} {{ form.email(size=32) }}
{{ errors(form.email) }}
</div>
<div>
{{ form.password.label }} {{ form.password(size=32) }}
{{ errors(form.password) }}
</div>
<div>
{{ form.password_again.label }} {{ form.password_again(size=32) }}
{{ errors(form.password_again) }}
</div>
<div>{{ form.submit() }}</div>
</form>
{% endblock content %}

@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% from "_formhelpers.html" import errors %}
{% block content %}
<h1>Log In</h1>
<form action="" method="post">
{{ form.csrf_token }}
<div>
{{ form.email.label }} {{ form.email(size=32) }}
{{ errors(form.email) }}
</div>
<div>{{ form.submit() }}</div>
</form>
{% endblock content %}

@ -0,0 +1,26 @@
{% extends 'base.html' %}
{% from "_formhelpers.html" import errors %}
{% block content %}
<h1>Upload image</h1>
<form action="" method="post" enctype="multipart/form-data">
{{ form.csrf_token }}
<div>
{{ form.post_img.label }} {{ form.post_img() }}
{{ errors(form.post_img) }}
</div>
<div>
{{ form.sauce.label }} {{ form.sauce() }}
{{ errors(form.sauce) }}
</div>
<div>
{{ form.tags.label }} {{ form.tags() }}
{{ errors(form.tags) }}
</div>
<div>
{{ form.rating.label }} {% for r in form.rating %}{{ r.label }}{{ r() }}{% endfor %}
{{ errors(form.rating) }}
</div>
<div>{{ form.submit() }}</div>
</form>
{% endblock content %}
Loading…
Cancel
Save