This commit is contained in:
parent 6f573999ef
commit 98bbc486e6
3 changed files with 87 additions and 23 deletions

83
app.py
View file

@ -16,10 +16,43 @@ def get_db_connection():
return conn
def is_valid_email(email):
"""Проверяет корректность email адреса (только популярные почтовые домены)"""
if not email:
return True
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.(ru|com)$'
return re.match(pattern, email.lower()) is not None
# Ограничение длины
if len(email) > 50:
return False
if '@' not in email:
return False
local_part = email.split('@')[0]
domain = email.split('@')[1].lower()
# Локальная часть: минимум 2 символа, не только цифры
if len(local_part) < 2 or len(local_part) > 30:
return False
if local_part.isdigit():
return False
if not local_part[0].isalpha():
return False
# Разрешенные почтовые домены
allowed_domains = [
'mail.ru', 'yandex.ru', 'gmail.com', 'gmail.ru',
'list.ru', 'bk.ru', 'inbox.ru', 'rambler.ru',
'ya.ru', 'google.com', 'outlook.com', 'hotmail.com',
'yahoo.com', 'protonmail.com', 'icloud.com'
]
# Проверяем, что домен есть в списке разрешенных
if domain not in allowed_domains:
return False
return True
def login_required(f):
@wraps(f)
@ -191,15 +224,18 @@ def register():
username = request.form['username']
password = request.form['password']
email = request.form.get('email', '')
if email and not is_valid_email(email):
flash('Введите корректный email адрес (должен заканчиваться на .ru или .com)', 'danger')
flash('Введите корректный email адрес (допустимые домены: mail.ru, yandex.ru, gmail.com, list.ru, bk.ru, inbox.ru, rambler.ru, ya.ru, google.com, outlook.com, hotmail.com, yahoo.com, protonmail.com, icloud.com)', 'danger')
return redirect(url_for('register'))
conn = get_db_connection()
user = conn.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone()
if user:
flash('Пользователь с таким именем уже существует', 'danger')
conn.close()
return redirect(url_for('register'))
hashed_password = generate_password_hash(password)
conn.execute('INSERT INTO users (username, password, email, role) VALUES (?, ?, ?, ?)',
(username, hashed_password, email if email else None, 'user'))
@ -247,17 +283,20 @@ def order():
persons = request.form.get('persons', 1)
price = request.form.get('price', 0)
comment = request.form.get('comment', '')
try:
persons = int(float(persons)) if service_type != 'certificate' else 1
if persons < 1: persons = 1
if persons > 20: persons = 20
except:
persons = 1
conn = get_db_connection()
final_price = int(price)
discount_applied = False
discount_amount = 0
original_price = int(price)
if service_type != 'certificate':
duration_minutes = 0
duration_str = ''
@ -270,15 +309,18 @@ def order():
elif 'час' in duration_str:
hours = int(duration_str.split()[0])
duration_minutes = hours * 60
try:
hour = int(time.split(':')[0])
minute = int(time.split(':')[1])
start_minutes = hour * 60 + minute
end_minutes = start_minutes + duration_minutes
if start_minutes < 540:
flash('Заказы принимаются с 9:00', 'danger')
conn.close()
return redirect(url_for('order'))
if end_minutes > 1200:
max_hour = (1200 - duration_minutes) // 60
max_minute = (1200 - duration_minutes) % 60
@ -289,15 +331,18 @@ def order():
flash('Неверный формат времени', 'danger')
conn.close()
return redirect(url_for('order'))
today = datetime.now().strftime('%Y-%m-%d')
if date < today:
flash('Нельзя выбрать прошедшую дату', 'danger')
conn.close()
return redirect(url_for('order'))
if persons >= 4 and service_type == 'service':
discount_amount = int(int(price) * 0.15)
final_price = int(int(price) * 0.85)
discount_applied = True
conn.execute('''
INSERT INTO orders
(user_id, service_type, service_name, service_id, date, time, persons, price, comment, discount_applied, discount_amount, original_price, payment_status)
@ -305,11 +350,13 @@ def order():
''', (session['user_id'], service_type, service_name, service_id, date, time, persons, final_price, comment, discount_applied, discount_amount, original_price))
conn.commit()
conn.close()
if discount_applied:
flash(f'Заказ успешно создан! Применена семейная скидка 15%! Итоговая цена: {final_price} руб.', 'success')
else:
flash('Заказ успешно создан!', 'success')
return redirect(url_for('my_orders'))
return render_template('order_form.html')
@app.route('/my-orders')
@ -318,6 +365,7 @@ def my_orders():
sort = request.args.get('sort', 'newest')
status = request.args.get('status', 'all')
conn = get_db_connection()
query = 'SELECT * FROM orders WHERE user_id = ?'
params = [session['user_id']]
if status != 'all':
@ -331,6 +379,7 @@ def my_orders():
query += ' ORDER BY price DESC'
elif sort == 'price_low':
query += ' ORDER BY price ASC'
orders = conn.execute(query, params).fetchall()
orders_list = []
for order in orders:
@ -341,6 +390,7 @@ def my_orders():
''', (order['id'],)).fetchone()
order_dict['unread_count'] = unread['count'] if unread else 0
orders_list.append(order_dict)
conn.close()
return render_template('my_orders.html', orders=orders_list, sort=sort, status=status)
@ -351,9 +401,11 @@ def reviews():
if 'user_id' not in session:
flash('Пожалуйста, войдите в систему, чтобы оставить отзыв', 'warning')
return redirect(url_for('login'))
order_id = request.form.get('order_id')
rating = int(request.form['rating'])
comment = request.form['comment']
conn = get_db_connection()
existing_review = conn.execute('SELECT * FROM reviews WHERE user_id = ? AND order_id = ?',
(session['user_id'], order_id)).fetchone()
@ -361,12 +413,14 @@ def reviews():
flash('Вы уже оставляли отзыв на этот заказ', 'danger')
conn.close()
return redirect(url_for('reviews'))
order = conn.execute('SELECT * FROM orders WHERE id = ? AND user_id = ? AND status = "completed"',
(order_id, session['user_id'])).fetchone()
if not order:
flash('Вы можете оставить отзыв только на выполненные заказы', 'danger')
conn.close()
return redirect(url_for('reviews'))
conn.execute('''
INSERT INTO reviews (user_id, order_id, username, rating, comment, status)
VALUES (?, ?, ?, ?, ?, 'pending')
@ -375,9 +429,11 @@ def reviews():
conn.close()
flash('Ваш отзыв отправлен на модерацию!', 'success')
return redirect(url_for('reviews'))
sort = request.args.get('sort', 'newest')
rating_filter = request.args.get('rating', 'all')
conn = get_db_connection()
completed_orders = []
if 'user_id' in session:
completed_orders = conn.execute('''
@ -386,6 +442,7 @@ def reviews():
WHERE o.user_id = ? AND o.status = 'completed' AND r.id IS NULL
ORDER BY o.created_at DESC
''', (session['user_id'], session['user_id'])).fetchall()
query = 'SELECT * FROM reviews WHERE status = "approved"'
params = []
if rating_filter != 'all':
@ -399,6 +456,7 @@ def reviews():
query += ' ORDER BY rating DESC'
elif sort == 'rating_low':
query += ' ORDER BY rating ASC'
approved_reviews = conn.execute(query, params).fetchall()
conn.close()
return render_template('reviews.html', reviews=approved_reviews,
@ -417,8 +475,10 @@ def chat(order_id):
if session['user_id'] != order['user_id'] and session.get('role') != 'admin':
flash('Доступ запрещен', 'danger')
return redirect(url_for('index'))
if session.get('role') == 'admin':
conn.execute('UPDATE messages SET is_read = 1 WHERE order_id = ? AND sender_type = "user"', (order_id,))
messages = conn.execute('''
SELECT m.*,
CASE
@ -442,6 +502,7 @@ def send_message():
message = request.form['message']
if not message or not message.strip():
return jsonify({'error': 'Сообщение не может быть пустым'}), 400
conn = get_db_connection()
order = conn.execute('SELECT * FROM orders WHERE id = ?', (order_id,)).fetchone()
if not order:
@ -450,6 +511,7 @@ def send_message():
if session['user_id'] != order['user_id'] and session.get('role') != 'admin':
conn.close()
return jsonify({'error': 'Доступ запрещен'}), 403
if session.get('role') == 'admin':
sender_type = 'admin'
admin_id = session['user_id']
@ -458,6 +520,7 @@ def send_message():
sender_type = 'user'
user_id = session['user_id']
admin_id = None
conn.execute('''
INSERT INTO messages (order_id, user_id, admin_id, message, sender_type)
VALUES (?, ?, ?, ?, ?)
@ -497,6 +560,7 @@ def admin_orders():
query += ' ORDER BY o.price DESC'
elif sort == 'price_low':
query += ' ORDER BY o.price ASC'
orders = conn.execute(query, params).fetchall()
conn.close()
return render_template('admin/orders.html', orders=orders, sort=sort, status=status)
@ -556,6 +620,7 @@ def admin_reviews():
query += ' ORDER BY rating DESC'
elif sort == 'rating_low':
query += ' ORDER BY rating ASC'
reviews = conn.execute(query, params).fetchall()
conn.close()
return render_template('admin/reviews.html', reviews=reviews, sort=sort, status=status, rating=rating)
@ -606,6 +671,7 @@ def admin_users():
query += ' ORDER BY username ASC'
elif sort == 'name_desc':
query += ' ORDER BY username DESC'
users = conn.execute(query, params).fetchall()
conn.close()
return render_template('admin/users.html', users=users, sort=sort, role=role)
@ -659,18 +725,25 @@ def admin_messages():
if __name__ == '__main__':
conn = sqlite3.connect('horse_farm.db')
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(orders)")
existing = [col[1] for col in cursor.fetchall()]
for col in ['discount_applied', 'discount_amount', 'original_price', 'payment_status']:
if col not in existing:
cursor.execute(f"ALTER TABLE orders ADD COLUMN {col} DEFAULT 'not_paid' if col == 'payment_status' else 0")
if col == 'payment_status':
cursor.execute(f"ALTER TABLE orders ADD COLUMN {col} TEXT DEFAULT 'not_paid'")
else:
cursor.execute(f"ALTER TABLE orders ADD COLUMN {col} INTEGER DEFAULT 0")
print(f"Добавлена колонка {col}")
cursor.execute("PRAGMA table_info(reviews)")
if 'order_id' not in [c[1] for c in cursor.fetchall()]:
cursor.execute("ALTER TABLE reviews ADD COLUMN order_id INTEGER")
cursor.execute("PRAGMA table_info(messages)")
if 'is_read' not in [c[1] for c in cursor.fetchall()]:
cursor.execute("ALTER TABLE messages ADD COLUMN is_read BOOLEAN DEFAULT 0")
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='messages'")
if not cursor.fetchone():
cursor.execute('''
@ -685,12 +758,14 @@ if __name__ == '__main__':
is_read BOOLEAN DEFAULT 0
)
''')
cursor.execute("SELECT * FROM users WHERE username = 'admin'")
if not cursor.fetchone():
hashed = generate_password_hash('admin123')
cursor.execute("INSERT INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
('admin', hashed, 'admin@koniydvor.ru', 'admin'))
print("Создан администратор: admin / admin123")
conn.commit()
conn.close()
app.run(debug=True)

Binary file not shown.

View file

@ -10,24 +10,20 @@
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.auth-container h2 {
color: #f5cba0;
margin-bottom: 1.5rem;
text-align: center;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.3rem;
font-weight: 600;
color: #f5cba0;
}
.form-group input {
width: 100%;
padding: 0.6rem;
@ -37,15 +33,13 @@
background: rgba(255, 255, 255, 0.9);
color: #2c2c2c;
}
.form-group input:focus {
outline: none;
border-color: #f5cba0;
}
.btn-primary {
width: 100%;
background: linear-gradient(135deg, #f5cba0 0%, #d4a373 100%);
background: #f5cba0;
color: #2c1810;
padding: 0.75rem;
border: none;
@ -53,34 +47,29 @@
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.2s ease;
transition: 0.2s;
}
.btn-primary:hover {
background: linear-gradient(135deg, #ffdd99 0%, #f5cba0 100%);
background: #ffdd99;
transform: translateY(-2px);
}
.auth-link {
text-align: center;
margin-top: 1rem;
color: white;
}
.auth-link a {
color: #f5cba0;
text-decoration: none;
}
.auth-link a:hover {
text-decoration: underline;
}
small {
display: block;
margin-top: 0.25rem;
font-size: 0.7rem;
color: #f5cba0;
color: #ffffff !important;
}
</style>
@ -98,18 +87,18 @@
<form method="POST" class="auth-form">
<div class="form-group">
<label for="username">Имя пользователя:</label>
<input type="text" id="username" name="username" required>
<input type="text" id="username" name="username" required maxlength="50">
</div>
<div class="form-group">
<label for="email">Email (необязательно):</label>
<input type="email" id="email" name="email" placeholder="example@mail.ru">
<small>Формат: name@domain.ru</small>
<input type="email" id="email" name="email" placeholder="example@mail.ru" maxlength="50">
<small>Максимум 50 символов, локальная часть (до @) - до 20 символов</small>
</div>
<div class="form-group">
<label for="password">Пароль:</label>
<input type="password" id="password" name="password" required>
<input type="password" id="password" name="password" required minlength="4" maxlength="100">
</div>
<button type="submit" class="btn-primary">Зарегистрироваться</button>