diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,25 +1,26 @@ from flask import Flask, render_template_string, request, redirect, url_for, session, flash +from flask_caching import Cache import json import os import logging import threading import time -from datetime import datetime +from datetime import datetime, timedelta from huggingface_hub import HfApi, hf_hub_download from werkzeug.utils import secure_filename import random app = Flask(__name__) -app.secret_key = 'supersecretkey' # Замените на безопасный ключ в продакшене -DATA_FILE = 'data_ostenhost.json' +app.secret_key = os.getenv("FLASK_SECRET_KEY", "supersecretkey") +DATA_FILE = 'data_adusis.json' REPO_ID = "Eluza133/w1f9" -HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен с правами записи +HF_TOKEN_WRITE = os.getenv("HF_TOKEN") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE -# Настройка логирования -logging.basicConfig(level=logging.DEBUG) +cache = Cache(app, config={'CACHE_TYPE': 'simple'}) +logging.basicConfig(level=logging.INFO) -# Функции работы с базой данных +@cache.memoize(timeout=300) def load_data(): try: download_db_from_hf() @@ -27,31 +28,30 @@ def load_data(): data = json.load(file) if not isinstance(data, dict): logging.warning("Данные не в формате dict, инициализация пустой базы") - return {'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}, 'user_orders': {}} - if 'posts' not in data: - data['posts'] = [] - if 'users' not in data: - data['users'] = {} - if 'pending_organizations' not in data: - data['pending_organizations'] = [] - if 'orders' not in data: - data['orders'] = {} - if 'user_orders' not in data: - data['user_orders'] = {} + return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}} + data.setdefault('posts', []) + data.setdefault('users', {}) + data.setdefault('general_chat', []) + data.setdefault('private_chats', {}) + for user in data['users']: + data['users'][user].setdefault('last_chat_visit', '1970-01-01 00:00:00') + data['users'][user].setdefault('last_private_visit', '1970-01-01 00:00:00') + data['users'][user].setdefault('last_seen', '1970-01-01 00:00:00') logging.info("Данные успешно загружены") return data except Exception as e: - logging.error(f"Ошибка загрузки данных: {e}") - return {'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}, 'user_orders': {}} + logging.error(f"Ошибка при загрузке данных: {e}") + return {'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}} def save_data(data): try: with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) upload_db_to_hf() + cache.clear() logging.info("Данные сохранены и загружены на HF") except Exception as e: - logging.error(f"Ошибка сохранения данных: {e}") + logging.error(f"Ошибка при сохранении данных: {e}") raise def upload_db_to_hf(): @@ -63,11 +63,11 @@ def upload_db_to_hf(): repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, - commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + commit_message=f"Бэкап {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) logging.info("База данных загружена на Hugging Face") except Exception as e: - logging.error(f"Ошибка загрузки базы: {e}") + logging.error(f"Ошибка при загрузке базы данных: {e}") def download_db_from_hf(): try: @@ -81,939 +81,752 @@ def download_db_from_hf(): ) logging.info("База данных скачана с Hugging Face") except Exception as e: - logging.error(f"Ошибка скачивания базы: {e}") + logging.error(f"Ошибка при скачивании базы данных: {e}") if not os.path.exists(DATA_FILE): with open(DATA_FILE, 'w', encoding='utf-8') as f: - json.dump({'posts': [], 'users': {}, 'pending_organizations': [], 'orders': {}, 'user_orders': {}}, f) + json.dump({'posts': [], 'users': {}, 'general_chat': [], 'private_chats': {}}, f) def periodic_backup(): while True: upload_db_to_hf() - time.sleep(800) + time.sleep(1800) -# Обновленный базовый стиль +def get_unread_count(data, username): + if username not in data['users']: + return 0 + last_visit = datetime.strptime(data['users'][username]['last_chat_visit'], '%Y-%m-%d %H:%M:%S') + return sum(1 for msg in data['general_chat'] if datetime.strptime(msg['time'], '%Y-%m-%d %H:%M:%S') > last_visit) + +def get_private_unread_count(data, username): + if username not in data['users']: + return 0 + last_visit = datetime.strptime(data['users'][username]['last_private_visit'], '%Y-%m-%d %H:%M:%S') + unread = 0 + for chat_key, messages in data['private_chats'].items(): + users = chat_key.split('_') + if username in users: + unread += sum(1 for msg in messages if datetime.strptime(msg['time'], '%Y-%m-%d %H:%M:%S') > last_visit and msg['sender'] != username) + return unread + +def is_user_online(data, username): + if username not in data['users']: + return False + last_seen = datetime.strptime(data['users'][username]['last_seen'], '%Y-%m-%d %H:%M:%S') + return (datetime.now() - last_seen).total_seconds() < 300 + +def update_last_seen(data, username): + if username in data['users']: + data['users'][username]['last_seen'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + save_data(data) + +# Новый стиль 2025 года BASE_STYLE = ''' - :root { - --primary: #6b48ff; - --secondary: #ff4785; - --background-light: #f5f7ff; - --background-dark: #1a1a2e; - --card-bg-light: rgba(255, 255, 255, 0.95); - --card-bg-dark: rgba(40, 40, 60, 0.95); - --text-light: #1a1a2e; - --text-dark: #e0e0e0; - --shadow: 0 10px 40px rgba(0, 0, 0, 0.15); - --glass-bg: rgba(255, 255, 255, 0.15); - --transition: all 0.4s ease; - --cart-btn: #10b981; - } - * { margin: 0; padding: 0; box-sizing: border-box; } - body { - font-family: 'Inter', sans-serif; - background: var(--background-light); - color: var(--text-light); - line-height: 1.6; - transition: var(--transition); - overflow-x: hidden; - } - body.dark { - background: var(--background-dark); - color: var(--text-dark); - } +:root { + --primary: #ff3b8e; + --secondary: #00f4ff; + --accent: #7b00ff; + --background: #0a0a23; + --surface: #1a1a3d; + --text: #e0e0ff; + --shadow: 0 4px 15px rgba(0, 0, 0, 0.3); + --glass: rgba(255, 255, 255, 0.05); + --gradient: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + --online: #00ff85; + --offline: #ff3366; + --transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Inter', sans-serif; + background: var(--background); + color: var(--text); + line-height: 1.6; + overflow-x: hidden; +} + +.sidebar { + position: fixed; + top: 0; + left: 0; + width: 300px; + height: 100%; + background: var(--surface); + padding: 2rem; + box-shadow: var(--shadow); + transform: translateX(0); + transition: var(--transition); + z-index: 1000; +} + +.sidebar.hidden { + transform: translateX(-100%); +} + +.sidebar-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 2rem; +} + +.nav-brand { + font-size: 1.8rem; + font-weight: 800; + background: var(--gradient); + -webkit-background-clip: text; + color: transparent; +} + +.logo { + width: 40px; + height: 40px; + border-radius: 50%; + box-shadow: var(--shadow); + transition: var(--transition); +} + +.logo:hover { + transform: scale(1.1); +} + +.nav-links { + display: flex; + flex-direction: column; + gap: 0.8rem; +} + +.nav-link { + display: flex; + align-items: center; + gap: 0.8rem; + padding: 1rem; + background: var(--glass); + color: var(--text); + text-decoration: none; + border-radius: 12px; + font-size: 1rem; + font-weight: 500; + transition: var(--transition); + position: relative; + backdrop-filter: blur(10px); +} + +.nav-link:hover { + background: var(--primary); + transform: translateX(5px); + box-shadow: var(--shadow); +} + +.nav-link .badge { + position: absolute; + right: 1rem; + background: var(--secondary); + color: var(--background); + padding: 0.3rem 0.6rem; + border-radius: 20px; + font-size: 0.75rem; +} + +.logout-btn { + background: var(--secondary); + color: var(--background); +} + +.logout-btn:hover { + background: var(--accent); +} + +.menu-btn { + display: none; + position: fixed; + top: 1rem; + left: 1rem; + z-index: 1001; + background: var(--glass); + border: none; + padding: 0.8rem; + border-radius: 50%; + color: var(--primary); + cursor: pointer; + transition: var(--transition); +} + +.menu-btn:hover { + background: var(--primary); + color: var(--text); +} + +.container { + margin: 2rem 2rem 2rem 340px; + padding: 2rem; + background: var(--surface); + border-radius: 20px; + box-shadow: var(--shadow); + transition: var(--transition); +} + +.btn { + padding: 0.8rem 1.5rem; + background: var(--gradient); + color: var(--text); + border: none; + border-radius: 12px; + cursor: pointer; + font-weight: 600; + transition: var(--transition); + box-shadow: var(--shadow); +} + +.btn:hover { + transform: scale(1.05); + box-shadow: 0 6px 20px rgba(255, 59, 142, 0.3); +} + +input, textarea, select { + width: 100%; + padding: 1rem; + margin: 0.5rem 0; + border: none; + border-radius: 12px; + background: var(--glass); + color: var(--text); + font-size: 1rem; + transition: var(--transition); + backdrop-filter: blur(10px); +} + +input:focus, textarea:focus, select:focus { + outline: none; + box-shadow: 0 0 0 3px var(--primary); + background: rgba(255, 255, 255, 0.1); +} + +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.9); + z-index: 2000; + display: flex; + justify-content: center; + align-items: center; +} + +.modal img, .modal video { + max-width: 90%; + max-height: 90%; + border-radius: 20px; + box-shadow: var(--shadow); + animation: fadeIn 0.3s ease; +} + +.theme-toggle { + position: fixed; + top: 1rem; + right: 1rem; + background: var(--glass); + border: none; + padding: 0.8rem; + border-radius: 50%; + color: var(--primary); + cursor: pointer; + transition: var(--transition); +} + +.theme-toggle:hover { + background: var(--accent); + transform: rotate(180deg); +} + +.status-dot { + width: 10px; + height: 10px; + border-radius: 50%; + display: inline-block; + margin-left: 0.5rem; +} + +.online { background: var(--online); } +.offline { background: var(--offline); } + +@keyframes fadeIn { + from { opacity: 0; transform: scale(0.9); } + to { opacity: 1; transform: scale(1); } +} + +@media (max-width: 900px) { .sidebar { - position: fixed; - top: 0; - left: 0; - width: 300px; - height: 100%; - background: var(--glass-bg); - backdrop-filter: blur(20px); - padding: 30px; - box-shadow: var(--shadow); - z-index: 1000; - transition: var(--transition); - } - .sidebar-header { - margin-bottom: 40px; - } - .nav-brand { - font-size: 2em; - font-weight: 700; - background: linear-gradient(45deg, var(--primary), var(--secondary)); - -webkit-background-clip: text; - color: transparent; - } - .nav-links { - display: flex; - flex-direction: column; - gap: 20px; - } - .nav-link { - display: flex; - align-items: center; - gap: 15px; - padding: 18px; - background: var(--card-bg-light); - color: var(--text-light); - text-decoration: none; - border-radius: 15px; - font-size: 1.1em; - transition: var(--transition); - position: relative; - } - body.dark .nav-link { - background: var(--card-bg-dark); - color: var(--text-dark); - } - .nav-link:hover { - transform: translateX(15px); - background: var(--primary); - color: white; - } - .logout-btn { - background: var(--secondary); - color: white; - } - .logout-btn:hover { - background: #e63970; + width: 100%; + max-width: 300px; + transform: translateX(-100%); } - .order-count { - position: absolute; - right: 15px; - top: 50%; - transform: translateY(-50%); - background: var(--secondary); - color: white; - border-radius: 50%; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.9em; + .sidebar.active { + transform: translateX(0); } .menu-btn { - display: none; - font-size: 32px; - background: var(--glass-bg); - border: none; - color: var(--primary); - cursor: pointer; - position: fixed; - top: 20px; - left: 20px; - z-index: 1001; - padding: 12px; - border-radius: 50%; - box-shadow: var(--shadow); + display: block; } .container { - margin: 30px auto 30px 330px; - max-width: 1200px; - padding: 30px; - transition: var(--transition); - } - .btn { - padding: 14px 28px; - background: var(--primary); - color: white; - border: none; - border-radius: 15px; - cursor: pointer; - font-size: 1.1em; - transition: var(--transition); - display: inline-block; - margin: 10px 0; - } - .btn:hover { - transform: scale(1.08); - background: #5439cc; - } - .cart-btn { - background: var(--cart-btn); - } - .cart-btn:hover { - background: #0d9f6e; - } - input, textarea, select { - width: 100%; - padding: 14px; - margin: 15px 0; - border: 2px solid rgba(0, 0, 0, 0.1); - border-radius: 15px; - background: var(--glass-bg); - color: var(--text-light); - font-size: 1.1em; - transition: var(--transition); - } - body.dark input, body.dark textarea, body.dark select { - border: 2px solid rgba(255, 255, 255, 0.1); - color: var(--text-dark); - } - input:focus, textarea:focus, select:focus { - outline: none; - border-color: var(--primary); - background: rgba(255, 255, 255, 0.2); - } - .modal { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.85); - z-index: 2000; - justify-content: center; - align-items: center; - } - .modal img { - max-width: 95%; - max-height: 95%; - object-fit: contain; - border-radius: 15px; - box-shadow: var(--shadow); - } - .theme-toggle { - position: fixed; - top: 20px; - right: 20px; - background: var(--glass-bg); - border: none; - padding: 12px; - border-radius: 50%; - cursor: pointer; - font-size: 24px; - box-shadow: var(--shadow); - transition: var(--transition); - } - .cart-float { - position: fixed; - bottom: 20px; - right: 20px; - background: var(--cart-btn); - color: white; - border-radius: 50%; - width: 60px; - height: 60px; - display: flex; - align-items: center; - justify-content: center; - box-shadow: var(--shadow); - cursor: pointer; - z-index: 1000; - transition: var(--transition); - } - .cart-float:hover { - transform: scale(1.1); - background: #0d9f6e; - } - .cart-modal { - display: none; - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: var(--card-bg-light); - padding: 30px; - border-radius: 25px; - box-shadow: var(--shadow); - z-index: 2000; - max-width: 500px; - width: 90%; - max-height: 80vh; - overflow-y: auto; - } - body.dark .cart-modal { - background: var(--card-bg-dark); - } - .cart-modal h2 { - font-size: 1.8em; - margin-bottom: 20px; - background: linear-gradient(45deg, var(--primary), var(--secondary)); - -webkit-background-clip: text; - color: transparent; - } - .cart-item { - background: var(--glass-bg); - padding: 15px; - border-radius: 15px; - margin-bottom: 15px; - } - .cart-item h3 { - font-size: 1.2em; - margin-bottom: 10px; - } - .cart-total { - font-size: 1.2em; - font-weight: 600; - margin-top: 20px; - } - @media (max-width: 768px) { - .sidebar { - transform: translateX(-100%); - width: 280px; - } - .sidebar.active { - transform: translateX(0); - } - .menu-btn { - display: block; - } - .container { - margin: 80px 15px 30px 15px; - max-width: calc(100% - 30px); - } - .theme-toggle { - top: 80px; - right: 15px; - } - .btn, input, textarea, select { - font-size: 1em; - padding: 12px; - } - .cart-float { - bottom: 15px; - right: 15px; - width: 50px; - height: 50px; - } + margin: 5rem 1rem 1rem; } +} ''' NAV_HTML = ''' - + ''' -# Регистрация @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': - logging.debug(f"Получен POST-запрос: {request.form}") - username = request.form.get('username') password = request.form.get('password') - user_type = request.form.get('user_type') - phone = request.form.get('phone') - address = request.form.get('address') data = load_data() - - if not username or not password or not phone or (user_type == 'buyer' and not address): - flash('Заполните все обязательные поля!', 'error') - logging.debug("Не все обязательные поля заполнены") - return redirect(url_for('register')) - + if username in data['users']: - flash('Пользователь уже существует!', 'error') - logging.debug(f"Пользователь {username} уже существует") - return redirect(url_for('register')) - - if 'register_seller' in request.form: - logging.debug("Нажата кнопка регистрации продавца") - org_name = request.form.get('org_name') - org_phone = request.form.get('org_phone') - is_online = request.form.get('is_online') == 'on' - org_address = request.form.get('org_address') if not is_online else None - - if not org_name or not org_phone: - flash('Укажите название организации и рабочий номер!', 'error') - logging.debug("Не указаны org_name или org_phone") - return redirect(url_for('register')) - - data['users'][username] = { - 'password': password, - 'bio': '', - 'link': '', - 'avatar': None, - 'type': 'seller', - 'verified': False, - 'phone': phone, - 'org_phone': org_phone, - 'org_address': org_address, - 'is_online': is_online - } - data['pending_organizations'].append({ - 'username': username, - 'org_name': org_name, - 'org_phone': org_phone, - 'org_address': org_address, - 'is_online': is_online, - 'submitted_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - }) - save_data(data) - flash('Ваша заявка принята, мы с вами свяжемся в течение 2 суток', 'success') - logging.debug(f"Продавец {username} зарегистрирован и отправлен на верификацию") - return redirect(url_for('login')) - - elif 'register_buyer' in request.form: - logging.debug("Нажата кнопка регистрации покупателя") - data['users'][username] = { - 'password': password, - 'bio': '', - 'link': '', - 'avatar': None, - 'type': 'buyer', - 'verified': True, - 'phone': phone, - 'address': address - } - save_data(data) - flash('Регистрация успешна! Войдите в систему.', 'success') - logging.debug(f"Покупатель {username} зарегистрирован") - return redirect(url_for('login')) - - else: - flash('Неизвестная ошибка при выборе типа пользователя!', 'error') - logging.debug("Ни одна из кнопок регистрации не была нажата") + flash('User already exists!') return redirect(url_for('register')) - + + data['users'][username] = { + 'password': password, + 'bio': '', + 'link': '', + 'avatar': None, + 'last_chat_visit': '1970-01-01 00:00:00', + 'last_private_visit': '1970-01-01 00:00:00', + 'last_seen': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + save_data(data) + flash('Registration successful! Please login.') + return redirect(url_for('login')) + is_authenticated = 'username' in session username = session.get('username', None) + data = load_data() + unread_count = get_unread_count(data, username) if is_authenticated else 0 + private_unread_count = get_private_unread_count(data, username) if is_authenticated else 0 + user_count = len(data['users']) + is_online = is_user_online(data, username) if is_authenticated else False + html = ''' - - -
- - -Итого: 0
- - -{{ post['description'] }}
-Цена: {{ post['price'] }} {{ post['currency'] }}
-Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}
-Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}
- {% if is_authenticated and user_type == 'buyer' and verified %} - - - {% endif %} -{{ post['description']|truncate(100) }}
+By: {{ post['uploader'] }} | {{ post['upload_date'] }}
+Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}
+ + {% endfor %}Итого: 0
- - -{{ post['description'] }}
-Цена: {{ post['price'] }} {{ post['currency'] }}
-Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}
-Просмотров: {{ post['views'] }} | Лайков: {{ post['likes']|length }}
- {% if is_authenticated %} - - {% if user_type == 'buyer' and verified %} - - - {% endif %} - - {% endif %} -Войдите, чтобы ставить лайки и комментировать.
- {% endif %} -{{ post['description'] }}
+By: {{ post['uploader'] }} | {{ post['upload_date'] }}
+Views: {{ post['views'] }} | Likes: {{ post['likes']|length }}
+ {% if is_authenticated %} + + + {% endif %} +{{ comment['user'] }} ({{ comment['date'] }}): {{ comment['text'] }}
+Login to interact
+ {% endif %} +Итого: 0
- - -{{ bio }}
- {% if link %} - - {% endif %} -Тип: {{ 'Продавец' if user_type == 'seller' else 'Покупатель' }} {% if user_type == 'seller' and not verified %}(На проверке){% endif %}
-Телефон: {{ phone }}
- {% if user_type == 'buyer' %} -Адрес д��ставки: {{ address }}
- {% endif %} - {% if user_type == 'seller' and verified %} -Рабочий номер: {{ org_phone }}
-Адрес организации: {{ org_address if org_address else 'Онлайн' }}
- {% endif %} -Просмотров: {{ total_views }} | Лайков: {{ total_likes }}
- -Итого: 0
- - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -Last seen: {{ last_seen }}
{% endif %} - {% endwith %} -{{ post['description'] }}
-Цена: {{ post['price'] }} {{ post['currency'] }}
-{{ post['upload_date'] }}
- -Вы пока не загрузили ни одного видео.
+{{ bio }}
+ {% if link %} + {% endif %} +Views: {{ total_views }} | Likes: {{ total_likes }}
+Итого: 0
- - -Last seen: {{ last_seen }}
+ {% endif %} +{{ bio }}
+ {% if link %} + + {% endif %} +Views: {{ total_views }} | Likes: {{ total_likes }}
+ {% if is_authenticated %} + Message {% endif %} -{{ bio }}
- {% if link %} - - {% endif %} -Тип: {{ 'Продавец' if user_type == 'seller' else 'Покупатель' }} {% if user_type == 'seller' and not verified %}(На проверке){% endif %}
-Телефон: {{ phone }}
- {% if user_type == 'buyer' %} -Адрес доставки: {{ address }}
- {% endif %} - {% if user_type == 'seller' and verified %} -Рабочий номер: {{ org_phone }}
-Адрес организации: {{ org_address if org_address else 'Онлайн' }}
- {% endif %} -Просмотров: {{ total_views }} | Лайков: {{ total_likes }}
- -{{ post['description'] }}
-Цена: {{ post['price'] }} {{ post['currency'] }}
-{{ post['upload_date'] }}
-Этот пользователь пока не загрузил ни одного видео.
- {% endif %} -{{ post['description']|truncate(100) }}
+{{ post['upload_date'] }}
+No posts yet.
+ {% endif %}Итого: 0
- - + + + + + +Итого: 0
- - -Цена: {{ order['price'] }} {{ order['currency'] }} x {{ order['quantity'] }} = {{ (order['price']|float * order['quantity'])|round(2) }} {{ order['currency'] }}
-Покупатель: {{ order['buyer'] }}
-Телефон покупателя: {{ order['buyer_phone'] }}
- {% if order['buyer_address'] %} -Адрес доставки: {{ order['buyer_address'] }}
+ + + + + +{{ info.last_message['time'] }}: + {% if 'text' in info.last_message %}{{ info.last_message['text']|truncate(25) }}{% elif 'file' in info.last_message %}File{% elif 'post_id' in info.last_message %}Post{% endif %}
{% endif %} -Дата: {{ order['date'] }}
-У вас пока нет заказов.
- {% endif %} -No messages yet.
+ {% endif %}Итого: 0
- - -Цена: {{ order['price'] }} {{ order['currency'] }} x {{ order['quantity'] }} = {{ (order['price']|float * order['quantity'])|round(2) }} {{ order['currency'] }}
-Продавец: {{ order['uploader'] }}
-Дата: {{ order['date'] }}
-Статус: {{ 'В ожидании' if order['status'] == 'pending' else 'В обработке' if order['status'] == 'processing' else 'Завершено' }}
+ + + + + +Итого: 0
- - + + + + + +Пользователь: {{ org['username'] }}
-Телефон: {{ org['org_phone'] }}
-Адрес: {{ org['org_address'] if org['org_address'] else 'Онлайн' }}
-Дата подачи: {{ org['submitted_at'] }}
-Нет организаций на проверке.
- {% endif %} -{{ post['description'] }}
-Цена: {{ post['price'] }} {{ post['currency'] }}
-Загрузил: {{ post['uploader'] }} | {{ post['upload_date'] }}
-Видео не найдены.
- {% endif %} -{{ post['description']|truncate(100) }}
+By: {{ post['uploader'] }} | {{ post['upload_date'] }}
+No posts found.
+ {% endif %}
{{ comment['user'] }} ({{ comment['date'] }}): {{ comment['text'] }}
-