Update app.py
Browse files
app.py
CHANGED
|
@@ -13,12 +13,12 @@ app = Flask(__name__)
|
|
| 13 |
DATA_FILE = 'data_udeda.json'
|
| 14 |
|
| 15 |
# Настройки Hugging Face
|
| 16 |
-
REPO_ID = "Kgshop/clients"
|
| 17 |
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
|
| 18 |
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
|
| 19 |
|
| 20 |
# Ссылка на логотип
|
| 21 |
-
LOGO_URL = "https://huggingface.co/spaces/Udeda/tkani/resolve/main/Picsart_25-03-18_13-31-30-910.jpg"
|
| 22 |
|
| 23 |
# Настройка логирования
|
| 24 |
logging.basicConfig(level=logging.DEBUG)
|
|
@@ -29,6 +29,12 @@ def load_data():
|
|
| 29 |
with open(DATA_FILE, 'r', encoding='utf-8') as file:
|
| 30 |
data = json.load(file)
|
| 31 |
logging.info("Данные успешно загружены из JSON")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
if not isinstance(data, dict) or 'products' not in data or 'categories' not in data:
|
| 33 |
return {'products': [], 'categories': [] if not isinstance(data, list) else data}
|
| 34 |
return data
|
|
@@ -535,7 +541,7 @@ def catalog():
|
|
| 535 |
zoom: { maxRatio: 3 }
|
| 536 |
});
|
| 537 |
}
|
| 538 |
-
|
| 539 |
function openQuantityModal(index) {
|
| 540 |
selectedProductIndex = index;
|
| 541 |
const product = products[index];
|
|
@@ -562,12 +568,20 @@ def catalog():
|
|
| 562 |
if (selectedProductIndex === null) return;
|
| 563 |
const quantity = parseInt(document.getElementById('quantityInput').value) || 1;
|
| 564 |
const color = document.getElementById('colorSelect').value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
if (quantity <= 0) {
|
| 566 |
alert("Укажите количество больше 0");
|
| 567 |
return;
|
| 568 |
}
|
| 569 |
let cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 570 |
-
|
| 571 |
const cartItemId = `${product.name}-${color}`;
|
| 572 |
const existingItem = cart.find(item => item.id === cartItemId);
|
| 573 |
|
|
@@ -580,7 +594,8 @@ def catalog():
|
|
| 580 |
price: product.price,
|
| 581 |
photo: product.photos && product.photos.length > 0 ? product.photos[0] : '',
|
| 582 |
quantity: quantity,
|
| 583 |
-
color: color
|
|
|
|
| 584 |
});
|
| 585 |
}
|
| 586 |
|
|
@@ -609,6 +624,7 @@ def catalog():
|
|
| 609 |
<div>
|
| 610 |
<strong>${item.name}</strong>
|
| 611 |
<p>$${item.price} × ${item.quantity} (Цвет: ${item.color})</p>
|
|
|
|
| 612 |
</div>
|
| 613 |
</div>
|
| 614 |
<span>$${itemTotal}</span>
|
|
@@ -631,10 +647,10 @@ def catalog():
|
|
| 631 |
cart.forEach((item, index) => {
|
| 632 |
const itemTotal = item.price * item.quantity;
|
| 633 |
total += itemTotal;
|
| 634 |
-
orderText += `${index + 1}. ${item.name} - $${item.price} × ${item.quantity} (Цвет: ${item.color})%0A`;
|
| 635 |
});
|
| 636 |
orderText += `Итого: $${total}`;
|
| 637 |
-
window.open(`https://api.whatsapp.com/send?phone=
|
| 638 |
}
|
| 639 |
|
| 640 |
function clearCart() {
|
|
@@ -711,8 +727,9 @@ def product_detail(index):
|
|
| 711 |
</div>
|
| 712 |
<p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
|
| 713 |
<p><strong>Цена:</strong> ${{ product['price'] }}</p>
|
| 714 |
-
<p><strong
|
| 715 |
<p><strong>Доступные цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
|
|
|
|
| 716 |
</div>
|
| 717 |
'''
|
| 718 |
return render_template_string(detail_html, product=product, repo_id=REPO_ID)
|
|
@@ -750,6 +767,7 @@ def admin():
|
|
| 750 |
category = request.form.get('category')
|
| 751 |
photos_files = request.files.getlist('photos')
|
| 752 |
colors = request.form.getlist('colors')
|
|
|
|
| 753 |
photos_list = []
|
| 754 |
|
| 755 |
if photos_files:
|
|
@@ -758,6 +776,7 @@ def admin():
|
|
| 758 |
photo_filename = secure_filename(photo.filename)
|
| 759 |
uploads_dir = 'uploads'
|
| 760 |
os.makedirs(uploads_dir, exist_ok=True)
|
|
|
|
| 761 |
temp_path = os.path.join(uploads_dir, photo_filename)
|
| 762 |
photo.save(temp_path)
|
| 763 |
api = HfApi()
|
|
@@ -773,17 +792,23 @@ def admin():
|
|
| 773 |
if os.path.exists(temp_path):
|
| 774 |
os.remove(temp_path)
|
| 775 |
|
| 776 |
-
if not name or not price or not description:
|
| 777 |
return "Ошибка: Заполните все обязательные поля", 400
|
| 778 |
|
| 779 |
price = float(price.replace(',', '.'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
new_product = {
|
| 781 |
'name': name,
|
| 782 |
'price': price,
|
| 783 |
'description': description,
|
| 784 |
'category': category if category in categories else 'Без категории',
|
| 785 |
'photos': photos_list,
|
| 786 |
-
'colors': colors if colors else []
|
|
|
|
| 787 |
}
|
| 788 |
products.append(new_product)
|
| 789 |
save_data(data)
|
|
@@ -797,6 +822,7 @@ def admin():
|
|
| 797 |
category = request.form.get('category')
|
| 798 |
photos_files = request.files.getlist('photos')
|
| 799 |
colors = request.form.getlist('colors')
|
|
|
|
| 800 |
|
| 801 |
if photos_files and any(photo.filename for photo in photos_files):
|
| 802 |
new_photos_list = []
|
|
@@ -820,12 +846,21 @@ def admin():
|
|
| 820 |
if os.path.exists(temp_path):
|
| 821 |
os.remove(temp_path)
|
| 822 |
products[index]['photos'] = new_photos_list
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 823 |
|
| 824 |
products[index]['name'] = name
|
| 825 |
products[index]['price'] = float(price.replace(',', '.'))
|
| 826 |
products[index]['description'] = description
|
| 827 |
products[index]['category'] = category if category in categories else 'Без категории'
|
| 828 |
products[index]['colors'] = colors if colors else []
|
|
|
|
| 829 |
save_data(data)
|
| 830 |
return redirect(url_for('admin'))
|
| 831 |
|
|
@@ -968,7 +1003,7 @@ def admin():
|
|
| 968 |
<input type="text" name="name" required>
|
| 969 |
<label>Цена ($):</label>
|
| 970 |
<input type="number" name="price" step="0.01" required>
|
| 971 |
-
<label
|
| 972 |
<textarea name="description" rows="4" required></textarea>
|
| 973 |
<label>Категория:</label>
|
| 974 |
<select name="category">
|
|
@@ -986,6 +1021,8 @@ def admin():
|
|
| 986 |
</div>
|
| 987 |
</div>
|
| 988 |
<button type="button" class="add-color-btn" onclick="addColorInput()">Добавить цвет</button>
|
|
|
|
|
|
|
| 989 |
<button type="submit">Добавить товар</button>
|
| 990 |
</form>
|
| 991 |
|
|
@@ -1026,8 +1063,9 @@ def admin():
|
|
| 1026 |
<h3>{{ product['name'] }}</h3>
|
| 1027 |
<p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
|
| 1028 |
<p><strong>Цена:</strong> ${{ product['price'] }}</p>
|
| 1029 |
-
<p><strong
|
| 1030 |
<p><strong>Цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
|
|
|
|
| 1031 |
{% if product.get('photos') and product['photos']|length > 0 %}
|
| 1032 |
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
|
| 1033 |
{% for photo in product['photos'] %}
|
|
@@ -1046,7 +1084,7 @@ def admin():
|
|
| 1046 |
<input type="text" name="name" value="{{ product['name'] }}" required>
|
| 1047 |
<label>Цена ($):</label>
|
| 1048 |
<input type="number" name="price" step="0.01" value="{{ product['price'] }}" required>
|
| 1049 |
-
<label
|
| 1050 |
<textarea name="description" rows="4" required>{{ product['description'] }}</textarea>
|
| 1051 |
<label>Категория:</label>
|
| 1052 |
<select name="category">
|
|
@@ -1066,6 +1104,8 @@ def admin():
|
|
| 1066 |
{% endfor %}
|
| 1067 |
</div>
|
| 1068 |
<button type="button" class="add-color-btn" onclick="addColorInput('edit-color-inputs-{{ loop.index0 }}')">Добавить цвет</button>
|
|
|
|
|
|
|
| 1069 |
<button type="submit">Сохранить</button>
|
| 1070 |
</form>
|
| 1071 |
</details>
|
|
|
|
| 13 |
DATA_FILE = 'data_udeda.json'
|
| 14 |
|
| 15 |
# Настройки Hugging Face
|
| 16 |
+
REPO_ID = "Kgshop/clients" # Замените на ваш ID репозитория
|
| 17 |
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
|
| 18 |
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
|
| 19 |
|
| 20 |
# Ссылка на логотип
|
| 21 |
+
LOGO_URL = "https://huggingface.co/spaces/Udeda/tkani/resolve/main/Picsart_25-03-18_13-31-30-910.jpg" # Замените на вашу ссылку
|
| 22 |
|
| 23 |
# Настройка логирования
|
| 24 |
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
| 29 |
with open(DATA_FILE, 'r', encoding='utf-8') as file:
|
| 30 |
data = json.load(file)
|
| 31 |
logging.info("Данные успешно загружены из JSON")
|
| 32 |
+
# Добавим проверку наличия roll_meters, если его нет - проинициализируем
|
| 33 |
+
if isinstance(data, dict) and 'products' in data:
|
| 34 |
+
for product in data['products']:
|
| 35 |
+
if 'roll_meters' not in product:
|
| 36 |
+
product['roll_meters'] = 0 # Или другое значение по умолчанию
|
| 37 |
+
|
| 38 |
if not isinstance(data, dict) or 'products' not in data or 'categories' not in data:
|
| 39 |
return {'products': [], 'categories': [] if not isinstance(data, list) else data}
|
| 40 |
return data
|
|
|
|
| 541 |
zoom: { maxRatio: 3 }
|
| 542 |
});
|
| 543 |
}
|
| 544 |
+
|
| 545 |
function openQuantityModal(index) {
|
| 546 |
selectedProductIndex = index;
|
| 547 |
const product = products[index];
|
|
|
|
| 568 |
if (selectedProductIndex === null) return;
|
| 569 |
const quantity = parseInt(document.getElementById('quantityInput').value) || 1;
|
| 570 |
const color = document.getElementById('colorSelect').value;
|
| 571 |
+
const product = products[selectedProductIndex];
|
| 572 |
+
|
| 573 |
+
// Проверка на минимальное количество метров (roll_meters)
|
| 574 |
+
if (quantity < product.roll_meters) {
|
| 575 |
+
alert(`Минимальный заказ для этого товара: ${product.roll_meters} метров.`);
|
| 576 |
+
return;
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
if (quantity <= 0) {
|
| 580 |
alert("Укажите количество больше 0");
|
| 581 |
return;
|
| 582 |
}
|
| 583 |
let cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 584 |
+
|
| 585 |
const cartItemId = `${product.name}-${color}`;
|
| 586 |
const existingItem = cart.find(item => item.id === cartItemId);
|
| 587 |
|
|
|
|
| 594 |
price: product.price,
|
| 595 |
photo: product.photos && product.photos.length > 0 ? product.photos[0] : '',
|
| 596 |
quantity: quantity,
|
| 597 |
+
color: color,
|
| 598 |
+
roll_meters: product.roll_meters // Добавляем roll_meters в корзину
|
| 599 |
});
|
| 600 |
}
|
| 601 |
|
|
|
|
| 624 |
<div>
|
| 625 |
<strong>${item.name}</strong>
|
| 626 |
<p>$${item.price} × ${item.quantity} (Цвет: ${item.color})</p>
|
| 627 |
+
<p>Метров в рулоне: ${item.roll_meters}</p>
|
| 628 |
</div>
|
| 629 |
</div>
|
| 630 |
<span>$${itemTotal}</span>
|
|
|
|
| 647 |
cart.forEach((item, index) => {
|
| 648 |
const itemTotal = item.price * item.quantity;
|
| 649 |
total += itemTotal;
|
| 650 |
+
orderText += `${index + 1}. ${item.name} - $${item.price} × ${item.quantity} (Цвет: ${item.color}, Метров в рулоне: ${item.roll_meters})%0A`;
|
| 651 |
});
|
| 652 |
orderText += `Итого: $${total}`;
|
| 653 |
+
window.open(`https://api.whatsapp.com/send?phone=996775581999&text=${orderText}`, '_blank');
|
| 654 |
}
|
| 655 |
|
| 656 |
function clearCart() {
|
|
|
|
| 727 |
</div>
|
| 728 |
<p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
|
| 729 |
<p><strong>Цена:</strong> ${{ product['price'] }}</p>
|
| 730 |
+
<p><strong>Состав:</strong> {{ product['description'] }}</p>
|
| 731 |
<p><strong>Доступные цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
|
| 732 |
+
<p><strong>Метров в рулоне:</strong> {{ product.get('roll_meters', 'Не указано') }}</p>
|
| 733 |
</div>
|
| 734 |
'''
|
| 735 |
return render_template_string(detail_html, product=product, repo_id=REPO_ID)
|
|
|
|
| 767 |
category = request.form.get('category')
|
| 768 |
photos_files = request.files.getlist('photos')
|
| 769 |
colors = request.form.getlist('colors')
|
| 770 |
+
roll_meters = request.form.get('roll_meters') # Получаем roll_meters
|
| 771 |
photos_list = []
|
| 772 |
|
| 773 |
if photos_files:
|
|
|
|
| 776 |
photo_filename = secure_filename(photo.filename)
|
| 777 |
uploads_dir = 'uploads'
|
| 778 |
os.makedirs(uploads_dir, exist_ok=True)
|
| 779 |
+
|
| 780 |
temp_path = os.path.join(uploads_dir, photo_filename)
|
| 781 |
photo.save(temp_path)
|
| 782 |
api = HfApi()
|
|
|
|
| 792 |
if os.path.exists(temp_path):
|
| 793 |
os.remove(temp_path)
|
| 794 |
|
| 795 |
+
if not name or not price or not description or not roll_meters:
|
| 796 |
return "Ошибка: Заполните все обязательные поля", 400
|
| 797 |
|
| 798 |
price = float(price.replace(',', '.'))
|
| 799 |
+
try:
|
| 800 |
+
roll_meters = int(roll_meters)
|
| 801 |
+
except ValueError:
|
| 802 |
+
return "Ошибка: Количество метров в рулоне должно быть целым числом", 400
|
| 803 |
+
|
| 804 |
new_product = {
|
| 805 |
'name': name,
|
| 806 |
'price': price,
|
| 807 |
'description': description,
|
| 808 |
'category': category if category in categories else 'Без категории',
|
| 809 |
'photos': photos_list,
|
| 810 |
+
'colors': colors if colors else [],
|
| 811 |
+
'roll_meters': roll_meters # Добавляем roll_meters в продукт
|
| 812 |
}
|
| 813 |
products.append(new_product)
|
| 814 |
save_data(data)
|
|
|
|
| 822 |
category = request.form.get('category')
|
| 823 |
photos_files = request.files.getlist('photos')
|
| 824 |
colors = request.form.getlist('colors')
|
| 825 |
+
roll_meters = request.form.get('roll_meters') # Добавляем roll_meters
|
| 826 |
|
| 827 |
if photos_files and any(photo.filename for photo in photos_files):
|
| 828 |
new_photos_list = []
|
|
|
|
| 846 |
if os.path.exists(temp_path):
|
| 847 |
os.remove(temp_path)
|
| 848 |
products[index]['photos'] = new_photos_list
|
| 849 |
+
|
| 850 |
+
if not roll_meters:
|
| 851 |
+
return "Ошибка: Укажите количество метров в рулоне", 400
|
| 852 |
+
|
| 853 |
+
try:
|
| 854 |
+
roll_meters = int(roll_meters)
|
| 855 |
+
except ValueError:
|
| 856 |
+
return "Ошибка: Количество метров в рулоне должно быть целым числом", 400
|
| 857 |
|
| 858 |
products[index]['name'] = name
|
| 859 |
products[index]['price'] = float(price.replace(',', '.'))
|
| 860 |
products[index]['description'] = description
|
| 861 |
products[index]['category'] = category if category in categories else 'Без категории'
|
| 862 |
products[index]['colors'] = colors if colors else []
|
| 863 |
+
products[index]['roll_meters'] = roll_meters # Обновляем roll_meters
|
| 864 |
save_data(data)
|
| 865 |
return redirect(url_for('admin'))
|
| 866 |
|
|
|
|
| 1003 |
<input type="text" name="name" required>
|
| 1004 |
<label>Цена ($):</label>
|
| 1005 |
<input type="number" name="price" step="0.01" required>
|
| 1006 |
+
<label>Состав:</label>
|
| 1007 |
<textarea name="description" rows="4" required></textarea>
|
| 1008 |
<label>Категория:</label>
|
| 1009 |
<select name="category">
|
|
|
|
| 1021 |
</div>
|
| 1022 |
</div>
|
| 1023 |
<button type="button" class="add-color-btn" onclick="addColorInput()">Добавить цвет</button>
|
| 1024 |
+
<label>Метров в рулоне:</label>
|
| 1025 |
+
<input type="number" name="roll_meters" required>
|
| 1026 |
<button type="submit">Добавить товар</button>
|
| 1027 |
</form>
|
| 1028 |
|
|
|
|
| 1063 |
<h3>{{ product['name'] }}</h3>
|
| 1064 |
<p><strong>Категория:</strong> {{ product.get('category', 'Без категории') }}</p>
|
| 1065 |
<p><strong>Цена:</strong> ${{ product['price'] }}</p>
|
| 1066 |
+
<p><strong>Состав:</strong> {{ product['description'] }}</p>
|
| 1067 |
<p><strong>Цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
|
| 1068 |
+
<p><strong>Метров в рулоне:</strong> {{ product.get('roll_meters', 'Не указано') }}</p>
|
| 1069 |
{% if product.get('photos') and product['photos']|length > 0 %}
|
| 1070 |
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
|
| 1071 |
{% for photo in product['photos'] %}
|
|
|
|
| 1084 |
<input type="text" name="name" value="{{ product['name'] }}" required>
|
| 1085 |
<label>Цена ($):</label>
|
| 1086 |
<input type="number" name="price" step="0.01" value="{{ product['price'] }}" required>
|
| 1087 |
+
<label>Состав:</label>
|
| 1088 |
<textarea name="description" rows="4" required>{{ product['description'] }}</textarea>
|
| 1089 |
<label>Категория:</label>
|
| 1090 |
<select name="category">
|
|
|
|
| 1104 |
{% endfor %}
|
| 1105 |
</div>
|
| 1106 |
<button type="button" class="add-color-btn" onclick="addColorInput('edit-color-inputs-{{ loop.index0 }}')">Добавить цвет</button>
|
| 1107 |
+
<label>Метров в рулоне:</label>
|
| 1108 |
+
<input type="number" name="roll_meters" value="{{ product.get('roll_meters', '') }}" required>
|
| 1109 |
<button type="submit">Сохранить</button>
|
| 1110 |
</form>
|
| 1111 |
</details>
|