Added Repository
This commit is contained in:
7
routes/__init__.py
Normal file
7
routes/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .main import main_bp
|
||||
from .cart import cart_bp
|
||||
from .auth import auth_bp
|
||||
from .admin import admin_bp
|
||||
from .user import user_bp
|
||||
|
||||
__all__ = ['main_bp', 'cart_bp', 'auth_bp', 'admin_bp', 'user_bp']
|
||||
526
routes/admin.py
Normal file
526
routes/admin.py
Normal file
@@ -0,0 +1,526 @@
|
||||
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, Product, Category, ProductImage, Review, SizeEnum, User, Order, OrderItem
|
||||
import os
|
||||
from werkzeug.utils import secure_filename
|
||||
import json
|
||||
from functools import wraps
|
||||
from datetime import datetime
|
||||
from sqlalchemy import func
|
||||
|
||||
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
|
||||
|
||||
|
||||
def admin_required(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not current_user.is_authenticated:
|
||||
flash("Please login to access admin area", "danger")
|
||||
return redirect(url_for('auth.login'))
|
||||
if not current_user.is_admin:
|
||||
flash("Admin access only!", "danger")
|
||||
return redirect(url_for('main.home'))
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
|
||||
|
||||
def allowed_file(filename):
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
|
||||
def get_or_create_default_category():
|
||||
default = Category.query.filter_by(name='Uncategorized').first()
|
||||
if not default:
|
||||
default = Category(name='Uncategorized')
|
||||
db.session.add(default)
|
||||
db.session.commit()
|
||||
return default
|
||||
|
||||
|
||||
def get_categories():
|
||||
categories = Category.query.all()
|
||||
if not categories:
|
||||
default_cat = get_or_create_default_category()
|
||||
categories = [default_cat]
|
||||
return categories
|
||||
|
||||
|
||||
def get_admin_stats():
|
||||
try:
|
||||
total_revenue_result = db.session.query(
|
||||
func.sum(OrderItem.price_at_purchase * OrderItem.quantity)
|
||||
).join(Order).filter(Order.status.in_(['delivered', 'completed'])).scalar()
|
||||
|
||||
total_revenue = float(
|
||||
total_revenue_result) if total_revenue_result else 0.0
|
||||
|
||||
except:
|
||||
|
||||
total_revenue = 0.0
|
||||
|
||||
stats = {
|
||||
'total_products': Product.query.count(),
|
||||
'total_categories': Category.query.count(),
|
||||
'total_reviews': Review.query.count(),
|
||||
'total_orders': Order.query.count() if hasattr(Order, 'query') else 0,
|
||||
'total_users': User.query.count() if hasattr(User, 'query') else 1,
|
||||
'total_revenue': total_revenue,
|
||||
'low_stock': Product.query.filter(Product.stock > 0, Product.stock <= 5).count(),
|
||||
'out_of_stock': Product.query.filter(Product.stock == 0).count(),
|
||||
'total_value': db.session.query(func.sum(Product.price * Product.stock)).scalar() or 0,
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
@admin_bp.route('/')
|
||||
@login_required
|
||||
@admin_required
|
||||
def dashboard():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = 20
|
||||
products = Product.query.order_by(
|
||||
Product.id.desc()).paginate(page=page, per_page=per_page)
|
||||
|
||||
stats = get_admin_stats()
|
||||
|
||||
return render_template('admin/dashboard.html',
|
||||
products=products,
|
||||
stats=stats,
|
||||
SizeEnum=SizeEnum)
|
||||
|
||||
@admin_bp.route('/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def add_product():
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
name = request.form['name']
|
||||
description = request.form.get('description', '')
|
||||
price = float(request.form['price'])
|
||||
stock = int(request.form.get('stock', 0))
|
||||
sku = request.form.get('sku', '')
|
||||
color = request.form.get('color', '')
|
||||
size = request.form.get('size', '')
|
||||
product_type = request.form.get('product_type', '')
|
||||
material = request.form.get('material', '')
|
||||
company = request.form.get('company', '')
|
||||
weight = request.form.get('weight')
|
||||
dimensions = request.form.get('dimensions', '')
|
||||
|
||||
if weight:
|
||||
try:
|
||||
weight = float(weight)
|
||||
except ValueError:
|
||||
weight = None
|
||||
else:
|
||||
weight = None
|
||||
|
||||
category_id = request.form.get('category_id')
|
||||
if not category_id:
|
||||
category = get_or_create_default_category()
|
||||
category_id = category.id
|
||||
|
||||
product = Product(
|
||||
name=name,
|
||||
description=description,
|
||||
price=price,
|
||||
stock=stock,
|
||||
sku=sku,
|
||||
color=color,
|
||||
size=size,
|
||||
material=material,
|
||||
company=company,
|
||||
weight=weight,
|
||||
dimensions=dimensions,
|
||||
category_id=category_id
|
||||
)
|
||||
|
||||
db.session.add(product)
|
||||
db.session.flush()
|
||||
|
||||
if 'images[]' in request.files:
|
||||
files = request.files.getlist('images[]')
|
||||
for i, file in enumerate(files):
|
||||
if file and file.filename and allowed_file(file.filename):
|
||||
upload_dir = 'static/uploads/products'
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
name_part, ext = os.path.splitext(filename)
|
||||
unique_filename = f"{name_part}_{product.id}_{i}{ext}"
|
||||
file_path = os.path.join(upload_dir, unique_filename)
|
||||
file.save(file_path)
|
||||
|
||||
product_image = ProductImage(
|
||||
product_id=product.id,
|
||||
filename=unique_filename,
|
||||
is_primary=(i == 0),
|
||||
display_order=i
|
||||
)
|
||||
db.session.add(product_image)
|
||||
|
||||
db.session.commit()
|
||||
flash(f"Product '{name}' added successfully!", "success")
|
||||
return redirect(url_for('admin.dashboard'))
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f"Error adding product: {str(e)}", "danger")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
categories = get_categories()
|
||||
return render_template('admin/add_product.html',
|
||||
categories=categories,
|
||||
sizes=SizeEnum.ALL)
|
||||
|
||||
@admin_bp.route('/edit/<int:id>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def edit_product(id):
|
||||
product = Product.query.get_or_404(id)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
product.name = request.form['name']
|
||||
product.description = request.form.get('description', '')
|
||||
product.price = float(request.form['price'])
|
||||
product.stock = int(request.form.get('stock', 0))
|
||||
product.sku = request.form.get('sku', '')
|
||||
product.color = request.form.get('color', '')
|
||||
product.size = request.form.get('size', '')
|
||||
product.material = request.form.get('material', '')
|
||||
product.company = request.form.get('company', '')
|
||||
weight = request.form.get('weight')
|
||||
if weight:
|
||||
try:
|
||||
product.weight = float(weight)
|
||||
except ValueError:
|
||||
product.weight = None
|
||||
else:
|
||||
product.weight = None
|
||||
|
||||
product.dimensions = request.form.get('dimensions', '')
|
||||
|
||||
category_id = request.form.get('category_id')
|
||||
if category_id:
|
||||
product.category_id = category_id
|
||||
|
||||
if 'images[]' in request.files:
|
||||
files = request.files.getlist('images[]')
|
||||
for i, file in enumerate(files):
|
||||
if file and file.filename and allowed_file(file.filename):
|
||||
upload_dir = 'static/uploads/products'
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
filename = secure_filename(file.filename)
|
||||
name_part, ext = os.path.splitext(filename)
|
||||
unique_filename = f"{name_part}_{product.id}_{len(product.images) + i}{ext}"
|
||||
file_path = os.path.join(upload_dir, unique_filename)
|
||||
file.save(file_path)
|
||||
product_image = ProductImage(
|
||||
product_id=product.id,
|
||||
filename=unique_filename,
|
||||
display_order=len(product.images) + i
|
||||
)
|
||||
db.session.add(product_image)
|
||||
|
||||
if 'deleted_images' in request.form:
|
||||
deleted_images_str = request.form['deleted_images']
|
||||
if deleted_images_str:
|
||||
try:
|
||||
deleted_ids = json.loads(deleted_images_str)
|
||||
for img_id in deleted_ids:
|
||||
image = ProductImage.query.get(img_id)
|
||||
if image and image.product_id == product.id:
|
||||
|
||||
file_path = os.path.join(
|
||||
'static/uploads/products', image.filename)
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
db.session.delete(image)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
|
||||
if 'primary_image' in request.form:
|
||||
primary_id = request.form['primary_image']
|
||||
if primary_id:
|
||||
try:
|
||||
primary_id = int(primary_id)
|
||||
for img in product.images:
|
||||
img.is_primary = (img.id == primary_id)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
db.session.commit()
|
||||
flash("Product updated successfully!", "success")
|
||||
return redirect(url_for('admin.dashboard'))
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f"Error updating product: {str(e)}", "danger")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
categories = get_categories()
|
||||
return render_template('admin/edit_product.html',
|
||||
product=product,
|
||||
categories=categories,
|
||||
sizes=SizeEnum.ALL)
|
||||
|
||||
@admin_bp.route('/delete/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_product(id):
|
||||
product = Product.query.get_or_404(id)
|
||||
try:
|
||||
for image in product.images:
|
||||
file_path = os.path.join('static/uploads/products', image.filename)
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
db.session.delete(product)
|
||||
db.session.commit()
|
||||
flash("Product deleted successfully!", "info")
|
||||
except Exception as e:
|
||||
flash(f"Error deleting product: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('admin.dashboard'))
|
||||
|
||||
@admin_bp.route('/image/delete/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_image(id):
|
||||
try:
|
||||
image = ProductImage.query.get_or_404(id)
|
||||
|
||||
file_path = os.path.join('static/uploads/products', image.filename)
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
db.session.delete(image)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'success': True})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@admin_bp.route('/reviews')
|
||||
@login_required
|
||||
@admin_required
|
||||
def manage_reviews():
|
||||
reviews = Review.query.order_by(Review.created_at.desc()).all()
|
||||
return render_template('admin/reviews.html', reviews=reviews)
|
||||
|
||||
@admin_bp.route('/review/delete/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_review(id):
|
||||
review = Review.query.get_or_404(id)
|
||||
try:
|
||||
db.session.delete(review)
|
||||
db.session.commit()
|
||||
flash("Review deleted successfully!", "info")
|
||||
except Exception as e:
|
||||
flash(f"Error deleting review: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('admin.manage_reviews'))
|
||||
|
||||
@admin_bp.route('/categories')
|
||||
@login_required
|
||||
@admin_required
|
||||
def manage_categories():
|
||||
categories = Category.query.order_by(Category.name.asc()).all()
|
||||
return render_template('admin/categories.html', categories=categories)
|
||||
|
||||
@admin_bp.route('/category/add', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def add_category():
|
||||
name = request.form.get('name', '').strip()
|
||||
if not name:
|
||||
flash("Category name cannot be empty", "danger")
|
||||
return redirect(url_for('admin.manage_categories'))
|
||||
|
||||
existing = Category.query.filter_by(name=name).first()
|
||||
if existing:
|
||||
flash(f"Category '{name}' already exists", "warning")
|
||||
return redirect(url_for('admin.manage_categories'))
|
||||
|
||||
try:
|
||||
category = Category(name=name)
|
||||
db.session.add(category)
|
||||
db.session.commit()
|
||||
flash(f"Category '{name}' added successfully!", "success")
|
||||
except Exception as e:
|
||||
flash(f"Error adding category: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('admin.manage_categories'))
|
||||
|
||||
@admin_bp.route('/category/delete/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_category(id):
|
||||
category = Category.query.get_or_404(id)
|
||||
if category.products:
|
||||
flash(
|
||||
f"Cannot delete category '{category.name}' because it has {len(category.products)} product(s)", "danger")
|
||||
return redirect(url_for('admin.manage_categories'))
|
||||
|
||||
try:
|
||||
db.session.delete(category)
|
||||
db.session.commit()
|
||||
flash(f"Category '{category.name}' deleted successfully!", "info")
|
||||
except Exception as e:
|
||||
flash(f"Error deleting category: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('admin.manage_categories'))
|
||||
|
||||
@admin_bp.route('/stats')
|
||||
@login_required
|
||||
@admin_required
|
||||
def stats():
|
||||
stats_data = get_admin_stats()
|
||||
return render_template('admin/stats.html', stats=stats_data)
|
||||
|
||||
@admin_bp.route('/users')
|
||||
@login_required
|
||||
@admin_required
|
||||
def manage_users():
|
||||
users = User.query.order_by(User.id.desc()).all()
|
||||
return render_template('admin/users.html', users=users)
|
||||
|
||||
@admin_bp.route('/user/toggle_admin/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def toggle_user_admin(id):
|
||||
user = User.query.get_or_404(id)
|
||||
|
||||
if user.id == current_user.id:
|
||||
flash("You cannot remove admin privileges from yourself", "warning")
|
||||
return redirect(url_for('admin.manage_users'))
|
||||
|
||||
try:
|
||||
user.is_admin = not user.is_admin
|
||||
db.session.commit()
|
||||
status = "granted" if user.is_admin else "removed"
|
||||
flash(f"Admin privileges {status} for {user.email}", "success")
|
||||
except Exception as e:
|
||||
flash(f"Error updating user: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('admin.manage_users'))
|
||||
|
||||
@admin_bp.route('/user/delete/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_user(id):
|
||||
user = User.query.get_or_404(id)
|
||||
|
||||
if user.id == current_user.id:
|
||||
flash("You cannot delete your own account", "warning")
|
||||
return redirect(url_for('admin.manage_users'))
|
||||
|
||||
try:
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
flash(f"User {user.email} deleted successfully", "info")
|
||||
except Exception as e:
|
||||
flash(f"Error deleting user: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('admin.manage_users'))
|
||||
|
||||
@admin_bp.route('/orders')
|
||||
@login_required
|
||||
@admin_required
|
||||
def manage_orders():
|
||||
orders = Order.query.order_by(Order.created_at.desc()).all()
|
||||
return render_template('admin/orders.html', orders=orders)
|
||||
|
||||
@admin_bp.route('/order/<int:id>')
|
||||
@login_required
|
||||
@admin_required
|
||||
def order_detail(id):
|
||||
order = Order.query.get_or_404(id)
|
||||
return render_template('admin/order_detail.html', order=order)
|
||||
|
||||
@admin_bp.route('/order/update_status/<int:id>', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def update_order_status(id):
|
||||
order = Order.query.get_or_404(id)
|
||||
new_status = request.form.get('status')
|
||||
|
||||
if new_status in ['pending', 'processing', 'shipped', 'delivered', 'cancelled']:
|
||||
try:
|
||||
order.status = new_status
|
||||
db.session.commit()
|
||||
flash(f"Order #{order.id} status updated to {new_status}", "success")
|
||||
except Exception as e:
|
||||
flash(f"Error updating order: {str(e)}", "danger")
|
||||
else:
|
||||
flash("Invalid status", "danger")
|
||||
|
||||
return redirect(url_for('admin.order_detail', id=id))
|
||||
|
||||
@admin_bp.route('/quick/restock_low')
|
||||
@login_required
|
||||
@admin_required
|
||||
def quick_restock_low():
|
||||
"""Quick action to restock low inventory items"""
|
||||
try:
|
||||
low_stock_products = Product.query.filter(
|
||||
Product.stock > 0,
|
||||
Product.stock <= 5
|
||||
).all()
|
||||
|
||||
count = 0
|
||||
for product in low_stock_products:
|
||||
product.stock += 10
|
||||
count += 1
|
||||
|
||||
if count > 0:
|
||||
db.session.commit()
|
||||
flash(f"Restocked {count} low inventory products", "success")
|
||||
else:
|
||||
flash("No low inventory products found", "info")
|
||||
|
||||
except Exception as e:
|
||||
flash(f"Error restocking products: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('admin.dashboard'))
|
||||
|
||||
@admin_bp.route('/quick/update_prices', methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
def quick_update_prices():
|
||||
"""Quick action to update prices by percentage"""
|
||||
try:
|
||||
percentage = float(request.form.get('percentage', 0))
|
||||
|
||||
if percentage != 0:
|
||||
|
||||
multiplier = 1 + (percentage / 100)
|
||||
|
||||
products = Product.query.all()
|
||||
for product in products:
|
||||
product.price = round(product.price * multiplier, 2)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
action = "increased" if percentage > 0 else "decreased"
|
||||
flash(
|
||||
f"Prices {action} by {abs(percentage)}% for all products", "success")
|
||||
else:
|
||||
flash("No price change specified", "warning")
|
||||
|
||||
except Exception as e:
|
||||
flash(f"Error updating prices: {str(e)}", "danger")
|
||||
|
||||
return redirect(url_for('admin.dashboard'))
|
||||
43
routes/auth.py
Normal file
43
routes/auth.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from models import db, User
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
@auth_bp.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
email = request.form['email']
|
||||
password = request.form['password']
|
||||
if User.query.filter_by(email=email).first():
|
||||
flash("Email already registered", "danger")
|
||||
return redirect(url_for('auth.register'))
|
||||
user = User(email=email)
|
||||
user.set_password(password)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
flash("Registered successfully!", "success")
|
||||
return redirect(url_for('auth.login'))
|
||||
return render_template('register.html')
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
email = request.form['email']
|
||||
password = request.form['password']
|
||||
user = User.query.filter_by(email=email).first()
|
||||
if user and user.check_password(password):
|
||||
login_user(user)
|
||||
flash("Logged in successfully!", "success")
|
||||
next_page = request.args.get('next')
|
||||
return redirect(next_page or url_for('main.home'))
|
||||
flash("Invalid credentials", "danger")
|
||||
return render_template('login.html')
|
||||
|
||||
@auth_bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash("Logged out successfully!", "info")
|
||||
return redirect(url_for('main.home'))
|
||||
218
routes/cart.py
Normal file
218
routes/cart.py
Normal file
@@ -0,0 +1,218 @@
|
||||
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash, request
|
||||
from flask_login import login_required, current_user
|
||||
from models import db, Cart, CartItem, Product
|
||||
|
||||
cart_bp = Blueprint('cart', __name__)
|
||||
|
||||
@cart_bp.route('/cart')
|
||||
@login_required
|
||||
def view_cart():
|
||||
|
||||
cart = Cart.query.filter_by(user_id=current_user.id).first()
|
||||
if not cart:
|
||||
cart = Cart(user_id=current_user.id)
|
||||
db.session.add(cart)
|
||||
db.session.commit()
|
||||
|
||||
items = cart.items
|
||||
total = 0
|
||||
item_details = []
|
||||
|
||||
for item in items:
|
||||
product = Product.query.get(item.product_id)
|
||||
if product:
|
||||
subtotal = product.price * item.quantity
|
||||
total += subtotal
|
||||
item_details.append({
|
||||
'id': item.id,
|
||||
'product': product,
|
||||
'quantity': item.quantity,
|
||||
'subtotal': subtotal,
|
||||
'max_quantity': product.stock
|
||||
})
|
||||
|
||||
return render_template('cart.html', items=item_details, total=total)
|
||||
|
||||
@cart_bp.route('/add/<int:product_id>')
|
||||
@login_required
|
||||
def add_to_cart(product_id):
|
||||
|
||||
cart = Cart.query.filter_by(user_id=current_user.id).first()
|
||||
if not cart:
|
||||
cart = Cart(user_id=current_user.id)
|
||||
db.session.add(cart)
|
||||
db.session.commit()
|
||||
|
||||
product = Product.query.get(product_id)
|
||||
if not product:
|
||||
flash("Product not found", "danger")
|
||||
return redirect(url_for('main.products'))
|
||||
|
||||
if product.stock <= 0:
|
||||
flash(f"Sorry, {product.name} is out of stock", "danger")
|
||||
return redirect(url_for('main.products'))
|
||||
|
||||
cart_item = CartItem.query.filter_by(
|
||||
cart_id=cart.id, product_id=product_id).first()
|
||||
|
||||
if cart_item:
|
||||
|
||||
if cart_item.quantity + 1 > product.stock:
|
||||
flash(f"Only {product.stock} items available in stock", "warning")
|
||||
return redirect(url_for('main.products'))
|
||||
|
||||
cart_item.quantity += 1
|
||||
else:
|
||||
|
||||
cart_item = CartItem(cart_id=cart.id, product_id=product_id, quantity=1)
|
||||
db.session.add(cart_item)
|
||||
|
||||
db.session.commit()
|
||||
flash(f"Added {product.name} to cart!", "success")
|
||||
|
||||
referrer = request.referrer
|
||||
if referrer and ('/product/' in referrer or '/products' in referrer):
|
||||
return redirect(referrer)
|
||||
return redirect(url_for('main.products'))
|
||||
|
||||
@cart_bp.route('/remove/<int:item_id>', methods=['POST'])
|
||||
@login_required
|
||||
def remove_from_cart(item_id):
|
||||
cart_item = CartItem.query.get_or_404(item_id)
|
||||
|
||||
if cart_item.cart.user_id != current_user.id:
|
||||
flash("You don't have permission to remove this item", "danger")
|
||||
return redirect(url_for('cart.view_cart'))
|
||||
|
||||
product_name = cart_item.product.name
|
||||
db.session.delete(cart_item)
|
||||
db.session.commit()
|
||||
flash(f"{product_name} removed from cart", "info")
|
||||
return redirect(url_for('cart.view_cart'))
|
||||
|
||||
@cart_bp.route('/update/<int:item_id>', methods=['POST'])
|
||||
@login_required
|
||||
def update_cart(item_id):
|
||||
cart_item = CartItem.query.get_or_404(item_id)
|
||||
|
||||
|
||||
if cart_item.cart.user_id != current_user.id:
|
||||
flash("You don't have permission to update this item", "danger")
|
||||
return redirect(url_for('cart.view_cart'))
|
||||
|
||||
new_quantity = request.form.get('quantity')
|
||||
product = Product.query.get(cart_item.product_id)
|
||||
|
||||
try:
|
||||
new_quantity = int(new_quantity)
|
||||
if new_quantity > 0:
|
||||
if new_quantity > product.stock:
|
||||
flash(
|
||||
f"Only {product.stock} items available in stock. Quantity adjusted.", "warning")
|
||||
new_quantity = product.stock
|
||||
|
||||
cart_item.quantity = new_quantity
|
||||
db.session.commit()
|
||||
flash("Cart updated", "success")
|
||||
elif new_quantity == 0:
|
||||
db.session.delete(cart_item)
|
||||
db.session.commit()
|
||||
flash("Item removed from cart", "info")
|
||||
else:
|
||||
flash("Quantity must be at least 0", "danger")
|
||||
|
||||
except ValueError:
|
||||
flash("Invalid quantity", "danger")
|
||||
|
||||
return redirect(url_for('cart.view_cart'))
|
||||
|
||||
@cart_bp.route('/clear', methods=['POST'])
|
||||
@login_required
|
||||
def clear_cart():
|
||||
cart = Cart.query.filter_by(user_id=current_user.id).first()
|
||||
if cart:
|
||||
items = cart.items
|
||||
if items:
|
||||
item_count = len(items)
|
||||
|
||||
CartItem.query.filter_by(cart_id=cart.id).delete()
|
||||
db.session.commit()
|
||||
flash(f"Cart cleared ({item_count} items removed)", "info")
|
||||
else:
|
||||
flash("Your cart is already empty", "info")
|
||||
|
||||
return redirect(url_for('cart.view_cart'))
|
||||
|
||||
@cart_bp.route('/checkout', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def checkout():
|
||||
cart = Cart.query.filter_by(user_id=current_user.id).first()
|
||||
if not cart or not cart.items:
|
||||
flash("Your cart is empty", "warning")
|
||||
return redirect(url_for('cart.view_cart'))
|
||||
|
||||
for item in cart.items:
|
||||
product = Product.query.get(item.product_id)
|
||||
if product.stock < item.quantity:
|
||||
flash(
|
||||
f"Sorry, {product.name} only has {product.stock} items in stock (you have {item.quantity} in cart)", "danger")
|
||||
return redirect(url_for('cart.view_cart'))
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
total = 0
|
||||
order_items = []
|
||||
|
||||
for item in cart.items:
|
||||
product = Product.query.get(item.product_id)
|
||||
|
||||
product.stock -= item.quantity
|
||||
|
||||
total += product.price * item.quantity
|
||||
|
||||
order_items.append({
|
||||
'product_id': product.id,
|
||||
'quantity': item.quantity,
|
||||
'price': product.price
|
||||
})
|
||||
|
||||
from models import Order, OrderItem
|
||||
from datetime import datetime
|
||||
|
||||
order = Order(user_id=current_user.id, created_at=datetime.utcnow())
|
||||
db.session.add(order)
|
||||
db.session.flush()
|
||||
|
||||
for item_data in order_items:
|
||||
order_item = OrderItem(
|
||||
order_id=order.id,
|
||||
product_id=item_data['product_id'],
|
||||
quantity=item_data['quantity']
|
||||
)
|
||||
db.session.add(order_item)
|
||||
|
||||
CartItem.query.filter_by(cart_id=cart.id).delete()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
flash(f"Order placed successfully! Total: ${total:.2f}", "success")
|
||||
return redirect(url_for('user.profile'))
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f"Checkout failed: {str(e)}", "danger")
|
||||
return redirect(url_for('cart.view_cart'))
|
||||
|
||||
total = sum(item.product.price * item.quantity for item in cart.items)
|
||||
return render_template('checkout.html', cart=cart, total=total)
|
||||
|
||||
@cart_bp.route('/count')
|
||||
@login_required
|
||||
def cart_count():
|
||||
cart = Cart.query.filter_by(user_id=current_user.id).first()
|
||||
if cart:
|
||||
count = sum(item.quantity for item in cart.items)
|
||||
else:
|
||||
count = 0
|
||||
return str(count)
|
||||
149
routes/main.py
Normal file
149
routes/main.py
Normal file
@@ -0,0 +1,149 @@
|
||||
from flask import Blueprint, render_template, request
|
||||
from flask_login import login_required, current_user
|
||||
from models import Product, Category, SizeEnum, db
|
||||
from sqlalchemy import or_, and_
|
||||
from extensions import db
|
||||
|
||||
main_bp = Blueprint('main', __name__)
|
||||
|
||||
@main_bp.route('/')
|
||||
def home():
|
||||
featured_products = Product.query.order_by(Product.id.desc()).limit(8).all()
|
||||
|
||||
|
||||
best_sellers = Product.query.order_by(
|
||||
Product.stock.desc(),
|
||||
Product.price.desc()
|
||||
).limit(4).all()
|
||||
|
||||
categories = Category.query.all()
|
||||
|
||||
return render_template('home.html',
|
||||
featured_products=featured_products,
|
||||
best_sellers=best_sellers,
|
||||
categories=categories)
|
||||
|
||||
@main_bp.route('/products')
|
||||
def products():
|
||||
search_query = request.args.get('q', '').strip()
|
||||
category_id = request.args.get('category', '')
|
||||
size = request.args.get('size', '')
|
||||
color = request.args.get('color', '')
|
||||
material = request.args.get('material', '')
|
||||
company = request.args.get('company', '')
|
||||
min_price = request.args.get('min_price', '')
|
||||
max_price = request.args.get('max_price', '')
|
||||
in_stock_only = request.args.get('in_stock') == '1'
|
||||
sort_by = request.args.get('sort', 'newest')
|
||||
|
||||
query = Product.query
|
||||
|
||||
if search_query:
|
||||
search_term = f"%{search_query}%"
|
||||
query = query.filter(
|
||||
or_(
|
||||
Product.name.ilike(search_term),
|
||||
Product.description.ilike(search_term),
|
||||
Product.color.ilike(search_term),
|
||||
Product.material.ilike(search_term),
|
||||
Product.company.ilike(search_term),
|
||||
Product.category.has(Category.name.ilike(search_term))
|
||||
)
|
||||
)
|
||||
|
||||
if category_id:
|
||||
try:
|
||||
query = query.filter(Product.category_id == int(category_id))
|
||||
selected_category_name = Category.query.get(int(category_id)).name
|
||||
except (ValueError, TypeError):
|
||||
selected_category_name = None
|
||||
else:
|
||||
selected_category_name = None
|
||||
|
||||
if size:
|
||||
query = query.filter(Product.size == size)
|
||||
|
||||
if color:
|
||||
query = query.filter(Product.color.ilike(f"%{color}%"))
|
||||
|
||||
if material:
|
||||
query = query.filter(Product.material.ilike(f"%{material}%"))
|
||||
|
||||
if company:
|
||||
query = query.filter(Product.company.ilike(f"%{company}%"))
|
||||
|
||||
if min_price:
|
||||
try:
|
||||
query = query.filter(Product.price >= float(min_price))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if max_price:
|
||||
try:
|
||||
query = query.filter(Product.price <= float(max_price))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if in_stock_only:
|
||||
query = query.filter(Product.stock > 0)
|
||||
|
||||
if sort_by == 'price_low':
|
||||
query = query.order_by(Product.price.asc())
|
||||
elif sort_by == 'price_high':
|
||||
query = query.order_by(Product.price.desc())
|
||||
elif sort_by == 'name':
|
||||
query = query.order_by(Product.name.asc())
|
||||
elif sort_by == 'rating':
|
||||
|
||||
query = query.order_by(Product.id.desc())
|
||||
else:
|
||||
query = query.order_by(Product.id.desc())
|
||||
|
||||
colors = db.session.query(Product.color).distinct().filter(
|
||||
Product.color != None, Product.color != '').all()
|
||||
colors = [c[0] for c in colors if c[0]]
|
||||
|
||||
materials = db.session.query(Product.material).distinct().filter(
|
||||
Product.material != None, Product.material != '').all()
|
||||
materials = [m[0] for m in materials if m[0]]
|
||||
|
||||
companies = db.session.query(Product.company).distinct().filter(
|
||||
Product.company != None, Product.company != '').all()
|
||||
companies = [c[0] for c in companies if c[0]]
|
||||
|
||||
products = query.all()
|
||||
|
||||
categories = Category.query.order_by(Category.name.asc()).all()
|
||||
|
||||
return render_template('products.html',
|
||||
products=products,
|
||||
categories=categories,
|
||||
sizes=SizeEnum.ALL,
|
||||
colors=colors,
|
||||
materials=materials,
|
||||
companies=companies,
|
||||
search_query=search_query,
|
||||
selected_category=category_id,
|
||||
selected_category_name=selected_category_name,
|
||||
selected_size=size,
|
||||
selected_color=color,
|
||||
selected_material=material,
|
||||
selected_company=company,
|
||||
min_price=min_price,
|
||||
max_price=max_price,
|
||||
sort_by=sort_by,
|
||||
in_stock_only=in_stock_only)
|
||||
|
||||
@main_bp.route('/product/<int:id>')
|
||||
def product_detail(id):
|
||||
product = Product.query.get_or_404(id)
|
||||
|
||||
|
||||
related_products = Product.query.filter(
|
||||
Product.category_id == product.category_id,
|
||||
Product.id != product.id
|
||||
).limit(4).all()
|
||||
|
||||
return render_template('product_detail.html',
|
||||
product=product,
|
||||
related_products=related_products)
|
||||
22
routes/user.py
Normal file
22
routes/user.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from flask import Blueprint, render_template, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from models import Order
|
||||
|
||||
user_bp = Blueprint('user', __name__)
|
||||
|
||||
@user_bp.route('/profile')
|
||||
@login_required
|
||||
def profile():
|
||||
|
||||
orders = Order.query.filter_by(user_id=current_user.id).all()
|
||||
return render_template('user_profile.html', orders=orders)
|
||||
|
||||
@user_bp.route('/orders/<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:
|
||||
flash("Access denied", "danger")
|
||||
return redirect(url_for('user.profile'))
|
||||
return render_template('order_details.html', order=order)
|
||||
Reference in New Issue
Block a user