flpolprojects commited on
Commit
38ac451
·
verified ·
1 Parent(s): 2a5cd5a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +302 -312
app.py CHANGED
@@ -12,15 +12,15 @@ from werkzeug.utils import secure_filename
12
  app = Flask(__name__)
13
  DATA_FILE = 'products.json'
14
 
15
- # --- Hugging Face Settings ---
16
  REPO_ID = "flpolprojects/Clients" # Замените на ваш репозиторий
17
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен для записи (загрузки)
18
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Токен для чтения (скачивания)
19
 
20
- # Logging Configuration
21
  logging.basicConfig(level=logging.DEBUG)
22
 
23
- # --- JSON Data Functions ---
24
  def load_data():
25
  """Загружает данные из JSON-файла или скачивает их из репозитория Hugging Face."""
26
  try:
@@ -50,7 +50,7 @@ def save_data(data):
50
  logging.error(f"Ошибка при сохранении данных: {e}")
51
  raise
52
 
53
- # --- Backup Functions ---
54
  def upload_db_to_hf():
55
  """Загружает JSON-файл базы данных в репозиторий Hugging Face."""
56
  try:
@@ -92,7 +92,7 @@ def periodic_backup():
92
  upload_db_to_hf()
93
  time.sleep(15)
94
 
95
- # --- Routes ---
96
  @app.route('/')
97
  def catalog():
98
  products = load_data()
@@ -102,108 +102,79 @@ def catalog():
102
  <head>
103
  <meta charset="UTF-8">
104
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
105
- <title>Каталог товаров</title>
106
  <style>
107
- :root {
108
- --gap: 15px;
109
- --card-radius: 12px;
110
- --shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
111
- }
112
-
113
  body {
114
- margin: 0;
115
- font-family: 'Segoe UI', system-ui;
116
- background: #f5f5f5;
117
- }
118
-
119
- .container {
120
- max-width: 1200px;
121
- margin: 0 auto;
122
- padding: 20px;
123
  }
124
-
125
  h1 {
 
126
  text-align: center;
127
- color: #2c3e50;
128
- margin: 40px 0;
129
- font-size: 2.8rem;
130
  }
131
-
132
  .product-grid {
133
  display: grid;
134
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); /* Three columns on mobile and larger */
135
- gap: var(--gap);
136
- padding: 0 15px;
137
  }
138
-
139
- .product-card {
140
- background: white;
141
- border-radius: var(--card-radius);
142
- overflow: hidden;
143
- box-shadow: var(--shadow);
144
- transition: transform 0.2s, box-shadow 0.2s;
145
  }
146
-
147
- .product-card:hover {
148
  transform: translateY(-5px);
149
- box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
150
- }
151
-
152
- .product-image {
153
- width: 100%;
154
- height: 220px;
155
- object-fit: cover;
156
- border-bottom: 1px solid #eee;
157
  }
158
-
159
- .product-content {
160
- padding: 20px;
161
  }
162
-
163
- .product-title {
164
- margin: 0 0 12px;
165
- color: #34495e;
166
- font-size: 1.4rem;
167
- line-height: 1.3;
168
  }
169
-
170
- .product-price {
171
- color: #e74c3c;
172
- font-weight: 700;
173
- font-size: 1.3rem;
174
- margin: 10px 0;
 
 
 
175
  }
176
 
177
- .product-description {
178
- color: #7f8c8d;
179
- font-size: 0.95rem;
180
- line-height: 1.5;
 
 
 
 
181
  }
182
  </style>
183
  </head>
184
  <body>
185
- <div class="container">
186
- <h1>Наши товары</h1>
187
- <div class="product-grid">
188
- {% for product in products %}
189
- <article class="product-card">
190
- {% if product.get('photo') %}
191
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}"
192
- alt="{{ product['name'] }}"
193
- class="product-image">
194
- {% endif %}
195
- <div class="product-content">
196
- <h2 class="product-title">{{ product['name'] }}</h2>
197
- <p class="product-price">{{ product['price'] }} ₽</p>
198
- <p class="product-description">{{ product['description'] }}</p>
199
- </div>
200
- </article>
201
- {% endfor %}
202
  </div>
 
203
  </div>
204
  </body>
205
  </html>
206
- '''
207
  return render_template_string(catalog_html, products=products, repo_id=REPO_ID)
208
 
209
  @app.route('/admin', methods=['GET', 'POST'])
@@ -212,235 +183,254 @@ def admin():
212
  if request.method == 'POST':
213
  action = request.form.get('action')
214
 
215
- if action == 'add':
216
- name = request.form.get('name')
217
- price = request.form.get('price')
218
- description = request.form.get('description')
219
- photo = request.files.get('photo')
220
-
221
- photo_filename = None
222
- if photo and photo.filename:
223
- photo_filename = secure_filename(photo.filename)
224
- uploads_dir = 'uploads'
225
- os.makedirs(uploads_dir, exist_ok=True)
226
- temp_path = os.path.join(uploads_dir, photo_filename)
227
- photo.save(temp_path)
228
-
229
- try:
230
- api = HfApi()
231
- api.upload_file(
232
- path_or_fileobj=temp_path,
233
- path_in_repo=f"photos/{photo_filename}",
234
- repo_id=REPO_ID,
235
- repo_type="dataset",
236
- token=HF_TOKEN_WRITE,
237
- commit_message=f"New photo for {name}"
238
- )
239
- except Exception as e:
240
- return f"Photo upload error: {e}", 500
241
- finally:
242
- os.remove(temp_path)
243
-
244
- try:
245
- price = float(price.replace(',', '.'))
246
- product_entry = {
247
- 'name': name,
248
- 'price': price,
249
- 'description': description,
250
- 'photo': photo_filename
251
- }
252
- products.append(product_entry)
253
- save_data(products)
254
- except ValueError:
255
- return "Invalid price format", 400
256
-
257
- return redirect(url_for('admin'))
258
-
259
- elif action == 'edit':
260
- index = int(request.form.get('index'))
261
- product = products[index]
262
-
263
- product['name'] = request.form.get('name')
264
- try:
265
- product['price'] = float(request.form.get('price').replace(',', '.'))
266
- except ValueError:
267
- return "Invalid price format", 400
268
- product['description'] = request.form.get('description')
269
-
270
- photo = request.files.get('photo')
271
- if photo and photo.filename:
272
- photo_filename = secure_filename(photo.filename)
273
- uploads_dir = 'uploads'
274
- os.makedirs(uploads_dir, exist_ok=True)
275
- temp_path = os.path.join(uploads_dir, photo_filename)
276
- photo.save(temp_path)
277
-
278
- try:
279
- api = HfApi()
280
- api.upload_file(
281
- path_or_fileobj=temp_path,
282
- path_in_repo=f"photos/{photo_filename}",
283
- repo_id=REPO_ID,
284
- repo_type="dataset",
285
- token=HF_TOKEN_WRITE,
286
- commit_message=f"Updated photo for {product['name']}"
287
- )
288
- product['photo'] = photo_filename
289
- except Exception as e:
290
- return f"Photo upload error: {e}", 500
291
- finally:
292
- os.remove(temp_path)
293
-
294
- save_data(products)
295
- return redirect(url_for('admin'))
296
-
297
- elif action == 'delete':
298
- index = int(request.form.get('index'))
299
- del products[index]
300
- save_data(products)
301
- return redirect(url_for('admin'))
302
-
303
- admin_html = '''
304
- <!DOCTYPE html>
305
- <html lang="ru">
306
- <head>
307
- <meta charset="UTF-8">
308
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
309
- <title>Админ-панель</title>
310
- <style>
311
- body {
312
- font-family: Arial, sans-serif;
313
- margin: 20px;
314
- background-color: #f9f9f9;
315
- }
316
- h1 {
317
- color: #333;
318
- }
319
- form {
320
- background-color: #fff;
321
- padding: 20px;
322
- border: 1px solid #ddd;
323
- border-radius: 5px;
324
- max-width: 400px;
325
- margin-bottom: 20px;
326
- }
327
- label {
328
- display: block;
329
- margin-top: 10px;
330
- color: #555;
331
- }
332
- input, textarea {
333
- width: 100%;
334
- padding: 8px;
335
- margin-top: 5px;
336
- border: 1px solid #ddd;
337
- border-radius: 4px;
338
- }
339
- button {
340
- margin-top: 15px;
341
- padding: 10px 15px;
342
- background-color: #28a745;
343
- color: white;
344
- border: none;
345
- border-radius: 4px;
346
- cursor: pointer;
347
- }
348
- button:hover {
349
- background-color: #218838;
350
- }
351
- .product-list {
352
- margin-top: 20px;
353
- }
354
- .product-item {
355
- background-color: #fff;
356
- border: 1px solid #ddd;
357
- padding: 15px;
358
- margin-bottom: 10px;
359
- border-radius: 5px;
360
- }
361
- .edit-form {
362
- margin-top: 10px;
363
- padding: 10px;
364
- border: 1px solid #ddd;
365
- border-radius: 5px;
366
- background-color: #f9f9f9;
367
- }
368
- </style>
369
- </head>
370
- <body>
371
- <h1>Добавление товара</h1>
372
- <form method="POST" enctype="multipart/form-data">
373
- <input type="hidden" name="action" value="add">
374
- <label for="name">Название товара:</label>
375
- <input type="text" id="name" name="name" required>
376
-
377
- <label for="price">Цена:</label>
378
- <input type="number" id="price" name="price" step="0.01" required>
379
-
380
- <label for="description">Описание:</label>
381
- <textarea id="description" name="description" rows="4" required></textarea>
382
-
383
- <label for="photo">Фотография товара (необязательно):</label>
384
- <input type="file" id="photo" name="photo" accept="image/*">
385
-
386
- <button type="submit">Добавить товар</button>
387
- </form>
388
-
389
- <h2>Управление базой данных</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
 
391
- <!-- Кнопки для резервной копии и скачивания -->
392
- <form method="POST" action="{{ url_for('backup') }}">
393
- <button type="submit">Создать резервную копию</button>
394
- </form>
395
-
396
- <form method="GET" action="{{ url_for('download') }}">
397
- <button type="submit">Скачать базу данных</button>
398
- </form>
399
-
400
- <h2>Список товаров</h2>
401
- <div class="product-list">
402
- {% for product in products %}
403
- <div class="product-item">
404
- <h3>{{ product['name'] }}</h3>
405
- <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
406
- <p><strong>Описание:</strong> {{ product['description'] }}</p>
407
- {% if product.get('photo') %}
408
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}" style="max-width: 100px;">
409
- {% endif %}
410
-
411
- <details>
412
- <summary>Редактировать</summary>
413
- <form method="POST" enctype="multipart/form-data" class="edit-form">
414
- <input type="hidden" name="action" value="edit">
415
- <input type="hidden" name="index" value="{{ loop.index0 }}">
416
- <label for="name">Название товара:</label>
417
- <input type="text" id="name" name="name" value="{{ product['name'] }}" required>
418
-
419
- <label for="price">Цена:</label>
420
- <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
421
-
422
- <label for="description">Описание:</label>
423
- <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
424
-
425
- <label for="photo">Фотография товара (необязательно):</label>
426
- <input type="file" id="photo" name="photo" accept="image/*">
427
-
428
- <button type="submit">Сохранить изменения</button>
429
- </form>
430
- </details>
431
-
432
- <form method="POST">
433
- <input type="hidden" name="action" value="delete">
434
- <input type="hidden" name="index" value="{{ loop.index0 }}">
435
- <button type="submit">Удалить</button>
436
- </form>
437
- </div>
438
- {% endfor %}
439
- </div>
440
-
441
- </body>
442
- </html>
443
- '''
444
  return render_template_string(admin_html, products=products, repo_id=REPO_ID)
445
 
446
  @app.route('/backup', methods=['POST'])
@@ -466,6 +456,6 @@ if __name__ == '__main__':
466
  except Exception as e:
467
  logging.error(f"Не удалось загрузить базу данных при запуске: {e}")
468
  # Здесь можно добавить логику для создания пустой базы данных, если это необходимо
469
- save_data([])
470
 
471
  app.run(debug=True, host='0.0.0.0', port=7860)
 
12
  app = Flask(__name__)
13
  DATA_FILE = 'products.json'
14
 
15
+ # --- Настройки Hugging Face ---
16
  REPO_ID = "flpolprojects/Clients" # Замените на ваш репозиторий
17
  HF_TOKEN_WRITE = os.getenv("HF_TOKEN") # Токен для записи (загрузки)
18
  HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Токен для чтения (скачивания)
19
 
20
+ # Настройка логирования
21
  logging.basicConfig(level=logging.DEBUG)
22
 
23
+ # --- Функции для работы с JSON ---
24
  def load_data():
25
  """Загружает данные из JSON-файла или скачивает их из репозитория Hugging Face."""
26
  try:
 
50
  logging.error(f"Ошибка при сохранении данных: {e}")
51
  raise
52
 
53
+ # --- Функции для резервного копирования ---
54
  def upload_db_to_hf():
55
  """Загружает JSON-файл базы данных в репозиторий Hugging Face."""
56
  try:
 
92
  upload_db_to_hf()
93
  time.sleep(15)
94
 
95
+ # --- Маршруты приложения ---
96
  @app.route('/')
97
  def catalog():
98
  products = load_data()
 
102
  <head>
103
  <meta charset="UTF-8">
104
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
105
+ <title>Каталог</title>
106
  <style>
 
 
 
 
 
 
107
  body {
108
+ font-family: Arial, sans-serif;
109
+ margin: 20px;
110
+ background-color: #f9f9f9;
 
 
 
 
 
 
111
  }
 
112
  h1 {
113
+ color: #333;
114
  text-align: center;
 
 
 
115
  }
 
116
  .product-grid {
117
  display: grid;
118
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); /* Автоматическое заполнение, минимум 300px на элемент */
119
+ gap: 20px;
 
120
  }
121
+ .product {
122
+ background-color: #fff;
123
+ border: 1px solid #ddd;
124
+ padding: 15px;
125
+ border-radius: 5px;
126
+ transition: transform 0.2s;
 
127
  }
128
+ .product:hover {
 
129
  transform: translateY(-5px);
130
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
131
  }
132
+ .product h2 {
133
+ margin-top: 0;
134
+ color: #555;
135
  }
136
+ .product p {
137
+ color: #777;
 
 
 
 
138
  }
139
+ .product img {
140
+ max-width: 100%; /* Адаптивное изображение */
141
+ height: auto;
142
+ margin-top: 10px;
143
+ border: 1px solid #ccc;
144
+ border-radius: 4px;
145
+ display: block; /* Убирает отступ снизу у встроенных элементов */
146
+ margin-left: auto;
147
+ margin-right: auto;
148
  }
149
 
150
+ /* Медиа-запрос для мобильных устройств */
151
+ @media (max-width: 768px) {
152
+ .product-grid {
153
+ grid-template-columns: 1fr; /* Одна колонка на мобильных */
154
+ }
155
+ .product {
156
+ padding: 10px;
157
+ }
158
  }
159
  </style>
160
  </head>
161
  <body>
162
+ <h1>Каталог товаров</h1>
163
+ <div class="product-grid">
164
+ {% for product in products %}
165
+ <div class="product">
166
+ <h2>{{ product['name'] }}</h2>
167
+ <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
168
+ <p><strong>Описание:</strong> {{ product['description'] }}</p>
169
+ {% if product.get('photo') %}
170
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}">
171
+ {% endif %}
 
 
 
 
 
 
 
172
  </div>
173
+ {% endfor %}
174
  </div>
175
  </body>
176
  </html>
177
+ '''
178
  return render_template_string(catalog_html, products=products, repo_id=REPO_ID)
179
 
180
  @app.route('/admin', methods=['GET', 'POST'])
 
183
  if request.method == 'POST':
184
  action = request.form.get('action')
185
 
186
+ if action == 'add':
187
+ name = request.form.get('name')
188
+ price = request.form.get('price')
189
+ description = request.form.get('description')
190
+ photo = request.files.get('photo')
191
+
192
+ logging.debug(f"Полученные данные из формы: name={name}, price={price}, description={description}")
193
+
194
+ photo_filename = None
195
+ if photo and photo.filename:
196
+ photo_filename = secure_filename(photo.filename)
197
+ # Сохраняем файл временно в папку uploads
198
+ uploads_dir = 'uploads'
199
+ os.makedirs(uploads_dir, exist_ok=True)
200
+ temp_path = os.path.join(uploads_dir, photo_filename)
201
+ photo.save(temp_path)
202
+
203
+ # Загружаем фото в репозиторий в папку "photos"
204
+ try:
205
+ api = HfApi()
206
+ api.upload_file(
207
+ path_or_fileobj=temp_path,
208
+ path_in_repo=f"photos/{photo_filename}",
209
+ repo_id=REPO_ID,
210
+ repo_type="dataset",
211
+ token=HF_TOKEN_WRITE,
212
+ commit_message=f"Добавлено фото для товара {name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
213
+ )
214
+ logging.info("Фото успешно загружено в репозиторий.")
215
+ except Exception as e:
216
+ logging.error(f"Ошибка при загрузке фото: {e}")
217
+ return f"Ошибка при загрузке фото: {e}", 500
218
+ finally:
219
+ # Удаляем временный файл
220
+ os.remove(temp_path)
221
+
222
+ if name and price and description:
223
+ try:
224
+ price = float(price.replace(',', '.'))
225
+ except ValueError:
226
+ logging.error("Ошибка: Цена должна быть числом.")
227
+ return "Ошибка: Цена должна быть числом.", 400
228
+
229
+ product_entry = {
230
+ 'name': name,
231
+ 'price': price,
232
+ 'description': description
233
+ }
234
+ if photo_filename:
235
+ product_entry['photo'] = photo_filename
236
+
237
+ products.append(product_entry)
238
+ save_data(products)
239
+ return redirect(url_for('admin'))
240
+
241
+ elif action == 'edit':
242
+ index = int(request.form.get('index'))
243
+ name = request.form.get('name')
244
+ price = request.form.get('price')
245
+ description = request.form.get('description')
246
+ photo = request.files.get('photo')
247
+
248
+ # Логика обновления фотографии
249
+ if photo:
250
+ photo_filename = secure_filename(photo.filename)
251
+ uploads_dir = 'uploads'
252
+ os.makedirs(uploads_dir, exist_ok=True)
253
+ temp_path = os.path.join(uploads_dir, photo_filename)
254
+ photo.save(temp_path)
255
+
256
+ try:
257
+ api = HfApi()
258
+ api.upload_file(
259
+ path_or_fileobj=temp_path,
260
+ path_in_repo=f"photos/{photo_filename}",
261
+ repo_id=REPO_ID,
262
+ repo_type="dataset",
263
+ token=HF_TOKEN_WRITE,
264
+ commit_message=f"Обновлено фото для товара {name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
265
+ )
266
+ logging.info("Фото успешно обновлено в репозитории.")
267
+ products[index]['photo'] = photo_filename # Обновляем имя файла
268
+ except Exception as e:
269
+ logging.error(f"Ошибка при загрузке фото: {e}")
270
+ return f"Ошибка при загрузке фото: {e}", 500
271
+ finally:
272
+ os.remove(temp_path)
273
+
274
+ # Обновление остальных полей
275
+ products[index]['name'] = name
276
+ try:
277
+ price = float(price.replace(',', '.'))
278
+ except ValueError:
279
+ logging.error("Ошибка: Цена должна быть числом.")
280
+ return "Ошибка: Цена должна быть числом.", 400
281
+ products[index]['price'] = price
282
+ products[index]['description'] = description
283
+
284
+ save_data(products)
285
+ return redirect(url_for('admin'))
286
+
287
+ elif action == 'delete':
288
+ index = int(request.form.get('index'))
289
+ del products[index]
290
+ save_data(products)
291
+ return redirect(url_for('admin'))
292
+
293
+ admin_html = '''
294
+ <!DOCTYPE html>
295
+ <html lang="ru">
296
+ <head>
297
+ <meta charset="UTF-8">
298
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
299
+ <title>Админ-панель</title>
300
+ <style>
301
+ body {
302
+ font-family: Arial, sans-serif;
303
+ margin: 20px;
304
+ background-color: #f9f9f9;
305
+ }
306
+ h1 {
307
+ color: #333;
308
+ }
309
+ form {
310
+ background-color: #fff;
311
+ padding: 20px;
312
+ border: 1px solid #ddd;
313
+ border-radius: 5px;
314
+ max-width: 400px;
315
+ margin-bottom: 20px;
316
+ }
317
+ label {
318
+ display: block;
319
+ margin-top: 10px;
320
+ color: #555;
321
+ }
322
+ input, textarea {
323
+ width: 100%;
324
+ padding: 8px;
325
+ margin-top: 5px;
326
+ border: 1px solid #ddd;
327
+ border-radius: 4px;
328
+ }
329
+ button {
330
+ margin-top: 15px;
331
+ padding: 10px 15px;
332
+ background-color: #28a745;
333
+ color: white;
334
+ border: none;
335
+ border-radius: 4px;
336
+ cursor: pointer;
337
+ }
338
+ button:hover {
339
+ background-color: #218838;
340
+ }
341
+ .product-list {
342
+ margin-top: 20px;
343
+ }
344
+ .product-item {
345
+ background-color: #fff;
346
+ border: 1px solid #ddd;
347
+ padding: 15px;
348
+ margin-bottom: 10px;
349
+ border-radius: 5px;
350
+ }
351
+ .edit-form {
352
+ margin-top: 10px;
353
+ padding: 10px;
354
+ border: 1px solid #ddd;
355
+ border-radius: 5px;
356
+ background-color: #f9f9f9;
357
+ }
358
+ </style>
359
+ </head>
360
+ <body>
361
+ <h1>Добавление товара</h1>
362
+ <form method="POST" enctype="multipart/form-data">
363
+ <input type="hidden" name="action" value="add">
364
+ <label for="name">Название товара:</label>
365
+ <input type="text" id="name" name="name" required>
366
+
367
+ <label for="price">Цена:</label>
368
+ <input type="number" id="price" name="price" step="0.01" required>
369
+
370
+ <label for="description">Описание:</label>
371
+ <textarea id="description" name="description" rows="4" required></textarea>
372
+
373
+ <label for="photo">Фотография товара (необязательно):</label>
374
+ <input type="file" id="photo" name="photo" accept="image/*">
375
+
376
+ <button type="submit">Добавить товар</button>
377
+ </form>
378
+
379
+ <h2>Управление базой данных</h2>
380
 
381
+ <!-- Кнопки для резервной копии и скачивания -->
382
+ <form method="POST" action="{{ url_for('backup') }}">
383
+ <button type="submit">Создать резервную копию</button>
384
+ </form>
385
+
386
+ <form method="GET" action="{{ url_for('download') }}">
387
+ <button type="submit">Скачать базу данных</button>
388
+ </form>
389
+
390
+ <h2>Список товаров</h2>
391
+ <div class="product-list">
392
+ {% for product in products %}
393
+ <div class="product-item">
394
+ <h3>{{ product['name'] }}</h3>
395
+ <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
396
+ <p><strong>Описание:</strong> {{ product['description'] }}</p>
397
+ {% if product.get('photo') %}
398
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}" style="max-width: 100px;">
399
+ {% endif %}
400
+
401
+ <details>
402
+ <summary>Редактировать</summary>
403
+ <form method="POST" enctype="multipart/form-data" class="edit-form">
404
+ <input type="hidden" name="action" value="edit">
405
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
406
+ <label for="name">Название товара:</label>
407
+ <input type="text" id="name" name="name" value="{{ product['name'] }}" required>
408
+
409
+ <label for="price">Цена:</label>
410
+ <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
411
+
412
+ <label for="description">Описание:</label>
413
+ <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
414
+
415
+ <label for="photo">Фотография товара (необязательно):</label>
416
+ <input type="file" id="photo" name="photo" accept="image/*">
417
+
418
+ <button type="submit">Сохранить изменения</button>
419
+ </form>
420
+ </details>
421
+
422
+ <form method="POST">
423
+ <input type="hidden" name="action" value="delete">
424
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
425
+ <button type="submit">Удалить</button>
426
+ </form>
427
+ </div>
428
+ {% endfor %}
429
+ </div>
430
+
431
+ </body>
432
+ </html>
433
+ '''
434
  return render_template_string(admin_html, products=products, repo_id=REPO_ID)
435
 
436
  @app.route('/backup', methods=['POST'])
 
456
  except Exception as e:
457
  logging.error(f"Не удалось загрузить базу данных при запуске: {e}")
458
  # Здесь можно добавить логику для создания пустой базы данных, если это необходимо
459
+ # Например: save_data([])
460
 
461
  app.run(debug=True, host='0.0.0.0', port=7860)