Fixed Flask not choosing new name for uploading

Moved uploading and removing files to APIs
Added max file upload to config from yml file
Jquery is now a file not a CDN
General Sass(y) fixes
This commit is contained in:
Michał Gdula 2023-01-11 19:46:31 +00:00
parent 5db8fa52e8
commit 2455d3f88c
13 changed files with 131 additions and 95 deletions

View file

@ -32,13 +32,13 @@ def create_app(test_config=None):
with open(os.path.join(app.root_path, 'user', 'conf.yml'), 'r') as f: with open(os.path.join(app.root_path, 'user', 'conf.yml'), 'r') as f:
conf = yaml.load(f, Loader=yaml.FullLoader) conf = yaml.load(f, Loader=yaml.FullLoader)
print("Loaded config") print("Loaded config")
print(conf['upload']['allowed-extensions'])
# App configuration # App configuration
app.config.from_mapping( app.config.from_mapping(
SECRET_KEY=os.environ.get('FLASK_SECRET'), SECRET_KEY=os.environ.get('FLASK_SECRET'),
DATABASE=os.path.join(app.instance_path, 'gallery.sqlite'), DATABASE=os.path.join(app.instance_path, 'gallery.sqlite'),
UPLOAD_FOLDER=os.path.join(app.root_path, 'user', 'uploads'), UPLOAD_FOLDER=os.path.join(app.root_path, 'user', 'uploads'),
MAX_CONTENT_LENGTH = 1024 * 1024 * conf['upload']['max-size'],
ALLOWED_EXTENSIONS=conf['upload']['allowed-extensions'], ALLOWED_EXTENSIONS=conf['upload']['allowed-extensions'],
) )

View file

@ -1,8 +1,12 @@
from flask import Blueprint, render_template, current_app, send_from_directory from flask import Blueprint, render_template, current_app, send_from_directory, request, g, abort, flash
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from gallery.auth import login_required
from gallery.db import get_db
from PIL import Image
import os import os
from uuid import uuid4
blueprint = Blueprint('viewsbp', __name__, url_prefix='/') blueprint = Blueprint('viewsbp', __name__, url_prefix='/api')
@blueprint.route('/uploads/<quality>/<file>') @blueprint.route('/uploads/<quality>/<file>')
@ -11,3 +15,81 @@ def uploads(quality, file):
file = secure_filename(file) file = secure_filename(file)
return send_from_directory(dir, file, as_attachment=True) return send_from_directory(dir, file, as_attachment=True)
@blueprint.route('/upload', methods=['POST'])
@login_required
def upload():
file = request.files['file']
form = request.form
# Check if file has been submitted
if not file:
flash('No selected file')
return abort(404)
# New file name and check if file extension is allowed
file_ext = os.path.splitext(file.filename)[1].lower()
file_name = f"GWAGWA_{uuid4().__str__()}{file_ext}"
if not file_ext in current_app.config['ALLOWED_EXTENSIONS']:
return 'File extension not allowed: '+file_ext
try:
file.save(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER']+'/original', file_name))
except:
return 'Could not save file'
# Resize image
thumbnail_size = 300, 300
preview_size = 1000, 1000
img_file = Image.open(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER']+'/original', file_name))
try:
# save thumbnail
img_file.thumbnail(thumbnail_size, Image.Resampling.LANCZOS)
img_file.save(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER']+'/thumbnail', file_name))
# save preview
img_file.thumbnail(preview_size, Image.Resampling.LANCZOS)
img_file.save(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER']+'/preview', file_name))
except Exception as e:
return 'Could not resize image: '+ str(e)
db = get_db()
db.execute(
'INSERT INTO posts (file_name, author_id, description, alt)'
' VALUES (?, ?, ?, ?)',
(file_name, g.user['id'], form['description'], form['alt'])
)
db.commit()
return 'Gwa Gwa'
@blueprint.route('/remove/<int:id>', methods=['POST'])
@login_required
def remove(id):
image = get_db().execute(
'SELECT author_id, file_name FROM posts WHERE id = ?',
(id,)
).fetchone()
if image is None:
abort(404)
if image['author_id'] != g.user['id']:
abort(403)
try:
os.remove(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER'], 'original', image['file_name']))
os.remove(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER'], 'thumbnail', image['file_name']))
os.remove(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER'], 'preview', image['file_name']))
except Exception as e:
return 'file error: '+str(e)
try:
db = get_db()
db.execute('DELETE FROM posts WHERE id = ?', (id,))
db.commit()
except:
return 'database error'
return 'Gwa Gwa'

View file

@ -3,6 +3,7 @@ from werkzeug.exceptions import abort
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from gallery.auth import login_required from gallery.auth import login_required
from gallery.db import get_db from gallery.db import get_db
from PIL import Image
import os import os
import datetime import datetime
dt = datetime.datetime.now() dt = datetime.datetime.now()
@ -29,33 +30,9 @@ def group(id):
return render_template('group.html', group_id=id) return render_template('group.html', group_id=id)
@blueprint.route('/upload', methods=('GET', 'POST')) @blueprint.route('/upload')
@login_required @login_required
def upload(): def upload():
if request.method == 'POST':
file = request.files['file']
form = request.form
if not file:
flash('No selected file')
return abort(404)
if not secure_filename(file.filename).lower().split('.')[-1] in current_app.config['ALLOWED_EXTENSIONS']:
abort(403)
file_name = file_name = f"GWAGWA_{dt.year}{dt.month}{dt.day}-{dt.microsecond}.{secure_filename(file.filename).lower().split('.')[-1]}"
file.save(os.path.join(current_app.config['UPLOAD_FOLDER']+'/original', file_name))
db = get_db()
db.execute(
'INSERT INTO posts (file_name, author_id, description, alt)'
' VALUES (?, ?, ?, ?)',
(file_name, g.user['id'], form['description'], form['alt'])
)
db.commit()
return 'Gwa Gwa'
# GET, or in human language, when you visit the page
return render_template('upload.html') return render_template('upload.html')

View file

@ -10,46 +10,8 @@ dt = datetime.datetime.now()
blueprint = Blueprint('image', __name__, url_prefix='/image') blueprint = Blueprint('image', __name__, url_prefix='/image')
@blueprint.route('/<int:id>')
def get_post(id, check_author=True):
post = get_db().execute(
'SELECT p.author_id FROM posts p JOIN users u ON p.author_id = u.id'
' WHERE p.id = ?',
(id,)
).fetchone()
if post is None:
return False
if check_author and post['author_id'] != g.user['id']:
return False
return post
@blueprint.route('/<int:id>', methods=('GET', 'POST'))
def image(id): def image(id):
if request.method == 'POST':
image = get_post(id)
action = request.form['action']
if not image:
abort(403)
if action == 'delete':
try:
db = get_db()
db.execute('DELETE FROM posts WHERE id = ?', (id,))
db.commit()
except:
return 'database error'
try:
os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'], 'original', image['file_name']))
except:
return 'os error'
# GET, it should be called Gwa Gwa because it sounds funny
# Get image from database # Get image from database
db = get_db() db = get_db()
image = db.execute( image = db.execute(
@ -63,7 +25,7 @@ def image(id):
# Get exif data from image # Get exif data from image
try: try:
file = Image.open(os.path.join(current_app.config['UPLOAD_FOLDER'], 'original', image['file_name'])) file = Image.open(os.path.join(current_app.instance_path, current_app.config['UPLOAD_FOLDER'], 'original', image['file_name']))
raw_exif = file.getexif() raw_exif = file.getexif()
human_exif = {} human_exif = {}

2
gallery/static/jquery-3.6.3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,13 +1,13 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block header %} {% block header %}
<img src="/uploads/original/{{ image['file_name'] }}" alt="leaves" onload="imgFade(this)" style="display: none;"/> <img src="/api/uploads/original/{{ image['file_name'] }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="image__fullscreen"> <div class="image__fullscreen">
<img <img
src="/uploads/original/{{ image['file_name'] }}" src="/api/uploads/original/{{ image['file_name'] }}"
onload="imgFade(this)" style="display:none;" onload="imgFade(this)" style="display:none;"
onerror="this.style.display='none'" onerror="this.style.display='none'"
/> />
@ -17,20 +17,12 @@
<div class="image__container"> <div class="image__container">
<img <img
class="image__item" class="image__item"
src="/uploads/original/{{ image['file_name'] }}" src="/api/uploads/original/{{ image['file_name'] }}"
onload="imgFade(this)" style="display:none;" onload="imgFade(this)" style="display:none;"
onerror="this.src='/static/images/error.png'" onerror="this.src='/static/images/error.png'"
/> />
</div> </div>
<div class="img-tools"> <div class="img-tools">
<div>
<button class="tool-btn" id="img-info">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-10a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"></path>
</svg>
<span class="tool-tip">Info</span>
</button>
</div>
<div> <div>
<button class="tool-btn" id="img-fullscreen"> <button class="tool-btn" id="img-fullscreen">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 24 24" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 24 24" fill="currentColor">
@ -61,6 +53,14 @@
</button> </button>
</div> </div>
{% endif %} {% endif %}
<div>
<button class="tool-btn" id="img-info">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" fill="currentColor">
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-10a1 1 0 0 1 1 1v5a1 1 0 0 1-2 0V9a1 1 0 0 1 1-1zm0-1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"></path>
</svg>
<span class="tool-tip">Info</span>
</button>
</div>
</div> </div>
{% if image['description'] != '' %} {% if image['description'] != '' %}
<div class="image__info"> <div class="image__info">
@ -104,13 +104,16 @@
{% if g.user['id'] == image['author_id'] %} {% if g.user['id'] == image['author_id'] %}
$('#img-delete').click(function() { $('#img-delete').click(function() {
$.ajax({ $.ajax({
url: '/image/{{ image['id'] }}', url: '/api/remove/{{ image['id'] }}',
type: 'post', type: 'post',
data: { data: {
action: 'delete' action: 'delete'
}, },
success: function(result) { success: function(response) {
window.location.href = '/'; window.location.href = '/';
},
error: function(response) {
alert(response);
} }
}); });
}); });

View file

@ -15,7 +15,7 @@
<h2>{{ image['file_name'] }}</h2> <h2>{{ image['file_name'] }}</h2>
</div> </div>
<span class="gallery__item-filter"></span> <span class="gallery__item-filter"></span>
<img class="gallery__item-image" src="/uploads/original/{{ image['file_name'] }}" onload="imgFade(this)" style="display:none;"> <img class="gallery__item-image" src="/api/uploads/original/{{ image['file_name'] }}" onload="imgFade(this)" style="display:none;">
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View file

@ -5,10 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gallery</title> <title>Gallery</title>
<link rel="stylesheet" href="{{url_for('static', filename='theme/style.css')}}" defer> <link rel="stylesheet" href="{{url_for('static', filename='theme/style.css')}}" defer>
<script <script src="{{url_for('static', filename='jquery-3.6.3.min.js')}}">
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous">
</script> </script>
</head> </head>
<body> <body>

View file

@ -52,7 +52,7 @@
// Upload the information // Upload the information
$.ajax({ $.ajax({
url: '/upload', url: '/api/upload',
type: 'post', type: 'post',
data: formData, data: formData,
contentType: false, contentType: false,

View file

@ -7,10 +7,10 @@ admin:
upload: upload:
allowed-extensions: allowed-extensions:
- png - .png
- jpg - .jpg
- jpeg - .jpeg
- webp - .webp
max-size: 69MB max-size: 69MB
rename: GWA_{{username}}_{{time}} rename: GWA_{{username}}_{{time}}

View file

@ -24,7 +24,7 @@
.tool-tip { .tool-tip {
opacity: 1; opacity: 1;
top: -2.5rem; top: -2.5rem;
//transition-delay: 0.5s; transform: translateX(calc(-50% + 1.25rem ));
} }
} }
} }
@ -39,6 +39,17 @@
color: $white100; color: $white100;
} }
} }
.tool-btn--blu {
color: $blue;
span {
background-color: $blue;
}
&:hover {
color: $white100;
}
}
.tool-tip { .tool-tip {
margin: 0; margin: 0;

View file

@ -311,6 +311,8 @@
height: 100%; height: 100%;
max-height: 69vh; max-height: 69vh;
background-color: $black200;
object-fit: contain; object-fit: contain;
object-position: center; object-position: center;
@ -378,7 +380,7 @@
padding: 0; padding: 0;
display: flex; display: flex;
gap: 0.5rem; //gap: 0.5rem;
background-color: $black200; background-color: $black200;
border-radius: $rad; border-radius: $rad;

View file

@ -87,7 +87,7 @@ nav {
span { span {
opacity: 1; opacity: 1;
left: 3.8rem; left: 3.8rem;
//transition-delay: 0.5s; transform: translateY(-50%);
} }
} }
} }