flpolprojects commited on
Commit
38eda2f
·
verified ·
1 Parent(s): 397b649

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -37
app.py CHANGED
@@ -26,19 +26,22 @@ def load_data():
26
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
27
  data = json.load(file)
28
  logging.info("Данные успешно загружены из JSON")
 
 
 
29
  return data
30
  except FileNotFoundError:
31
  logging.warning("Локальный файл базы данных не найден после скачивания.")
32
- return []
33
  except json.JSONDecodeError:
34
  logging.error("Ошибка: Невозможно декодировать JSON файл.")
35
- return []
36
  except RepositoryNotFoundError:
37
  logging.error("Репозиторий не найден. Создание локальной базы данных.")
38
- return []
39
  except Exception as e:
40
  logging.error(f"Произошла ошибка при загрузке данных: {e}")
41
- return []
42
 
43
  def save_data(data):
44
  try:
@@ -90,7 +93,10 @@ def periodic_backup():
90
 
91
  @app.route('/')
92
  def catalog():
93
- products = load_data()
 
 
 
94
  catalog_html = '''
95
  <!DOCTYPE html>
96
  <html lang="ru">
@@ -117,6 +123,13 @@ def catalog():
117
  max-width: 1200px;
118
  margin: 0 auto;
119
  }
 
 
 
 
 
 
 
120
  .search-container {
121
  text-align: center;
122
  margin-bottom: 20px;
@@ -134,13 +147,26 @@ def catalog():
134
  border-color: #3498db;
135
  box-shadow: 0 0 5px rgba(52, 152, 219, 0.5);
136
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  .products-grid {
138
  display: grid;
139
  grid-template-columns: repeat(2, 1fr);
140
  gap: 10px;
141
  padding: 0 5px;
142
  overflow-y: auto;
143
- max-height: calc(100vh - 120px);
144
  }
145
  .product {
146
  background: #ffffff;
@@ -346,12 +372,21 @@ def catalog():
346
  </head>
347
  <body>
348
  <div class="container">
 
 
 
 
 
 
349
  <div class="search-container">
350
  <input type="text" id="search-input" placeholder="Поиск по названию или описанию...">
351
  </div>
352
  <div class="products-grid" id="products-grid">
353
  {% for product in products %}
354
- <div class="product" data-name="{{ product['name']|lower }}" data-description="{{ product['description']|lower }}">
 
 
 
355
  {% if product.get('photos') and product['photos']|length > 0 %}
356
  <div class="product-image">
357
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
@@ -583,22 +618,40 @@ def catalog():
583
  }
584
  }
585
 
586
- // Поисковая строка
587
  document.getElementById('search-input').addEventListener('input', function(e) {
588
- const searchTerm = e.target.value.toLowerCase();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  const productElements = document.querySelectorAll('.product');
590
-
591
  productElements.forEach(product => {
592
  const name = product.getAttribute('data-name');
593
  const description = product.getAttribute('data-description');
594
-
595
- if (name.includes(searchTerm) || description.includes(searchTerm)) {
 
 
 
 
596
  product.style.display = 'flex';
597
  } else {
598
  product.style.display = 'none';
599
  }
600
  });
601
- });
602
 
603
  console.log("Инициализация страницы, товары:", products);
604
  updateCartButton();
@@ -606,11 +659,12 @@ def catalog():
606
  </body>
607
  </html>
608
  '''
609
- return render_template_string(catalog_html, products=products, repo_id=REPO_ID)
610
 
611
  @app.route('/product/<int:index>')
612
  def product_detail(index):
613
- products = load_data()
 
614
  try:
615
  product = products[index]
616
  except IndexError:
@@ -640,6 +694,7 @@ def product_detail(index):
640
  <div class="swiper-button-next"></div>
641
  <div class="swiper-button-prev"></div>
642
  </div>
 
643
  <p><strong>Цена:</strong> {{ product['price'] }}</p>
644
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
645
  </div>
@@ -648,17 +703,45 @@ def product_detail(index):
648
 
649
  @app.route('/admin', methods=['GET', 'POST'])
650
  def admin():
651
- products = load_data()
 
 
 
652
  if request.method == 'POST':
653
  action = request.form.get('action')
654
- if action == 'add':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  name = request.form.get('name')
656
  price = request.form.get('price')
657
  description = request.form.get('description')
 
658
  photos_files = request.files.getlist('photos')
659
  photos_list = []
660
 
661
- logging.info(f"Добавление нового товара: name={name}, price={price}, description={description}")
662
 
663
  if photos_files:
664
  for i, photo in enumerate(photos_files[:2]):
@@ -701,25 +784,21 @@ def admin():
701
  'name': name,
702
  'price': price,
703
  'description': description,
 
704
  'photos': photos_list
705
  }
706
 
707
  products.append(new_product)
708
- logging.info(f"Новый товар добавлен в список: {new_product}")
709
-
710
- try:
711
- save_data(products)
712
- logging.info("Товар успешно сохранен в базе данных")
713
- return redirect(url_for('admin'))
714
- except Exception as e:
715
- logging.error(f"Ошибка при сохранении товара: {e}")
716
- return f"Ошибка при сохранении товара: {e}", 500
717
 
718
  elif action == 'edit':
719
  index = int(request.form.get('index'))
720
  name = request.form.get('name')
721
  price = request.form.get('price')
722
  description = request.form.get('description')
 
723
  photos_files = request.files.getlist('photos')
724
 
725
  logging.info(f"Редактирование товара с индексом {index}")
@@ -760,8 +839,9 @@ def admin():
760
  logging.error("Ошибка: Цена должна быть числом")
761
  return "Ошибка: Цена должна быть числом", 400
762
  products[index]['description'] = description
 
763
 
764
- save_data(products)
765
  logging.info(f"Товар с индексом {index} успешно отредактирован")
766
  return redirect(url_for('admin'))
767
 
@@ -769,7 +849,7 @@ def admin():
769
  index = int(request.form.get('index'))
770
  logging.info(f"Удаление товара с индексом {index}")
771
  del products[index]
772
- save_data(products)
773
  logging.info("Товар успешно удален")
774
  return redirect(url_for('admin'))
775
 
@@ -786,7 +866,7 @@ def admin():
786
  margin: 20px;
787
  background-color: #f9f9f9;
788
  }
789
- h1 {
790
  color: #333;
791
  }
792
  form {
@@ -802,7 +882,7 @@ def admin():
802
  margin-top: 10px;
803
  color: #555;
804
  }
805
- input, textarea {
806
  width: 100%;
807
  padding: 8px;
808
  margin-top: 5px;
@@ -822,10 +902,16 @@ def admin():
822
  button:hover {
823
  background-color: #218838;
824
  }
825
- .product-list {
 
 
 
 
 
 
826
  margin-top: 20px;
827
  }
828
- .product-item {
829
  background-color: #fff;
830
  border: 1px solid #ddd;
831
  padding: 10px;
@@ -846,14 +932,14 @@ def admin():
846
  form {
847
  padding: 10px;
848
  }
849
- input, textarea {
850
  font-size: 0.9em;
851
  }
852
  button {
853
  padding: 6px 10px;
854
  font-size: 0.9em;
855
  }
856
- .product-item {
857
  padding: 8px;
858
  }
859
  }
@@ -869,11 +955,40 @@ def admin():
869
  <input type="number" id="price" name="price" step="0.01" required>
870
  <label for="description">Описание:</label>
871
  <textarea id="description" name="description" rows="4" required></textarea>
 
 
 
 
 
 
 
872
  <label for="photos">Фотографии товара (максимум 2):</label>
873
  <input type="file" id="photos" name="photos" accept="image/*" multiple>
874
  <button type="submit">Добавить товар</button>
875
  </form>
876
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
877
  <h2>Управление базой данных</h2>
878
  <form method="POST" action="{{ url_for('backup') }}">
879
  <button type="submit">Создать резервную копию</button>
@@ -887,6 +1002,7 @@ def admin():
887
  {% for product in products %}
888
  <div class="product-item">
889
  <h3>{{ product['name'] }}</h3>
 
890
  <p><strong>Цена:</strong> {{ product['price'] }}</p>
891
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
892
  {% if product.get('photos') and product['photos']|length > 0 %}
@@ -907,6 +1023,13 @@ def admin():
907
  <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
908
  <label for="description">Описание:</label>
909
  <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
 
 
 
 
 
 
 
910
  <label for="photos">Фотографии товара (максимум 2):</label>
911
  <input type="file" id="photos" name="photos" accept="image/*" multiple>
912
  <button type="submit">Сохранить изменения</button>
@@ -915,7 +1038,7 @@ def admin():
915
  <form method="POST">
916
  <input type="hidden" name="action" value="delete">
917
  <input type="hidden" name="index" value="{{ loop.index0 }}">
918
- <button type="submit">Удалить</button>
919
  </form>
920
  </div>
921
  {% endfor %}
@@ -923,7 +1046,7 @@ def admin():
923
  </body>
924
  </html>
925
  '''
926
- return render_template_string(admin_html, products=products, repo_id=REPO_ID)
927
 
928
  @app.route('/backup', methods=['POST'])
929
  def backup():
 
26
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
27
  data = json.load(file)
28
  logging.info("Данные успешно загружены из JSON")
29
+ # Проверяем структуру данных и добавляем categories, если его нет
30
+ if not isinstance(data, dict) or 'products' not in data or 'categories' not in data:
31
+ return {'products': [], 'categories': [] if not isinstance(data, list) else data}
32
  return data
33
  except FileNotFoundError:
34
  logging.warning("Локальный файл базы данных не найден после скачивания.")
35
+ return {'products': [], 'categories': []}
36
  except json.JSONDecodeError:
37
  logging.error("Ошибка: Невозможно декодировать JSON файл.")
38
+ return {'products': [], 'categories': []}
39
  except RepositoryNotFoundError:
40
  logging.error("Репозиторий не найден. Создание локальной базы данных.")
41
+ return {'products': [], 'categories': []}
42
  except Exception as e:
43
  logging.error(f"Произошла ошибка при загрузке данных: {e}")
44
+ return {'products': [], 'categories': []}
45
 
46
  def save_data(data):
47
  try:
 
93
 
94
  @app.route('/')
95
  def catalog():
96
+ data = load_data()
97
+ products = data['products']
98
+ categories = data['categories']
99
+
100
  catalog_html = '''
101
  <!DOCTYPE html>
102
  <html lang="ru">
 
123
  max-width: 1200px;
124
  margin: 0 auto;
125
  }
126
+ .filters-container {
127
+ margin-bottom: 20px;
128
+ display: flex;
129
+ flex-wrap: wrap;
130
+ gap: 10px;
131
+ justify-content: center;
132
+ }
133
  .search-container {
134
  text-align: center;
135
  margin-bottom: 20px;
 
147
  border-color: #3498db;
148
  box-shadow: 0 0 5px rgba(52, 152, 219, 0.5);
149
  }
150
+ .category-filter {
151
+ padding: 6px 12px;
152
+ border: 1px solid #ddd;
153
+ border-radius: 5px;
154
+ background-color: white;
155
+ cursor: pointer;
156
+ transition: all 0.3s ease;
157
+ }
158
+ .category-filter.active {
159
+ background-color: #3498db;
160
+ color: white;
161
+ border-color: #3498db;
162
+ }
163
  .products-grid {
164
  display: grid;
165
  grid-template-columns: repeat(2, 1fr);
166
  gap: 10px;
167
  padding: 0 5px;
168
  overflow-y: auto;
169
+ max-height: calc(100vh - 160px);
170
  }
171
  .product {
172
  background: #ffffff;
 
372
  </head>
373
  <body>
374
  <div class="container">
375
+ <div class="filters-container">
376
+ <button class="category-filter active" data-category="all">Все категории</button>
377
+ {% for category in categories %}
378
+ <button class="category-filter" data-category="{{ category }}">{{ category }}</button>
379
+ {% endfor %}
380
+ </div>
381
  <div class="search-container">
382
  <input type="text" id="search-input" placeholder="Поиск по названию или описанию...">
383
  </div>
384
  <div class="products-grid" id="products-grid">
385
  {% for product in products %}
386
+ <div class="product"
387
+ data-name="{{ product['name']|lower }}"
388
+ data-description="{{ product['description']|lower }}"
389
+ data-category="{{ product.get('category', 'Без категории') }}">
390
  {% if product.get('photos') and product['photos']|length > 0 %}
391
  <div class="product-image">
392
  <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ product['photos'][0] }}"
 
618
  }
619
  }
620
 
621
+ // Поисковая строка и фильтрация
622
  document.getElementById('search-input').addEventListener('input', function(e) {
623
+ filterProducts();
624
+ });
625
+
626
+ const categoryFilters = document.querySelectorAll('.category-filter');
627
+ categoryFilters.forEach(filter => {
628
+ filter.addEventListener('click', function() {
629
+ categoryFilters.forEach(f => f.classList.remove('active'));
630
+ this.classList.add('active');
631
+ filterProducts();
632
+ });
633
+ });
634
+
635
+ function filterProducts() {
636
+ const searchTerm = document.getElementById('search-input').value.toLowerCase();
637
+ const activeCategory = document.querySelector('.category-filter.active').dataset.category;
638
  const productElements = document.querySelectorAll('.product');
639
+
640
  productElements.forEach(product => {
641
  const name = product.getAttribute('data-name');
642
  const description = product.getAttribute('data-description');
643
+ const category = product.getAttribute('data-category');
644
+
645
+ const matchesSearch = name.includes(searchTerm) || description.includes(searchTerm);
646
+ const matchesCategory = activeCategory === 'all' || category === activeCategory;
647
+
648
+ if (matchesSearch && matchesCategory) {
649
  product.style.display = 'flex';
650
  } else {
651
  product.style.display = 'none';
652
  }
653
  });
654
+ }
655
 
656
  console.log("Инициализация страницы, товары:", products);
657
  updateCartButton();
 
659
  </body>
660
  </html>
661
  '''
662
+ return render_template_string(catalog_html, products=products, categories=categories, repo_id=REPO_ID)
663
 
664
  @app.route('/product/<int:index>')
665
  def product_detail(index):
666
+ data = load_data()
667
+ products = data['products']
668
  try:
669
  product = products[index]
670
  except IndexError:
 
694
  <div class="swiper-button-next"></div>
695
  <div class="swiper-button-prev"></div>
696
  </div>
697
+ <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
698
  <p><strong>Цена:</strong> {{ product['price'] }}</p>
699
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
700
  </div>
 
703
 
704
  @app.route('/admin', methods=['GET', 'POST'])
705
  def admin():
706
+ data = load_data()
707
+ products = data['products']
708
+ categories = data['categories']
709
+
710
  if request.method == 'POST':
711
  action = request.form.get('action')
712
+
713
+ # Обработка категорий
714
+ if action == 'add_category':
715
+ category_name = request.form.get('category_name')
716
+ if category_name and category_name not in categories:
717
+ categories.append(category_name)
718
+ save_data(data)
719
+ logging.info(f"Добавлена новая категория: {category_name}")
720
+ return redirect(url_for('admin'))
721
+ else:
722
+ return "Ошибка: Категория уже существует или не указано название", 400
723
+
724
+ elif action == 'delete_category':
725
+ category_index = int(request.form.get('category_index'))
726
+ deleted_category = categories.pop(category_index)
727
+ # Обновляем продукты, убирая удаленную категорию
728
+ for product in products:
729
+ if product.get('category') == deleted_category:
730
+ product['category'] = 'Без категории'
731
+ save_data(data)
732
+ logging.info(f"Удалена категория: {deleted_category}")
733
+ return redirect(url_for('admin'))
734
+
735
+ # Обработка продуктов
736
+ elif action == 'add':
737
  name = request.form.get('name')
738
  price = request.form.get('price')
739
  description = request.form.get('description')
740
+ category = request.form.get('category')
741
  photos_files = request.files.getlist('photos')
742
  photos_list = []
743
 
744
+ logging.info(f"Добавление нового товара: name={name}, price={price}, description={description}, category={category}")
745
 
746
  if photos_files:
747
  for i, photo in enumerate(photos_files[:2]):
 
784
  'name': name,
785
  'price': price,
786
  'description': description,
787
+ 'category': category if category in categories else 'Без категории',
788
  'photos': photos_list
789
  }
790
 
791
  products.append(new_product)
792
+ save_data(data)
793
+ logging.info(f"Новый товар добавлен: {new_product}")
794
+ return redirect(url_for('admin'))
 
 
 
 
 
 
795
 
796
  elif action == 'edit':
797
  index = int(request.form.get('index'))
798
  name = request.form.get('name')
799
  price = request.form.get('price')
800
  description = request.form.get('description')
801
+ category = request.form.get('category')
802
  photos_files = request.files.getlist('photos')
803
 
804
  logging.info(f"Редактирование товара с индексом {index}")
 
839
  logging.error("Ошибка: Цена должна быть числом")
840
  return "Ошибка: Цена должна быть числом", 400
841
  products[index]['description'] = description
842
+ products[index]['category'] = category if category in categories else 'Без категории'
843
 
844
+ save_data(data)
845
  logging.info(f"Товар с индексом {index} успешно отредактирован")
846
  return redirect(url_for('admin'))
847
 
 
849
  index = int(request.form.get('index'))
850
  logging.info(f"Удаление товара с индексом {index}")
851
  del products[index]
852
+ save_data(data)
853
  logging.info("Товар успешно удален")
854
  return redirect(url_for('admin'))
855
 
 
866
  margin: 20px;
867
  background-color: #f9f9f9;
868
  }
869
+ h1, h2 {
870
  color: #333;
871
  }
872
  form {
 
882
  margin-top: 10px;
883
  color: #555;
884
  }
885
+ input, textarea, select {
886
  width: 100%;
887
  padding: 8px;
888
  margin-top: 5px;
 
902
  button:hover {
903
  background-color: #218838;
904
  }
905
+ .delete-button {
906
+ background-color: #dc3545;
907
+ }
908
+ .delete-button:hover {
909
+ background-color: #c82333;
910
+ }
911
+ .product-list, .category-list {
912
  margin-top: 20px;
913
  }
914
+ .product-item, .category-item {
915
  background-color: #fff;
916
  border: 1px solid #ddd;
917
  padding: 10px;
 
932
  form {
933
  padding: 10px;
934
  }
935
+ input, textarea, select {
936
  font-size: 0.9em;
937
  }
938
  button {
939
  padding: 6px 10px;
940
  font-size: 0.9em;
941
  }
942
+ .product-item, .category-item {
943
  padding: 8px;
944
  }
945
  }
 
955
  <input type="number" id="price" name="price" step="0.01" required>
956
  <label for="description">Описание:</label>
957
  <textarea id="description" name="description" rows="4" required></textarea>
958
+ <label for="category">Категория:</label>
959
+ <select id="category" name="category">
960
+ <option value="Без категории">Без категории</option>
961
+ {% for category in categories %}
962
+ <option value="{{ category }}">{{ category }}</option>
963
+ {% endfor %}
964
+ </select>
965
  <label for="photos">Фотографии товара (максимум 2):</label>
966
  <input type="file" id="photos" name="photos" accept="image/*" multiple>
967
  <button type="submit">Добавить товар</button>
968
  </form>
969
 
970
+ <h1>Управление категориями</h1>
971
+ <form method="POST">
972
+ <input type="hidden" name="action" value="add_category">
973
+ <label for="category_name">Название категории:</label>
974
+ <input type="text" id="category_name" name="category_name" required>
975
+ <button type="submit">Добавить категорию</button>
976
+ </form>
977
+
978
+ <h2>Список категорий</h2>
979
+ <div class="category-list">
980
+ {% for category in categories %}
981
+ <div class="category-item">
982
+ <h3>{{ category }}</h3>
983
+ <form method="POST" style="display: inline;">
984
+ <input type="hidden" name="action" value="delete_category">
985
+ <input type="hidden" name="category_index" value="{{ loop.index0 }}">
986
+ <button type="submit" class="delete-button">Удалить</button>
987
+ </form>
988
+ </div>
989
+ {% endfor %}
990
+ </div>
991
+
992
  <h2>Управление базой данных</h2>
993
  <form method="POST" action="{{ url_for('backup') }}">
994
  <button type="submit">Создать резервную копию</button>
 
1002
  {% for product in products %}
1003
  <div class="product-item">
1004
  <h3>{{ product['name'] }}</h3>
1005
+ <p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
1006
  <p><strong>Цена:</strong> {{ product['price'] }}</p>
1007
  <p><strong>Описание:</strong> {{ product['description'] }}</p>
1008
  {% if product.get('photos') and product['photos']|length > 0 %}
 
1023
  <input type="number" id="price" name="price" step="0.01" value="{{ product['price'] }}" required>
1024
  <label for="description">Описание:</label>
1025
  <textarea id="description" name="description" rows="4" required>{{ product['description'] }}</textarea>
1026
+ <label for="category">Категория:</label>
1027
+ <select id="category" name="category">
1028
+ <option value="Без категории" {% if product.get('category', 'Без категории') == 'Без категории' %}selected{% endif %}>Без категории</option>
1029
+ {% for category in categories %}
1030
+ <option value="{{ category }}" {% if product.get('category') == category %}selected{% endif %}>{{ category }}</option>
1031
+ {% endfor %}
1032
+ </select>
1033
  <label for="photos">Фотографии товара (максимум 2):</label>
1034
  <input type="file" id="photos" name="photos" accept="image/*" multiple>
1035
  <button type="submit">Сохранить изменения</button>
 
1038
  <form method="POST">
1039
  <input type="hidden" name="action" value="delete">
1040
  <input type="hidden" name="index" value="{{ loop.index0 }}">
1041
+ <button type="submit" class="delete-button">Удалить</button>
1042
  </form>
1043
  </div>
1044
  {% endfor %}
 
1046
  </body>
1047
  </html>
1048
  '''
1049
+ return render_template_string(admin_html, products=products, categories=categories, repo_id=REPO_ID)
1050
 
1051
  @app.route('/backup', methods=['POST'])
1052
  def backup():