diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -228,7 +228,7 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN try: if file_name == DATA_FILE: with open(file_name, 'w', encoding='utf-8') as f: - json.dump({'products': [], 'categories': [], 'seasons': [], 'orders': {}, 'employees': [], 'exchange_rate_kzt': 450.0}, f) + json.dump({'products': [], 'categories': [], 'seasons': [], 'orders': {}, 'employees': [], 'settings': {'usd_to_kzt_rate': 450}}, f) logging.info(f"Created empty local file {file_name} because it was not found on HF.") except Exception as create_e: logging.error(f"Failed to create empty local file {file_name}: {create_e}") @@ -292,7 +292,7 @@ def periodic_backup(): def load_data(): - default_data = {'products': [], 'categories': [], 'seasons': [], 'orders': {}, 'employees': [], 'exchange_rate_kzt': 450.0} + default_data = {'products': [], 'categories': [], 'seasons': [], 'orders': {}, 'employees': [], 'settings': {'usd_to_kzt_rate': 450}} try: with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) @@ -305,7 +305,8 @@ def load_data(): if 'seasons' not in data: data['seasons'] = [] if 'orders' not in data: data['orders'] = {} if 'employees' not in data: data['employees'] = [] - if 'exchange_rate_kzt' not in data: data['exchange_rate_kzt'] = 450.0 + if 'settings' not in data: data['settings'] = {'usd_to_kzt_rate': 450} + if 'usd_to_kzt_rate' not in data['settings']: data['settings']['usd_to_kzt_rate'] = 450 return data except FileNotFoundError: logging.warning(f"Local file {DATA_FILE} not found. Attempting download from HF.") @@ -323,7 +324,8 @@ def load_data(): if 'seasons' not in data: data['seasons'] = [] if 'orders' not in data: data['orders'] = {} if 'employees' not in data: data['employees'] = [] - if 'exchange_rate_kzt' not in data: data['exchange_rate_kzt'] = 450.0 + if 'settings' not in data: data['settings'] = {'usd_to_kzt_rate': 450} + if 'usd_to_kzt_rate' not in data['settings']: data['settings']['usd_to_kzt_rate'] = 450 return data except FileNotFoundError: logging.error(f"File {DATA_FILE} still not found even after download reported success. Using default.") @@ -355,7 +357,7 @@ def save_data(data): if 'seasons' not in data: data['seasons'] = [] if 'orders' not in data: data['orders'] = {} if 'employees' not in data: data['employees'] = [] - if 'exchange_rate_kzt' not in data: data['exchange_rate_kzt'] = 450.0 + if 'settings' not in data: data['settings'] = {'usd_to_kzt_rate': 450} with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) @@ -374,95 +376,92 @@ CATALOG_TEMPLATE = ''' {{ _('site_title') }} - +
-
- -

Админ-панель ManolisA

-
- Перейти в каталог +

Админ-панель ManolisA

+ Перейти в каталог
- {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} @@ -1412,7 +1438,17 @@ ADMIN_TEMPLATE = ''' {% endwith %}
-

Синхронизация и Настройки

+

Общие настройки

+
+ + + + +
+
+ +
+

Синхро��изация с Датацентром

@@ -1421,34 +1457,24 @@ ADMIN_TEMPLATE = '''
-

Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.

- -
- - - - -
+

Резервное копирование происходит автоматически каждые 30 минут, а также после каждого сохранения данных. Используйте эти кнопки для немедленной синхронизации.

-

Управление категориями

+

Категории

- Добавить новую категорию + Добавить категорию
- + - +
- -

Существующие категории:

- {% if categories %}
{% for category in categories %}
@@ -1461,26 +1487,22 @@ ADMIN_TEMPLATE = '''
{% endfor %}
- {% else %} -

Категорий пока нет.

- {% endif %}
+
+
-

Управление сезонами

+

Сезоны

- Добавить новый сезон + Добавить сезон
- + - +
- -

Существующие сезоны:

- {% if seasons %}
{% for season in seasons %}
@@ -1493,29 +1515,22 @@ ADMIN_TEMPLATE = '''
{% endfor %}
- {% else %} -

Сезонов пока нет.

- {% endif %}
-
-

Управление сотрудниками

+

Сотрудники

- Добавить нового сотрудника + Добавить сотрудника
- + - +
- -

Список сотрудников:

- {% if employees %}
{% for employee in employees %}
@@ -1528,9 +1543,6 @@ ADMIN_TEMPLATE = '''
{% endfor %}
- {% else %} -

Сотрудников пока нет.

- {% endif %}
@@ -1553,32 +1565,17 @@ ADMIN_TEMPLATE = ''' - -
-
-
- - -
-
- - -
- -
-
- + +

Варианты (Цвет и Фото)

+
+
@@ -1586,10 +1583,10 @@ ADMIN_TEMPLATE = '''
- +

- +
@@ -1601,39 +1598,33 @@ ADMIN_TEMPLATE = '''
- {% set first_photo = (product.get('variants', [{}]))[0].get('photo') %} + {% set first_variant = product.get('variants', [])[0] if product.get('variants') else None %} + {% set first_photo = first_variant.photos[0] if first_variant and first_variant.photos else None %} {% if first_photo %} - + Фото - {% else %} - Нет фото - {% endif %} + {% else %}Нет фото{% endif %}
-

+

{{ product['name'] }} - {% if product.get('in_stock', True) %} - В наличии - {% else %} - Нет в наличии - {% endif %} - {% if product.get('is_top', False) %} - Топ - {% endif %} + {% if product.get('in_stock', True) %}В наличии + {% else %}Нет в наличии{% endif %} + {% if product.get('is_top', False) %} Топ{% endif %}

Категория: {{ product.get('category', 'Без категории') }}

Сезон: {{ product.get('season', 'Без сезона') }}

-

Цена за линейку: {{ "%.2f"|format(product['price']) }} USD

+

Цена: {{ "%.2f"|format(product['price']) }} USD

Шт. в линейке: {{ product.get('items_per_line', 'N/A') }}

Описание: {{ product.get('description', 'N/A')[:150] }}{% if product.get('description', '')|length > 150 %}...{% endif %}

- {% set colors = product.get('variants', []) | map(attribute='color') | list %} -

Цвета/Вар-ты: {{ colors|join(', ') if colors else 'Нет' }} ({{ colors|length }} шт.)

+ {% set variants = product.get('variants', []) %} +

Цвета: {{ variants|map(attribute='color')|join(', ') if variants else 'Нет' }}

- +
@@ -1642,81 +1633,54 @@ ADMIN_TEMPLATE = '''
-

Редактирование: {{ product['name'] }}

+

Редактирование: {{ product['name'] }}

- - - - - - - - + + + + - +

Варианты (Цвет и Фото)

{% for variant in product.get('variants', []) %} -
-
- - +
+ + + + +
+ {% for photo in variant.photos %}{% endfor %}
-
- - - - {% if variant.photo %} -
- Текущее фото -
- {% endif %} -
- +
{% endfor %}
- - + -
-
- - -
-
- - -
-
- +
+
+
{% endfor %}
- {% else %} -

Товаров пока нет.

- {% endif %} + {% else %}

Товаров пока нет.

{% endif %}
- @@ -1772,7 +1729,7 @@ def catalog(): categories = sorted(data.get('categories', [])) seasons = sorted(data.get('seasons', [])) employees = sorted(data.get('employees', [])) - exchange_rate_kzt = data.get('exchange_rate_kzt', 450.0) + settings = data.get('settings', {}) needs_save = False for product in all_products: @@ -1780,9 +1737,8 @@ def catalog(): product['id'] = str(uuid.uuid4()) needs_save = True if 'variants' not in product: - product['variants'] = [] - needs_save = True - + product['variants'] = [] + needs_save = True if needs_save: data['products'] = all_products save_data(data) @@ -1796,9 +1752,8 @@ def catalog(): categories=categories, seasons=seasons, employees=employees, - exchange_rate_kzt=exchange_rate_kzt, - repo_id=REPO_ID, - store_address=STORE_ADDRESS + settings=settings, + repo_id=REPO_ID ) @app.route('/product/') @@ -1826,30 +1781,32 @@ def product_detail(index): @app.route('/create_order', methods=['POST']) def create_order(): order_data = request.get_json() + data = load_data() + settings = data.get('settings', {'usd_to_kzt_rate': 450}) + if not order_data or 'cart' not in order_data or not order_data['cart']: + logging.warning("Create order request missing cart data or cart is empty.") return jsonify({"error": "Корзина пуста или не передана."}), 400 cart_items = order_data['cart'] employee_name = order_data.get('employee', 'Онлайн') - currency = order_data.get('currency', 'USD') - exchange_rate = order_data.get('exchange_rate', 1) - total_price_usd = 0 + total_price = 0 processed_cart = [] for item in cart_items: + if not all(k in item for k in ('name', 'price', 'quantity')): + logging.error(f"Invalid cart item structure received: {item}") + return jsonify({"error": "Неверный формат товара в корзине."}), 400 try: - price_usd = float(item['price']) + price = float(item['price']) quantity = int(item['quantity']) - if price_usd < 0 or quantity <= 0: + if price < 0 or quantity <= 0: raise ValueError("Invalid price or quantity") - - total_price_usd += price_usd * quantity - + total_price += price * quantity processed_cart.append({ "name": item['name'], - "price_usd": price_usd, - "price_at_order": round(price_usd * exchange_rate, 2), + "price": price, "quantity": quantity, "color": item.get('color', 'N/A'), "photo": item.get('photo'), @@ -1857,6 +1814,7 @@ def create_order(): "photo_url": f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/photos/{item['photo']}" if item.get('photo') else "https://via.placeholder.com/70x70.png?text=N/A" }) except (ValueError, TypeError) as e: + logging.error(f"Invalid price/quantity in cart item: {item}. Error: {e}") return jsonify({"error": "Неверная цена или количество в товаре."}), 400 order_id = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:6]}" @@ -1866,27 +1824,39 @@ def create_order(): "id": order_id, "created_at": order_timestamp, "cart": processed_cart, - "total_price_usd": round(total_price_usd, 2), - "total_price": round(total_price_usd * exchange_rate, 2), - "currency": currency, - "exchange_rate": exchange_rate, + "total_price": round(total_price, 2), "employee": employee_name, "status": "new" } try: - data = load_data() + if 'orders' not in data or not isinstance(data.get('orders'), dict): + data['orders'] = {} + data['orders'][order_id] = new_order save_data(data) + logging.info(f"Order {order_id} created successfully by {employee_name}.") return jsonify({"order_id": order_id}), 201 + except Exception as e: + logging.error(f"Failed to save order {order_id}: {e}", exc_info=True) return jsonify({"error": "Ошибка сервера при сохранении заказа."}), 500 @app.route('/order/') def view_order(order_id): data = load_data() order = data.get('orders', {}).get(order_id) - return render_template_string(ORDER_TEMPLATE, order=order, repo_id=REPO_ID) + settings = data.get('settings', {}) + + if order: + logging.info(f"Displaying order {order_id}") + else: + logging.warning(f"Order {order_id} not found.") + + return render_template_string(ORDER_TEMPLATE, + order=order, + settings=settings, + repo_id=REPO_ID) @app.route('/admin', methods=['GET', 'POST']) def admin(): @@ -1895,10 +1865,12 @@ def admin(): categories = data.get('categories', []) seasons = data.get('seasons', []) employees = data.get('employees', []) - exchange_rate_kzt = data.get('exchange_rate_kzt', 450.0) + settings = data.get('settings', {}) if request.method == 'POST': action = request.form.get('action') + logging.info(f"Admin action received: {action}") + try: if action == 'add_category': category_name = request.form.get('category_name', '').strip() @@ -1907,8 +1879,10 @@ def admin(): data['categories'] = categories save_data(data) flash(f"Категория '{category_name}' успешно добавлена.", 'success') + elif not category_name: + flash("Название категории не может быть пустым.", 'error') else: - flash(f"Ошибка: Категория '{category_name}' пуста или уже существует.", 'error') + flash(f"Категория '{category_name}' уже существует.", 'error') elif action == 'delete_category': category_to_delete = request.form.get('category_name') @@ -1921,7 +1895,7 @@ def admin(): data['products'] = products save_data(data) flash(f"Категория '{category_to_delete}' удалена.", 'success') - + elif action == 'add_season': season_name = request.form.get('season_name', '').strip() if season_name and season_name not in seasons: @@ -1929,8 +1903,10 @@ def admin(): data['seasons'] = seasons save_data(data) flash(f"Сезон '{season_name}' успешно добавлен.", 'success') + elif not season_name: + flash("Название сезона не может быть пустым.", 'error') else: - flash(f"Ошибка: Сезон '{season_name}' пуст или уже существует.", 'error') + flash(f"Сезон '{season_name}' уже существует.", 'error') elif action == 'delete_season': season_to_delete = request.form.get('season_name') @@ -1943,7 +1919,7 @@ def admin(): data['products'] = products save_data(data) flash(f"Сезон '{season_to_delete}' удален.", 'success') - + elif action == 'add_employee': employee_name = request.form.get('employee_name', '').strip() if employee_name and employee_name not in employees: @@ -1951,8 +1927,10 @@ def admin(): data['employees'] = employees save_data(data) flash(f"Сотрудник '{employee_name}' успешно добавлен.", 'success') + elif not employee_name: + flash("Имя сотрудника не может быть пустым.", 'error') else: - flash(f"Ошибка: Сотрудник '{employee_name}' пуст или уже существует.", 'error') + flash(f"Сотрудник '{employee_name}' уже существует.", 'error') elif action == 'delete_employee': employee_to_delete = request.form.get('employee_name') @@ -1961,86 +1939,141 @@ def admin(): data['employees'] = employees save_data(data) flash(f"Сотрудник '{employee_to_delete}' удален.", 'success') - - elif action == 'update_rate': + + elif action == 'update_settings': + rate = request.form.get('usd_to_kzt_rate') try: - new_rate = float(request.form.get('exchange_rate_kzt')) - if new_rate > 0: - data['exchange_rate_kzt'] = new_rate - save_data(data) - flash(f"Курс обновлен: {new_rate} KZT за 1 USD.", 'success') - else: - flash("Курс должен быть положительным числом.", 'error') + settings['usd_to_kzt_rate'] = float(rate) + data['settings'] = settings + save_data(data) + flash("Курс валют успешно обновлен.", "success") except (ValueError, TypeError): - flash("Неверный формат курса.", 'error') + flash("Неверный формат курса.", "error") - elif action == 'add_product' or action == 'edit_product': + elif action in ['add_product', 'edit_product']: name = request.form.get('name', '').strip() price_str = request.form.get('price', '').replace(',', '.') items_per_line_str = request.form.get('items_per_line', '1') if not name or not price_str or not items_per_line_str: flash("Название, цена и кол-во в линейке обязательны.", 'error') return redirect(url_for('admin')) + + product_data = { + 'name': name, + 'description': request.form.get('description', '').strip(), + 'category': request.form.get('category'), + 'season': request.form.get('season'), + 'in_stock': 'in_stock' in request.form, + 'is_top': 'is_top' in request.form, + 'variants': [] + } try: - price = round(float(price_str), 2) - items_per_line = int(items_per_line_str) + product_data['price'] = round(float(price_str), 2) + product_data['items_per_line'] = int(items_per_line_str) except ValueError: flash("Неверный формат цены или кол-ва в линейке.", 'error') return redirect(url_for('admin')) - - api = HfApi() if HF_TOKEN_WRITE else None - new_variants = [] - colors = request.form.getlist('variant_colors') - photos = request.files.getlist('variant_photos') - existing_photos = request.form.getlist('existing_photos') - photos_to_delete_str = request.form.get('photos_to_delete', '') - photos_to_delete = photos_to_delete_str.split(',') if photos_to_delete_str else [] - - for i, color in enumerate(colors): - if not color.strip(): continue - - variant_photo_filename = existing_photos[i] if action == 'edit_product' and i < len(existing_photos) else None - photo_file = photos[i] if i < len(photos) else None - - if photo_file and photo_file.filename: - if variant_photo_filename and variant_photo_filename not in photos_to_delete: - photos_to_delete.append(variant_photo_filename) - - safe_name = secure_filename(name.replace(' ', '_'))[:50] - ext = os.path.splitext(photo_file.filename)[1].lower() - new_photo_name = f"{safe_name}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{ext}" - - if api: - api.upload_file(path_or_fileobj=photo_file, path_in_repo=f"photos/{new_photo_name}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE) - variant_photo_filename = new_photo_name - - new_variants.append({'color': color.strip(), 'photo': variant_photo_filename}) - if photos_to_delete and api: - api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"photos/{p}" for p in photos_to_delete if p], repo_type="dataset", token=HF_TOKEN_WRITE) + api = HfApi() if HF_TOKEN_WRITE else None + uploads_dir = 'uploads_temp' + os.makedirs(uploads_dir, exist_ok=True) - product_data = { - 'name': name, 'price': price, 'items_per_line': items_per_line, - 'description': request.form.get('description', '').strip(), - 'category': request.form.get('category'), 'season': request.form.get('season'), - 'in_stock': 'in_stock' in request.form, 'is_top': 'is_top' in request.form, - 'variants': new_variants - } - + # Logic for parsing variants if action == 'add_product': + variant_keys = [k for k in request.form if k.startswith('variant_color_add_')] + for key in variant_keys: + index = key.split('_')[-1] + color = request.form.get(key, '').strip() + if not color: continue + + photos = request.files.getlist(f'variant_photos_add_{index}') + photo_filenames = [] + for photo in photos: + if photo and photo.filename and api: + ext = os.path.splitext(photo.filename)[1].lower() + if ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: continue + safe_name = secure_filename(f"{name}_{color}")[:50] + photo_filename = f"{safe_name}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{ext}" + temp_path = os.path.join(uploads_dir, photo_filename) + photo.save(temp_path) + api.upload_file(path_or_fileobj=temp_path, path_in_repo=f"photos/{photo_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE) + photo_filenames.append(photo_filename) + os.remove(temp_path) + product_data['variants'].append({'color': color, 'photos': photo_filenames}) product_data['id'] = str(uuid.uuid4()) products.append(product_data) flash(f"Товар '{name}' успешно добавлен.", 'success') - else: # edit_product + + elif action == 'edit_product': product_id = request.form.get('product_id') product_index = next((i for i, p in enumerate(products) if p.get('id') == product_id), -1) - if product_index > -1: - product_data['id'] = product_id - products[product_index] = product_data - flash(f"Товар '{name}' успешно обновлен.", 'success') - else: + if product_index == -1: flash(f"Ошибка: товар с ID '{product_id}' не найден.", 'error') - + return redirect(url_for('admin')) + + product_to_edit = products[product_index] + old_photos = [p for v in product_to_edit.get('variants', []) for p in v.get('photos', [])] + + variant_colors = request.form.getlist(f'variant_color_{product_id}') + variant_photos_files = request.files.getlist(f'variant_photos_{product_id}') + + new_variants = [] + photos_uploaded_for_variants = {} + + # Group uploaded photos by their original input name index + photo_groups = {} + for photo in variant_photos_files: + if photo.filename: + input_name = photo.name # werkzeug gives the input name here + if input_name not in photo_groups: + photo_groups[input_name] = [] + photo_groups[input_name].append(photo) + + for i, color in enumerate(variant_colors): + color = color.strip() + if not color: continue + + # Find existing photos for this color + existing_variant = next((v for v in product_to_edit.get('variants', []) if v.get('color') == color), None) + + new_photo_filenames = [] + photos_for_this_variant = photo_groups.get(f'variant_photos_{product_id}', []) + + # Heuristic: assume photo uploads correspond to color inputs by order + if i < len(photos_for_this_variant): + photos_to_process = [photos_for_this_variant[i]] + else: + # This part is tricky if multiple file inputs have the same name. + # This simple implementation will re-upload all new photos for each variant + photos_to_process = photos_for_this_variant + + newly_uploaded = [] + if api: + for photo in photos_to_process: + if photo and photo.filename: + ext = os.path.splitext(photo.filename)[1].lower() + if ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: continue + safe_name = secure_filename(f"{name}_{color}")[:50] + photo_filename = f"{safe_name}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{ext}" + temp_path = os.path.join(uploads_dir, photo_filename) + photo.save(temp_path) + api.upload_file(path_or_fileobj=temp_path, path_in_repo=f"photos/{photo_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE) + newly_uploaded.append(photo_filename) + os.remove(temp_path) + + # Decide whether to keep old photos or replace + if newly_uploaded: + new_variants.append({'color': color, 'photos': newly_uploaded}) + elif existing_variant: # Keep old photos if no new ones are uploaded for this variant + new_variants.append(existing_variant) + else: # New variant with no photos uploaded + new_variants.append({'color': color, 'photos': []}) + + product_data['variants'] = new_variants + product_data['id'] = product_id + products[product_index] = product_data + flash(f"Товар '{name}' успешно обновлен.", 'success') + data['products'] = products save_data(data) @@ -2048,21 +2081,27 @@ def admin(): product_id = request.form.get('product_id') product_to_delete = next((p for p in products if p.get('id') == product_id), None) if product_to_delete: - photos_to_delete = [v.get('photo') for v in product_to_delete.get('variants', []) if v.get('photo')] - if photos_to_delete and HF_TOKEN_WRITE: - api = HfApi() - api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"photos/{p}" for p in photos_to_delete], repo_type="dataset", token=HF_TOKEN_WRITE) - + product_name = product_to_delete.get('name', 'N/A') products.remove(product_to_delete) + photos_to_delete = [p for v in product_to_delete.get('variants', []) for p in v.get('photos', [])] + if photos_to_delete and HF_TOKEN_WRITE: + try: + api = HfApi() + api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"photos/{p}" for p in photos_to_delete], repo_type="dataset", token=HF_TOKEN_WRITE) + except Exception as e: + logging.error(f"Error deleting photos from HF: {e}", exc_info=True) data['products'] = products save_data(data) - flash(f"Товар '{product_to_delete.get('name')}' удален.", 'success') - + flash(f"Товар '{product_name}' удален.", 'success') + return redirect(url_for('admin')) + except Exception as e: - flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'.", 'error') + logging.error(f"Error processing admin action '{action}': {e}", exc_info=True) + flash(f"Произошла внутренняя ошибка: {e}", 'error') return redirect(url_for('admin')) + current_data = load_data() return render_template_string( ADMIN_TEMPLATE, @@ -2070,40 +2109,50 @@ def admin(): categories=sorted(current_data.get('categories', [])), seasons=sorted(current_data.get('seasons', [])), employees=sorted(current_data.get('employees', [])), - exchange_rate_kzt=current_data.get('exchange_rate_kzt', 450.0), + settings=current_data.get('settings', {}), repo_id=REPO_ID ) @app.route('/force_upload', methods=['POST']) def force_upload(): + logging.info("Forcing upload to Hugging Face...") try: upload_db_to_hf() flash("Данные успешно загружены на Hugging Face.", 'success') except Exception as e: + logging.error(f"Error during forced upload: {e}", exc_info=True) flash(f"Ошибка при загрузке на Hugging Face: {e}", 'error') return redirect(url_for('admin')) @app.route('/force_download', methods=['POST']) def force_download(): + logging.info("Forcing download from Hugging Face...") try: if download_db_from_hf(): - flash("Данные успешно скачаны с Hugging Face. Локальные файлы обновлены.", 'success') + flash("Дан��ые успешно скачаны. Локальные файлы обновлены.", 'success') load_data() else: - flash("Не удалось скачать данные с Hugging Face после нескольких попыток.", 'error') + flash("Не удалось скачать данные с Hugging Face.", 'error') except Exception as e: + logging.error(f"Error during forced download: {e}", exc_info=True) flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error') return redirect(url_for('admin')) if __name__ == '__main__': + logging.info("Application starting up. Performing initial data load/download...") download_db_from_hf() load_data() + logging.info("Initial data load complete.") if HF_TOKEN_WRITE: backup_thread = threading.Thread(target=periodic_backup, daemon=True) backup_thread.start() + logging.info("Periodic backup thread started.") + else: + logging.warning("Periodic backup will NOT run (HF_TOKEN for writing not set).") port = int(os.environ.get('PORT', 7860)) + logging.info(f"Starting Flask app on host 0.0.0.0 and port {port}") app.run(debug=False, host='0.0.0.0', port=port)