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

Update app.py

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