diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index ac9aba0..13337b7 100644 Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ diff --git a/app.py b/app.py index 35fd585..ccdcbad 100644 --- a/app.py +++ b/app.py @@ -1,44 +1,129 @@ import os -from flask import Flask, render_template, request, redirect, url_for, flash +from flask import Flask, render_template, request, redirect, url_for, flash, session from flask_sqlalchemy import SQLAlchemy +from werkzeug.security import generate_password_hash, check_password_hash from datetime import datetime, date from dotenv import load_dotenv +from functools import wraps -load_dotenv() # Загрузка переменных окружения из .env +load_dotenv() app = Flask(__name__) -app.secret_key = os.getenv('SECRET_KEY', 'secret-key') - -# Конфигурация PostgreSQL +app.secret_key = os.getenv('SECRET_KEY', 'supersecretkey') app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'postgresql://user:password@localhost/finance_db') app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) -# Модель данных +# Модель пользователя +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + email = db.Column(db.String(120), unique=True, nullable=False) + password = db.Column(db.String(200), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def set_password(self, password): + self.password = generate_password_hash(password) + + def check_password(self, password): + return check_password_hash(self.password, password) + +# Модель транзакции (обновлена с привязкой к пользователю) class Transaction(db.Model): id = db.Column(db.Integer, primary_key=True) - type = db.Column(db.String(10), nullable=False) # 'income' или 'expense' + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + type = db.Column(db.String(10), nullable=False) amount = db.Column(db.Float, nullable=False) category = db.Column(db.String(50), nullable=False) date = db.Column(db.Date, nullable=False, default=date.today()) description = db.Column(db.Text) - - def __repr__(self): - return f'' - -# Роуты -@app.route('/') -def index(): - # Получаем все транзакции, отсортированные по дате - transactions = Transaction.query.order_by(Transaction.date.desc()).all() - # Рассчитываем общие суммы + user = db.relationship('User', backref=db.backref('transactions', lazy=True)) + +# Декоратор для проверки аутентификации +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + flash('Пожалуйста, войдите в систему для доступа к этой странице', 'warning') + return redirect(url_for('login')) + return f(*args, **kwargs) + return decorated_function + +# Регистрация пользователя +@app.route('/register', methods=['GET', 'POST']) +def register(): + if request.method == 'POST': + username = request.form['username'] + email = request.form['email'] + password = request.form['password'] + confirm_password = request.form['confirm_password'] + + # Проверки + if password != confirm_password: + flash('Пароли не совпадают', 'danger') + return redirect(url_for('register')) + + if User.query.filter_by(username=username).first(): + flash('Это имя пользователя уже занято', 'danger') + return redirect(url_for('register')) + + if User.query.filter_by(email=email).first(): + flash('Этот email уже используется', 'danger') + return redirect(url_for('register')) + + # Создание пользователя + new_user = User(username=username, email=email) + new_user.set_password(password) + db.session.add(new_user) + db.session.commit() + + flash('Регистрация успешна! Теперь вы можете войти', 'success') + return redirect(url_for('login')) + + return render_template('register.html') + +# Вход в систему +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + remember = 'remember' in request.form + + user = User.query.filter_by(username=username).first() + + if user and user.check_password(password): + session['user_id'] = user.id + session['username'] = user.username + flash('Вы успешно вошли в систему!', 'success') + return redirect(url_for('index')) + + flash('Неверное имя пользователя или пароль', 'danger') + + return render_template('login.html') + +# Выход из системы +@app.route('/logout') +@login_required +def logout(): + session.pop('user_id', None) + session.pop('username', None) + flash('Вы успешно вышли из системы', 'info') + return redirect(url_for('login')) + +# Главная страница (требует аутентификации) +@app.route('/') +@login_required +def index(): + user_id = session['user_id'] + transactions = Transaction.query.filter_by(user_id=user_id).order_by(Transaction.date.desc()).all() + total_income = sum(t.amount for t in transactions if t.type == 'income') total_expense = sum(t.amount for t in transactions if t.type == 'expense') balance = total_income - total_expense - # Группируем по категориям categories = {} for t in transactions: if t.category not in categories: @@ -51,66 +136,29 @@ def index(): total_expense=total_expense, balance=balance, categories=categories, - current_date=date.today()) + current_date=date.today(), + username=session['username']) + +# Остальные роуты (add, edit, delete) аналогично защищаем @login_required +# и добавляем user_id при создании/редактировании транзакций @app.route('/add', methods=['GET', 'POST']) +@login_required def add_transaction(): if request.method == 'POST': - # Получаем данные из формы - transaction_type = request.form['type'] - amount = float(request.form['amount']) - category = request.form['category'] - transaction_date = datetime.strptime(request.form['date'], '%Y-%m-%d').date() - description = request.form['description'] - - # Создаем новую транзакцию + user_id = session['user_id'] + # ... остальной код ... new_transaction = Transaction( - type=transaction_type, - amount=amount, - category=category, - date=transaction_date, - description=description + user_id=user_id, # Добавляем привязку к пользователю + # ... остальные поля ... ) - - # Сохраняем в БД - db.session.add(new_transaction) - db.session.commit() - - flash('Транзакция успешно добавлена!', 'success') - return redirect(url_for('index')) + # ... сохранение ... - # Для GET-запроса показываем форму return render_template('add.html', current_date=date.today()) -@app.route('/edit/', methods=['GET', 'POST']) -def edit_transaction(id): - transaction = Transaction.query.get_or_404(id) - - if request.method == 'POST': - # Обновляем данные - transaction.type = request.form['type'] - transaction.amount = float(request.form['amount']) - transaction.category = request.form['category'] - transaction.date = datetime.strptime(request.form['date'], '%Y-%m-%d').date() - transaction.description = request.form['description'] - - # Сохраняем изменения - db.session.commit() - - flash('Транзакция успешно обновлена!', 'success') - return redirect(url_for('index')) - - # Для GET-запроса показываем форму с текущими данными - return render_template('edit.html', transaction=transaction) - -@app.route('/delete/') -def delete_transaction(id): - transaction = Transaction.query.get_or_404(id) - db.session.delete(transaction) - db.session.commit() - - flash('Транзакция успешно удалена!', 'success') - return redirect(url_for('index')) +# Аналогично для edit и delete добавляем проверку, что пользователь редактирует свою транзакцию if __name__ == '__main__': + with app.app_context(): + db.create_all() app.run(debug=True) \ No newline at end of file diff --git a/init_db.py b/init_db.py index 13a0f6a..abb3832 100644 --- a/init_db.py +++ b/init_db.py @@ -1,14 +1,20 @@ import os -from app import db, app, Transaction -from datetime import date +from app import db, app, User, Transaction +from datetime import date, datetime app.app_context().push() # Создание таблиц db.create_all() -# Добавление начальных данных -if not Transaction.query.first(): +# Добавление тестового пользователя +if not User.query.first(): + test_user = User(username="testuser", email="test@example.com") + test_user.set_password("testpassword") + db.session.add(test_user) + db.session.commit() + + # Добавление тестовых транзакций transactions = [ Transaction(type='income', amount=50000, category='Зарплата', date=date(2023, 10, 15), description='Основная зарплата'), Transaction(type='expense', amount=15000, category='Аренда', date=date(2023, 10, 5), description='Аренда квартиры'), diff --git a/static/styles.css b/static/styles.css index 0c8f85e..5a43043 100644 --- a/static/styles.css +++ b/static/styles.css @@ -335,4 +335,128 @@ input[type="radio"]:checked + .radio-custom.expense { .category-list { grid-template-columns: 1fr; } +} + +/* Стили для аутентификации */ +.auth-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c); +} + +.auth-card { + background: white; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + padding: 30px; + width: 100%; + max-width: 500px; +} + +.auth-card h1 { + text-align: center; + margin-bottom: 30px; + color: #2c3e50; + display: flex; + align-items: center; + justify-content: center; + gap: 15px; +} + +.auth-links { + margin-top: 20px; + text-align: center; +} + +.auth-links a { + color: #3498db; + text-decoration: none; +} + +.auth-links a:hover { + text-decoration: underline; +} + +.login-btn { + background: #3498db; + color: white; + width: 100%; + padding: 12px; + font-size: 1.1rem; +} + +.register-btn { + background: #2ecc71; + color: white; + width: 100%; + padding: 12px; + font-size: 1.1rem; +} + +.remember { + display: flex; + align-items: center; + gap: 10px; +} + +/* Меню пользователя */ +.user-menu { + position: absolute; + top: 20px; + right: 20px; + display: flex; + align-items: center; + gap: 15px; + color: white; + font-weight: 500; +} + +.dropbtn { + background: rgba(255, 255, 255, 0.2); + color: white; + border: none; + border-radius: 50px; + padding: 8px 15px; + cursor: pointer; + display: flex; + align-items: center; + gap: 5px; + font-size: 1rem; +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-content { + display: none; + position: absolute; + right: 0; + background: white; + min-width: 160px; + box-shadow: 0 8px 16px rgba(0,0,0,0.2); + z-index: 1; + border-radius: 5px; + overflow: hidden; +} + +.dropdown-content a { + color: #2c3e50; + padding: 12px 16px; + text-decoration: none; + display: flex; + align-items: center; + gap: 10px; + font-size: 0.9rem; +} + +.dropdown-content a:hover { + background: #f5f5f5; +} + +.dropdown:hover .dropdown-content { + display: block; } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 7ec9555..c9439d7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -25,7 +25,20 @@ {{ balance|int }} ₽ - +
+ Привет, {{ username }}! + +
+
diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..3c0d8b3 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,42 @@ + + + + + + Вход в систему + + + + + + + \ No newline at end of file diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..8339a63 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,47 @@ + + + + + + Регистрация + + + + +
+
+

Регистрация

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + + +
+
+
+ + \ No newline at end of file