testflask/site/app.py

179 lines
7.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import datetime
import os
import psycopg2
from flask import Flask, redirect, render_template_string, request
app = Flask(__name__)
# Соединение с Postgres, DATABASE_URL имеет приоритет.
def get_conn():
db_url = os.getenv("DATABASE_URL")
if db_url:
return psycopg2.connect(db_url)
return psycopg2.connect(
host=os.getenv("PGHOST"),
port=os.getenv("PGPORT", "5432"),
user=os.getenv("PGUSER"),
password=os.getenv("PGPASSWORD"),
dbname=os.getenv("PGDATABASE", "postgres"),
sslmode=os.getenv("PGSSLMODE", "require"),
)
# Гарантируем наличие таблицы для демо.
def ensure_table():
with get_conn() as conn, conn.cursor() as cur:
cur.execute(
"""
CREATE TABLE IF NOT EXISTS nubes_test_table (
id SERIAL PRIMARY KEY,
test_data TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
# HTML-шаблон интерфейса.
PAGE = """
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Flask + Postgres Demo</title>
<link rel="icon" href="https://nubes.ru/themes/custom/nubes/images/nubes-ico.svg" type="image/svg+xml">
<style>
:root { --nubes-blue: #005BFF; --nubes-dark: #1A1A1A; --nubes-grey: #F8F9FA; --nubes-border: #E5E7EB; }
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 0; padding: 0; background: var(--nubes-grey); color: var(--nubes-dark); }
.header-bg { position: sticky; top: 0; z-index: 1000; background: #fff; border-bottom: 1px solid var(--nubes-border); padding: 15px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
.container { max-width: 1000px; margin: auto; padding: 0 20px; }
.header-content { display: flex; align-items: center; justify-content: space-between; }
.logo { height: 40px; }
.main-content { padding: 40px 0; }
.card { background: #fff; padding: 32px; border-radius: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.04); }
.btn { display: inline-flex; align-items: center; justify-content: center; cursor: pointer; padding: 12px 24px; border: none; border-radius: 8px; font-weight: 600; font-size: 14px; }
.btn-primary { background: var(--nubes-blue); color: #fff; }
.btn-action { padding: 12px; background: #fff; border: 1px solid var(--nubes-border); border-radius: 8px; font-size: 18px; line-height: 1; cursor: pointer; min-width: 44px; }
.btn-action:hover { background: var(--nubes-grey); border-color: var(--nubes-blue); }
.input-group { display: flex; gap: 12px; margin-bottom: 32px; }
input[type="text"] { flex-grow: 1; width: 100%; padding: 12px 16px; border: 1px solid var(--nubes-border); border-radius: 8px; font-size: 14px; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; padding: 16px; font-size: 12px; text-transform: uppercase; color: #6B7280; border-bottom: 1px solid var(--nubes-border); }
td { padding: 16px; border-bottom: 1px solid var(--nubes-border); }
tbody tr:nth-child(even) { background-color: #FAFBFC; }
tbody tr:hover { background-color: #F3F4F6; }
.id-cell { font-family: monospace; color: #9CA3AF; width: 60px; }
.actions-cell { display: flex; gap: 12px; width: 130px; }
</style>
</head>
<body>
<div class="header-bg">
<div class="container header-content">
<img src="https://nubes.ru/themes/custom/nubes_2025/logo.svg" alt="Nubes" class="logo">
<div style="font-size: 14px; color: var(--nubes-blue); font-weight: 600;">Flask + Postgres Demo</div>
</div>
</div>
<div class="container main-content">
<div class="card">
{% if error %}<p style="color:red">{{ error }}</p>{% endif %}
<form method="post" class="input-group" onsubmit="const btn=this.querySelector('button'); if(btn){btn.disabled=true; btn.textContent='Добавляем...';}">
<input type="text" name="txt_content" placeholder="Новое сообщение..." required>
<button type="submit" class="btn btn-primary">Добавить</button>
</form>
<table>
<thead><tr><th>ID</th><th>Содержимое</th><th>Действия</th></tr></thead>
<tbody>
{% for row in rows %}
<tr>
<td class="id-cell">{{ row[0] }}</td>
<td>
<form method="post" action="/update" id="upd_{{ row[0] }}" style="margin:0">
<input type="hidden" name="id" value="{{ row[0] }}">
<input type="text" name="txt_content" value="{{ row[1] }}" style="width:100%; border:none; background:transparent;">
</form>
</td>
<td class="actions-cell">
<button type="submit" form="upd_{{ row[0] }}" class="btn-action">💾</button>
<form method="post" action="/delete" style="margin:0" onsubmit="return confirm('Удалить?')">
<input type="hidden" name="id" value="{{ row[0] }}">
<button type="submit" class="btn-action">🗑</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>
"""
@app.route("/", methods=["GET", "POST"])
def index():
ensure_table()
error = None
if request.method == "POST":
content = request.form.get("txt_content") or "Это Flask сделал"
try:
with get_conn() as conn, conn.cursor() as cur:
# Проверка дубликатов: не вставляем повтор за короткое время.
cur.execute(
"SELECT test_data, created_at FROM nubes_test_table ORDER BY id DESC LIMIT 1"
)
last = cur.fetchone()
is_duplicate = False
if last:
last_text, last_created = last
now = (
datetime.datetime.now(last_created.tzinfo)
if isinstance(last_created, datetime.datetime)
else datetime.datetime.utcnow()
)
is_duplicate = (
last_text == content
and (now - last_created).total_seconds() < 3
)
if not is_duplicate:
cur.execute(
"INSERT INTO nubes_test_table (test_data) VALUES (%s)",
(content,),
)
return redirect("/")
except Exception as exc:
error = str(exc)
with get_conn() as conn, conn.cursor() as cur:
cur.execute(
"SELECT id, test_data FROM nubes_test_table ORDER BY id DESC LIMIT 20"
)
rows = cur.fetchall()
return render_template_string(PAGE, rows=rows, error=error)
@app.post("/update")
def update():
# Обновление записи по id.
with get_conn() as conn, conn.cursor() as cur:
cur.execute(
"UPDATE nubes_test_table SET test_data=%s WHERE id=%s",
(request.form.get("txt_content"), request.form.get("id")),
)
return redirect("/")
@app.post("/delete")
def delete():
# Удаление записи по id.
with get_conn() as conn, conn.cursor() as cur:
cur.execute("DELETE FROM nubes_test_table WHERE id=%s", (request.form.get("id"),))
return redirect("/")
if __name__ == "__main__":
# Локальный запуск для отладки.
app.run(debug=True, host="0.0.0.0", port=5000)