flpolprojects commited on
Commit
c4bb4cb
·
verified ·
1 Parent(s): df913b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +463 -367
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  from flask import Flask, render_template_string, request, redirect, url_for
2
  import json
3
  import os
@@ -12,19 +13,16 @@ 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)
@@ -36,13 +34,12 @@ def load_data():
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)
@@ -50,9 +47,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:
57
  api = HfApi()
58
  api.upload_file(
@@ -68,7 +63,6 @@ def upload_db_to_hf():
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,
@@ -81,101 +75,221 @@ def download_db_from_hf():
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: 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: repeat(3, 1fr); /* Три колонки на мобильных устройствах */
154
- gap: 20px; /* Промежуток между колонками */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
 
 
 
 
 
 
 
 
 
 
 
 
156
  .product {
157
- padding: 10px; /* Уменьшение отступов на мобильных */
158
- }
159
- }
160
- </style>
161
- </head>
162
- <body>
163
- <h1>Каталог товаров</h1>
164
- <div class="product-grid">
165
- {% for product in products %}
166
- <div class="product">
167
- <h2>{{ product['name'] }}</h2>
168
- <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
169
- <p><strong>Описание:</strong> {{ product['description'] }}</p>
170
- {% if product.get('photo') %}
171
- <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}">
172
- {% endif %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  </div>
174
- {% endfor %}
175
- </div>
176
- </body>
177
- </html>
178
- '''
179
  return render_template_string(catalog_html, products=products, repo_id=REPO_ID)
180
 
181
  @app.route('/admin', methods=['GET', 'POST'])
@@ -184,279 +298,261 @@ def admin():
184
  if request.method == 'POST':
185
  action = request.form.get('action')
186
 
187
- if action == 'add':
188
- name = request.form.get('name')
189
- price = request.form.get('price')
190
- description = request.form.get('description')
191
- photo = request.files.get('photo')
192
-
193
- logging.debug(f"Полученные данные из формы: name={name}, price={price}, description={description}")
194
-
195
- photo_filename = None
196
- if photo and photo.filename:
197
- photo_filename = secure_filename(photo.filename)
198
- # Сохраняем файл временно в папку uploads
199
- uploads_dir = 'uploads'
200
- os.makedirs(uploads_dir, exist_ok=True)
201
- temp_path = os.path.join(uploads_dir, photo_filename)
202
- photo.save(temp_path)
203
-
204
- # Загружаем фото в репозиторий в папку "photos"
205
- try:
206
- api = HfApi()
207
- api.upload_file(
208
- path_or_fileobj=temp_path,
209
- path_in_repo=f"photos/{photo_filename}",
210
- repo_id=REPO_ID,
211
- repo_type="dataset",
212
- token=HF_TOKEN_WRITE,
213
- commit_message=f"Добавлено фото для товара {name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
214
- )
215
- logging.info("Фото успешно загружено в репозиторий.")
216
- except Exception as e:
217
- logging.error(f"Ошибка при загрузке фото: {e}")
218
- return f"Ошибка при загрузке фото: {e}", 500
219
- finally:
220
- # Удаляем временный файл
221
- os.remove(temp_path)
222
-
223
- if name and price and description:
224
- try:
225
- price = float(price.replace(',', '.'))
226
- except ValueError:
227
- logging.error("Ошибка: Цена должна быть числом.")
228
- return "Ошибка: Цена должна быть числом.", 400
229
-
230
- product_entry = {
231
- 'name': name,
232
- 'price': price,
233
- 'description': description
234
- }
235
- if photo_filename:
236
- product_entry['photo'] = photo_filename
237
-
238
- products.append(product_entry)
239
- save_data(products)
240
- return redirect(url_for('admin'))
241
-
242
- elif action == 'edit':
243
- index = int(request.form.get('index'))
244
- name = request.form.get('name')
245
- price = request.form.get('price')
246
- description = request.form.get('description')
247
- photo = request.files.get('photo')
248
-
249
- # Логика обновления фотографии
250
- if photo:
251
- photo_filename = secure_filename(photo.filename)
252
- uploads_dir = 'uploads'
253
- os.makedirs(uploads_dir, exist_ok=True)
254
- temp_path = os.path.join(uploads_dir, photo_filename)
255
- photo.save(temp_path)
256
-
257
- try:
258
- api = HfApi()
259
- api.upload_file(
260
- path_or_fileobj=temp_path,
261
- path_in_repo=f"photos/{photo_filename}",
262
- repo_id=REPO_ID,
263
- repo_type="dataset",
264
- token=HF_TOKEN_WRITE,
265
- commit_message=f"Обновлено фото для товара {name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
266
- )
267
- logging.info("Фото успешно обновлено в репозитории.")
268
- products[index]['photo'] = photo_filename # Обновляем имя файла
269
- except Exception as e:
270
- logging.error(f"Ошибка при загрузке фото: {e}")
271
- return f"Ошибка при загрузке фото: {e}", 500
272
- finally:
273
- os.remove(temp_path)
274
-
275
- # Обновление остальных полей
276
- products[index]['name'] = name
277
- try:
278
- price = float(price.replace(',', '.'))
279
- except ValueError:
280
- logging.error("Ошибка: Цена должна быть числом.")
281
- return "Ошибка: Цена должна быть числом.", 400
282
- products[index]['price'] = price
283
- products[index]['description'] = description
284
-
285
- save_data(products)
286
- return redirect(url_for('admin'))
287
-
288
- elif action == 'delete':
289
- index = int(request.form.get('index'))
290
- del products[index]
291
- save_data(products)
292
- return redirect(url_for('admin'))
293
-
294
- admin_html = '''
295
- <!DOCTYPE html>
296
- <html lang="ru">
297
- <head>
298
- <meta charset="UTF-8">
299
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
300
- <title>Админ-панель</title>
301
- <style>
302
- body {
303
- font-family: Arial, sans-serif;
304
- margin: 20px;
305
- background-color: #f9f9f9;
306
- }
307
- h1 {
308
- color: #333;
309
- }
310
- form {
311
- background-color: #fff;
312
- padding: 20px;
313
- border: 1px solid #ddd;
314
- border-radius: 5px;
315
- max-width: 400px;
316
- margin-bottom: 20px;
317
- }
318
- label {
319
- display: block;
320
- margin-top: 10px;
321
- color:#555;
322
- }
323
- input, textarea {
324
- width :100%;
325
- padding :8 px ;
326
- margin-top :5 px ;
327
- border :1 px solid#ddd ;
328
- border-radius :4 px ;
329
- }
330
- button{
331
- margin-top :15 px ;
332
- padding :10 px15 px ;
333
- background-color :#28 a745 ;
334
- color:white ;
335
- border:none ;
336
- border-radius :4 px ;
337
- cursor:pointer ;
338
- }
339
- button:hover{
340
- background-color :#218838 ;
341
- }
342
- .product-list{
343
- margin-top :20 px ;
344
- }
345
- .product-item{
346
- background-color :#fff ;
347
- border :1 px solid#ddd ;
348
- padding :15 px ;
349
- margin-bottom :10 px ;
350
- border-radius :5 px ;
351
- }
352
- .edit-form{
353
- margin-top :10 px ;
354
- padding :10 px ;
355
- border :1 px solid#ddd ;
356
- border-radius :5 px ;
357
- background-color :#f9f9f9 ;
358
- }
359
- </style>
360
- </head>
361
- <body>
362
- <h1>Добавление товара</h1>
363
- <form method="POST" enctype="multipart/form-data">
364
- <input type="hidden" name="action" value="add">
365
- <label for="name">Название товара:</label>
366
- <input type="text" id="name" name="name" required>
367
-
368
- <label for="price">Цена:</label>
369
- <input type="number" id="price" name="price" step="0.01" required>
370
-
371
- <label for="description">Описание:</label>
372
- <textarea id="description" name="description" rows="4" required></textarea>
373
-
374
- <label for="photo">Фотография товара (необязательно):</label>
375
- <input type="file" id="photo" name="photo" accept="image/*">
376
-
377
- <button type="submit">Добавить товар</button>
378
- </form>
379
-
380
- <h2>Управление базой данных</h2>
381
-
382
- <!-- Кнопки для резервной копии и скачивания -->
383
- <form method="POST" action="{{ url_for('backup') }}">
384
- <button type="submit">Создать резервную копию</button>
385
- </form>
386
-
387
- <form method="GET" action="{{ url_for('download') }}">
388
- <button type="submit">Скачать базу данных</button>
389
- </form>
390
-
391
- <h2>Список товаров</h2>
392
- <div class="product-list">
393
- {% for product in products %}
394
- <div class="product-item">
395
- <h3>{{ product['name'] }}</h3>
396
- <p><strong>Цена:</strong>{{ product['price'] }} руб.</p>
397
- <p><strong>Описание:</strong>{{ product['description'] }}</p>
398
- {% if product.get('photo') %}
399
- <img src ="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}" style ="max-width :100 px;">
400
- {% endif %}
401
-
402
- <details >
403
- <summary >Редактировать</summary >
404
- <form method ="POST " enctype ="multipart/form-data " class ="edit-form ">
405
- <input type ="hidden " name ="action " value ="edit ">
406
- <input type ="hidden " name ="index " value="{{ loop.index0 }}">
407
- <label for ="name ">Название товара:</label >
408
- <input type ="text " id ="name " name ="name " value="{{ product['name'] }}" required >
409
-
410
- <label for ="price ">Цена:</label >
411
- <input type ="number " id ="price " name ="price " step ="0.01 " value="{{ product['price'] }}" required >
412
-
413
- <label for ="description ">Описание:</label >
414
- <textarea id ="description " name ="description " rows ="4 " required >{{ product['description'] }}</textarea >
415
-
416
- <label for ="photo ">Фотография товара (необязательно):</label >
417
- <input type ="file " id ="photo " name ="photo " accept ="image/* ">
418
-
419
- <button type ="submit ">Сохранить изменения</button >
420
- </form >
421
- </details >
422
-
423
- <form method ="POST ">
424
- <input type ="hidden " name ="action " value ="delete ">
425
- <input type ="hidden " name ="index " value="{{ loop.index0 }}">
426
- <button type ="submit ">Удалить</button >
427
- </form >
428
- </div >
429
- {% endfor %}
430
- </div >
431
-
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
- """Маршрут для создания резервной копии."""
440
- upload_db_to_hf()
441
- return "Резервная копия успешно создана." ,200
442
-
443
- @app.route('/download' , methods=['GET'])
444
- def download():
445
- """Маршрут для скачивания базы данных."""
446
- download_db_from_hf()
447
- return "База данных успешно скачана." ,200
448
-
449
- if __name__ == '__main__':
450
- # Запуск фонового потока для периодического резервного копирования
451
- backup_thread = threading.Thread(target=periodic_backup , daemon=True)
452
- backup_thread.start()
453
-
454
- # Попытка загрузить базу данных из репозитория перед запуском приложения
455
- try :
456
- load_data()
457
- except Exception as e :
458
- logging.error(f"Не удалось загрузить базу данных при запуске:{e}")
459
- # Здесь можно добавить логику для создания пустой базы данных , если это необходимо
460
- # Например:savedata([])
461
-
462
- app.run(debug=True , host='0.0.0.0' , port=7860)
 
1
+
2
  from flask import Flask, render_template_string, request, redirect, url_for
3
  import json
4
  import os
 
13
  app = Flask(__name__)
14
  DATA_FILE = 'products.json'
15
 
16
+ # Настройки Hugging Face
17
+ REPO_ID = "flpolprojects/Clients"
18
+ HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
19
+ HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
20
 
21
  # Настройка логирования
22
  logging.basicConfig(level=logging.DEBUG)
23
 
 
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)
 
34
  return []
35
  except RepositoryNotFoundError:
36
  logging.error("Репозиторий не найден. Создание локальной базы данных.")
37
+ return []
38
  except Exception as e:
39
  logging.error(f"Произошла ошибка при загрузке данных: {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)
 
47
  logging.error(f"Ошибка при сохранении данных: {e}")
48
  raise
49
 
 
50
  def upload_db_to_hf():
 
51
  try:
52
  api = HfApi()
53
  api.upload_file(
 
63
  logging.error(f"Ошибка при загрузке резервной копии: {e}")
64
 
65
  def download_db_from_hf():
 
66
  try:
67
  hf_hub_download(
68
  repo_id=REPO_ID,
 
75
  logging.info("JSON база успешно скачана из Hugging Face.")
76
  except RepositoryNotFoundError as e:
77
  logging.error(f"Репозиторий не найден: {e}")
78
+ raise
79
  except Exception as e:
80
  logging.error(f"Ошибка при скачивании JSON базы: {e}")
81
  raise
82
 
83
  def periodic_backup():
 
84
  while True:
85
  upload_db_to_hf()
86
  time.sleep(15)
87
 
 
88
  @app.route('/')
89
  def catalog():
90
  products = load_data()
91
  catalog_html = '''
92
+ <!DOCTYPE html>
93
+ <html lang="ru">
94
+ <head>
95
+ <meta charset="UTF-8">
96
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
97
+ <title>Каталог</title>
98
+ <style>
99
+ * {
100
+ margin: 0;
101
+ padding: 0;
102
+ box-sizing: border-box;
103
+ }
104
+
105
+ body {
106
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
107
+ background-color: #f5f5f5;
108
+ color: #333;
109
+ line-height: 1.6;
110
+ padding: 20px;
111
+ }
112
+
113
+ .container {
114
+ max-width: 1200px;
115
+ margin: 0 auto;
116
+ }
117
+
118
+ h1 {
119
+ text-align: center;
120
+ color: #2c3e50;
121
+ margin-bottom: 40px;
122
+ font-size: 2.5em;
123
+ font-weight: 700;
124
+ }
125
+
126
+ .products-grid {
127
+ display: grid;
128
+ grid-template-columns: repeat(3, 1fr);
129
+ gap: 20px;
130
+ padding: 0 15px;
131
+ }
132
+
133
+ .product {
134
+ background: #ffffff;
135
+ border-radius: 12px;
136
+ padding: 20px;
137
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
138
+ display: flex;
139
+ flex-direction: column;
140
+ box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
141
+ }
142
+
143
+ .product:hover {
144
+ transform: translateY(-5px);
145
+ box-shadow: 0 5px 25px rgba(0, 0, 0, 0.15);
146
+ }
147
+
148
+ .product-image {
149
+ width: 100%;
150
+ height: 200px;
151
+ overflow: hidden;
152
+ border-radius: 8px;
153
+ margin-bottom: 15px;
154
+ }
155
+
156
+ .product-image img {
157
+ width: 100%;
158
+ height: 100%;
159
+ object-fit: cover;
160
+ transition: transform 0.3s ease;
161
+ }
162
+
163
+ .product-image img:hover {
164
+ transform: scale(1.05);
165
+ }
166
+
167
+ .product h2 {
168
+ font-size: 1.2em;
169
+ color: #2c3e50;
170
+ margin-bottom: 10px;
171
+ font-weight: 600;
172
+ }
173
+
174
+ .product-price {
175
+ font-size: 1.3em;
176
+ color: #e74c3c;
177
+ font-weight: 700;
178
+ margin: 10px 0;
179
+ }
180
+
181
+ .product-description {
182
+ color: #7f8c8d;
183
+ font-size: 0.9em;
184
+ flex-grow: 1;
185
+ margin-bottom: 15px;
186
+ }
187
+
188
+ .product-button {
189
+ background-color: #3498db;
190
+ color: white;
191
+ padding: 10px 20px;
192
+ border: none;
193
+ border-radius: 5px;
194
+ cursor: pointer;
195
+ transition: background-color 0.3s ease;
196
+ text-align: center;
197
+ text-decoration: none;
198
+ display: inline-block;
199
+ font-weight: 500;
200
+ }
201
+
202
+ .product-button:hover {
203
+ background-color: #2980b9;
204
+ }
205
+
206
+ @media (max-width: 1024px) {
207
+ .products-grid {
208
+ grid-template-columns: repeat(2, 1fr);
209
+ }
210
+ }
211
+
212
+ @media (max-width: 768px) {
213
+ .products-grid {
214
+ grid-template-columns: repeat(1, 1fr);
215
+ gap: 15px;
216
+ }
217
+
218
+ body {
219
+ padding: 10px;
220
+ }
221
+
222
+ h1 {
223
+ font-size: 2em;
224
+ margin-bottom: 20px;
225
+ }
226
+
227
+ .product {
228
+ padding: 15px;
229
+ }
230
+
231
+ .product-image {
232
+ height: 180px;
233
+ }
234
  }
235
+
236
+ @keyframes fadeIn {
237
+ from {
238
+ opacity: 0;
239
+ transform: translateY(20px);
240
+ }
241
+ to {
242
+ opacity: 1;
243
+ transform: translateY(0);
244
+ }
245
+ }
246
+
247
  .product {
248
+ animation: fadeIn 0.5s ease-out forwards;
249
+ }
250
+
251
+ ::-webkit-scrollbar {
252
+ width: 8px;
253
+ }
254
+
255
+ ::-webkit-scrollbar-track {
256
+ background: #f1f1f1;
257
+ }
258
+
259
+ ::-webkit-scrollbar-thumb {
260
+ background: #888;
261
+ border-radius: 4px;
262
+ }
263
+
264
+ ::-webkit-scrollbar-thumb:hover {
265
+ background: #555;
266
+ }
267
+ </style>
268
+ </head>
269
+ <body>
270
+ <div class="container">
271
+ <h1>Каталог товаров</h1>
272
+ <div class="products-grid">
273
+ {% for product in products %}
274
+ <div class="product">
275
+ {% if product.get('photo') %}
276
+ <div class="product-image">
277
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}"
278
+ alt="{{ product['name'] }}"
279
+ loading="lazy">
280
+ </div>
281
+ {% endif %}
282
+ <h2>{{ product['name'] }}</h2>
283
+ <div class="product-price">{{ product['price'] }} ₽</div>
284
+ <p class="product-description">{{ product['description'] }}</p>
285
+ <a href="#" class="product-button">Подробнее</a>
286
+ </div>
287
+ {% endfor %}
288
+ </div>
289
  </div>
290
+ </body>
291
+ </html>
292
+ '''
 
 
293
  return render_template_string(catalog_html, products=products, repo_id=REPO_ID)
294
 
295
  @app.route('/admin', methods=['GET', 'POST'])
 
298
  if request.method == 'POST':
299
  action = request.form.get('action')
300
 
301
+ if action == 'add':
302
+ name = request.form.get('name')
303
+ price = request.form.get('price')
304
+ description = request.form.get('description')
305
+ photo = request.files.get('photo')
306
+
307
+ photo_filename = None
308
+ if photo and photo.filename:
309
+ photo_filename = secure_filename(photo.filename)
310
+ uploads_dir = 'uploads'
311
+ os.makedirs(uploads_dir, exist_ok=True)
312
+ temp_path = os.path.join(uploads_dir, photo_filename)
313
+ photo.save(temp_path)
314
+
315
+ try:
316
+ api = HfApi()
317
+ api.upload_file(
318
+ path_or_fileobj=temp_path,
319
+ path_in_repo=f"photos/{photo_filename}",
320
+ repo_id=REPO_ID,
321
+ repo_type="dataset",
322
+ token=HF_TOKEN_WRITE,
323
+ commit_message=f"Добавлено фото для товара {name}"
324
+ )
325
+ except Exception as e:
326
+ logging.error(f"Ошибка при загрузке фото: {e}")
327
+ return f"Ошибка при загрузке фото: {e}", 500
328
+ finally:
329
+ os.remove(temp_path)
330
+
331
+ if name and price and description:
332
+ try:
333
+ price = float(price.replace(',', '.'))
334
+ except ValueError:
335
+ return "Ошибка: Цена должна быть числом.", 400
336
+
337
+ product = {
338
+ 'name': name,
339
+ 'price': price,
340
+ 'description': description
341
+ }
342
+ if photo_filename:
343
+ product['photo'] = photo_filename
344
+
345
+ products.append(product)
346
+ save_data(products)
347
+ return redirect(url_for('admin'))
348
+
349
+ elif action == 'edit':
350
+ index = int(request.form.get('index'))
351
+ name = request.form.get('name')
352
+ price = request.form.get('price')
353
+ description = request.form.get('description')
354
+ photo = request.files.get('photo')
355
+
356
+ if photo:
357
+ photo_filename = secure_filename(photo.filename)
358
+ uploads_dir = 'uploads'
359
+ os.makedirs(uploads_dir, exist_ok=True)
360
+ temp_path = os.path.join(uploads_dir, photo_filename)
361
+ photo.save(temp_path)
362
+
363
+ try:
364
+ api = HfApi()
365
+ api.upload_file(
366
+ path_or_fileobj=temp_path,
367
+ path_in_repo=f"photos/{photo_filename}",
368
+ repo_id=REPO_ID,
369
+ repo_type="dataset",
370
+ token=HF_TOKEN_WRITE,
371
+ commit_message=f"Обновлено фото для товара {name}"
372
+ )
373
+ products[index]['photo'] = photo_filename
374
+ except Exception as e:
375
+ logging.error(f"Ошибка при загрузке фото: {e}")
376
+ return f"Ошибка при загрузке фото: {e}", 500
377
+ finally:
378
+ os.remove(temp_path)
379
+
380
+ products[index]['name'] = name
381
+ try:
382
+ price = float(price.replace(',', '.'))
383
+ except ValueError:
384
+ return "Ошибка: Цена должна быть числом.", 400
385
+ products[index]['price'] = price
386
+ products[index]['description'] = description
387
+
388
+ save_data(products)
389
+ return redirect(url_for('admin'))
390
+
391
+ elif action == 'delete':
392
+ index = int(request.form.get('index'))
393
+ del products[index]
394
+ save_data(products)
395
+ return redirect(url_for('admin'))
396
+
397
+ admin_html = '''
398
+ <!DOCTYPE html>
399
+ <html lang="ru">
400
+ <head>
401
+ <meta charset="UTF-8">
402
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
403
+ <title>Админ-панель</title>
404
+ <style>
405
+ body {
406
+ font-family: Arial, sans-serif;
407
+ margin: 20px;
408
+ background-color: #f9f9f9;
409
+ }
410
+ h1 {
411
+ color: #333;
412
+ }
413
+ form {
414
+ background-color: #fff;
415
+ padding: 20px;
416
+ border: 1px solid #ddd;
417
+ border-radius: 5px;
418
+ max-width: 400px;
419
+ margin-bottom: 20px;
420
+ }
421
+ label {
422
+ display: block;
423
+ margin-top: 10px;
424
+ color: #555;
425
+ }
426
+ input, textarea {
427
+ width: 100%;
428
+ padding: 8px;
429
+ margin-top: 5px;
430
+ border: 1px solid #ddd;
431
+ border-radius: 4px;
432
+ }
433
+ button {
434
+ margin-top: 15px;
435
+ padding: 10px 15px;
436
+ background-color: #28a745;
437
+ color: white;
438
+ border: none;
439
+ border-radius: 4px;
440
+ cursor: pointer;
441
+ }
442
+ button:hover {
443
+ background-color: #218838;
444
+ }
445
+ .product-list {
446
+ margin-top: 20px;
447
+ }
448
+ .product-item {
449
+ background-color: #fff;
450
+ border: 1px solid #ddd;
451
+ padding: 15px;
452
+ margin-bottom: 10px;
453
+ border-radius: 5px;
454
+ }
455
+ .edit-form {
456
+ margin-top: 10px;
457
+ padding: 10px;
458
+ border: 1px solid #ddd;
459
+ border-radius: 5px;
460
+ background-color: #f9f9f9;
461
+ }
462
+ </style>
463
+ </head>
464
+ <body>
465
+ <h1>Добавление товара</h1>
466
+ <form method="POST" enctype="multipart/form-data">
467
+ <input type="hidden" name="action" value="add">
468
+ <label for="name">Название товара:</label>
469
+ <input type="text" id="name" name="name" required>
470
+
471
+ <label for="price">Цена:</label>
472
+ <input type="number" id="price" name="price" step="0.01" required>
473
+
474
+ <label for="description">Описание:</label>
475
+ <textarea id="description" name="description" rows="4" required></textarea>
476
+
477
+ <label for="photo">Фотография товара (необязательно):</label>
478
+ <input type="file" id="photo" name="photo" accept="image/*">
479
+
480
+ <button type="submit">Добавить товар</button>
481
+ </form>
482
+
483
+ <h2>Управление базой данных</h2>
484
+ <form method="POST" action="{{ url_for('backup') }}">
485
+ <button type="submit">Создать резервную копию</button>
486
+ </form>
487
+
488
+ <form method="GET" action="{{ url_for('download') }}">
489
+ <button type="submit">Скачать базу данных</button>
490
+ </form>
491
+
492
+ <h2>Список товаров</h2>
493
+ <div class="product-list">
494
+ {% for product in products %}
495
+ <div class="product-item">
496
+ <h3>{{ product['name'] }}</h3>
497
+ <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
498
+ <p><strong>Описание:</strong> {{ product['description'] }}</p>
499
+ {% if product.get('photo') %}
500
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}"
501
+ alt="{{ product['name'] }}"
502
+ style="max-width: 100px;">
503
+ {% endif %}
504
+
505
+ <details>
506
+ <summary>Редактировать</summary>
507
+ <form method="POST" enctype="multipart/form-data" class="edit-form">
508
+ <input type="hidden" name="action" value="edit">
509
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
510
+ <label for="name">Название товара:</label>
511
+ <input type="text" id="name" name="name" value="{{ product['name'] }}" required>
512
+
513
+ <label for="price">Цена:</label>
514
+ <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
515
+
516
+ <label for="description">Описание:</label>
517
+ <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
518
+
519
+ <label for="photo">Фотография товара (необязательно):</label>
520
+ <input type="file" id="photo" name="photo" accept="image/*">
521
+
522
+ <button type="submit">Сохранить изменения</button>
523
+ </form>
524
+ </details>
525
+
526
+ <form method="POST">
527
+ <input type="hidden" name="action" value="delete">
528
+ <input type="hidden" name="index" value="{{ loop.index0 }}">
529
+ <button type="submit">Удалить</button>
530
+ </form>
531
+ </div>
532
+ {% endfor %}
533
+ </div>
534
+ </body>
535
+ </html>
536
+ '''
537
+ return render_template_string(admin_html, products=products, repo_id=REPO_ID)
538
+
539
+ @app.route('/backup', methods=['POST'])
540
+ def backup():
541
+ upload_db_to_hf()
542
+ return "Резервная копия успешно создана.", 200
543
+
544
+ @app.route('/download', methods=['GET'])
545
+ def download():
546
+ download_db_from_hf()
547
+ return "База данных успешно скачана.", 200
548
+
549
+ if __name__ == '__main__':
550
+ backup_thread = threading.Thread(target=periodic_backup, daemon=True)
551
+ backup_thread.start()
552
+
553
+ try:
554
+ load_data()
555
+ except Exception as e:
556
+ logging.error(f"Не удалось загрузить базу данных при запуске: {e}")
557
+
558
+ app.run(debug=True, host='0.0.0.0', port=7860)