Created
February 20, 2026 13:20
-
-
Save sunmeat/37cabef9b4e22d965e9f661d26aef77f to your computer and use it in GitHub Desktop.
MVT (flask)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from flask import Flask, request, redirect, url_for, render_template_string | |
| app = Flask(__name__) | |
| # ──────────────────────────────────────────────── | |
| # model — дані та бізнес-логіка | |
| # ──────────────────────────────────────────────── | |
| class ShoppingModel: | |
| # ініціалізація | |
| def __init__(self): | |
| self.items = [] | |
| # додавання товару | |
| def add_item(self, name, qty, price): | |
| self.items.append({ | |
| "name": name, | |
| "quantity": qty, | |
| "price": price | |
| }) | |
| # очищення списку | |
| def clear(self): | |
| self.items.clear() | |
| # отримання копії | |
| def get_items(self): | |
| return self.items.copy() | |
| # підрахунок суми | |
| def total(self): | |
| return sum(i["quantity"] * i["price"] for i in self.items) | |
| model = ShoppingModel() | |
| # ──────────────────────────────────────────────── | |
| # template — html шаблон | |
| # ──────────────────────────────────────────────── | |
| HTML_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html lang="uk"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>список покупок</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| font-family: "Segoe UI", Roboto, sans-serif; | |
| } | |
| body { | |
| margin: 0; | |
| min-height: 100vh; | |
| background: linear-gradient(135deg, #0f1022, #1b1d40 60%, #2b2f7a); | |
| color: #f1f2ff; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .card { | |
| width: 760px; | |
| background: rgba(255,255,255,0.06); | |
| backdrop-filter: blur(18px); | |
| border-radius: 20px; | |
| padding: 28px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.6); | |
| } | |
| h1 { | |
| text-align: center; | |
| margin-top: 0; | |
| font-weight: 600; | |
| letter-spacing: 1px; | |
| } | |
| .form-row { | |
| display: grid; | |
| grid-template-columns: 1fr 120px 140px auto; | |
| gap: 10px; | |
| margin-bottom: 12px; | |
| } | |
| input { | |
| padding: 12px; | |
| border-radius: 10px; | |
| border: none; | |
| background: rgba(0,0,0,0.35); | |
| color: #fff; | |
| font-size: 15px; | |
| outline: none; | |
| transition: 0.2s; | |
| } | |
| input:focus { | |
| background: rgba(0,0,0,0.55); | |
| box-shadow: 0 0 0 2px #7c82ff; | |
| } | |
| button { | |
| border: none; | |
| border-radius: 10px; | |
| padding: 12px 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: 0.2s; | |
| white-space: nowrap; | |
| } | |
| .btn-add { | |
| background: linear-gradient(135deg, #6b73ff, #000dff); | |
| color: white; | |
| } | |
| .btn-add:hover { | |
| transform: translateY(-1px); | |
| box-shadow: 0 6px 18px rgba(0,0,0,0.5); | |
| } | |
| .btn-clear { | |
| background: rgba(255,255,255,0.12); | |
| color: #fff; | |
| margin-top: 8px; | |
| width: 100%; | |
| } | |
| .btn-clear:hover { | |
| background: rgba(255,255,255,0.22); | |
| } | |
| .list { | |
| margin-top: 18px; | |
| background: rgba(0,0,0,0.35); | |
| border-radius: 14px; | |
| padding: 16px; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| th { | |
| text-align: left; | |
| font-weight: 500; | |
| padding-bottom: 8px; | |
| opacity: 0.8; | |
| } | |
| td { | |
| padding: 8px 0; | |
| border-bottom: 1px solid rgba(255,255,255,0.08); | |
| } | |
| .sum { | |
| text-align: right; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .empty { | |
| text-align: center; | |
| opacity: 0.7; | |
| padding: 30px 0; | |
| } | |
| .total { | |
| text-align: right; | |
| font-size: 18px; | |
| font-weight: 600; | |
| margin-top: 12px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card"> | |
| <h1>список покупок</h1> | |
| <form method="post" action="/add"> | |
| <div class="form-row"> | |
| <input name="name" placeholder="назва товару" required> | |
| <input name="qty" placeholder="кількість" required> | |
| <input name="price" placeholder="ціна" required> | |
| <button class="btn-add" type="submit">додати</button> | |
| </div> | |
| </form> | |
| <form method="post" action="/clear"> | |
| <button class="btn-clear" type="submit">очистити список</button> | |
| </form> | |
| <div class="list"> | |
| {% if not items %} | |
| <div class="empty">список порожній</div> | |
| {% else %} | |
| <table> | |
| <tr> | |
| <th>#</th> | |
| <th>товар</th> | |
| <th>к-сть</th> | |
| <th>ціна</th> | |
| <th class="sum">сума</th> | |
| </tr> | |
| {% for i, item in items %} | |
| <tr> | |
| <td>{{ i }}</td> | |
| <td>{{ item.name }}</td> | |
| <td>{{ item.quantity }}</td> | |
| <td>{{ "%.2f"|format(item.price) }} ₴</td> | |
| <td class="sum"> | |
| {{ "%.2f"|format(item.quantity * item.price) }} ₴ | |
| </td> | |
| </tr> | |
| {% endfor %} | |
| </table> | |
| <div class="total"> | |
| разом: {{ "%.2f"|format(total) }} ₴ | |
| </div> | |
| {% endif %} | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| # ──────────────────────────────────────────────── | |
| # view — маршрути flask | |
| # ──────────────────────────────────────────────── | |
| # головна сторінка | |
| @app.route("/") | |
| def index(): | |
| items = [ | |
| (i + 1, item) | |
| for i, item in enumerate(model.get_items()) | |
| ] | |
| return render_template_string( | |
| HTML_TEMPLATE, | |
| items=items, | |
| total=model.total() | |
| ) | |
| # додавання товару | |
| @app.route("/add", methods=["POST"]) | |
| def add_item(): | |
| name = request.form.get("name", "").strip() | |
| qty_str = request.form.get("qty", "").strip() | |
| price_str = request.form.get("price", "").strip() | |
| if not name or not qty_str or not price_str: | |
| return redirect(url_for("index")) | |
| try: | |
| qty = int(qty_str) | |
| price = float(price_str) | |
| except ValueError: | |
| return redirect(url_for("index")) | |
| if qty < 1 or price <= 0: | |
| return redirect(url_for("index")) | |
| model.add_item(name, qty, price) | |
| return redirect(url_for("index")) | |
| # очищення списку | |
| @app.route("/clear", methods=["POST"]) | |
| def clear(): | |
| model.clear() | |
| return redirect(url_for("index")) | |
| # ──────────────────────────────────────────────── | |
| # запуск | |
| # ──────────────────────────────────────────────── | |
| if __name__ == "__main__": | |
| app.run(debug=True) | |
| # запустити в браузері: http://127.0.0.1:5000/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment