Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
# --- START OF FILE app.py ---
|
| 2 |
|
| 3 |
from flask import Flask, render_template_string, request, redirect, url_for, session, send_file, flash, jsonify
|
|
@@ -362,6 +363,9 @@ CATALOG_TEMPLATE = '''
|
|
| 362 |
body.dark-mode .no-results-message { color: #8aa39a; }
|
| 363 |
.top-product-indicator { position: absolute; top: 8px; right: 8px; background-color: rgba(255, 215, 0, 0.8); color: #333; padding: 2px 6px; font-size: 0.7rem; border-radius: 4px; font-weight: bold; z-index: 10; backdrop-filter: blur(2px); }
|
| 364 |
.product { position: relative; }
|
|
|
|
|
|
|
|
|
|
| 365 |
</style>
|
| 366 |
</head>
|
| 367 |
<body>
|
|
@@ -1051,6 +1055,9 @@ ADMIN_TEMPLATE = '''
|
|
| 1051 |
.status-indicator.in-stock { background-color: #c6f6d5; color: #2f855a; }
|
| 1052 |
.status-indicator.out-of-stock { background-color: #fed7d7; color: #c53030; }
|
| 1053 |
.status-indicator.top-product { background-color: #feebc8; color: #9c4221; margin-left: 5px;}
|
|
|
|
|
|
|
|
|
|
| 1054 |
</style>
|
| 1055 |
</head>
|
| 1056 |
<body>
|
|
@@ -1218,11 +1225,18 @@ ADMIN_TEMPLATE = '''
|
|
| 1218 |
</div>
|
| 1219 |
</details>
|
| 1220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1221 |
<h3>Список товаров:</h3>
|
| 1222 |
{% if products %}
|
| 1223 |
-
<div class="item-list">
|
| 1224 |
{% for product in products %}
|
| 1225 |
-
<div class="item"
|
|
|
|
|
|
|
| 1226 |
<div style="display: flex; gap: 15px; align-items: flex-start;">
|
| 1227 |
<div class="photo-preview" style="flex-shrink: 0;">
|
| 1228 |
{% if product.get('photos') %}
|
|
@@ -1328,9 +1342,10 @@ ADMIN_TEMPLATE = '''
|
|
| 1328 |
</div>
|
| 1329 |
</div>
|
| 1330 |
{% endfor %}
|
|
|
|
| 1331 |
</div>
|
| 1332 |
{% else %}
|
| 1333 |
-
|
| 1334 |
{% endif %}
|
| 1335 |
</div>
|
| 1336 |
|
|
@@ -1379,6 +1394,49 @@ ADMIN_TEMPLATE = '''
|
|
| 1379 |
console.warn("Could not find parent .color-input-group for remove button");
|
| 1380 |
}
|
| 1381 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1382 |
</script>
|
| 1383 |
</body>
|
| 1384 |
</html>
|
|
@@ -1680,8 +1738,7 @@ def create_order():
|
|
| 1680 |
order_timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
| 1681 |
|
| 1682 |
user_info_for_order = session.get('user_info', None)
|
| 1683 |
-
|
| 1684 |
-
# and to store only relevant info, excluding potentially sensitive session data
|
| 1685 |
user_info_for_order_copy = None
|
| 1686 |
if user_info_for_order:
|
| 1687 |
user_info_for_order_copy = {
|
|
@@ -1692,7 +1749,7 @@ def create_order():
|
|
| 1692 |
'city': user_info_for_order.get('city', ''),
|
| 1693 |
'phone': user_info_for_order.get('phone', '')
|
| 1694 |
}
|
| 1695 |
-
|
| 1696 |
user_info_for_order_copy = {k: v for k, v in user_info_for_order_copy.items() if v}
|
| 1697 |
|
| 1698 |
|
|
@@ -1733,34 +1790,34 @@ def view_order(order_id):
|
|
| 1733 |
order=order,
|
| 1734 |
repo_id=REPO_ID,
|
| 1735 |
currency_code=CURRENCY_CODE,
|
| 1736 |
-
request=request
|
| 1737 |
)
|
| 1738 |
|
| 1739 |
@app.route('/admin', methods=['GET', 'POST'])
|
| 1740 |
def admin():
|
| 1741 |
data = load_data()
|
| 1742 |
-
|
| 1743 |
products = sorted(data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 1744 |
categories = sorted(data.get('categories', []))
|
| 1745 |
-
users = dict(sorted(load_users().items()))
|
| 1746 |
|
| 1747 |
|
| 1748 |
if request.method == 'POST':
|
| 1749 |
action = request.form.get('action')
|
| 1750 |
logging.info(f"Admin action received: {action}")
|
| 1751 |
|
| 1752 |
-
|
| 1753 |
data = load_data()
|
| 1754 |
-
products = data.get('products', [])
|
| 1755 |
-
categories = data.get('categories', [])
|
| 1756 |
-
users = load_users()
|
| 1757 |
|
| 1758 |
try:
|
| 1759 |
if action == 'add_category':
|
| 1760 |
category_name = request.form.get('category_name', '').strip()
|
| 1761 |
if category_name and category_name not in categories:
|
| 1762 |
categories.append(category_name)
|
| 1763 |
-
categories.sort()
|
| 1764 |
data['categories'] = categories
|
| 1765 |
save_data(data)
|
| 1766 |
logging.info(f"Category '{category_name}' added.")
|
|
@@ -1831,8 +1888,8 @@ def admin():
|
|
| 1831 |
flash(f"Файл {photo.filename} не является изображением и был пропущен.", "warning")
|
| 1832 |
continue
|
| 1833 |
|
| 1834 |
-
safe_name = secure_filename(name.replace(' ', '_'))[:50].rstrip('_')
|
| 1835 |
-
|
| 1836 |
photo_filename = f"{safe_name}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{ext}"
|
| 1837 |
temp_path = os.path.join(uploads_dir, photo_filename)
|
| 1838 |
photo.save(temp_path)
|
|
@@ -1873,7 +1930,7 @@ def admin():
|
|
| 1873 |
'in_stock': in_stock, 'is_top': is_top
|
| 1874 |
}
|
| 1875 |
products.append(new_product)
|
| 1876 |
-
|
| 1877 |
products.sort(key=lambda p: p.get('name', '').lower())
|
| 1878 |
data['products'] = products
|
| 1879 |
save_data(data)
|
|
@@ -1888,7 +1945,7 @@ def admin():
|
|
| 1888 |
|
| 1889 |
try:
|
| 1890 |
index = int(index_str)
|
| 1891 |
-
products = sorted(data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 1892 |
if not (0 <= index < len(products)):
|
| 1893 |
raise IndexError("Product index out of range")
|
| 1894 |
product_to_edit = products[index]
|
|
@@ -1896,7 +1953,7 @@ def admin():
|
|
| 1896 |
|
| 1897 |
except (ValueError, IndexError):
|
| 1898 |
flash(f"Ошибка редактирования: неверный индекс товара '{index_str}'.", 'error')
|
| 1899 |
-
|
| 1900 |
display_products = sorted(load_data().get('products', []), key=lambda p: p.get('name', '').lower())
|
| 1901 |
display_categories = sorted(load_data().get('categories', []))
|
| 1902 |
display_users = dict(sorted(load_users().items()))
|
|
@@ -1981,7 +2038,7 @@ def admin():
|
|
| 1981 |
api = HfApi()
|
| 1982 |
api.delete_files(
|
| 1983 |
repo_id=REPO_ID,
|
| 1984 |
-
paths_in_repo=[f"photos/{p}" for p in old_photos if p],
|
| 1985 |
repo_type="dataset",
|
| 1986 |
token=HF_TOKEN_WRITE,
|
| 1987 |
commit_message=f"Delete old photos for product {product_to_edit['name']}"
|
|
@@ -1998,12 +2055,11 @@ def admin():
|
|
| 1998 |
flash("HF_TOKEN (write) не настроен. Фотографии не были обновлены.", "warning")
|
| 1999 |
|
| 2000 |
|
| 2001 |
-
|
| 2002 |
-
# We just need to ensure the overall list in 'data' is the one we save.
|
| 2003 |
data['products'] = products
|
| 2004 |
-
|
| 2005 |
products.sort(key=lambda p: p.get('name', '').lower())
|
| 2006 |
-
data['products'] = products
|
| 2007 |
save_data(data)
|
| 2008 |
logging.info(f"Product '{original_name}' (original index {index}) updated to '{product_to_edit['name']}'.")
|
| 2009 |
flash(f"Товар '{product_to_edit['name']}' успешно обновлен.", 'success')
|
|
@@ -2016,7 +2072,7 @@ def admin():
|
|
| 2016 |
return redirect(url_for('admin'))
|
| 2017 |
try:
|
| 2018 |
index = int(index_str)
|
| 2019 |
-
products = sorted(data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 2020 |
if not (0 <= index < len(products)): raise IndexError("Product index out of range")
|
| 2021 |
deleted_product = products.pop(index)
|
| 2022 |
product_name = deleted_product.get('name', 'N/A')
|
|
@@ -2028,7 +2084,7 @@ def admin():
|
|
| 2028 |
api = HfApi()
|
| 2029 |
api.delete_files(
|
| 2030 |
repo_id=REPO_ID,
|
| 2031 |
-
paths_in_repo=[f"photos/{p}" for p in photos_to_delete if p],
|
| 2032 |
repo_type="dataset",
|
| 2033 |
token=HF_TOKEN_WRITE,
|
| 2034 |
commit_message=f"Delete photos for deleted product {product_name}"
|
|
@@ -2052,7 +2108,7 @@ def admin():
|
|
| 2052 |
|
| 2053 |
elif action == 'add_user':
|
| 2054 |
login = request.form.get('login', '').strip()
|
| 2055 |
-
password = request.form.get('password', '').strip()
|
| 2056 |
first_name = request.form.get('first_name', '').strip()
|
| 2057 |
last_name = request.form.get('last_name', '').strip()
|
| 2058 |
phone = request.form.get('phone', '').strip()
|
|
@@ -2067,7 +2123,7 @@ def admin():
|
|
| 2067 |
return redirect(url_for('admin'))
|
| 2068 |
|
| 2069 |
users[login] = {
|
| 2070 |
-
'password': password,
|
| 2071 |
'first_name': first_name, 'last_name': last_name,
|
| 2072 |
'phone': phone,
|
| 2073 |
'country': country, 'city': city
|
|
@@ -2097,7 +2153,7 @@ def admin():
|
|
| 2097 |
|
| 2098 |
try:
|
| 2099 |
product_index = int(product_index_str)
|
| 2100 |
-
products = sorted(data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 2101 |
if not (0 <= product_index < len(products)):
|
| 2102 |
raise IndexError("Product index out of range")
|
| 2103 |
product = products[product_index]
|
|
@@ -2115,12 +2171,12 @@ def admin():
|
|
| 2115 |
commit_message=f"Delete photo {photo_filename} for product {product.get('name', 'N/A')}"
|
| 2116 |
)
|
| 2117 |
logging.info(f"Photo '{photo_filename}' deleted from HF.")
|
| 2118 |
-
|
| 2119 |
product['photos'].remove(photo_filename)
|
| 2120 |
data['products'] = products
|
| 2121 |
-
|
| 2122 |
products.sort(key=lambda p: p.get('name', '').lower())
|
| 2123 |
-
data['products'] = products
|
| 2124 |
save_data(data)
|
| 2125 |
flash(f"Фото '{photo_filename}' успешно удалено.", 'success')
|
| 2126 |
except Exception as e:
|
|
@@ -2146,11 +2202,10 @@ def admin():
|
|
| 2146 |
logging.error(f"Error processing admin action '{action}': {e}", exc_info=True)
|
| 2147 |
flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'. Подробности в логе сервера.", 'error')
|
| 2148 |
|
| 2149 |
-
|
| 2150 |
return redirect(url_for('admin'))
|
| 2151 |
|
| 2152 |
-
|
| 2153 |
-
# Always reload data and users for the GET request template rendering
|
| 2154 |
current_data = load_data()
|
| 2155 |
display_products = sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 2156 |
display_categories = sorted(current_data.get('categories', []))
|
|
@@ -2182,8 +2237,8 @@ def force_download():
|
|
| 2182 |
try:
|
| 2183 |
if download_db_from_hf():
|
| 2184 |
flash("Данные успешно скачаны с Hugging Face. Локальные файлы обновлены.", 'success')
|
| 2185 |
-
load_data()
|
| 2186 |
-
load_users()
|
| 2187 |
else:
|
| 2188 |
flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Проверьте логи.", 'error')
|
| 2189 |
except Exception as e:
|
|
@@ -2194,7 +2249,7 @@ def force_download():
|
|
| 2194 |
|
| 2195 |
if __name__ == '__main__':
|
| 2196 |
logging.info("Application starting up. Performing initial data load/download...")
|
| 2197 |
-
|
| 2198 |
download_db_from_hf()
|
| 2199 |
load_data()
|
| 2200 |
load_users()
|
|
@@ -2210,3 +2265,4 @@ if __name__ == '__main__':
|
|
| 2210 |
port = int(os.environ.get('PORT', 7860))
|
| 2211 |
logging.info(f"Starting Flask app on host 0.0.0.0 and port {port}")
|
| 2212 |
app.run(debug=False, host='0.0.0.0', port=port)
|
|
|
|
|
|
| 1 |
+
|
| 2 |
# --- START OF FILE app.py ---
|
| 3 |
|
| 4 |
from flask import Flask, render_template_string, request, redirect, url_for, session, send_file, flash, jsonify
|
|
|
|
| 363 |
body.dark-mode .no-results-message { color: #8aa39a; }
|
| 364 |
.top-product-indicator { position: absolute; top: 8px; right: 8px; background-color: rgba(255, 215, 0, 0.8); color: #333; padding: 2px 6px; font-size: 0.7rem; border-radius: 4px; font-weight: bold; z-index: 10; backdrop-filter: blur(2px); }
|
| 365 |
.product { position: relative; }
|
| 366 |
+
@media (min-width: 768px) { .products-grid { grid-template-columns: repeat(3, 1fr); } }
|
| 367 |
+
@media (min-width: 1024px) { .products-grid { grid-template-columns: repeat(4, 1fr); } }
|
| 368 |
+
@media (min-width: 1200px) { .products-grid { grid-template-columns: repeat(5, 1fr); } }
|
| 369 |
</style>
|
| 370 |
</head>
|
| 371 |
<body>
|
|
|
|
| 1055 |
.status-indicator.in-stock { background-color: #c6f6d5; color: #2f855a; }
|
| 1056 |
.status-indicator.out-of-stock { background-color: #fed7d7; color: #c53030; }
|
| 1057 |
.status-indicator.top-product { background-color: #feebc8; color: #9c4221; margin-left: 5px;}
|
| 1058 |
+
.search-container { margin-bottom: 20px; }
|
| 1059 |
+
.no-results-message { display: none; grid-column: 1 / -1; text-align: center; padding: 20px; font-size: 1rem; color: #5e6e68; }
|
| 1060 |
+
|
| 1061 |
</style>
|
| 1062 |
</head>
|
| 1063 |
<body>
|
|
|
|
| 1225 |
</div>
|
| 1226 |
</details>
|
| 1227 |
|
| 1228 |
+
<div class="search-container" id="admin-search-container">
|
| 1229 |
+
<label for="admin-product-search">Поиск по названию/описанию:</label>
|
| 1230 |
+
<input type="text" id="admin-product-search" placeholder="Введите текст для поиска..." style="width: 100%; max-width: 500px; display: block; margin-top: 5px;">
|
| 1231 |
+
</div>
|
| 1232 |
+
|
| 1233 |
<h3>Список товаров:</h3>
|
| 1234 |
{% if products %}
|
| 1235 |
+
<div class="item-list" id="admin-product-list">
|
| 1236 |
{% for product in products %}
|
| 1237 |
+
<div class="item product-item"
|
| 1238 |
+
data-name="{{ product['name']|lower }}"
|
| 1239 |
+
data-description="{{ product.get('description', '')|lower }}">
|
| 1240 |
<div style="display: flex; gap: 15px; align-items: flex-start;">
|
| 1241 |
<div class="photo-preview" style="flex-shrink: 0;">
|
| 1242 |
{% if product.get('photos') %}
|
|
|
|
| 1342 |
</div>
|
| 1343 |
</div>
|
| 1344 |
{% endfor %}
|
| 1345 |
+
<p id="admin-no-results" class="no-results-message" style="display: none;">Товары по вашему запросу не найдены.</p>
|
| 1346 |
</div>
|
| 1347 |
{% else %}
|
| 1348 |
+
<p id="admin-no-products-initial">Товаров пока нет.</p>
|
| 1349 |
{% endif %}
|
| 1350 |
</div>
|
| 1351 |
|
|
|
|
| 1394 |
console.warn("Could not find parent .color-input-group for remove button");
|
| 1395 |
}
|
| 1396 |
}
|
| 1397 |
+
|
| 1398 |
+
function filterAdminProducts() {
|
| 1399 |
+
const searchTerm = document.getElementById('admin-product-search').value.toLowerCase().trim();
|
| 1400 |
+
const productItems = document.querySelectorAll('#admin-product-list .product-item');
|
| 1401 |
+
const noResultsMessage = document.getElementById('admin-no-results');
|
| 1402 |
+
let visibleCount = 0;
|
| 1403 |
+
|
| 1404 |
+
productItems.forEach(item => {
|
| 1405 |
+
const name = item.dataset.name || '';
|
| 1406 |
+
const description = item.dataset.description || '';
|
| 1407 |
+
|
| 1408 |
+
if (!searchTerm || name.includes(searchTerm) || description.includes(searchTerm)) {
|
| 1409 |
+
item.style.display = '';
|
| 1410 |
+
visibleCount++;
|
| 1411 |
+
} else {
|
| 1412 |
+
item.style.display = 'none';
|
| 1413 |
+
}
|
| 1414 |
+
});
|
| 1415 |
+
|
| 1416 |
+
if (noResultsMessage) {
|
| 1417 |
+
noResultsMessage.style.display = visibleCount === 0 && searchTerm ? 'block' : 'none';
|
| 1418 |
+
}
|
| 1419 |
+
}
|
| 1420 |
+
|
| 1421 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 1422 |
+
const searchInput = document.getElementById('admin-product-search');
|
| 1423 |
+
const searchContainer = document.getElementById('admin-search-container');
|
| 1424 |
+
const noProductsInitialMessage = document.getElementById('admin-no-products-initial');
|
| 1425 |
+
const productList = document.getElementById('admin-product-list');
|
| 1426 |
+
const hasProducts = productList && productList.querySelector('.product-item');
|
| 1427 |
+
|
| 1428 |
+
if (searchInput) {
|
| 1429 |
+
searchInput.addEventListener('input', filterAdminProducts);
|
| 1430 |
+
}
|
| 1431 |
+
|
| 1432 |
+
if (!hasProducts && noProductsInitialMessage) {
|
| 1433 |
+
if (searchContainer) searchContainer.style.display = 'none';
|
| 1434 |
+
} else if (searchContainer) {
|
| 1435 |
+
searchContainer.style.display = 'block';
|
| 1436 |
+
}
|
| 1437 |
+
|
| 1438 |
+
});
|
| 1439 |
+
|
| 1440 |
</script>
|
| 1441 |
</body>
|
| 1442 |
</html>
|
|
|
|
| 1738 |
order_timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
| 1739 |
|
| 1740 |
user_info_for_order = session.get('user_info', None)
|
| 1741 |
+
|
|
|
|
| 1742 |
user_info_for_order_copy = None
|
| 1743 |
if user_info_for_order:
|
| 1744 |
user_info_for_order_copy = {
|
|
|
|
| 1749 |
'city': user_info_for_order.get('city', ''),
|
| 1750 |
'phone': user_info_for_order.get('phone', '')
|
| 1751 |
}
|
| 1752 |
+
|
| 1753 |
user_info_for_order_copy = {k: v for k, v in user_info_for_order_copy.items() if v}
|
| 1754 |
|
| 1755 |
|
|
|
|
| 1790 |
order=order,
|
| 1791 |
repo_id=REPO_ID,
|
| 1792 |
currency_code=CURRENCY_CODE,
|
| 1793 |
+
request=request
|
| 1794 |
)
|
| 1795 |
|
| 1796 |
@app.route('/admin', methods=['GET', 'POST'])
|
| 1797 |
def admin():
|
| 1798 |
data = load_data()
|
| 1799 |
+
|
| 1800 |
products = sorted(data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 1801 |
categories = sorted(data.get('categories', []))
|
| 1802 |
+
users = dict(sorted(load_users().items()))
|
| 1803 |
|
| 1804 |
|
| 1805 |
if request.method == 'POST':
|
| 1806 |
action = request.form.get('action')
|
| 1807 |
logging.info(f"Admin action received: {action}")
|
| 1808 |
|
| 1809 |
+
|
| 1810 |
data = load_data()
|
| 1811 |
+
products = data.get('products', [])
|
| 1812 |
+
categories = data.get('categories', [])
|
| 1813 |
+
users = load_users()
|
| 1814 |
|
| 1815 |
try:
|
| 1816 |
if action == 'add_category':
|
| 1817 |
category_name = request.form.get('category_name', '').strip()
|
| 1818 |
if category_name and category_name not in categories:
|
| 1819 |
categories.append(category_name)
|
| 1820 |
+
categories.sort()
|
| 1821 |
data['categories'] = categories
|
| 1822 |
save_data(data)
|
| 1823 |
logging.info(f"Category '{category_name}' added.")
|
|
|
|
| 1888 |
flash(f"Файл {photo.filename} не является изображением и был пропущен.", "warning")
|
| 1889 |
continue
|
| 1890 |
|
| 1891 |
+
safe_name = secure_filename(name.replace(' ', '_'))[:50].rstrip('_')
|
| 1892 |
+
|
| 1893 |
photo_filename = f"{safe_name}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}{ext}"
|
| 1894 |
temp_path = os.path.join(uploads_dir, photo_filename)
|
| 1895 |
photo.save(temp_path)
|
|
|
|
| 1930 |
'in_stock': in_stock, 'is_top': is_top
|
| 1931 |
}
|
| 1932 |
products.append(new_product)
|
| 1933 |
+
|
| 1934 |
products.sort(key=lambda p: p.get('name', '').lower())
|
| 1935 |
data['products'] = products
|
| 1936 |
save_data(data)
|
|
|
|
| 1945 |
|
| 1946 |
try:
|
| 1947 |
index = int(index_str)
|
| 1948 |
+
products = sorted(data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 1949 |
if not (0 <= index < len(products)):
|
| 1950 |
raise IndexError("Product index out of range")
|
| 1951 |
product_to_edit = products[index]
|
|
|
|
| 1953 |
|
| 1954 |
except (ValueError, IndexError):
|
| 1955 |
flash(f"Ошибка редактирования: неверный индекс товара '{index_str}'.", 'error')
|
| 1956 |
+
|
| 1957 |
display_products = sorted(load_data().get('products', []), key=lambda p: p.get('name', '').lower())
|
| 1958 |
display_categories = sorted(load_data().get('categories', []))
|
| 1959 |
display_users = dict(sorted(load_users().items()))
|
|
|
|
| 2038 |
api = HfApi()
|
| 2039 |
api.delete_files(
|
| 2040 |
repo_id=REPO_ID,
|
| 2041 |
+
paths_in_repo=[f"photos/{p}" for p in old_photos if p],
|
| 2042 |
repo_type="dataset",
|
| 2043 |
token=HF_TOKEN_WRITE,
|
| 2044 |
commit_message=f"Delete old photos for product {product_to_edit['name']}"
|
|
|
|
| 2055 |
flash("HF_TOKEN (write) не настроен. Фотографии не были обновлены.", "warning")
|
| 2056 |
|
| 2057 |
|
| 2058 |
+
|
|
|
|
| 2059 |
data['products'] = products
|
| 2060 |
+
|
| 2061 |
products.sort(key=lambda p: p.get('name', '').lower())
|
| 2062 |
+
data['products'] = products
|
| 2063 |
save_data(data)
|
| 2064 |
logging.info(f"Product '{original_name}' (original index {index}) updated to '{product_to_edit['name']}'.")
|
| 2065 |
flash(f"Товар '{product_to_edit['name']}' успешно обновлен.", 'success')
|
|
|
|
| 2072 |
return redirect(url_for('admin'))
|
| 2073 |
try:
|
| 2074 |
index = int(index_str)
|
| 2075 |
+
products = sorted(data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 2076 |
if not (0 <= index < len(products)): raise IndexError("Product index out of range")
|
| 2077 |
deleted_product = products.pop(index)
|
| 2078 |
product_name = deleted_product.get('name', 'N/A')
|
|
|
|
| 2084 |
api = HfApi()
|
| 2085 |
api.delete_files(
|
| 2086 |
repo_id=REPO_ID,
|
| 2087 |
+
paths_in_repo=[f"photos/{p}" for p in photos_to_delete if p],
|
| 2088 |
repo_type="dataset",
|
| 2089 |
token=HF_TOKEN_WRITE,
|
| 2090 |
commit_message=f"Delete photos for deleted product {product_name}"
|
|
|
|
| 2108 |
|
| 2109 |
elif action == 'add_user':
|
| 2110 |
login = request.form.get('login', '').strip()
|
| 2111 |
+
password = request.form.get('password', '').strip()
|
| 2112 |
first_name = request.form.get('first_name', '').strip()
|
| 2113 |
last_name = request.form.get('last_name', '').strip()
|
| 2114 |
phone = request.form.get('phone', '').strip()
|
|
|
|
| 2123 |
return redirect(url_for('admin'))
|
| 2124 |
|
| 2125 |
users[login] = {
|
| 2126 |
+
'password': password,
|
| 2127 |
'first_name': first_name, 'last_name': last_name,
|
| 2128 |
'phone': phone,
|
| 2129 |
'country': country, 'city': city
|
|
|
|
| 2153 |
|
| 2154 |
try:
|
| 2155 |
product_index = int(product_index_str)
|
| 2156 |
+
products = sorted(data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 2157 |
if not (0 <= product_index < len(products)):
|
| 2158 |
raise IndexError("Product index out of range")
|
| 2159 |
product = products[product_index]
|
|
|
|
| 2171 |
commit_message=f"Delete photo {photo_filename} for product {product.get('name', 'N/A')}"
|
| 2172 |
)
|
| 2173 |
logging.info(f"Photo '{photo_filename}' deleted from HF.")
|
| 2174 |
+
|
| 2175 |
product['photos'].remove(photo_filename)
|
| 2176 |
data['products'] = products
|
| 2177 |
+
|
| 2178 |
products.sort(key=lambda p: p.get('name', '').lower())
|
| 2179 |
+
data['products'] = products
|
| 2180 |
save_data(data)
|
| 2181 |
flash(f"Фото '{photo_filename}' успешно удалено.", 'success')
|
| 2182 |
except Exception as e:
|
|
|
|
| 2202 |
logging.error(f"Error processing admin action '{action}': {e}", exc_info=True)
|
| 2203 |
flash(f"Произошла внутренняя ошибка при выполнении действия '{action}'. Подробности в логе сервера.", 'error')
|
| 2204 |
|
| 2205 |
+
|
| 2206 |
return redirect(url_for('admin'))
|
| 2207 |
|
| 2208 |
+
|
|
|
|
| 2209 |
current_data = load_data()
|
| 2210 |
display_products = sorted(current_data.get('products', []), key=lambda p: p.get('name', '').lower())
|
| 2211 |
display_categories = sorted(current_data.get('categories', []))
|
|
|
|
| 2237 |
try:
|
| 2238 |
if download_db_from_hf():
|
| 2239 |
flash("Данные успешно скачаны с Hugging Face. Локальные файлы обновлены.", 'success')
|
| 2240 |
+
load_data()
|
| 2241 |
+
load_users()
|
| 2242 |
else:
|
| 2243 |
flash("Не удалось скачать данные с Hugging Face после нескольких попыток. Проверьте логи.", 'error')
|
| 2244 |
except Exception as e:
|
|
|
|
| 2249 |
|
| 2250 |
if __name__ == '__main__':
|
| 2251 |
logging.info("Application starting up. Performing initial data load/download...")
|
| 2252 |
+
|
| 2253 |
download_db_from_hf()
|
| 2254 |
load_data()
|
| 2255 |
load_users()
|
|
|
|
| 2265 |
port = int(os.environ.get('PORT', 7860))
|
| 2266 |
logging.info(f"Starting Flask app on host 0.0.0.0 and port {port}")
|
| 2267 |
app.run(debug=False, host='0.0.0.0', port=port)
|
| 2268 |
+
|