flpolprojects commited on
Commit
2551934
·
verified ·
1 Parent(s): c9097d0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -264
app.py CHANGED
@@ -12,47 +12,43 @@ 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:
27
- # Пытаемся скачать базу данных из Hugging Face
28
  download_db_from_hf()
29
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
30
  return json.load(file)
31
  except FileNotFoundError:
32
- logging.warning("Локальный файл базы данных не найден после скачивания.")
33
  return []
34
  except json.JSONDecodeError:
35
- logging.error("Ошибка: Невозможно декодировать JSON файл.")
36
  return []
37
  except RepositoryNotFoundError:
38
- logging.error("Репозиторий не найден. Создание локальной базы данных.")
39
- return [] # Возвращаем пустой список, чтобы начать с пустой базы
40
  except Exception as e:
41
- logging.error(f"Произошла ошибка при загрузке данных: {e}")
42
  return []
43
 
44
  def save_data(data):
45
- """Сохраняет данные в JSON-файл."""
46
  try:
47
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
48
  json.dump(data, file, ensure_ascii=False, indent=4)
49
  except Exception as e:
50
- logging.error(f"Ошибка при сохранении данных: {e}")
51
  raise
52
 
53
- # --- Функции для резервного копирования ---
54
  def upload_db_to_hf():
55
- """Загружает JSON-файл базы данных в репозиторий Hugging Face."""
56
  try:
57
  api = HfApi()
58
  api.upload_file(
@@ -61,14 +57,13 @@ def upload_db_to_hf():
61
  repo_id=REPO_ID,
62
  repo_type="dataset",
63
  token=HF_TOKEN_WRITE,
64
- commit_message=f"Автоматическое резервное копирование базы данных {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
65
  )
66
- logging.info("Резервная копия JSON базы успешно загружена на Hugging Face.")
67
  except Exception as e:
68
- logging.error(f"Ошибка при загрузке резервной копии: {e}")
69
 
70
  def download_db_from_hf():
71
- """Скачивает JSON-файл базы данных из репозитория Hugging Face."""
72
  try:
73
  hf_hub_download(
74
  repo_id=REPO_ID,
@@ -78,112 +73,112 @@ def download_db_from_hf():
78
  local_dir=".",
79
  local_dir_use_symlinks=False
80
  )
81
- logging.info("JSON база успешно скачана из Hugging Face.")
82
  except RepositoryNotFoundError as e:
83
- logging.error(f"Репозиторий не найден: {e}")
84
- raise # Прокидываем исключение выше, чтобы load_data обработал его
85
  except Exception as e:
86
- logging.error(f"Ошибка при скачивании JSON базы: {e}")
87
  raise
88
 
89
  def periodic_backup():
90
- """Периодически вызывает функцию upload_db_to_hf() каждые 15 секунд."""
91
  while True:
92
  upload_db_to_hf()
93
  time.sleep(15)
94
 
95
- # --- Маршруты приложения ---
96
  @app.route('/')
97
  def catalog():
98
  products = load_data()
99
  catalog_html = '''
100
- <!DOCTYPE html>
101
- <html lang="ru">
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: 0;
110
- padding: 0;
111
- background-color: #f9f9f9;
112
- }
113
- header {
114
- background-color: #4CAF50;
115
- color: white;
116
- padding: 15px;
117
- text-align: center;
118
- }
119
- .container {
120
- padding: 20px;
121
- }
122
- .catalog-container {
123
- display: grid;
124
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
125
- grid-gap: 20px;
126
- }
127
- .product-card {
128
- background-color: #fff;
129
- border: 1px solid #ddd;
130
- padding: 15px;
131
- border-radius: 8px;
132
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
133
- transition: transform 0.2s, box-shadow 0.2s;
134
- }
135
- .product-card:hover {
136
- transform: scale(1.02);
137
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
138
- }
139
- .product-card h2 {
140
- margin-top: 0;
141
- font-size: 1.2em;
142
- color: #333;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  }
144
- .product-card p {
145
- font-size: 0.95em;
146
- color: #555;
147
- }
148
- .product-card img {
149
- width: 100%;
150
- height: auto;
151
- margin-top: 10px;
152
- border-radius: 5px;
153
- }
154
- @media (max-width: 600px) {
155
- .catalog-container {
156
- grid-template-columns: 1fr;
157
- }
158
- }
159
- </style>
160
- </head>
161
- <body>
162
- <header>
163
- <h1>Каталог товаров</h1>
164
- </header>
165
- <div class="container">
166
- <div class="catalog-container">
167
- {% for product in products %}
168
- <div class="product-card">
169
- <h2>{{ product['name'] }}</h2>
170
- <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
171
- <p><strong>Описание:</strong> {{ product['description'] }}</p>
172
- {% if product.get('photo') %}
173
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}">
174
- {% endif %}
175
- </div>
176
- {% endfor %}
177
- </div>
178
  </div>
179
- </body>
180
- </html>
181
- '''
 
 
182
  return render_template_string(catalog_html, products=products, repo_id=REPO_ID)
183
 
184
  @app.route('/admin', methods=['GET', 'POST'])
185
  def admin():
186
  products = load_data()
 
187
  if request.method == 'POST':
188
  action = request.form.get('action')
189
 
@@ -193,18 +188,14 @@ def admin():
193
  description = request.form.get('description')
194
  photo = request.files.get('photo')
195
 
196
- logging.debug(f"Полученные данные из формы: name={name}, price={price}, description={description}")
197
-
198
  photo_filename = None
199
  if photo and photo.filename:
200
  photo_filename = secure_filename(photo.filename)
201
- # Сохраняем файл временно в папку uploads
202
  uploads_dir = 'uploads'
203
  os.makedirs(uploads_dir, exist_ok=True)
204
  temp_path = os.path.join(uploads_dir, photo_filename)
205
  photo.save(temp_path)
206
 
207
- # Загружаем фото в репозиторий в папку "photos"
208
  try:
209
  api = HfApi()
210
  api.upload_file(
@@ -220,7 +211,6 @@ def admin():
220
  logging.error(f"Ошибка при загрузке фото: {e}")
221
  return f"Ошибка при загрузке фото: {e}", 500
222
  finally:
223
- # Удаляем временный файл
224
  os.remove(temp_path)
225
 
226
  if name and price and description:
@@ -230,11 +220,7 @@ def admin():
230
  logging.error("Ошибка: Цена должна быть числом.")
231
  return "Ошибка: Цена должна быть числом.", 400
232
 
233
- product_entry = {
234
- 'name': name,
235
- 'price': price,
236
- 'description': description
237
- }
238
  if photo_filename:
239
  product_entry['photo'] = photo_filename
240
 
@@ -249,7 +235,6 @@ def admin():
249
  description = request.form.get('description')
250
  photo = request.files.get('photo')
251
 
252
- # Логика обновления фотографии
253
  if photo:
254
  photo_filename = secure_filename(photo.filename)
255
  uploads_dir = 'uploads'
@@ -268,20 +253,20 @@ def admin():
268
  commit_message=f"Обновлено фото для товара {name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
269
  )
270
  logging.info("Фото успешно обновлено в репозитории.")
271
- products[index]['photo'] = photo_filename # Обновляем имя файла
272
  except Exception as e:
273
  logging.error(f"Ошибка при загрузке фото: {e}")
274
  return f"Ошибка при загрузке фото: {e}", 500
275
  finally:
276
  os.remove(temp_path)
277
-
278
- # Обновление остальных полей
279
  products[index]['name'] = name
280
  try:
281
  price = float(price.replace(',', '.'))
282
  except ValueError:
283
  logging.error("Ошибка: Цена должна быть числом.")
284
  return "Ошибка: Цена должна быть числом.", 400
 
285
  products[index]['price'] = price
286
  products[index]['description'] = description
287
 
@@ -295,171 +280,177 @@ def admin():
295
  return redirect(url_for('admin'))
296
 
297
  admin_html = '''
298
- <!DOCTYPE html>
299
- <html lang="ru">
300
- <head>
301
- <meta charset="UTF-8">
302
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
303
- <title>Админ-панель</title>
304
- <style>
305
- body {
306
- font-family: Arial, sans-serif;
307
- margin: 20px;
308
- background-color: #f9f9f9;
309
- }
310
- h1 {
311
- color: #333;
312
- }
313
- form {
314
- background-color: #fff;
315
- padding: 20px;
316
- border: 1px solid #ddd;
317
- border-radius: 5px;
318
- max-width: 400px;
319
- margin-bottom: 20px;
320
- }
321
- label {
322
- display: block;
323
- margin-top: 10px;
324
- color: #555;
325
- }
326
- input, textarea {
327
- width: 100%;
328
- padding: 8px;
329
- margin-top: 5px;
330
- border: 1px solid #ddd;
331
- border-radius: 4px;
332
- }
333
- button {
334
- margin-top: 15px;
335
- padding: 10px 15px;
336
- background-color: #28a745;
337
- color: white;
338
- border: none;
339
- border-radius: 4px;
340
- cursor: pointer;
341
- }
342
- button:hover {
343
- background-color: #218838;
344
- }
345
- .product-list {
346
- margin-top: 20px;
347
- }
348
- .product-item {
349
- background-color: #fff;
350
- border: 1px solid #ddd;
351
- padding: 15px;
352
- margin-bottom: 10px;
353
- border-radius: 5px;
354
- }
355
- .edit-form {
356
- margin-top: 10px;
357
- padding: 10px;
358
- border: 1px solid #ddd;
359
- border-radius: 5px;
360
- background-color: #f9f9f9;
361
- }
362
- </style>
363
- </head>
364
- <body>
365
- <h1>Добавление товара</h1>
366
- <form method="POST" enctype="multipart/form-data">
367
- <input type="hidden" name="action" value="add">
368
- <label for="name">Название товара:</label>
369
- <input type="text" id="name" name="name" required>
370
-
371
- <label for="price">Цена:</label>
372
- <input type="number" id="price" name="price" step="0.01" required>
373
-
374
- <label for="description">Описание:</label>
375
- <textarea id="description" name="description" rows="4" required></textarea>
376
-
377
- <label for="photo">Фотография товара (необязательно):</label>
378
- <input type="file" id="photo" name="photo" accept="image/*">
379
-
380
- <button type="submit">Добавить товар</button>
381
- </form>
382
-
383
- <h2>Управление базой данных</h2>
384
-
385
- <!-- Кнопки для резервной копии и скачивания -->
386
- <form method="POST" action="{{ url_for('backup') }}">
387
- <button type="submit">Создать резервную копию</button>
388
- </form>
389
-
390
- <form method="GET" action="{{ url_for('download') }}">
391
- <button type="submit">Скачать базу данных</button>
392
- </form>
393
-
394
- <h2>Список товаров</h2>
395
- <div class="product-list">
396
- {% for product in products %}
397
- <div class="product-item">
398
- <h3>{{ product['name'] }}</h3>
399
- <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
400
- <p><strong>Описание:</strong> {{ product['description'] }}</p>
401
- {% if product.get('photo') %}
402
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}" style="max-width: 100px;">
403
- {% endif %}
404
-
405
- <details>
406
- <summary>Редактировать</summary>
407
- <form method="POST" enctype="multipart/form-data" class="edit-form">
408
- <input type="hidden" name="action" value="edit">
409
- <input type="hidden" name="index" value="{{ loop.index0 }}">
410
- <label for="name">Название товара:</label>
411
- <input type="text" id="name" name="name" value="{{ product['name'] }}" required>
412
-
413
- <label for="price">Цена:</label>
414
- <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
415
-
416
- <label for="description">Описание:</label>
417
- <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
418
-
419
- <label for="photo">Фотография товара (необязательно):</label>
420
- <input type="file" id="photo" name="photo" accept="image/*">
421
-
422
- <button type="submit">Сохранить изменения</button>
423
- </form>
424
- </details>
425
-
426
- <form method="POST">
427
- <input type="hidden" name="action" value="delete">
428
  <input type="hidden" name="index" value="{{ loop.index0 }}">
429
- <button type="submit">Удалить</button>
 
 
 
 
 
 
 
 
 
 
 
 
430
  </form>
431
- </div>
432
- {% endfor %}
 
 
 
 
 
433
  </div>
 
 
 
 
 
 
 
 
434
 
435
- </body>
436
- </html>
437
- '''
 
 
 
438
  return render_template_string(admin_html, products=products, repo_id=REPO_ID)
439
 
440
  @app.route('/backup', methods=['POST'])
441
  def backup():
442
- """Маршрут для создания резервной копии."""
443
  upload_db_to_hf()
444
  return "Резервная копия успешно создана.", 200
445
 
446
  @app.route('/download', methods=['GET'])
447
  def download():
448
- """Маршрут для скачивания базы данных."""
449
  download_db_from_hf()
450
  return "База данных успешно скачана.", 200
451
 
452
  if __name__ == '__main__':
453
- # Запуск фонового потока для периодического резервного копирования
454
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
455
  backup_thread.start()
456
 
457
- # Попытка загрузить базу данных из репозитория перед запуском приложения
458
  try:
459
  load_data()
460
  except Exception as e:
461
  logging.error(f"Не удалось загрузить базу данных при запуске: {e}")
462
- # Здесь можно добавить логику для создания пустой базы данных, если это необходимо
463
- # Например: save_data([])
464
 
465
- 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 setup
21
  logging.basicConfig(level=logging.DEBUG)
22
 
23
+ # Functions for JSON data handling
24
  def load_data():
 
25
  try:
 
26
  download_db_from_hf()
27
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
28
  return json.load(file)
29
  except FileNotFoundError:
30
+ logging.warning("Database file not found after download.")
31
  return []
32
  except json.JSONDecodeError:
33
+ logging.error("Error decoding JSON file.")
34
  return []
35
  except RepositoryNotFoundError:
36
+ logging.error("Repository not found. Creating local database.")
37
+ return []
38
  except Exception as e:
39
+ logging.error(f"Error loading data: {e}")
40
  return []
41
 
42
  def save_data(data):
 
43
  try:
44
  with open(DATA_FILE, 'w', encoding='utf-8') as file:
45
  json.dump(data, file, ensure_ascii=False, indent=4)
46
  except Exception as e:
47
+ logging.error(f"Error saving data: {e}")
48
  raise
49
 
50
+ # Functions for backup
51
  def upload_db_to_hf():
 
52
  try:
53
  api = HfApi()
54
  api.upload_file(
 
57
  repo_id=REPO_ID,
58
  repo_type="dataset",
59
  token=HF_TOKEN_WRITE,
60
+ commit_message=f"Automatic backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
61
  )
62
+ logging.info("Backup successfully uploaded to Hugging Face.")
63
  except Exception as e:
64
+ logging.error(f"Error uploading backup: {e}")
65
 
66
  def download_db_from_hf():
 
67
  try:
68
  hf_hub_download(
69
  repo_id=REPO_ID,
 
73
  local_dir=".",
74
  local_dir_use_symlinks=False
75
  )
76
+ logging.info("Database successfully downloaded from Hugging Face.")
77
  except RepositoryNotFoundError as e:
78
+ logging.error(f"Repository not found: {e}")
79
+ raise
80
  except Exception as e:
81
+ logging.error(f"Error downloading database: {e}")
82
  raise
83
 
84
  def periodic_backup():
 
85
  while True:
86
  upload_db_to_hf()
87
  time.sleep(15)
88
 
89
+ # Application routes
90
  @app.route('/')
91
  def catalog():
92
  products = load_data()
93
  catalog_html = '''
94
+ <!DOCTYPE html>
95
+ <html lang="ru">
96
+ <head>
97
+ <meta charset="UTF-8">
98
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
99
+ <title>Каталог</title>
100
+ <style>
101
+ body {
102
+ font-family: Arial, sans-serif;
103
+ margin: 20px;
104
+ background-color: #f9f9f9;
105
+ color: #333;
106
+ }
107
+
108
+ h1 {
109
+ color: #333;
110
+ text-align: center;
111
+ margin-bottom: 20px;
112
+ }
113
+
114
+ .catalog {
115
+ display: grid;
116
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
117
+ gap: 20px;
118
+ padding: 20px;
119
+ }
120
+
121
+ .product {
122
+ background-color: #fff;
123
+ border: 1px solid #ddd;
124
+ padding: 15px;
125
+ border-radius: 8px;
126
+ transition: transform 0.2s ease-in-out;
127
+ }
128
+
129
+ .product:hover {
130
+ transform: scale(1.05);
131
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
132
+ }
133
+
134
+ .product h2 {
135
+ margin-top: 0;
136
+ color: #555;
137
+ font-size: 1.2em;
138
+ }
139
+
140
+ .product p {
141
+ color: #777;
142
+ font-size: 0.9em;
143
+ }
144
+
145
+ .product img {
146
+ max-width: 100%;
147
+ height: auto;
148
+ margin-top: 10px;
149
+ border-radius: 4px;
150
+ border: 1px solid #eee;
151
+ }
152
+
153
+ /* Responsive adjustments */
154
+ @media (max-width: 600px) {
155
+ .catalog {
156
+ grid-template-columns: repeat(auto-fit, minmax(100%, 1fr)); /* One column on small screens */
157
  }
158
+ }
159
+ </style>
160
+ </head>
161
+ <body>
162
+ <h1>Каталог товаров</h1>
163
+ <div class="catalog">
164
+ {% for product in products %}
165
+ <div class="product">
166
+ <h2>{{ product['name'] }}</h2>
167
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}">
168
+ <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
169
+ <p><strong>Описание:</strong> {{ product['description'] }}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  </div>
171
+ {% endfor %}
172
+ </div>
173
+ </body>
174
+ </html>
175
+ '''
176
  return render_template_string(catalog_html, products=products, repo_id=REPO_ID)
177
 
178
  @app.route('/admin', methods=['GET', 'POST'])
179
  def admin():
180
  products = load_data()
181
+
182
  if request.method == 'POST':
183
  action = request.form.get('action')
184
 
 
188
  description = request.form.get('description')
189
  photo = request.files.get('photo')
190
 
 
 
191
  photo_filename = None
192
  if photo and photo.filename:
193
  photo_filename = secure_filename(photo.filename)
 
194
  uploads_dir = 'uploads'
195
  os.makedirs(uploads_dir, exist_ok=True)
196
  temp_path = os.path.join(uploads_dir, photo_filename)
197
  photo.save(temp_path)
198
 
 
199
  try:
200
  api = HfApi()
201
  api.upload_file(
 
211
  logging.error(f"Ошибка при загрузке фото: {e}")
212
  return f"Ошибка при загрузке фото: {e}", 500
213
  finally:
 
214
  os.remove(temp_path)
215
 
216
  if name and price and description:
 
220
  logging.error("Ошибка: Цена должна быть числом.")
221
  return "Ошибка: Цена должна быть числом.", 400
222
 
223
+ product_entry = {'name': name, 'price': price, 'description': description}
 
 
 
 
224
  if photo_filename:
225
  product_entry['photo'] = photo_filename
226
 
 
235
  description = request.form.get('description')
236
  photo = request.files.get('photo')
237
 
 
238
  if photo:
239
  photo_filename = secure_filename(photo.filename)
240
  uploads_dir = 'uploads'
 
253
  commit_message=f"Обновлено фото для товара {name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
254
  )
255
  logging.info("Фото успешно обновлено в репозитории.")
256
+ products[index]['photo'] = photo_filename
257
  except Exception as e:
258
  logging.error(f"Ошибка при загрузке фото: {e}")
259
  return f"Ошибка при загрузке фото: {e}", 500
260
  finally:
261
  os.remove(temp_path)
262
+
 
263
  products[index]['name'] = name
264
  try:
265
  price = float(price.replace(',', '.'))
266
  except ValueError:
267
  logging.error("Ошибка: Цена должна быть числом.")
268
  return "Ошибка: Цена должна быть числом.", 400
269
+
270
  products[index]['price'] = price
271
  products[index]['description'] = description
272
 
 
280
  return redirect(url_for('admin'))
281
 
282
  admin_html = '''
283
+ <!DOCTYPE html>
284
+ <html lang="ru">
285
+ <head>
286
+ <meta charset="UTF-8">
287
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
288
+ <title>Админ-панель</title>
289
+ <style>
290
+ body {
291
+ font-family: Arial, sans-serif;
292
+ margin: 20px;
293
+ background-color: #f9f9f9;
294
+ color: #333;
295
+ }
296
+
297
+ h1 {
298
+ color: #333;
299
+ text-align: center;
300
+ margin-bottom: 20px;
301
+ }
302
+
303
+ form {
304
+ background-color: #fff;
305
+ padding: 20px;
306
+ border: 1px solid #ddd;
307
+ border-radius: 8px;
308
+ max-width: 500px;
309
+ margin: 20px auto;
310
+ }
311
+
312
+ label {
313
+ display: block;
314
+ margin-top: 10px;
315
+ color: #555;
316
+ }
317
+
318
+ input[type="text"],
319
+ input[type="number"],
320
+ textarea {
321
+ width: 100%;
322
+ padding: 8px;
323
+ margin-top: 5px;
324
+ border: 1px solid #ddd;
325
+ border-radius: 4px;
326
+ box-sizing: border-box;
327
+ }
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
+
339
+ button:hover {
340
+ background-color: #218838;
341
+ }
342
+
343
+ .product-list {
344
+ margin-top: 20px;
345
+ }
346
+
347
+ .product-item {
348
+ background-color: #fff;
349
+ border: 1px solid #ddd;
350
+ padding: 15px;
351
+ margin-bottom: 10px;
352
+ border-radius: 8px;
353
+ }
354
+
355
+ .edit-form {
356
+ margin-top: 10px;
357
+ padding: 10px;
358
+ border: 1px solid #ddd;
359
+ border-radius: 8px;
360
+ background-color: #f9f9f9;
361
+ }
362
+ </style>
363
+ </head>
364
+ <body>
365
+ <h1>Админ-панель</h1>
366
+
367
+ <form method="POST" enctype="multipart/form-data">
368
+ <input type="hidden" name="action" value="add">
369
+ <label for="name">Название товара:</label>
370
+ <input type="text" id="name" name="name" required>
371
+
372
+ <label for="price">Цена:</label>
373
+ <input type="number" id="price" name="price" step="0.01" required>
374
+
375
+ <label for="description">Описание:</label>
376
+ <textarea id="description" name="description" rows="4" required></textarea>
377
+
378
+ <label for="photo">Фотография товара (необязательно):</label>
379
+ <input type="file" id="photo" name="photo" accept="image/*">
380
+
381
+ <button type="submit">Добавить товар</button>
382
+ </form>
383
+
384
+ <h2>Список товаров</h2>
385
+ <div class="product-list">
386
+ {% for product in products %}
387
+ <div class="product-item">
388
+ <h3>{{ product['name'] }}</h3>
389
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}" style="max-width: 100%;">
390
+ <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
391
+ <p><strong>Описание:</strong> {{ product['description'] }}</p>
392
+
393
+ <details>
394
+ <summary>Редактировать</summary>
395
+ <form method="POST" enctype="multipart/form-data" class="edit-form">
396
+ <input type="hidden" name="action" value="edit">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  <input type="hidden" name="index" value="{{ loop.index0 }}">
398
+ <label for="name">Название товара:</label>
399
+ <input type="text" id="name" name="name" value="{{ product['name'] }}" required>
400
+
401
+ <label for="price">Цена:</label>
402
+ <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
403
+
404
+ <label for="description">Описание:</label>
405
+ <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
406
+
407
+ <label for="photo">Фотография товара (необязательно):</label>
408
+ <input type="file" id="photo" name="photo" accept="image/*">
409
+
410
+ <button type="submit">Сохранить изменения</button>
411
  </form>
412
+ </details>
413
+
414
+ <form method="POST">
415
+ <input type="hidden" name="action" value="delete">
416
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
417
+ <button type="submit">Удалить</button>
418
+ </form>
419
  </div>
420
+ {% endfor %}
421
+ </div>
422
+
423
+ <h2>Управление базой данных</h2>
424
+
425
+ <form method="POST" action="{{ url_for('backup') }}">
426
+ <button type="submit">Создать резервную копию</button>
427
+ </form>
428
 
429
+ <form method="GET" action="{{ url_for('download') }}">
430
+ <button type="submit">Скачать базу данных</button>
431
+ </form>
432
+ </body>
433
+ </html>
434
+ '''
435
  return render_template_string(admin_html, products=products, repo_id=REPO_ID)
436
 
437
  @app.route('/backup', methods=['POST'])
438
  def backup():
 
439
  upload_db_to_hf()
440
  return "Резервная копия успешно создана.", 200
441
 
442
  @app.route('/download', methods=['GET'])
443
  def download():
 
444
  download_db_from_hf()
445
  return "База данных успешно скачана.", 200
446
 
447
  if __name__ == '__main__':
 
448
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
449
  backup_thread.start()
450
 
 
451
  try:
452
  load_data()
453
  except Exception as e:
454
  logging.error(f"Не удалось загрузить базу данных при запуске: {e}")
 
 
455
 
456
+ app.run(debug=True, host='0.0.0.0', port=7860)