Shveiauto commited on
Commit
b8b80ed
·
verified ·
1 Parent(s): f08e1b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -38
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
- <p>Товаров пока нет.</p>
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
- # Make a copy to avoid modifying the session object directly if needed later
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
- # Clean up empty strings/None values if desired, but keeping them might be useful
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 # Pass request to access request.url in template
1737
  )
1738
 
1739
  @app.route('/admin', methods=['GET', 'POST'])
1740
  def admin():
1741
  data = load_data()
1742
- # Sort products by name for consistent display and indexing in admin
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())) # Load and sort users
1746
 
1747
 
1748
  if request.method == 'POST':
1749
  action = request.form.get('action')
1750
  logging.info(f"Admin action received: {action}")
1751
 
1752
- # Reload data and users immediately after POST to ensure actions operate on the latest state
1753
  data = load_data()
1754
- products = data.get('products', []) # Reload products
1755
- categories = data.get('categories', []) # Reload categories
1756
- users = load_users() # Reload 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() # Keep categories sorted
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('_') # Limit name length and trailing underscore
1835
- # Ensure filename is unique and avoids collisions
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
- # Re-sort products after adding
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()) # Ensure we are editing based on the *sorted* list
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
- # Reload products list based on current data for the template render after error
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], # Ensure valid paths
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
- # The product_to_edit object is already a reference to the list element, so modifying it updates the list directly.
2002
- # We just need to ensure the overall list in 'data' is the one we save.
2003
  data['products'] = products
2004
- # Re-sort products after edit, as name might have changed affecting sort order
2005
  products.sort(key=lambda p: p.get('name', '').lower())
2006
- data['products'] = products # Update data dict with sorted list
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()) # Ensure we are deleting based on the *sorted* list
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], # Ensure valid paths
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() # Storing plain text password
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, # Store plain text 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()) # Ensure we are deleting based on the *sorted* list
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
- # Remove photo from product data only after successful HF deletion
2119
  product['photos'].remove(photo_filename)
2120
  data['products'] = products
2121
- # Re-sort products after modifying (though photo deletion shouldn't change sort order)
2122
  products.sort(key=lambda p: p.get('name', '').lower())
2123
- data['products'] = products # Update data dict with sorted list
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
- # Redirect after any POST action to prevent resubmission on refresh
2150
  return redirect(url_for('admin'))
2151
 
2152
- # --- GET request or after POST redirect ---
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() # Reload data in memory after download
2186
- load_users() # Reload users in memory after download
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
- # Attempt to download first, then load
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
+