File Uploads, Email, Background Work and Payments
Safe file uploads
import os, uuid
from werkzeug.utils import secure_filename
ALLOWED = {"png", "jpg", "jpeg", "webp", "pdf"}
app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024 # 10 MB, enforced by Flask
def save_upload(file, subdir):
ext = file.filename.rsplit(".", 1)[-1].lower() if "." in file.filename else ""
if ext not in ALLOWED:
raise ValueError("File type not allowed")
name = f"{uuid.uuid4().hex}.{ext}" # never trust the original name
rel = os.path.join(subdir, name)
file.save(os.path.join(app.config["UPLOAD_ROOT"], rel))
return rel # store the relative path
- Store files outside
static/and serve them through a route that checks permissions. - Check content, not just the extension (for images, open them with Pillow).
- On platforms with ephemeral disks, use object storage (S3, R2) instead.
Transactional email
Use a provider API (Resend, Postmark, SendGrid) rather than raw SMTP:
import resend
resend.api_key = os.environ["RESEND_API_KEY"]
resend.Emails.send({
"from": "PandaDev <hello@mail.pandadevhub.com>",
"to": [user.email],
"subject": "Verify your email",
"html": render_template("emails/verify.html", link=link),
})
Email tokens (verification, password reset) must be random, single-use, hashed in the database and time-limited. The Email Setup track covers DNS (SPF, DKIM, DMARC).
Work outside the request
A request should finish in well under a few seconds. Send email, resize images and call slow APIs in the background:
- Simple: a thread or
concurrent.futuresfor fire-and-forget tasks (lost on restart). - Robust: a task queue (RQ or Celery with Redis), or the platform's background workers and cron jobs.
Payments with Stripe Checkout
import stripe
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
@shop_bp.post("/checkout/<int:product_id>")
@login_required
def checkout(product_id):
product = db.get_or_404(Product, product_id)
session = stripe.checkout.Session.create(
mode="payment",
line_items=[{"price_data": {"currency": "usd", "unit_amount": product.price_cents,
"product_data": {"name": product.name}}, "quantity": 1}],
success_url=url_for("shop.success", _external=True) + "?session_id={CHECKOUT_SESSION_ID}",
cancel_url=url_for("shop.product", product_id=product.id, _external=True),
metadata={"user_id": current_user.id, "product_id": product.id},
)
return redirect(session.url, code=303)
Grant access in the webhook (checkout.session.completed), verified with the signing secret, not on the success page, which users can open without paying. The Stripe Payments Integration track covers this fully.
Exercise
Add avatar uploads (images only, 2 MB max, random names, served through a permission-checked route) and a "verify your email" flow with a hashed, expiring token.
Check yourself
- Why rename uploaded files?
- Why should slow work leave the request cycle?
- Where should paid access be granted, and why?
Your progress is saved in this browser only.
Submit for review (10 points)