flpolprojects commited on
Commit
8172b9d
·
verified ·
1 Parent(s): cc994dc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -84
app.py CHANGED
@@ -12,43 +12,47 @@ 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 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,13 +61,14 @@ def upload_db_to_hf():
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,20 +78,21 @@ def download_db_from_hf():
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()
@@ -102,71 +108,72 @@ def catalog():
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(3, minmax(0, 1fr)); /* Three columns on all screens */
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 (optional, for smaller screens if needed) */
154
  @media (max-width: 600px) {
155
- .catalog {
156
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); /* Adjust minmax for smaller items */
 
 
 
 
 
 
 
 
 
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>
@@ -178,7 +185,6 @@ def catalog():
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,14 +194,18 @@ def admin():
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,6 +221,7 @@ def admin():
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,7 +231,11 @@ def admin():
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,6 +250,7 @@ def admin():
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,20 +269,20 @@ def admin():
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
 
@@ -291,41 +307,30 @@ def admin():
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;
@@ -335,35 +340,30 @@ def admin():
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>
@@ -381,14 +381,27 @@ def admin():
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>
@@ -420,15 +433,6 @@ def admin():
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
  '''
@@ -436,21 +440,27 @@ def admin():
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)
 
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
  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
  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()
 
108
  font-family: Arial, sans-serif;
109
  margin: 20px;
110
  background-color: #f9f9f9;
 
111
  }
 
112
  h1 {
113
  color: #333;
114
  text-align: center;
115
  margin-bottom: 20px;
116
  }
117
+ .container {
 
118
  display: grid;
119
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); /* Адаптивная сетка */
120
  gap: 20px;
 
121
  }
 
122
  .product {
123
  background-color: #fff;
124
  border: 1px solid #ddd;
125
  padding: 15px;
126
+ border-radius: 5px;
127
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
128
  }
 
 
 
 
 
 
129
  .product h2 {
130
  margin-top: 0;
131
  color: #555;
132
+ font-size: 1.5em;
133
  }
 
134
  .product p {
135
  color: #777;
136
+ font-size: 1em;
137
  }
 
138
  .product img {
139
  max-width: 100%;
140
  height: auto;
141
  margin-top: 10px;
142
+ border: 1px solid #ccc;
143
  border-radius: 4px;
144
+ display: block;
145
+ margin-left: auto;
146
+ margin-right: auto;
147
  }
148
 
149
+ /* Адаптивные стили для экранов меньше 600px */
150
  @media (max-width: 600px) {
151
+ .container {
152
+ grid-template-columns: 1fr; /* Одна колонка на мобильных устройствах */
153
+ }
154
+ .product {
155
+ padding: 10px;
156
+ }
157
+ .product h2 {
158
+ font-size: 1.2em;
159
+ }
160
+ .product p {
161
+ font-size: 0.9em;
162
  }
163
  }
164
  </style>
165
  </head>
166
  <body>
167
  <h1>Каталог товаров</h1>
168
+ <div class="container">
169
  {% for product in products %}
170
  <div class="product">
171
  <h2>{{ product['name'] }}</h2>
 
172
  <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
173
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
174
+ {% if product.get('photo') %}
175
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}">
176
+ {% endif %}
177
  </div>
178
  {% endfor %}
179
  </div>
 
185
  @app.route('/admin', methods=['GET', 'POST'])
186
  def admin():
187
  products = load_data()
 
188
  if request.method == 'POST':
189
  action = request.form.get('action')
190
 
 
194
  description = request.form.get('description')
195
  photo = request.files.get('photo')
196
 
197
+ logging.debug(f"Полученные данные из формы: name={name}, price={price}, description={description}")
198
+
199
  photo_filename = None
200
  if photo and photo.filename:
201
  photo_filename = secure_filename(photo.filename)
202
+ # Сохраняем файл временно в папку uploads
203
  uploads_dir = 'uploads'
204
  os.makedirs(uploads_dir, exist_ok=True)
205
  temp_path = os.path.join(uploads_dir, photo_filename)
206
  photo.save(temp_path)
207
 
208
+ # Загружаем фото в репозиторий в папку "photos"
209
  try:
210
  api = HfApi()
211
  api.upload_file(
 
221
  logging.error(f"Ошибка при загрузке фото: {e}")
222
  return f"Ошибка при загрузке фото: {e}", 500
223
  finally:
224
+ # Удаляем временный файл
225
  os.remove(temp_path)
226
 
227
  if name and price and description:
 
231
  logging.error("Ошибка: Цена должна быть числом.")
232
  return "Ошибка: Цена должна быть числом.", 400
233
 
234
+ product_entry = {
235
+ 'name': name,
236
+ 'price': price,
237
+ 'description': description
238
+ }
239
  if photo_filename:
240
  product_entry['photo'] = photo_filename
241
 
 
250
  description = request.form.get('description')
251
  photo = request.files.get('photo')
252
 
253
+ # Логика обновления фотографии
254
  if photo:
255
  photo_filename = secure_filename(photo.filename)
256
  uploads_dir = 'uploads'
 
269
  commit_message=f"Обновлено фото для товара {name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
270
  )
271
  logging.info("Фото успешно обновлено в репозитории.")
272
+ products[index]['photo'] = photo_filename # Обновляем имя файла
273
  except Exception as e:
274
  logging.error(f"Ошибка при загрузке фото: {e}")
275
  return f"Ошибка при загрузке фото: {e}", 500
276
  finally:
277
  os.remove(temp_path)
278
+
279
+ # Обновление остальных полей
280
  products[index]['name'] = name
281
  try:
282
  price = float(price.replace(',', '.'))
283
  except ValueError:
284
  logging.error("Ошибка: Цена должна быть числом.")
285
  return "Ошибка: Цена должна быть числом.", 400
 
286
  products[index]['price'] = price
287
  products[index]['description'] = description
288
 
 
307
  font-family: Arial, sans-serif;
308
  margin: 20px;
309
  background-color: #f9f9f9;
 
310
  }
 
311
  h1 {
312
  color: #333;
 
 
313
  }
 
314
  form {
315
  background-color: #fff;
316
  padding: 20px;
317
  border: 1px solid #ddd;
318
+ border-radius: 5px;
319
+ max-width: 400px;
320
+ margin-bottom: 20px;
321
  }
 
322
  label {
323
  display: block;
324
  margin-top: 10px;
325
  color: #555;
326
  }
327
+ input, textarea {
 
 
 
328
  width: 100%;
329
  padding: 8px;
330
  margin-top: 5px;
331
  border: 1px solid #ddd;
332
  border-radius: 4px;
 
333
  }
 
334
  button {
335
  margin-top: 15px;
336
  padding: 10px 15px;
 
340
  border-radius: 4px;
341
  cursor: pointer;
342
  }
 
343
  button:hover {
344
  background-color: #218838;
345
  }
 
346
  .product-list {
347
  margin-top: 20px;
348
  }
 
349
  .product-item {
350
  background-color: #fff;
351
  border: 1px solid #ddd;
352
  padding: 15px;
353
  margin-bottom: 10px;
354
+ border-radius: 5px;
355
  }
 
356
  .edit-form {
357
  margin-top: 10px;
358
  padding: 10px;
359
  border: 1px solid #ddd;
360
+ border-radius: 5px;
361
  background-color: #f9f9f9;
362
  }
363
  </style>
364
  </head>
365
  <body>
366
+ <h1>Добавление товара</h1>
 
367
  <form method="POST" enctype="multipart/form-data">
368
  <input type="hidden" name="action" value="add">
369
  <label for="name">Название товара:</label>
 
381
  <button type="submit">Добавить товар</button>
382
  </form>
383
 
384
+ <h2>Управление базой данных</h2>
385
+
386
+ <!-- Кнопки для резервной копии и скачивания -->
387
+ <form method="POST" action="{{ url_for('backup') }}">
388
+ <button type="submit">Создать резервную копию</button>
389
+ </form>
390
+
391
+ <form method="GET" action="{{ url_for('download') }}">
392
+ <button type="submit">Скачать базу данных</button>
393
+ </form>
394
+
395
  <h2>Список товаров</h2>
396
  <div class="product-list">
397
  {% for product in products %}
398
  <div class="product-item">
399
  <h3>{{ product['name'] }}</h3>
 
400
  <p><strong>Цена:</strong> {{ product['price'] }} руб.</p>
401
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
402
+ {% if product.get('photo') %}
403
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photo'] }}" alt="{{ product['name'] }}" style="max-width: 100px;">
404
+ {% endif %}
405
 
406
  <details>
407
  <summary>Редактировать</summary>
 
433
  {% endfor %}
434
  </div>
435
 
 
 
 
 
 
 
 
 
 
436
  </body>
437
  </html>
438
  '''
 
440
 
441
  @app.route('/backup', methods=['POST'])
442
  def backup():
443
+ """Маршрут для создания резервной копии."""
444
  upload_db_to_hf()
445
  return "Резервная копия успешно создана.", 200
446
 
447
  @app.route('/download', methods=['GET'])
448
  def download():
449
+ """Маршрут для скачивания базы данных."""
450
  download_db_from_hf()
451
  return "База данных успешно скачана.", 200
452
 
453
  if __name__ == '__main__':
454
+ # Запуск фонового потока для периодического резервного копирования
455
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
456
  backup_thread.start()
457
 
458
+ # Попытка загрузить базу данных из репозитория перед запуском приложения
459
  try:
460
  load_data()
461
  except Exception as e:
462
  logging.error(f"Не удалось загрузить базу данных при запуске: {e}")
463
+ # Здесь можно добавить логику для создания пустой базы данных, если это необходимо
464
+ # Например: save_data([])
465
 
466
  app.run(debug=True, host='0.0.0.0', port=7860)