lab4 kiri

This commit is contained in:
2026-04-03 21:12:02 +07:00
parent 7a53cda294
commit f799085d55
11 changed files with 532 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
import re
from datetime import datetime
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///accounting.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = "lab4_secret_key"
db = SQLAlchemy(app)
# ---------------- МОДЕЛЬ ----------------
class Employee(db.Model):
id = db.Column(db.Integer, primary_key=True)
fullname = db.Column(db.String(120), nullable=False)
salary = db.Column(db.Integer, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self):
return f'<Employee {self.id}>'
# ----------- ВАЛИДАЦИЯ БЭКЕНДА ------------
def validate_employee(fullname: str, salary: str):
errors = []
fullname = fullname.strip()
if not fullname:
errors.append("ФИО не может быть пустым")
elif len(fullname) < 5 or len(fullname) > 120:
errors.append("ФИО должно быть от 5 до 120 символов")
elif not re.fullmatch(r"[A-Za-zА-Яа-яЁё\s\-\']+", fullname):
errors.append("ФИО может содержать только буквы, пробелы, дефисы и апострофы")
if not salary:
errors.append("Зарплата обязательна")
else:
try:
salary = int(salary)
if salary <= 0 or salary > 10_000_000:
errors.append("Зарплата должна быть от 1 до 10 000 000")
except:
errors.append("Зарплата должна быть числом")
return errors
# ---------------- РОУТЫ ----------------
@app.route("/")
def index():
return render_template("index.html")
@app.route("/employees")
def employees():
items = Employee.query.order_by(Employee.created_at.desc()).all()
total_salary = sum(e.salary for e in items)
return render_template("employees.html", employees=items, total_salary=total_salary)
@app.route("/create", methods=["GET", "POST"])
def create_employee():
if request.method == "POST":
fullname = request.form.get("fullname", "")
salary = request.form.get("salary", "")
errors = validate_employee(fullname, salary)
if errors:
for e in errors:
flash(e, "error")
return render_template("create_employee.html", fullname=fullname, salary=salary)
employee = Employee(fullname=fullname.strip(), salary=int(salary))
try:
db.session.add(employee)
db.session.commit()
return redirect(url_for("employees"))
except:
return render_template("error.html", msg="Ошибка добавления")
return render_template("create_employee.html")
@app.route("/employees/<int:id>")
def employee_detail(id):
emp = Employee.query.get_or_404(id)
return render_template("employee_detail.html", emp=emp)
@app.route("/employees/<int:id>/delete")
def employee_delete(id):
emp = Employee.query.get_or_404(id)
try:
db.session.delete(emp)
db.session.commit()
return redirect(url_for("employees"))
except:
return render_template("error.html", msg="Ошибка удаления")
@app.route("/employees/<int:id>/update", methods=["GET", "POST"])
def employee_update(id):
emp = Employee.query.get_or_404(id)
if request.method == "POST":
fullname = request.form.get("fullname", "")
salary = request.form.get("salary", "")
errors = validate_employee(fullname, salary)
if errors:
for e in errors:
flash(e, "error")
return render_template("employee_update.html", emp=emp)
emp.fullname = fullname.strip()
emp.salary = int(salary)
try:
db.session.commit()
return redirect(url_for("employees"))
except:
return render_template("error.html", msg="Ошибка обновления")
return render_template("employee_update.html", emp=emp)
if __name__ == "__main__":
with app.app_context():
db.create_all()
app.run(debug=True)
+2
View File
@@ -0,0 +1,2 @@
Flask==3.0.0
Flask-SQLAlchemy==3.1.1
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# Get the directory where this script is located
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"
echo "🔧 Lab4 Application Setup & Launch"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Check and create venv if not exists
if [ ! -d "venv" ]; then
echo "📦 Virtual environment not found. Creating venv..."
python3 -m venv venv
echo "✅ venv created"
else
echo "✅ venv already exists"
fi
# Activate venv
echo "🔄 Activating virtual environment..."
source venv/bin/activate
# Install/upgrade requirements
if [ -f "requirements.txt" ]; then
echo "📥 Installing dependencies from requirements.txt..."
pip install -q --upgrade pip
pip install -q -r requirements.txt
echo "✅ Dependencies installed"
else
echo "⚠️ requirements.txt not found!"
exit 1
fi
# Create instance directory if not exists
if [ ! -d "instance" ]; then
echo "📁 Creating instance directory..."
mkdir -p instance
fi
# Remove old database to create fresh one
if [ -f "instance/accounting.db" ]; then
echo "🗑️ Removing old database..."
rm instance/accounting.db
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🚀 Starting Flask Application..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Run the Flask app
python app.py
+198
View File
@@ -0,0 +1,198 @@
body {
margin: 0;
font-family: Arial, sans-serif;
background: linear-gradient(120deg,#e6f4ff,#f5fbff);
color:#1b2b34;
}
.header {
background: linear-gradient(90deg, #084b83, #0d6ba3);
color: white;
padding: 20px 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.header-inner {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 24px;
font-weight: bold;
}
.header nav {
display: flex;
gap: 30px;
}
.header nav a {
color: white;
text-decoration: none;
font-weight: 500;
transition: opacity 0.3s;
}
.header nav a:hover {
opacity: 0.8;
}
.footer {
text-align: center;
padding: 20px;
background: #f0f8ff;
border-top: 1px solid #b5d9ff;
margin-top: 40px;
color: #666;
}
.container {
max-width: 900px;
margin: auto;
padding: 30px;
}
.nav {
background: #cce7ff;
padding: 15px;
display:flex;
gap:20px;
justify-content:center;
}
.nav a {
text-decoration:none;
font-weight:bold;
color:#084b83;
}
h1,h2,h3 { word-wrap: break-word; }
.card {
background:white;
padding:20px;
margin:20px 0;
border-radius:12px;
box-shadow:0 4px 12px rgba(0,0,0,0.1);
word-wrap: break-word;
}
.form {
display:flex;
flex-direction:column;
gap:15px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-group label {
font-weight: 600;
color: #084b83;
font-size: 15px;
}
.form-group small {
color: #666;
font-size: 13px;
font-weight: 400;
}
input, textarea {
padding:12px;
border-radius:8px;
border:1px solid #b5d9ff;
font-size:16px;
}
button, .btn {
background:#4da6ff;
color:white;
padding:10px 15px;
border-radius:8px;
text-decoration:none;
border:none;
cursor:pointer;
display:inline-block;
font-weight: 600;
transition: background 0.2s;
}
button:hover, .btn:hover {
background: #3a8ee6;
}
.btn-primary {
background: linear-gradient(135deg, #4da6ff 0%, #2e7fd6 100%);
padding: 12px 24px;
font-size: 16px;
}
.btn-primary:hover {
background: linear-gradient(135deg, #3a8ee6 0%, #1f5db8 100%);
}
.btn.danger { background:#ff6b6b; }
.btn.danger:hover { background: #ff5252; }
.alert {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8787 100%);
border: none;
border-radius: 12px;
padding: 16px 20px;
margin-bottom: 25px;
color: white;
font-weight: 600;
box-shadow: 0 6px 20px rgba(255, 107, 107, 0.3);
animation: slideDown 0.3s ease-out;
}
.error-window {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8787 100%);
border: 3px solid #d63031;
border-radius: 14px;
padding: 20px;
margin-bottom: 30px;
box-shadow: 0 8px 25px rgba(255, 107, 107, 0.4);
animation: slideDown 0.3s ease-out;
}
.error-header {
color: white;
font-weight: 700;
font-size: 18px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.error-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.error-item {
color: white;
font-weight: 500;
font-size: 16px;
line-height: 1.4;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
+48
View File
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %}Бухгалтерия{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='main.css') }}">
</head>
<body>
<header class="header">
<div class="container header-inner">
<div class="logo">💼 Бухгалтерия</div>
<nav>
<a href="/">Главная</a>
<a href="/employees">Сотрудники</a>
<a href="/create">Добавить</a>
</nav>
</div>
</header>
<main class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="error-window">
<div class="error-header">⚠️ Ошибки валидации</div>
<div class="error-list">
{% for category, message in messages %}
<div class="error-item">• {{ message }}</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endwith %}
{% block body %}{% endblock %}
</main>
<footer class="footer">
Flask + SQLite • Лабораторная №4
</footer>
</body>
</html>
+26
View File
@@ -0,0 +1,26 @@
{% extends 'base.html' %}
{% block body %}
<div class="card">
<h1>Добавить сотрудника</h1>
<form method="post" class="form">
<div class="form-group">
<label for="fullname">ФИО (полное имя и фамилия)</label>
<input type="text" id="fullname" name="fullname"
placeholder="Введите ФИО"
value="{{ fullname or '' }}">
<small>Минимум 5 символов, поддерживаются дефисы и апострофы</small>
</div>
<div class="form-group">
<label for="salary">Зарплата (в рублях)</label>
<input type="number" id="salary" name="salary"
placeholder="Введите сумму"
value="{{ salary or '' }}">
<small>От 1 до 10 000 000 рублей</small>
</div>
<button type="submit" class="btn-primary">Добавить сотрудника</button>
</form>
</div>
{% endblock %}
+11
View File
@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block body %}
<div class="card">
<h1>{{ emp.fullname }}</h1>
<p>Зарплата: {{ emp.salary }} ₽</p>
<p>Добавлен: {{ emp.created_at.date() }}</p>
<a class="btn" href="/employees/{{ emp.id }}/update">Редактировать</a>
<a class="btn danger" href="/employees/{{ emp.id }}/delete">Удалить</a>
</div>
{% endblock %}
+24
View File
@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% block body %}
<div class="card">
<h1>Редактирование сотрудника</h1>
<form method="post" class="form">
<div class="form-group">
<label for="fullname">ФИО (полное имя и фамилия)</label>
<input type="text" id="fullname" name="fullname"
value="{{ emp.fullname }}">
<small>Минимум 5 символов, поддерживаются дефисы и апострофы</small>
</div>
<div class="form-group">
<label for="salary">Зарплата (в рублях)</label>
<input type="number" id="salary" name="salary"
value="{{ emp.salary }}">
<small>От 1 до 10 000 000 рублей</small>
</div>
<button type="submit" class="btn-primary">Обновить сотрудника</button>
</form>
</div>
{% endblock %}
+20
View File
@@ -0,0 +1,20 @@
{% extends 'base.html' %}
{% block title %}Сотрудники{% endblock %}
{% block body %}
<h1>Список сотрудников</h1>
<h3>Общий фонд зарплат: {{ total_salary }} ₽</h3>
{% if employees %}
{% for e in employees %}
<div class="card">
<h2>{{ e.fullname }}</h2>
<p>Зарплата: <b>{{ e.salary }} ₽</b></p>
<a class="btn" href="/employees/{{ e.id }}">Подробнее</a>
</div>
{% endfor %}
{% else %}
<p>Сотрудников пока нет</p>
{% endif %}
{% endblock %}
+5
View File
@@ -0,0 +1,5 @@
{% extends 'base.html' %}
{% block body %}
<h1>Ошибка</h1>
<p>{{ msg }}</p>
{% endblock %}
+6
View File
@@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% block body %}
<h1>Добро пожаловать в систему бухгалтерии</h1>
<p>Используйте меню для навигации по приложению.</p>
{% endblock %}