first commit
This commit is contained in:
commit
bf87da57ed
246
app.py
Normal file
246
app.py
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from flask import Flask, render_template, request, redirect, url_for, flash, session
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = 'super_secret_key_for_exam'
|
||||||
|
DB_PATH = 'shoe_shop.db'
|
||||||
|
|
||||||
|
# --- РАБОТА С БД ---
|
||||||
|
def get_db_connection():
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
# Если базы нет, создаем её из файла database.sql
|
||||||
|
if not os.path.exists(DB_PATH):
|
||||||
|
conn = get_db_connection()
|
||||||
|
with open('database.sql', 'r', encoding='utf-8') as f:
|
||||||
|
conn.executescript(f.read())
|
||||||
|
|
||||||
|
# Наполняем базу тестовыми данными (Импорт данных)
|
||||||
|
# Роли
|
||||||
|
conn.execute("INSERT INTO roles (name) VALUES ('admin'), ('manager'), ('user')")
|
||||||
|
|
||||||
|
# Пользователи (Логины без пароля)
|
||||||
|
conn.execute("INSERT INTO users (login, last_name, first_name, middle_name, role_id) VALUES ('admin', 'Админов', 'Админ', 'Админович', 1)")
|
||||||
|
conn.execute("INSERT INTO users (login, last_name, first_name, middle_name, role_id) VALUES ('manager', 'Менеджеров', 'Менеджер', 'Менеджерович', 2)")
|
||||||
|
conn.execute("INSERT INTO users (login, last_name, first_name, middle_name, role_id) VALUES ('user', 'Петров', 'Петр', 'Петрович', 3)")
|
||||||
|
|
||||||
|
# Категории и подкатегории
|
||||||
|
conn.execute("INSERT INTO categories (name) VALUES ('Мужская'), ('Женская')")
|
||||||
|
conn.execute("INSERT INTO subcategories (category_id, name) VALUES (1, 'Кроссовки'), (1, 'Ботинки'), (2, 'Кроссовки')")
|
||||||
|
conn.execute("INSERT INTO manufacturers (name) VALUES ('Nike'), ('Adidas')")
|
||||||
|
|
||||||
|
# Товары
|
||||||
|
conn.execute("""INSERT INTO products (article, name, subcategory_id, manufacturer_id, description, composition, price, image_path)
|
||||||
|
VALUES ('A001', 'Air Max', 1, 1, 'Спортивные', 'Кожа', 10000, '')""")
|
||||||
|
conn.execute("""INSERT INTO products (article, name, subcategory_id, manufacturer_id, description, composition, price, image_path)
|
||||||
|
VALUES ('A002', 'Superstar', 3, 2, 'Классика', 'Текстиль', 8000, '')""")
|
||||||
|
|
||||||
|
# Размеры и остатки (для проверки "мало/много")
|
||||||
|
conn.execute("INSERT INTO sizes (size_value) VALUES (40), (41), (42)")
|
||||||
|
conn.execute("INSERT INTO product_stock (product_id, size_id, quantity) VALUES (1, 1, 1), (1, 2, 2), (2, 1, 10), (2, 2, 15)")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# --- БИЗНЕС-ЛОГИКА ---
|
||||||
|
def calculate_discount(product_id, base_price):
|
||||||
|
"""Скидка 25%, если на товар не было заказов за последний месяц (30 дней)"""
|
||||||
|
conn = get_db_connection()
|
||||||
|
one_month_ago = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
|
||||||
|
has_recent_orders = conn.execute(
|
||||||
|
"SELECT COUNT(*) FROM order_items oi JOIN orders o ON oi.order_id = o.id WHERE oi.product_id = ? AND o.order_date >= ?",
|
||||||
|
(product_id, one_month_ago)
|
||||||
|
).fetchone()[0]
|
||||||
|
conn.close()
|
||||||
|
return base_price * 0.75 if has_recent_orders == 0 else base_price
|
||||||
|
|
||||||
|
def get_user_role():
|
||||||
|
"""Получение роли текущего пользователя"""
|
||||||
|
if 'user_id' not in session:
|
||||||
|
return 'guest'
|
||||||
|
conn = get_db_connection()
|
||||||
|
role = conn.execute("SELECT r.name FROM roles r JOIN users u ON u.role_id = r.id WHERE u.id = ?", (session['user_id'],)).fetchone()
|
||||||
|
conn.close()
|
||||||
|
return role['name'] if role else 'guest'
|
||||||
|
|
||||||
|
# Передаем роль и имя во все шаблоны автоматически
|
||||||
|
@app.context_processor
|
||||||
|
def inject_globals():
|
||||||
|
return dict(current_role=get_user_role(), current_user=session.get('user_name'))
|
||||||
|
|
||||||
|
# --- МАРШРУТЫ (АВТОРИЗАЦИЯ) ---
|
||||||
|
|
||||||
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
if request.method == 'POST':
|
||||||
|
login_val = request.form.get('login')
|
||||||
|
conn = get_db_connection()
|
||||||
|
user = conn.execute("SELECT * FROM users WHERE login = ?", (login_val,)).fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
session['user_id'] = user['id']
|
||||||
|
session['user_name'] = f"{user['last_name']} {user['first_name']}"
|
||||||
|
flash(f'Вы вошли как {user["last_name"]}', 'message')
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
else:
|
||||||
|
flash('Пользователь с таким логином не найден', 'error')
|
||||||
|
return render_template('login.html')
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
session.clear()
|
||||||
|
flash('Вы вышли из системы', 'message')
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
# --- КАТАЛОГ ---
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
conn = get_db_connection()
|
||||||
|
|
||||||
|
# Фильтрация и поиск (доступна только не гостям)
|
||||||
|
search = request.args.get('search', '')
|
||||||
|
category_filter = request.args.get('category', '')
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT p.*, sc.name as subcategory_name, c.name as category_name, m.name as manufacturer_name,
|
||||||
|
(SELECT SUM(quantity) FROM product_stock WHERE product_id = p.id) as total_qty
|
||||||
|
FROM products p
|
||||||
|
JOIN subcategories sc ON p.subcategory_id = sc.id
|
||||||
|
JOIN categories c ON sc.category_id = c.id
|
||||||
|
JOIN manufacturers m ON p.manufacturer_id = m.id
|
||||||
|
WHERE 1=1
|
||||||
|
"""
|
||||||
|
params = []
|
||||||
|
|
||||||
|
# Гостю фильтры не применяем (по ТЗ)
|
||||||
|
if get_user_role() != 'guest':
|
||||||
|
if search:
|
||||||
|
query += " AND (p.name LIKE ? OR p.description LIKE ?)"
|
||||||
|
params.extend([f'%{search}%', f'%{search}%'])
|
||||||
|
if category_filter:
|
||||||
|
query += " AND c.id = ?"
|
||||||
|
params.append(category_filter)
|
||||||
|
|
||||||
|
products_raw = conn.execute(query, params).fetchall()
|
||||||
|
categories = conn.execute("SELECT * FROM categories").fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
products = []
|
||||||
|
for p in products_raw:
|
||||||
|
p_dict = dict(p)
|
||||||
|
# Расчет цены со скидкой
|
||||||
|
p_dict['final_price'] = round(calculate_discount(p['id'], p['price']), 2)
|
||||||
|
# Проверка количества (мало/много)
|
||||||
|
if p_dict['total_qty'] is None or p_dict['total_qty'] <= 5:
|
||||||
|
p_dict['qty_text'] = 'мало'
|
||||||
|
p_dict['highlight'] = False
|
||||||
|
else:
|
||||||
|
p_dict['qty_text'] = 'много'
|
||||||
|
p_dict['highlight'] = True
|
||||||
|
# Заглушка для картинки
|
||||||
|
if not p_dict['image_path']:
|
||||||
|
p_dict['image_path'] = 'picture.png'
|
||||||
|
products.append(p_dict)
|
||||||
|
|
||||||
|
return render_template('index.html', products=products, categories=categories, search=search, cat_filter=category_filter)
|
||||||
|
|
||||||
|
# --- КОРЗИНА И ЗАКАЗЫ ---
|
||||||
|
|
||||||
|
@app.route('/add_to_cart/<int:product_id>', methods=['POST'])
|
||||||
|
def add_to_cart(product_id):
|
||||||
|
if get_user_role() == 'guest':
|
||||||
|
flash('Войдите, чтобы оформлять заказы', 'error')
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
size_id = request.form.get('size_id')
|
||||||
|
qty = int(request.form.get('quantity', 1))
|
||||||
|
|
||||||
|
if 'cart' not in session:
|
||||||
|
session['cart'] = []
|
||||||
|
|
||||||
|
cart = session['cart']
|
||||||
|
found = False
|
||||||
|
for item in cart:
|
||||||
|
if item['product_id'] == product_id and item['size_id'] == int(size_id):
|
||||||
|
item['quantity'] += qty
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
cart.append({'product_id': product_id, 'size_id': int(size_id), 'quantity': qty})
|
||||||
|
|
||||||
|
session['cart'] = cart
|
||||||
|
flash('Товар добавлен в корзину', 'message')
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
@app.route('/cart')
|
||||||
|
def cart():
|
||||||
|
if get_user_role() == 'guest':
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
return render_template('cart.html', cart=session.get('cart', []))
|
||||||
|
|
||||||
|
@app.route('/checkout', methods=['POST'])
|
||||||
|
def checkout():
|
||||||
|
if get_user_role() == 'guest' or not session.get('cart'):
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
client_name = request.form.get('client_name')
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Создаем заказ
|
||||||
|
cursor.execute("INSERT INTO orders (user_id, client_full_name, order_date) VALUES (?, ?, ?)",
|
||||||
|
(session['user_id'], client_name, datetime.now().strftime('%Y-%m-%d')))
|
||||||
|
order_id = cursor.lastrowid
|
||||||
|
|
||||||
|
# Добавляем позиции
|
||||||
|
for item in session['cart']:
|
||||||
|
price = conn.execute("SELECT price FROM products WHERE id = ?", (item['product_id'],)).fetchone()['price']
|
||||||
|
final_price = calculate_discount(item['product_id'], price)
|
||||||
|
cursor.execute("INSERT INTO order_items (order_id, product_id, size_id, quantity, price_at_moment) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
(order_id, item['product_id'], item['size_id'], item['quantity'], final_price))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
session.pop('cart', None)
|
||||||
|
flash('Заказ успешно оформлен!', 'message')
|
||||||
|
return redirect(url_for('orders'))
|
||||||
|
|
||||||
|
@app.route('/orders')
|
||||||
|
def orders():
|
||||||
|
# Просмотр заказов только для менеджера и админа
|
||||||
|
if get_user_role() in ['guest', 'user']:
|
||||||
|
flash('Доступ запрещен', 'error')
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
conn = get_db_connection()
|
||||||
|
orders_list = conn.execute("""
|
||||||
|
SELECT o.id, o.order_date, o.client_full_name, u.last_name as manager_name
|
||||||
|
FROM orders o
|
||||||
|
LEFT JOIN users u ON o.user_id = u.id
|
||||||
|
ORDER BY o.order_date DESC
|
||||||
|
""").fetchall()
|
||||||
|
conn.close()
|
||||||
|
return render_template('orders.html', orders=orders_list)
|
||||||
|
|
||||||
|
@app.route('/delete_order/<int:order_id>')
|
||||||
|
def delete_order(order_id):
|
||||||
|
if get_user_role() not in ['admin', 'manager']:
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
conn = get_db_connection()
|
||||||
|
conn.execute("DELETE FROM orders WHERE id = ?", (order_id,))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
flash('Заказ удален', 'message')
|
||||||
|
return redirect(url_for('orders'))
|
||||||
|
|
||||||
|
# --- ЗАПУСК ---
|
||||||
|
if __name__ == '__main__':
|
||||||
|
init_db() # Проверяем/создаем базу при запуске
|
||||||
|
app.run(debug=True)
|
||||||
11
database.sql
Normal file
11
database.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
CREATE TABLE roles (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE);
|
||||||
|
CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, login TEXT NOT NULL UNIQUE, last_name TEXT NOT NULL, first_name TEXT NOT NULL, middle_name TEXT, role_id INTEGER, FOREIGN KEY (role_id) REFERENCES roles(id));
|
||||||
|
CREATE TABLE categories (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);
|
||||||
|
CREATE TABLE subcategories (id INTEGER PRIMARY KEY AUTOINCREMENT, category_id INTEGER, name TEXT NOT NULL, FOREIGN KEY (category_id) REFERENCES categories(id));
|
||||||
|
CREATE TABLE manufacturers (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);
|
||||||
|
CREATE TABLE products (id INTEGER PRIMARY KEY AUTOINCREMENT, article TEXT UNIQUE NOT NULL, name TEXT NOT NULL, subcategory_id INTEGER, manufacturer_id INTEGER, description TEXT, composition TEXT, price REAL NOT NULL, image_path TEXT, FOREIGN KEY (subcategory_id) REFERENCES subcategories(id), FOREIGN KEY (manufacturer_id) REFERENCES manufacturers(id));
|
||||||
|
CREATE TABLE sizes (id INTEGER PRIMARY KEY AUTOINCREMENT, size_value REAL NOT NULL);
|
||||||
|
CREATE TABLE product_stock (product_id INTEGER, size_id INTEGER, quantity INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (product_id, size_id), FOREIGN KEY (product_id) REFERENCES products(id), FOREIGN KEY (size_id) REFERENCES sizes(id));
|
||||||
|
CREATE TABLE orders (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, client_full_name TEXT NOT NULL, order_date DATE NOT NULL, status TEXT DEFAULT 'Новый', FOREIGN KEY (user_id) REFERENCES users(id));
|
||||||
|
CREATE TABLE order_items (id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER, product_id INTEGER, size_id INTEGER, quantity INTEGER NOT NULL, price_at_moment REAL NOT NULL, FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id), FOREIGN KEY (size_id) REFERENCES sizes(id));
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Flask==3.0.0
|
||||||
BIN
shoe_shop.db
Normal file
BIN
shoe_shop.db
Normal file
Binary file not shown.
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
BIN
static/logo.png
Normal file
BIN
static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
BIN
static/picture.png
Normal file
BIN
static/picture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
31
static/style.css
Normal file
31
static/style.css
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* Обновленный style.css */
|
||||||
|
body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; }
|
||||||
|
header { background: #333; color: white; padding: 10px 20px; display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
header nav a { color: #4CAF50; margin-left: 15px; text-decoration: none; }
|
||||||
|
header nav .user-info { color: #ff9800; margin-left: 15px; font-size: 14px; }
|
||||||
|
main { max-width: 900px; margin: 20px auto; padding: 0 20px; }
|
||||||
|
|
||||||
|
.filters { margin-bottom: 20px; display: flex; gap: 10px; }
|
||||||
|
.filters input, .filters select { padding: 5px; }
|
||||||
|
|
||||||
|
.product-card { display: flex; background: white; border: 1px solid #ccc; margin-bottom: 15px; padding: 15px; align-items: center; border-radius: 5px; }
|
||||||
|
.product-card.highlight { background-color: #ffcccc; } /* Светло-красный */
|
||||||
|
.product-card img { width: 120px; height: 120px; object-fit: contain; margin-right: 20px; }
|
||||||
|
.info { flex-grow: 1; }
|
||||||
|
.info h3 { margin: 0 0 5px 0; }
|
||||||
|
.info p { margin: 2px 0; font-size: 14px; color: #555; }
|
||||||
|
.price-block { text-align: right; min-width: 150px; }
|
||||||
|
.old-price { text-decoration: line-through; color: red; font-size: 14px; display: block; }
|
||||||
|
.new-price { font-weight: bold; font-size: 18px; }
|
||||||
|
.add-form { margin-top: 10px; display: flex; flex-direction: column; gap: 5px; }
|
||||||
|
|
||||||
|
table { width: 100%; border-collapse: collapse; background: white; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||||
|
th { background: #333; color: white; }
|
||||||
|
|
||||||
|
.btn-danger { color: red; text-decoration: none; }
|
||||||
|
.btn-success { background: #4CAF50; color: white; padding: 5px 10px; border: none; cursor: pointer; }
|
||||||
|
|
||||||
|
.flash { padding: 10px; margin-bottom: 15px; border-radius: 3px; }
|
||||||
|
.flash.error { background: #f8d7da; color: #721c24; }
|
||||||
|
.flash.message { background: #d4edda; color: #155724; }
|
||||||
45
templates/base.html
Normal file
45
templates/base.html
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Чудо Обувь</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="logo-area">
|
||||||
|
<img src="{{ url_for('static', filename='logo.png') }}" alt="Логотип" height="40">
|
||||||
|
<h1>Чудо Обувь</h1>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<a href="{{ url_for('index') }}">Каталог</a>
|
||||||
|
{% if current_role != 'guest' %}
|
||||||
|
<a href="{{ url_for('cart') }}">Корзина</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if current_role in ['admin', 'manager'] %}
|
||||||
|
<a href="{{ url_for('orders') }}">Заказы</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_role == 'guest' %}
|
||||||
|
<a href="{{ url_for('login') }}">Войти</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="user-info">{{ current_user }} ({{ current_role }})</span>
|
||||||
|
<a href="{{ url_for('logout') }}">Выйти</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="flash {{ category }}">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
templates/cart.html
Normal file
20
templates/cart.html
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Корзина</h2>
|
||||||
|
{% if cart %}
|
||||||
|
<form method="POST" action="{{ url_for('checkout') }}">
|
||||||
|
<label>ФИО Клиента для заказа:</label>
|
||||||
|
<input type="text" name="client_name" required value="{{ current_user }}">
|
||||||
|
<button type="submit" class="btn-success">Оформить заказ</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ul class="cart-list">
|
||||||
|
<!-- Здесь в реальности нужно выводить названия товаров, подтягивая их из БД -->
|
||||||
|
{% for item in cart %}
|
||||||
|
<li>Товар ID: {{ item.product_id }}, Размер ID: {{ item.size_id }}, Кол-во: {{ item.quantity }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p>Корзина пуста</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
54
templates/index.html
Normal file
54
templates/index.html
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if current_role != 'guest' %}
|
||||||
|
<form method="GET" class="filters">
|
||||||
|
<input type="text" name="search" placeholder="Поиск..." value="{{ search }}">
|
||||||
|
<select name="category">
|
||||||
|
<option value="">Все категории</option>
|
||||||
|
{% for cat in categories %}
|
||||||
|
<option value="{{ cat.id }}" {% if cat_filter == cat.id|string %}selected{% endif %}>{{ cat.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button type="submit">Найти</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="catalog">
|
||||||
|
{% for product in products %}
|
||||||
|
<div class="product-card {% if product.highlight %}highlight{% endif %}">
|
||||||
|
<img src="{{ url_for('static', filename=product.image_path) }}" alt="Фото">
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<h3>{{ product.manufacturer_name }} | {{ product.name }}</h3>
|
||||||
|
<p><b>Категория:</b> {{ product.category_name }} / {{ product.subcategory_name }}</p>
|
||||||
|
<p><b>Количество:</b> {{ product.qty_text }}</p>
|
||||||
|
<p><b>Состав:</b> {{ product.composition }}</p>
|
||||||
|
<p><b>Описание:</b> {{ product.description }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="price-block">
|
||||||
|
{% if product.final_price < product.price %}
|
||||||
|
<span class="old-price">{{ product.price }} руб.</span>
|
||||||
|
<span class="new-price">{{ product.final_price }} руб.</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="price">{{ product.price }} руб.</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_role != 'guest' and product.total_qty > 0 %}
|
||||||
|
<form method="POST" action="{{ url_for('add_to_cart', product_id=product.id) }}" class="add-form">
|
||||||
|
<select name="size_id">
|
||||||
|
<!-- В реальности здесь нужно выводить только те размеры, что есть в наличии -->
|
||||||
|
<option value="1">40</option>
|
||||||
|
<option value="2">41</option>
|
||||||
|
<option value="3">42</option>
|
||||||
|
</select>
|
||||||
|
<input type="number" name="quantity" value="1" min="1" style="width: 50px;">
|
||||||
|
<button type="submit">В корзину</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
9
templates/login.html
Normal file
9
templates/login.html
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Вход в систему</h2>
|
||||||
|
<p>Пароль не требуется. Введите логин (admin / manager / user).</p>
|
||||||
|
<form method="POST" class="login-form">
|
||||||
|
<input type="text" name="login" placeholder="Логин" required>
|
||||||
|
<button type="submit">Войти</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
24
templates/orders.html
Normal file
24
templates/orders.html
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>Список заказов</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Дата</th>
|
||||||
|
<th>Клиент</th>
|
||||||
|
<th>Менеджер</th>
|
||||||
|
<th>Действия</th>
|
||||||
|
</tr>
|
||||||
|
{% for order in orders %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ order.id }}</td>
|
||||||
|
<td>{{ order.order_date }}</td>
|
||||||
|
<td>{{ order.client_full_name }}</td>
|
||||||
|
<td>{{ order.manager_name }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('delete_order', order_id=order.id) }}" class="btn-danger" onclick="return confirm('Удалить заказ?')">Удалить</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in a new issue