
Flask E-commerce Website Project
Ready to build your own online store using Python? In this comprehensive guide, we'll walk through creating a full-featured e-commerce website using Flask. Whether you're looking to start your own online business or just want to add a impressive project to your portfolio, this tutorial will give you all the tools you need.
Setting Up Your Project Environment
Before we dive into coding, let's set up our development environment properly. You'll need Python installed on your system, preferably Python 3.8 or higher. Start by creating a virtual environment and installing the necessary packages.
Create your project directory and set up a virtual environment:
mkdir flask_ecommerce
cd flask_ecommerce
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
Now install the required packages:
pip install flask flask-sqlalchemy flask-login flask-bcrypt flask-mail
pip install pillow # For image processing
Create a requirements.txt file to keep track of your dependencies:
pip freeze > requirements.txt
Project Structure and Configuration
A well-organized project structure makes your code more maintainable and scalable. Here's how we'll structure our Flask e-commerce application:
flask_ecommerce/
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── routes.py
│ ├── forms.py
│ ├── templates/
│ │ ├── base.html
│ │ ├── index.html
│ │ ├── product/
│ │ ├── auth/
│ │ └── cart/
│ ├── static/
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ └── utils/
├── config.py
├── run.py
└── instance/
└── database.db
Let's set up our configuration in config.py:
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret-key-here'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'instance', 'database.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USERNAME = os.environ.get('EMAIL_USER')
MAIL_PASSWORD = os.environ.get('EMAIL_PASS')
Database Models Design
The heart of our e-commerce application lies in its database structure. We'll use SQLAlchemy to define our models. Let's create the essential models for our store.
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from datetime import datetime
import json
db = SQLAlchemy()
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
orders = db.relationship('Order', backref='customer', lazy=True)
addresses = db.relationship('Address', backref='user', lazy=True)
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text, nullable=False)
price = db.Column(db.Float, nullable=False)
stock = db.Column(db.Integer, nullable=False)
image = db.Column(db.String(20), nullable=False, default='default.jpg')
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True, nullable=False)
products = db.relationship('Product', backref='category', lazy=True)
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
date_ordered = db.Column(db.DateTime, default=datetime.utcnow)
complete = db.Column(db.Boolean, default=False)
transaction_id = db.Column(db.String(100), nullable=False)
order_items = db.relationship('OrderItem', backref='order', lazy=True)
class OrderItem(db.Model):
id = db.Column(db.Integer, primary_key=True)
quantity = db.Column(db.Integer, nullable=False)
date_added = db.Column(db.DateTime, default=datetime.utcnow)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False)
order_id = db.Column(db.Integer, db.ForeignKey('order.id'), nullable=False)
Essential e-commerce database tables and their purposes:
Table Name | Description | Key Fields |
---|---|---|
User | Stores customer information | id, username, email, password |
Product | Contains product details | id, name, price, stock, category_id |
Category | Organizes products into categories | id, name |
Order | Manages customer orders | id, user_id, transaction_id, complete |
OrderItem | Links products to orders with quantities | id, product_id, order_id, quantity |
User Authentication System
A secure authentication system is crucial for any e-commerce platform. Let's implement user registration, login, and session management.
First, let's create our forms in forms.py:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from app.models import User
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sign Up')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('That username is taken. Please choose a different one.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('That email is taken. Please choose a different one.')
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Login')
Now let's implement the authentication routes:
from flask import render_template, url_for, flash, redirect, request
from flask_login import login_user, current_user, logout_user, login_required
from app import app, db, bcrypt
from app.models import User
from app.forms import RegistrationForm, LoginForm
@app.route("/register", methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('home'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data, email=form.email.data, password=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to log in', 'success')
return redirect(url_for('login'))
return render_template('auth/register.html', title='Register', form=form)
@app.route("/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('home'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password, form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return render_template('auth/login.html', title='Login', form=form)
@app.route("/logout")
def logout():
logout_user()
return redirect(url_for('home'))
Key authentication features to implement: - Password hashing with bcrypt - Session management with Flask-Login - Remember me functionality - Protected routes using @login_required decorator - Password reset functionality via email
Product Management System
The product catalog is the backbone of any e-commerce site. Let's create a robust product management system.
First, let's add product-related forms:
from flask_wtf.file import FileField, FileAllowed
from wtforms import TextAreaField, FloatField, IntegerField, SelectField
class ProductForm(FlaskForm):
name = StringField('Product Name', validators=[DataRequired()])
description = TextAreaField('Description', validators=[DataRequired()])
price = FloatField('Price', validators=[DataRequired()])
stock = IntegerField('Stock', validators=[DataRequired()])
category = SelectField('Category', coerce=int, validators=[DataRequired()])
image = FileField('Product Image', validators=[FileAllowed(['jpg', 'png'])])
submit = SubmitField('Add Product')
Now let's create product management routes:
import os
from werkzeug.utils import secure_filename
from app.models import Product, Category
@app.route("/admin/add_product", methods=['GET', 'POST'])
@login_required
def add_product():
form = ProductForm()
form.category.choices = [(c.id, c.name) for c in Category.query.all()]
if form.validate_on_submit():
if form.image.data:
filename = secure_filename(form.image.data.filename)
image_path = os.path.join(app.root_path, 'static/product_images', filename)
form.image.data.save(image_path)
product = Product(
name=form.name.data,
description=form.description.data,
price=form.price.data,
stock=form.stock.data,
category_id=form.category.data,
image=filename if form.image.data else 'default.jpg'
)
db.session.add(product)
db.session.commit()
flash('Product added successfully!', 'success')
return redirect(url_for('product', product_id=product.id))
return render_template('admin/add_product.html', title='Add Product', form=form)
@app.route("/product/<int:product_id>")
def product(product_id):
product = Product.query.get_or_404(product_id)
return render_template('product/product.html', title=product.name, product=product)
@app.route("/category/<int:category_id>")
def category(category_id):
category = Category.query.get_or_404(category_id)
products = Product.query.filter_by(category_id=category_id).all()
return render_template('product/category.html', title=category.name, category=category, products=products)
Common e-commerce product attributes and their importance:
Attribute | Data Type | Importance | Example |
---|---|---|---|
Name | String | High | "Wireless Headphones" |
Description | Text | High | "Premium wireless headphones with noise cancellation" |
Price | Float | Critical | 129.99 |
Stock | Integer | Critical | 50 |
Category | Foreign Key | High | Electronics → Audio |
Image | String | High | "headphones.jpg" |
SKU | String | Medium | "WH-2023-BLK" |
Shopping Cart Functionality
The shopping cart is where customers build their orders. Let's implement a robust cart system using sessions.
First, let's create cart utility functions:
import json
from datetime import datetime
def get_cart():
cart = session.get('cart', {})
return cart
def update_cart(product_id, quantity):
cart = get_cart()
if quantity == 0:
cart.pop(str(product_id), None)
else:
cart[str(product_id)] = quantity
session['cart'] = cart
return cart
def get_cart_total():
cart = get_cart()
total = 0
for product_id, quantity in cart.items():
product = Product.query.get(int(product_id))
if product:
total += product.price * quantity
return total
def get_cart_items():
cart = get_cart()
items = []
for product_id, quantity in cart.items():
product = Product.query.get(int(product_id))
if product:
items.append({
'product': product,
'quantity': quantity,
'total': product.price * quantity
})
return items
Now let's implement cart routes:
@app.route("/add_to_cart/<int:product_id>")
def add_to_cart(product_id):
product = Product.query.get_or_404(product_id)
cart = get_cart()
current_quantity = cart.get(str(product_id), 0)
if current_quantity + 1 > product.stock:
flash('Not enough stock available', 'danger')
return redirect(url_for('product', product_id=product_id))
update_cart(product_id, current_quantity + 1)
flash(f'{product.name} added to cart!', 'success')
return redirect(request.referrer or url_for('home'))
@app.route("/remove_from_cart/<int:product_id>")
def remove_from_cart(product_id):
update_cart(product_id, 0)
flash('Item removed from cart', 'info')
return redirect(url_for('view_cart'))
@app.route("/update_cart/<int:product_id>/<int:quantity>")
def update_cart_item(product_id, quantity):
product = Product.query.get_or_404(product_id)
if quantity > product.stock:
flash('Not enough stock available', 'danger')
return redirect(url_for('view_cart'))
update_cart(product_id, quantity)
return redirect(url_for('view_cart'))
@app.route("/cart")
def view_cart():
cart_items = get_cart_items()
total = get_cart_total()
return render_template('cart/cart.html', title='Shopping Cart', cart_items=cart_items, total=total)
Order Processing and Checkout
The checkout process is where customers complete their purchases. Let's implement a secure checkout system.
First, let's create order-related forms:
from wtforms import HiddenField
class CheckoutForm(FlaskForm):
name = StringField('Full Name', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
address = StringField('Shipping Address', validators=[DataRequired()])
city = StringField('City', validators=[DataRequired()])
state = StringField('State', validators=[DataRequired()])
zip_code = StringField('Zip Code', validators=[DataRequired()])
card_number = StringField('Credit Card Number', validators=[DataRequired()])
exp_date = StringField('Expiration Date', validators=[DataRequired()])
cvv = StringField('CVV', validators=[DataRequired()])
submit = SubmitField('Complete Order')
Now let's implement the checkout process:
import uuid
from datetime import datetime
@app.route("/checkout", methods=['GET', 'POST'])
@login_required
def checkout():
cart_items = get_cart_items()
if not cart_items:
flash('Your cart is empty', 'warning')
return redirect(url_for('home'))
form = CheckoutForm()
if form.validate_on_submit():
# Process payment (in real application, integrate with payment gateway)
# For demo purposes, we'll simulate successful payment
# Create order
transaction_id = str(uuid.uuid4())
order = Order(
user_id=current_user.id,
transaction_id=transaction_id,
complete=True
)
db.session.add(order)
# Add order items
for item in cart_items:
order_item = OrderItem(
quantity=item['quantity'],
product_id=item['product'].id,
order=order
)
db.session.add(order_item)
# Update product stock
item['product'].stock -= item['quantity']
# Clear cart
session.pop('cart', None)
db.session.commit()
flash('Order placed successfully!', 'success')
return redirect(url_for('order_confirmation', order_id=order.id))
return render_template('checkout/checkout.html', title='Checkout', form=form, cart_items=cart_items, total=get_cart_total())
@app.route("/order/<int:order_id>")
@login_required
def order_confirmation(order_id):
order = Order.query.get_or_404(order_id)
if order.user_id != current_user.id:
abort(403)
return render_template('order/confirmation.html', title='Order Confirmation', order=order)
Essential steps in the e-commerce checkout process:
- Cart Review - Display all items with quantities and totals
- Customer Information - Collect shipping and billing details
- Payment Processing - Secure credit card handling
- Order Confirmation - Generate order number and receipt
- Inventory Update - Reduce stock levels for purchased items
- Email Notification - Send order confirmation to customer
- Admin Notification - Notify store owner of new order
User Dashboard and Order History
Customers should be able to view their order history and manage their account. Let's create a user dashboard.
@app.route("/dashboard")
@login_required
def dashboard():
orders = Order.query.filter_by(user_id=current_user.id)\
.order_by(Order.date_ordered.desc()).all()
return render_template('user/dashboard.html', title='Dashboard', orders=orders)
@app.route("/order_details/<int:order_id>")
@login_required
def order_details(order_id):
order = Order.query.get_or_404(order_id)
if order.user_id != current_user.id:
abort(403)
return render_template('user/order_details.html', title='Order Details', order=order)
@app.route("/profile", methods=['GET', 'POST'])
@login_required
def profile():
form = UpdateProfileForm()
if form.validate_on_submit():
current_user.username = form.username.data
current_user.email = form.email.data
db.session.commit()
flash('Your profile has been updated!', 'success')
return redirect(url_for('profile'))
elif request.method == 'GET':
form.username.data = current_user.username
form.email.data = current_user.email
return render_template('user/profile.html', title='Profile', form=form)
Search and Filter Functionality
Help customers find products quickly with search and filtering capabilities.
@app.route("/search")
def search():
query = request.args.get('q', '')
category_id = request.args.get('category', 0, type=int)
min_price = request.args.get('min_price', 0, type=float)
max_price = request.args.get('max_price', float('inf'), type=float)
sort_by = request.args.get('sort', 'name')
# Build base query
products_query = Product.query
# Apply search filter
if query:
products_query = products_query.filter(
(Product.name.ilike(f'%{query}%')) |
(Product.description.ilike(f'%{query}%'))
)
# Apply category filter
if category_id:
products_query = products_query.filter_by(category_id=category_id)
# Apply price range filter
products_query = products_query.filter(Product.price >= min_price)
if max_price != float('inf'):
products_query = products_query.filter(Product.price <= max_price)
# Apply sorting
if sort_by == 'price_low':
products_query = products_query.order_by(Product.price.asc())
elif sort_by == 'price_high':
products_query = products_query.order_by(Product.price.desc())
elif sort_by == 'newest':
products_query = products_query.order_by(Product.created_at.desc())
else:
products_query = products_query.order_by(Product.name.asc())
products = products_query.all()
categories = Category.query.all()
return render_template('product/search.html', title='Search Results',
products=products, categories=categories, query=query,
selected_category=category_id, min_price=min_price,
max_price=max_price, sort_by=sort_by)
Admin Panel for Management
Store administrators need tools to manage products, orders, and customers.
@app.route("/admin")
@login_required
def admin_dashboard():
if not current_user.is_admin: # You'll need to add is_admin to your User model
abort(403)
total_products = Product.query.count()
total_orders = Order.query.count()
total_users = User.query.count()
recent_orders = Order.query.order_by(Order.date_ordered.desc()).limit(5).all()
return render_template('admin/dashboard.html', title='Admin Dashboard',
total_products=total_products, total_orders=total_orders,
total_users=total_users, recent_orders=recent_orders)
@app.route("/admin/orders")
@login_required
def admin_orders():
if not current_user.is_admin:
abort(403)
orders = Order.query.order_by(Order.date_ordered.desc()).all()
return render_template('admin/orders.html', title='Manage Orders', orders=orders)
@app.route("/admin/products")
@login_required
def admin_products():
if not current_user.is_admin:
abort(403)
products = Product.query.all()
return render_template('admin/products.html', title='Manage Products', products=products)
Deployment Considerations
When you're ready to deploy your Flask e-commerce application, consider these important factors:
Security Considerations: - Use environment variables for sensitive data - Implement CSRF protection - Use HTTPS in production - Sanitize user inputs to prevent SQL injection - Implement rate limiting for authentication endpoints
Performance Optimization: - Implement database indexing - Use caching for frequently accessed data - Optimize images for web delivery - Minify CSS and JavaScript files - Use a production-ready web server (Gunicorn, uWSGI)
Payment Integration: - Integrate with a payment gateway (Stripe, PayPal) - Never store credit card information - Implement webhooks for payment notifications - Handle failed payments gracefully
Monitoring and Maintenance: - Set up error logging and monitoring - Implement backup strategies - Monitor performance metrics - Keep dependencies updated - Regularly test checkout process
Building a Flask e-commerce website is a comprehensive project that covers many aspects of web development. Start with the basic functionality and gradually add features as you become more comfortable with the codebase. Remember to test thoroughly, especially the checkout process, before going live with real customers.