feat: add rule-based assistant, seed script and projects module
This commit is contained in:
parent
d2fbee3a12
commit
b46c0eceec
59
app/main.py
59
app/main.py
|
|
@ -2,14 +2,13 @@ from flask import Flask, render_template, jsonify, redirect, url_for, request, s
|
||||||
from .db import init_db, engine
|
from .db import init_db, engine
|
||||||
from . import config, auth, assistant
|
from . import config, auth, assistant
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from .models import User, Event, GlobalStatus
|
from .models import User, Event, GlobalStatus, Project
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = config.WEB_SECRET
|
app.secret_key = config.WEB_SECRET
|
||||||
|
|
||||||
# Инициализация БД и дефолтного статуса
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
init_db()
|
init_db()
|
||||||
with Session(engine) as db_session:
|
with Session(engine) as db_session:
|
||||||
|
|
@ -18,7 +17,6 @@ with app.app_context():
|
||||||
db_session.add(GlobalStatus(status="free", message="Вне расписания"))
|
db_session.add(GlobalStatus(status="free", message="Вне расписания"))
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|
||||||
# --- Декоратор проверки ролей ---
|
|
||||||
def require_role(allowed_roles):
|
def require_role(allowed_roles):
|
||||||
if isinstance(allowed_roles, str):
|
if isinstance(allowed_roles, str):
|
||||||
allowed_roles = [allowed_roles]
|
allowed_roles = [allowed_roles]
|
||||||
|
|
@ -110,6 +108,50 @@ def logout():
|
||||||
session.clear()
|
session.clear()
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
# --- ПРОЕКТЫ ---
|
||||||
|
@app.route("/projects")
|
||||||
|
def projects_list():
|
||||||
|
with Session(engine) as db_session:
|
||||||
|
projects = db_session.exec(select(Project).order_by(Project.created_at.desc())).all()
|
||||||
|
return render_template("projects.html", projects=projects, role=session.get("role"))
|
||||||
|
|
||||||
|
@app.route("/projects/new", methods=["GET", "POST"])
|
||||||
|
@require_role(["admin", "professor", "lead"])
|
||||||
|
def project_new():
|
||||||
|
if request.method == "POST":
|
||||||
|
title = request.form.get("title")
|
||||||
|
description = request.form.get("description")
|
||||||
|
status = request.form.get("status")
|
||||||
|
gitea_url = request.form.get("gitea_url")
|
||||||
|
is_public = request.form.get("is_public") == "on"
|
||||||
|
|
||||||
|
with Session(engine) as db_session:
|
||||||
|
new_proj = Project(
|
||||||
|
title=title,
|
||||||
|
description=description,
|
||||||
|
status=status,
|
||||||
|
gitea_url=gitea_url,
|
||||||
|
is_public=is_public,
|
||||||
|
owner_id=session.get("user_id")
|
||||||
|
)
|
||||||
|
db_session.add(new_proj)
|
||||||
|
db_session.commit()
|
||||||
|
return redirect(url_for("projects_list"))
|
||||||
|
|
||||||
|
return render_template("project_form.html")
|
||||||
|
|
||||||
|
@app.route("/projects/<int:project_id>/delete", methods=["POST"])
|
||||||
|
@require_role(["admin", "professor", "lead"])
|
||||||
|
def project_delete(project_id):
|
||||||
|
with Session(engine) as db_session:
|
||||||
|
proj = db_session.get(Project, project_id)
|
||||||
|
if proj:
|
||||||
|
# Удалять может только админ или владелец
|
||||||
|
if session.get("role") == "admin" or proj.owner_id == session.get("user_id"):
|
||||||
|
db_session.delete(proj)
|
||||||
|
db_session.commit()
|
||||||
|
return redirect(url_for("projects_list"))
|
||||||
|
|
||||||
# --- АДМИНКА: ПОЛЬЗОВАТЕЛИ ---
|
# --- АДМИНКА: ПОЛЬЗОВАТЕЛИ ---
|
||||||
@app.route("/admin/users")
|
@app.route("/admin/users")
|
||||||
@require_role("admin")
|
@require_role("admin")
|
||||||
|
|
@ -132,11 +174,9 @@ def change_user_role(user_id):
|
||||||
if user:
|
if user:
|
||||||
if user.id == session.get("user_id") and new_role != "admin":
|
if user.id == session.get("user_id") and new_role != "admin":
|
||||||
return "Ошибка: нельзя лишить самого себя прав администратора", 400
|
return "Ошибка: нельзя лишить самого себя прав администратора", 400
|
||||||
|
|
||||||
user.role = new_role
|
user.role = new_role
|
||||||
db_session.add(user)
|
db_session.add(user)
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|
||||||
return redirect(url_for("admin_users"))
|
return redirect(url_for("admin_users"))
|
||||||
|
|
||||||
# --- РАСПИСАНИЕ: ПУБЛИЧНАЯ ЧАСТЬ ---
|
# --- РАСПИСАНИЕ: ПУБЛИЧНАЯ ЧАСТЬ ---
|
||||||
|
|
@ -144,7 +184,6 @@ def change_user_role(user_id):
|
||||||
def public_schedule():
|
def public_schedule():
|
||||||
with Session(engine) as db_session:
|
with Session(engine) as db_session:
|
||||||
current_status = db_session.exec(select(GlobalStatus)).first()
|
current_status = db_session.exec(select(GlobalStatus)).first()
|
||||||
|
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
events = db_session.exec(
|
events = db_session.exec(
|
||||||
select(Event)
|
select(Event)
|
||||||
|
|
@ -153,7 +192,6 @@ def public_schedule():
|
||||||
.order_by(Event.start_time)
|
.order_by(Event.start_time)
|
||||||
.limit(10)
|
.limit(10)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
return render_template("schedule.html", status=current_status, events=events)
|
return render_template("schedule.html", status=current_status, events=events)
|
||||||
|
|
||||||
# --- АССИСТЕНТ: API ---
|
# --- АССИСТЕНТ: API ---
|
||||||
|
|
@ -162,10 +200,7 @@ def api_assistant():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data or "message" not in data:
|
if not data or "message" not in data:
|
||||||
return jsonify({"error": "Нет сообщения"}), 400
|
return jsonify({"error": "Нет сообщения"}), 400
|
||||||
|
reply = assistant.get_response(data["message"])
|
||||||
user_message = data["message"]
|
|
||||||
reply = assistant.get_response(user_message)
|
|
||||||
|
|
||||||
return jsonify({"reply": reply})
|
return jsonify({"reply": reply})
|
||||||
|
|
||||||
# --- РАСПИСАНИЕ: АДМИН-ПАНЕЛЬ ---
|
# --- РАСПИСАНИЕ: АДМИН-ПАНЕЛЬ ---
|
||||||
|
|
@ -198,7 +233,6 @@ def update_status():
|
||||||
current_status.updated_at = datetime.now(timezone.utc)
|
current_status.updated_at = datetime.now(timezone.utc)
|
||||||
db_session.add(current_status)
|
db_session.add(current_status)
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|
||||||
return redirect(url_for("admin_schedule"))
|
return redirect(url_for("admin_schedule"))
|
||||||
|
|
||||||
@app.route("/admin/schedule/event/add", methods=["POST"])
|
@app.route("/admin/schedule/event/add", methods=["POST"])
|
||||||
|
|
@ -227,7 +261,6 @@ def add_event():
|
||||||
)
|
)
|
||||||
db_session.add(new_event)
|
db_session.add(new_event)
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|
||||||
return redirect(url_for("admin_schedule"))
|
return redirect(url_for("admin_schedule"))
|
||||||
|
|
||||||
@app.route("/admin/schedule/event/<int:event_id>/delete", methods=["POST"])
|
@app.route("/admin/schedule/event/<int:event_id>/delete", methods=["POST"])
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,36 @@ class User(SQLModel, table=True):
|
||||||
username: str = Field(index=True)
|
username: str = Field(index=True)
|
||||||
full_name: Optional[str] = None
|
full_name: Optional[str] = None
|
||||||
email: Optional[str] = None
|
email: Optional[str] = None
|
||||||
role: str = Field(default="student") # admin, professor, lead, member, student
|
role: str = Field(default="student")
|
||||||
is_active: bool = Field(default=True)
|
is_active: bool = Field(default=True)
|
||||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
class Event(SQLModel, table=True):
|
class Event(SQLModel, table=True):
|
||||||
"""Событие в расписании."""
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
title: str
|
title: str
|
||||||
event_type: str = Field(default="lecture") # lecture, workshop, meeting, personal
|
event_type: str = Field(default="lecture")
|
||||||
start_time: datetime
|
start_time: datetime
|
||||||
end_time: datetime
|
end_time: datetime
|
||||||
location: Optional[str] = None
|
location: Optional[str] = None
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
status: str = Field(default="scheduled") # scheduled, moved, cancelled, delayed
|
status: str = Field(default="scheduled")
|
||||||
is_public: bool = Field(default=True)
|
is_public: bool = Field(default=True)
|
||||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
class GlobalStatus(SQLModel, table=True):
|
class GlobalStatus(SQLModel, table=True):
|
||||||
"""Текущий статус Морозова."""
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
status: str = Field(default="free") # free, on_place, delayed, away, cancelled
|
status: str = Field(default="free")
|
||||||
delay_minutes: int = Field(default=0)
|
delay_minutes: int = Field(default=0)
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
# --- НОВАЯ МОДЕЛЬ ---
|
||||||
|
class Project(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
title: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
status: str = Field(default="idea") # idea, in_progress, completed, frozen
|
||||||
|
gitea_url: Optional[str] = None
|
||||||
|
owner_id: int = Field(foreign_key="user.id")
|
||||||
|
is_public: bool = Field(default=True)
|
||||||
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
.btn-profile { background: #1f6feb; }
|
.btn-profile { background: #1f6feb; }
|
||||||
.btn-admin { background: #da3633; }
|
.btn-admin { background: #da3633; }
|
||||||
.btn-schedule { background: #d29922; }
|
.btn-schedule { background: #d29922; }
|
||||||
|
.btn-projects { background: #a371f7; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/profile" class="btn btn-profile">Мой профиль</a>
|
<a href="/profile" class="btn btn-profile">Мой профиль</a>
|
||||||
<a href="/schedule" class="btn btn-schedule">Расписание</a>
|
<a href="/schedule" class="btn btn-schedule">Расписание</a>
|
||||||
|
<a href="/projects" class="btn btn-projects">Проекты</a>
|
||||||
|
|
||||||
{% if session.get('role') in ['admin', 'professor'] %}
|
{% if session.get('role') in ['admin', 'professor'] %}
|
||||||
<a href="/admin/schedule" class="btn btn-schedule">Управление расписанием</a>
|
<a href="/admin/schedule" class="btn btn-schedule">Управление расписанием</a>
|
||||||
|
|
@ -35,13 +37,14 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Скелет системы успешно запущен! (Flask Edition)</p>
|
<p>Скелет системы успешно запущен!</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/login" class="btn">Войти через Gitea</a>
|
<a href="/login" class="btn">Войти через Gitea</a>
|
||||||
<a href="/schedule" class="btn btn-schedule">Расписание</a>
|
<a href="/schedule" class="btn btn-schedule">Расписание</a>
|
||||||
|
<a href="/projects" class="btn btn-projects">Проекты</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="badge">Stage 2.2: Schedule Management OK</div>
|
<div class="badge">Stage 3.1: Projects Module OK</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
54
app/templates/project_form.html
Normal file
54
app/templates/project_form.html
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Новый проект — Morozovka Portal</title>
|
||||||
|
<style>
|
||||||
|
body { background: #0d1117; color: #e6edf3; font-family: sans-serif; padding: 40px; }
|
||||||
|
.container { max-width: 600px; margin: 0 auto; }
|
||||||
|
h2 { color: #58a6ff; }
|
||||||
|
.form-group { margin-bottom: 15px; display: flex; flex-direction: column; }
|
||||||
|
label { margin-bottom: 5px; color: #8b949e; font-size: 0.9rem; }
|
||||||
|
input, select, textarea { padding: 10px; background: #0d1117; color: white; border: 1px solid #30363d; border-radius: 5px; font-family: inherit; }
|
||||||
|
textarea { resize: vertical; min-height: 100px; }
|
||||||
|
button { padding: 10px 20px; background: #238636; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; margin-top: 10px; }
|
||||||
|
button:hover { background: #2ea043; }
|
||||||
|
.checkbox-group { flex-direction: row; align-items: center; gap: 10px; }
|
||||||
|
.back-link { display: block; margin-bottom: 20px; color: #8b949e; text-decoration: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<a href="/projects" class="back-link">← К проектам</a>
|
||||||
|
<h2>Создание нового проекта</h2>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Название проекта *</label>
|
||||||
|
<input type="text" name="title" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Описание</label>
|
||||||
|
<textarea name="description"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Статус</label>
|
||||||
|
<select name="status">
|
||||||
|
<option value="idea">Идея</option>
|
||||||
|
<option value="in_progress">В работе</option>
|
||||||
|
<option value="completed">Завершён</option>
|
||||||
|
<option value="frozen">Заморожен</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Ссылка на репозиторий Gitea</label>
|
||||||
|
<input type="url" name="gitea_url" placeholder="https://git.morozovka.space/user/repo">
|
||||||
|
</div>
|
||||||
|
<div class="form-group checkbox-group">
|
||||||
|
<input type="checkbox" name="is_public" id="is_public" checked>
|
||||||
|
<label for="is_public" style="margin: 0;">Показывать публично</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Создать проект</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
74
app/templates/projects.html
Normal file
74
app/templates/projects.html
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Проекты — Morozovka Portal</title>
|
||||||
|
<style>
|
||||||
|
body { background: #0d1117; color: #e6edf3; font-family: sans-serif; padding: 40px; }
|
||||||
|
.container { max-width: 1000px; margin: 0 auto; }
|
||||||
|
h1 { color: #58a6ff; margin-top: 0; }
|
||||||
|
.header-actions { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||||
|
.btn-add { background: #238636; color: white; text-decoration: none; padding: 10px 20px; border-radius: 5px; font-weight: bold; }
|
||||||
|
.btn-add:hover { background: #2ea043; }
|
||||||
|
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
|
||||||
|
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; display: flex; flex-direction: column; }
|
||||||
|
.card h3 { margin: 0 0 10px 0; color: #e6edf3; }
|
||||||
|
.card p { color: #8b949e; font-size: 0.9rem; flex-grow: 1; margin-bottom: 15px; }
|
||||||
|
.status-badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: bold; text-transform: uppercase; margin-bottom: 10px; }
|
||||||
|
.status-idea { background: #30363d; color: white; }
|
||||||
|
.status-in_progress { background: #1f6feb; color: white; }
|
||||||
|
.status-completed { background: #238636; color: white; }
|
||||||
|
.status-frozen { background: #da3633; color: white; }
|
||||||
|
|
||||||
|
.card-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 15px; border-top: 1px solid #30363d; padding-top: 15px; }
|
||||||
|
.repo-link { color: #58a6ff; text-decoration: none; font-size: 0.85rem; }
|
||||||
|
.delete-btn { background: none; border: none; color: #da3633; cursor: pointer; font-size: 0.85rem; }
|
||||||
|
.delete-btn:hover { text-decoration: underline; }
|
||||||
|
.back-link { display: block; margin-bottom: 20px; color: #8b949e; text-decoration: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<a href="/" class="back-link">← На главную</a>
|
||||||
|
<div class="header-actions">
|
||||||
|
<h1>Каталог проектов</h1>
|
||||||
|
{% if role in ['admin', 'professor', 'lead'] %}
|
||||||
|
<a href="/projects/new" class="btn-add">+ Добавить проект</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
{% for proj in projects %}
|
||||||
|
<div class="card">
|
||||||
|
<div>
|
||||||
|
<span class="status-badge status-{{ proj.status }}">
|
||||||
|
{% if proj.status == 'idea' %}Идея
|
||||||
|
{% elif proj.status == 'in_progress' %}В работе
|
||||||
|
{% elif proj.status == 'completed' %}Завершён
|
||||||
|
{% else %}Заморожен{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3>{{ proj.title }}</h3>
|
||||||
|
<p>{{ proj.description or 'Описание отсутствует.' }}</p>
|
||||||
|
<div class="card-footer">
|
||||||
|
{% if proj.gitea_url %}
|
||||||
|
<a href="{{ proj.gitea_url }}" target="_blank" class="repo-link">📦 Репозиторий</a>
|
||||||
|
{% else %}
|
||||||
|
<span style="color: #8b949e; font-size: 0.85rem;">Без репозитория</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if role == 'admin' or (role in ['professor', 'lead'] and proj.owner_id == session.get('user_id')) %}
|
||||||
|
<form action="/projects/{{ proj.id }}/delete" method="POST" onsubmit="return confirm('Удалить проект?');">
|
||||||
|
<button type="submit" class="delete-btn">Удалить</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p style="color: #8b949e;">Проектов пока нет.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
53
seed_schedule.py
Normal file
53
seed_schedule.py
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from sqlmodel import Session, select
|
||||||
|
from app.db import engine
|
||||||
|
from app.models import Event, GlobalStatus
|
||||||
|
|
||||||
|
def seed_data():
|
||||||
|
with Session(engine) as session:
|
||||||
|
# Обновляем статус
|
||||||
|
status = session.exec(select(GlobalStatus)).first()
|
||||||
|
if status:
|
||||||
|
status.status = "on_place"
|
||||||
|
status.message = "Провожу пары по расписанию"
|
||||||
|
status.updated_at = datetime.now(timezone.utc)
|
||||||
|
session.add(status)
|
||||||
|
|
||||||
|
# Добавляем события (относительно текущего времени)
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
events = [
|
||||||
|
Event(
|
||||||
|
title="Лекция: Введение в веб-разработку",
|
||||||
|
event_type="lecture",
|
||||||
|
start_time=now + timedelta(days=1, hours=2),
|
||||||
|
end_time=now + timedelta(days=1, hours=4),
|
||||||
|
location="Ауд. 405",
|
||||||
|
description="Разбираем основы HTTP и клиент-серверной архитектуры."
|
||||||
|
),
|
||||||
|
Event(
|
||||||
|
title="Мастер-класс по пайке",
|
||||||
|
event_type="workshop",
|
||||||
|
start_time=now + timedelta(days=2, hours=5),
|
||||||
|
end_time=now + timedelta(days=2, hours=8),
|
||||||
|
location="Лаборатория 101",
|
||||||
|
description="Сборка простого светодиодного индикатора."
|
||||||
|
),
|
||||||
|
Event(
|
||||||
|
title="Встреча с командой Неймарк",
|
||||||
|
event_type="meeting",
|
||||||
|
start_time=now + timedelta(days=3, hours=1),
|
||||||
|
end_time=now + timedelta(days=3, hours=2),
|
||||||
|
location="Переговорная",
|
||||||
|
is_public=False
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
for e in events:
|
||||||
|
session.add(e)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
print("[+] Тестовые события успешно добавлены в БД!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
seed_data()
|
||||||
Loading…
Reference in New Issue
Block a user