diff --git a/nerochan/config.py b/nerochan/config.py index 6a498d1..88265cd 100644 --- a/nerochan/config.py +++ b/nerochan/config.py @@ -27,6 +27,7 @@ DATA_PATH = getenv('DATA_PATH', f'{getcwd()}/data') SESSION_LENGTH = int(getenv('SESSION_LENGTH', 300)) PERMANENT_SESSION_LIFETIME = timedelta(minutes=SESSION_LENGTH) MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50 MB +ALLOWED_UPLOADS = ['jpg', 'jpeg', 'png', 'svg', 'gif', 'mp4', 'webm'] # Development TEMPLATES_AUTO_RELOAD = True diff --git a/nerochan/forms.py b/nerochan/forms.py index 01cd418..9a5788d 100644 --- a/nerochan/forms.py +++ b/nerochan/forms.py @@ -1,6 +1,7 @@ from flask_wtf import FlaskForm -from wtforms import StringField, FloatField +from wtforms import StringField, BooleanField from wtforms.validators import DataRequired, ValidationError +from flask_wtf.file import FileField, FileRequired, FileAllowed from monero.address import address from nerochan.models import User @@ -46,3 +47,9 @@ class ConfirmTip(FlaskForm): tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={'placeholder': 'TX ID', 'class': 'u-full-width', 'type': 'text'}) tx_key = StringField('TX Key:', validators=[DataRequired()], render_kw={'placeholder': 'TX Key', 'class': 'u-full-width', 'type': 'text'}) + +class CreateArtwork(FlaskForm): + title = StringField('Title:', validators=[DataRequired()], render_kw={'placeholder': 'Title', 'class': 'u-full-width', 'type': 'text'}) + description = StringField('Description:', validators=[], render_kw={'placeholder': 'Description', 'class': 'u-full-width', 'type': 'text'}) + nsfw = BooleanField('NSFW:') + content = FileField('Upload:', validators=[FileRequired(), FileAllowed(config.ALLOWED_UPLOADS)]) diff --git a/nerochan/models.py b/nerochan/models.py index 568717f..f68a801 100644 --- a/nerochan/models.py +++ b/nerochan/models.py @@ -2,7 +2,7 @@ from os import path from datetime import datetime from secrets import token_urlsafe -from PIL import Image, ImageSequence +from PIL import Image, ImageSequence, ImageFilter import peewee as pw @@ -86,6 +86,7 @@ class Artwork(pw.Model): last_edit_date = pw.DateTimeField(default=datetime.utcnow) approved = pw.BooleanField(default=False) hidden = pw.BooleanField(default=False) + nsfw = pw.BooleanField(default=False) title = pw.CharField() description = pw.TextField(null=True) @@ -98,8 +99,6 @@ class Artwork(pw.Model): _t = f'thumbnail-{self.image}' i = f'{config.DATA_PATH}/uploads/{self.image}' t = f'{config.DATA_PATH}/uploads/{_t}' - if path.exists(t): - return True try: size = (150,150) image = Image.open(i) @@ -109,14 +108,21 @@ class Artwork(pw.Model): for frame in frames: thumbnail = frame.copy() thumbnail.thumbnail(size, Image.ANTIALIAS) + if self.nsfw: + thumbnail = thumbnail.filter(ImageFilter.GaussianBlur(radius = 4)) yield thumbnail _frames = thumbnails(frames) _image = next(_frames) _image.info = image.info + _image.save(t, format=image.format, save_all=True, append_images=list(_frames), disposal=2) else: image.thumbnail(size, Image.ANTIALIAS) + if self.nsfw: + image = image.filter(ImageFilter.GaussianBlur(radius = 4)) image.save(t, format=image.format) + + image.close() self.thumbnail = _t self.save() diff --git a/nerochan/routes/artwork.py b/nerochan/routes/artwork.py index 461f7e6..681ac51 100644 --- a/nerochan/routes/artwork.py +++ b/nerochan/routes/artwork.py @@ -1,9 +1,11 @@ from pathlib import Path +from secrets import token_urlsafe from flask import Blueprint, render_template, flash, redirect, url_for, request from flask_login import login_required, current_user +from werkzeug.utils import secure_filename -from nerochan.forms import ConfirmTip +from nerochan.forms import ConfirmTip, CreateArtwork from nerochan.decorators import admin_required from nerochan.models import Artwork, Transaction from nerochan import config @@ -65,12 +67,16 @@ def manage(id, action): elif not artwork.hidden: flash('Cannot delete an artwork unless it is hidden first', 'warning') return redirect(url_for('artwork.show', id=artwork.id)) - base = Path(config.DATA_PATH).joinpath('uploads') + base = Path(config.DATA_PATH, 'uploads') base.joinpath(artwork.image).unlink(missing_ok=True) base.joinpath(artwork.thumbnail).unlink(missing_ok=True) artwork.delete_instance() flash('Artwork has been deleted from the system.', 'success') return redirect(url_for('artwork.hidden')) + elif action == 'regenerate_thumbnail': + artwork.generate_thumbnail() + flash(f'Generated new thumbnail for artwork {artwork.id}', 'success') + return redirect(url_for('artwork.show', id=artwork.id)) return redirect(url_for('artwork.pending')) @@ -116,7 +122,37 @@ def show(id): form=form ) -@bp.route('/new', methods=['GET', 'POST']) +@bp.route('/create', methods=['GET', 'POST']) @login_required def create(): - return 'upload your artwork' \ No newline at end of file + form = CreateArtwork() + if form.validate_on_submit(): + rand = token_urlsafe(12) + f = form.content.data + filename = secure_filename(f'{rand}-{f.filename}') + try: + f.save(Path(config.DATA_PATH, 'uploads', filename)) + except Exception as e: + flash(f'There was an issue saving the file: {e}') + return redirect(request.referrer) + + artwork = Artwork( + user=current_user, + image=filename, + approved=current_user.is_verified, + nsfw=form.nsfw.data, + title=form.title.data, + description=form.description.data + ) + artwork.save() + artwork.generate_thumbnail() + if current_user.is_verified: + return redirect(url_for('artwork.show', id=artwork.id)) + else: + flash('Artwork has been posted! Please wait for an admin to review and approve it.', 'success') + return redirect(url_for('main.index')) + + return render_template( + 'artwork/create.html', + form=form + ) \ No newline at end of file diff --git a/nerochan/static/css/main.css b/nerochan/static/css/main.css index ddc5f47..b53f34c 100644 --- a/nerochan/static/css/main.css +++ b/nerochan/static/css/main.css @@ -12,6 +12,10 @@ a, a:visited { margin-left: 2em; } +.ml-4 { + margin-left: 4em; +} + .mt-4 { margin-top: 4em; } diff --git a/nerochan/templates/artwork/create.html b/nerochan/templates/artwork/create.html new file mode 100644 index 0000000..a318878 --- /dev/null +++ b/nerochan/templates/artwork/create.html @@ -0,0 +1,34 @@ +{% extends 'includes/base.html' %} + +{% block content %} + +
+
+
+

create

+

Upload new artwork for the community. Please limit submissions to Monero-Chan (and Wownero-Chan) content only. Please tag NSFW appropriately.

+
+ {% for f in form %} + {% if f.name == 'csrf_token' %} + {{ f }} + {% else %} + {{ f.label }} + {{ f }} + {% endif %} + {% endfor %} +
    + {%- for field, errors in form.errors.items() %} +
  • {{ form[field].label }}: {{ ', '.join(errors) }}
  • + {%- endfor %} +
+ +
+
+
+
+ +{% endblock %} + + + + diff --git a/nerochan/templates/artwork/show.html b/nerochan/templates/artwork/show.html index 01cc76b..deb3e80 100644 --- a/nerochan/templates/artwork/show.html +++ b/nerochan/templates/artwork/show.html @@ -10,17 +10,22 @@ posted by {{ artwork.user.handle }} - {{ artwork.upload_date | humanize }}

{{ artwork.description }}

- {% if not artwork.approved %} -
- {% if artwork.hidden %} - - {% else %} - - {% endif %} - - -
- {% endif %} +
+ {% if not artwork.approved %} + {% if artwork.hidden %} + + {% else %} + + {% endif %} + + + {% endif %} + {% if current_user.is_authenticated and current_user.is_admin %} + + + + {% endif %} +

diff --git a/nerochan/templates/includes/navbar.html b/nerochan/templates/includes/navbar.html index 8aa6f02..9f8813f 100644 --- a/nerochan/templates/includes/navbar.html +++ b/nerochan/templates/includes/navbar.html @@ -4,6 +4,7 @@ {%- if current_user.is_authenticated %} + {%- if current_user.is_admin %} {% endif %}