Add user auth, personal cabinet, admin panel and first admin bootstrap
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+72
-10
@@ -13,9 +13,12 @@ from flask import (
|
||||
send_from_directory,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import current_user, login_required
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from app import Photo, db
|
||||
from app import db
|
||||
from app.auth_utils import photo_owner_or_admin
|
||||
from app.models import Photo
|
||||
|
||||
bp = Blueprint("main", __name__)
|
||||
|
||||
@@ -29,31 +32,33 @@ def allowed_file(filename):
|
||||
|
||||
@bp.route("/")
|
||||
def index():
|
||||
photos = Photo.query.order_by(Photo.created_at.desc()).all()
|
||||
total_size = sum(p.file_size for p in photos)
|
||||
photos = Photo.query.order_by(Photo.created_at.desc()).limit(24).all()
|
||||
total_photos = Photo.query.count()
|
||||
total_size = db.session.query(db.func.coalesce(db.func.sum(Photo.file_size), 0)).scalar() or 0
|
||||
return render_template(
|
||||
"index.html",
|
||||
photos=photos,
|
||||
total_photos=len(photos),
|
||||
total_size=total_size,
|
||||
total_photos=total_photos,
|
||||
total_size=int(total_size),
|
||||
max_upload_mb=current_app.config["MAX_CONTENT_LENGTH"] // (1024 * 1024),
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/upload", methods=["POST"])
|
||||
@login_required
|
||||
def upload():
|
||||
if "photo" not in request.files:
|
||||
flash("Файл не выбран", "error")
|
||||
return redirect(url_for("main.index"))
|
||||
return redirect(request.referrer or url_for("main.index"))
|
||||
|
||||
file = request.files["photo"]
|
||||
if file.filename == "":
|
||||
flash("Файл не выбран", "error")
|
||||
return redirect(url_for("main.index"))
|
||||
return redirect(request.referrer or url_for("main.index"))
|
||||
|
||||
if not allowed_file(file.filename):
|
||||
flash("Недопустимый формат. Разрешены: PNG, JPG, GIF, WEBP, BMP", "error")
|
||||
return redirect(url_for("main.index"))
|
||||
return redirect(request.referrer or url_for("main.index"))
|
||||
|
||||
ext = file.filename.rsplit(".", 1)[1].lower()
|
||||
stored_name = f"{uuid.uuid4().hex}.{ext}"
|
||||
@@ -69,13 +74,14 @@ def upload():
|
||||
original_name=safe_original,
|
||||
file_size=file_size,
|
||||
mime_type=file.content_type or f"image/{ext}",
|
||||
user_id=current_user.id,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.session.add(photo)
|
||||
db.session.commit()
|
||||
|
||||
flash("Фото успешно загружено", "success")
|
||||
return redirect(url_for("main.index"))
|
||||
return redirect(url_for("cabinet.index"))
|
||||
|
||||
|
||||
@bp.route("/api/photos")
|
||||
@@ -89,6 +95,7 @@ def api_photos():
|
||||
"original_name": p.original_name,
|
||||
"file_size": p.file_size,
|
||||
"size_human": p.size_human,
|
||||
"user_id": p.user_id,
|
||||
"created_at": p.created_at.isoformat(),
|
||||
}
|
||||
for p in photos
|
||||
@@ -102,12 +109,67 @@ def uploaded_file(filename):
|
||||
|
||||
|
||||
@bp.route("/delete/<int:photo_id>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_photo(photo_id):
|
||||
photo = Photo.query.get_or_404(photo_id)
|
||||
photo_owner_or_admin(photo)
|
||||
|
||||
filepath = os.path.join(current_app.config["UPLOAD_FOLDER"], photo.filename)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
db.session.delete(photo)
|
||||
db.session.commit()
|
||||
flash("Фото удалено", "success")
|
||||
return redirect(url_for("main.index"))
|
||||
return redirect(request.referrer or url_for("main.index"))
|
||||
|
||||
|
||||
cabinet_bp = Blueprint("cabinet", __name__, url_prefix="/cabinet")
|
||||
|
||||
|
||||
@cabinet_bp.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
photos = (
|
||||
Photo.query.filter_by(user_id=current_user.id)
|
||||
.order_by(Photo.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
total_size = sum(p.file_size for p in photos)
|
||||
return render_template(
|
||||
"cabinet/index.html",
|
||||
photos=photos,
|
||||
total_photos=len(photos),
|
||||
total_size=total_size,
|
||||
max_upload_mb=current_app.config["MAX_CONTENT_LENGTH"] // (1024 * 1024),
|
||||
)
|
||||
|
||||
|
||||
@cabinet_bp.route("/profile", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def profile():
|
||||
from app.models import User
|
||||
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email", "").strip().lower()
|
||||
current_password = request.form.get("current_password", "")
|
||||
new_password = request.form.get("new_password", "")
|
||||
new_password2 = request.form.get("new_password2", "")
|
||||
|
||||
other = User.query.filter(User.email == email, User.id != current_user.id).first()
|
||||
if other:
|
||||
flash("Этот email уже используется", "error")
|
||||
elif not current_user.check_password(current_password):
|
||||
flash("Неверный текущий пароль", "error")
|
||||
elif new_password and len(new_password) < 6:
|
||||
flash("Новый пароль — минимум 6 символов", "error")
|
||||
elif new_password and new_password != new_password2:
|
||||
flash("Новые пароли не совпадают", "error")
|
||||
else:
|
||||
current_user.email = email
|
||||
if new_password:
|
||||
current_user.set_password(new_password)
|
||||
db.session.commit()
|
||||
flash("Профиль обновлён", "success")
|
||||
return redirect(url_for("cabinet.profile"))
|
||||
|
||||
return render_template("cabinet/profile.html")
|
||||
|
||||
Reference in New Issue
Block a user