Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -12,15 +12,12 @@ from werkzeug.utils import secure_filename
|
|
| 12 |
app = Flask(__name__)
|
| 13 |
DATA_FILE = 'data.json'
|
| 14 |
|
| 15 |
-
# Настройки Hugging Face
|
| 16 |
REPO_ID = "Kgshop/ecokozha.data"
|
| 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/Kgshop/ecokozha/resolve/main/412767655_723792496081352_8094264164475669882_n.jpg"
|
| 22 |
|
| 23 |
-
# Настройка логирования
|
| 24 |
logging.basicConfig(level=logging.DEBUG)
|
| 25 |
|
| 26 |
def load_data():
|
|
@@ -450,7 +447,6 @@ def catalog():
|
|
| 450 |
</div>
|
| 451 |
</div>
|
| 452 |
|
| 453 |
-
<!-- Product Modal -->
|
| 454 |
<div id="productModal" class="modal">
|
| 455 |
<div class="modal-content">
|
| 456 |
<span class="close" onclick="closeModal('productModal')">×</span>
|
|
@@ -458,18 +454,16 @@ def catalog():
|
|
| 458 |
</div>
|
| 459 |
</div>
|
| 460 |
|
| 461 |
-
<!-- Quantity and Color Modal -->
|
| 462 |
<div id="quantityModal" class="modal">
|
| 463 |
<div class="modal-content">
|
| 464 |
<span class="close" onclick="closeModal('quantityModal')">×</span>
|
| 465 |
<h2>Укажите количество и цвет</h2>
|
| 466 |
-
<input type="number" id="quantityInput" class="quantity-input" min="1" value="1">
|
| 467 |
<select id="colorSelect" class="color-select"></select>
|
| 468 |
<button class="product-button" onclick="confirmAddToCart()">Добавить</button>
|
| 469 |
</div>
|
| 470 |
</div>
|
| 471 |
|
| 472 |
-
<!-- Cart Modal -->
|
| 473 |
<div id="cartModal" class="modal">
|
| 474 |
<div class="modal-content">
|
| 475 |
<span class="close" onclick="closeModal('cartModal')">×</span>
|
|
@@ -560,12 +554,14 @@ def catalog():
|
|
| 560 |
|
| 561 |
function confirmAddToCart() {
|
| 562 |
if (selectedProductIndex === null) return;
|
| 563 |
-
const quantity =
|
| 564 |
const color = document.getElementById('colorSelect').value;
|
| 565 |
-
|
| 566 |
-
|
|
|
|
| 567 |
return;
|
| 568 |
}
|
|
|
|
| 569 |
let cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 570 |
const product = products[selectedProductIndex];
|
| 571 |
const cartItemId = `${product.name}-${color}`;
|
|
@@ -608,15 +604,15 @@ def catalog():
|
|
| 608 |
${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/${item.photo}" alt="${item.name}">` : ''}
|
| 609 |
<div>
|
| 610 |
<strong>${item.name}</strong>
|
| 611 |
-
<p>${item.price} с × ${item.quantity} (Цвет: ${item.color})</p>
|
| 612 |
</div>
|
| 613 |
</div>
|
| 614 |
-
<span>${itemTotal} с</span>
|
| 615 |
</div>
|
| 616 |
`;
|
| 617 |
}).join('');
|
| 618 |
|
| 619 |
-
document.getElementById('cartTotal').textContent = total;
|
| 620 |
document.getElementById('cartModal').style.display = 'block';
|
| 621 |
}
|
| 622 |
|
|
@@ -631,9 +627,9 @@ 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=996702588388&text=${orderText}`, '_blank');
|
| 638 |
}
|
| 639 |
|
|
@@ -753,7 +749,7 @@ def admin():
|
|
| 753 |
photos_list = []
|
| 754 |
|
| 755 |
if photos_files:
|
| 756 |
-
for photo in photos_files[:10]:
|
| 757 |
if photo and photo.filename:
|
| 758 |
photo_filename = secure_filename(photo.filename)
|
| 759 |
uploads_dir = 'uploads'
|
|
@@ -783,7 +779,7 @@ def admin():
|
|
| 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)
|
|
@@ -800,7 +796,7 @@ def admin():
|
|
| 800 |
|
| 801 |
if photos_files and any(photo.filename for photo in photos_files):
|
| 802 |
new_photos_list = []
|
| 803 |
-
for photo in photos_files[:10]:
|
| 804 |
if photo and photo.filename:
|
| 805 |
photo_filename = secure_filename(photo.filename)
|
| 806 |
uploads_dir = 'uploads'
|
|
@@ -825,7 +821,7 @@ def admin():
|
|
| 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 |
|
|
@@ -897,6 +893,7 @@ def admin():
|
|
| 897 |
border-radius: 8px;
|
| 898 |
font-size: 1rem;
|
| 899 |
transition: all 0.3s ease;
|
|
|
|
| 900 |
}
|
| 901 |
input:focus, textarea:focus, select:focus {
|
| 902 |
border-color: #3b82f6;
|
|
@@ -946,9 +943,20 @@ def admin():
|
|
| 946 |
display: flex;
|
| 947 |
gap: 10px;
|
| 948 |
margin-top: 5px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 949 |
}
|
| 950 |
.add-color-btn {
|
| 951 |
background-color: #10b981;
|
|
|
|
| 952 |
}
|
| 953 |
.add-color-btn:hover {
|
| 954 |
background-color: #059669;
|
|
@@ -980,12 +988,13 @@ def admin():
|
|
| 980 |
<label>Фотографии (до 10):</label>
|
| 981 |
<input type="file" name="photos" accept="image/*" multiple>
|
| 982 |
<label>Цвета:</label>
|
| 983 |
-
<div id="color-inputs">
|
| 984 |
<div class="color-input-group">
|
| 985 |
<input type="text" name="colors" placeholder="Например: Красный">
|
|
|
|
| 986 |
</div>
|
| 987 |
</div>
|
| 988 |
-
<button type="button" class="add-color-btn" onclick="addColorInput()">Добавить цвет</button>
|
| 989 |
<button type="submit">Добавить товар</button>
|
| 990 |
</form>
|
| 991 |
|
|
@@ -1029,7 +1038,7 @@ def admin():
|
|
| 1029 |
<p><strong>Описание:</strong> {{ product['description'] }}</p>
|
| 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'] %}
|
| 1034 |
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}"
|
| 1035 |
alt="{{ product['name'] }}"
|
|
@@ -1051,25 +1060,32 @@ def admin():
|
|
| 1051 |
<label>Категория:</label>
|
| 1052 |
<select name="category">
|
| 1053 |
<option value="Без категории" {% if product.get('category', 'Без категории') == 'Без категории' %}selected{% endif %}>Без категории</option>
|
| 1054 |
-
{% for
|
| 1055 |
-
<option value="{{
|
| 1056 |
{% endfor %}
|
| 1057 |
</select>
|
| 1058 |
-
<label>Фотографии (до 10):</label>
|
| 1059 |
<input type="file" name="photos" accept="image/*" multiple>
|
| 1060 |
<label>Цвета:</label>
|
| 1061 |
<div id="edit-color-inputs-{{ loop.index0 }}">
|
| 1062 |
{% for color in product.get('colors', []) %}
|
| 1063 |
<div class="color-input-group">
|
| 1064 |
<input type="text" name="colors" value="{{ color }}">
|
|
|
|
| 1065 |
</div>
|
| 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>
|
| 1072 |
-
<form method="POST">
|
| 1073 |
<input type="hidden" name="action" value="delete">
|
| 1074 |
<input type="hidden" name="index" value="{{ loop.index0 }}">
|
| 1075 |
<button type="submit" class="delete-button">Удалить</button>
|
|
@@ -1079,12 +1095,27 @@ def admin():
|
|
| 1079 |
</div>
|
| 1080 |
</div>
|
| 1081 |
<script>
|
| 1082 |
-
function addColorInput(containerId
|
| 1083 |
const container = document.getElementById(containerId);
|
| 1084 |
-
const
|
| 1085 |
-
|
| 1086 |
-
|
| 1087 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1088 |
}
|
| 1089 |
</script>
|
| 1090 |
</body>
|
|
@@ -1103,8 +1134,9 @@ def download():
|
|
| 1103 |
return "База данных скачана.", 200
|
| 1104 |
|
| 1105 |
if __name__ == '__main__':
|
| 1106 |
-
|
| 1107 |
-
|
|
|
|
| 1108 |
try:
|
| 1109 |
load_data()
|
| 1110 |
except Exception as e:
|
|
|
|
| 12 |
app = Flask(__name__)
|
| 13 |
DATA_FILE = 'data.json'
|
| 14 |
|
|
|
|
| 15 |
REPO_ID = "Kgshop/ecokozha.data"
|
| 16 |
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
|
| 17 |
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ")
|
| 18 |
|
|
|
|
| 19 |
LOGO_URL = "https://huggingface.co/spaces/Kgshop/ecokozha/resolve/main/412767655_723792496081352_8094264164475669882_n.jpg"
|
| 20 |
|
|
|
|
| 21 |
logging.basicConfig(level=logging.DEBUG)
|
| 22 |
|
| 23 |
def load_data():
|
|
|
|
| 447 |
</div>
|
| 448 |
</div>
|
| 449 |
|
|
|
|
| 450 |
<div id="productModal" class="modal">
|
| 451 |
<div class="modal-content">
|
| 452 |
<span class="close" onclick="closeModal('productModal')">×</span>
|
|
|
|
| 454 |
</div>
|
| 455 |
</div>
|
| 456 |
|
|
|
|
| 457 |
<div id="quantityModal" class="modal">
|
| 458 |
<div class="modal-content">
|
| 459 |
<span class="close" onclick="closeModal('quantityModal')">×</span>
|
| 460 |
<h2>Укажите количество и цвет</h2>
|
| 461 |
+
<input type="number" id="quantityInput" class="quantity-input" step="0.1" min="0.1" value="1">
|
| 462 |
<select id="colorSelect" class="color-select"></select>
|
| 463 |
<button class="product-button" onclick="confirmAddToCart()">Добавить</button>
|
| 464 |
</div>
|
| 465 |
</div>
|
| 466 |
|
|
|
|
| 467 |
<div id="cartModal" class="modal">
|
| 468 |
<div class="modal-content">
|
| 469 |
<span class="close" onclick="closeModal('cartModal')">×</span>
|
|
|
|
| 554 |
|
| 555 |
function confirmAddToCart() {
|
| 556 |
if (selectedProductIndex === null) return;
|
| 557 |
+
const quantity = parseFloat(document.getElementById('quantityInput').value);
|
| 558 |
const color = document.getElementById('colorSelect').value;
|
| 559 |
+
|
| 560 |
+
if (isNaN(quantity) || quantity <= 0) {
|
| 561 |
+
alert("Укажите корректное количество больше 0");
|
| 562 |
return;
|
| 563 |
}
|
| 564 |
+
|
| 565 |
let cart = JSON.parse(localStorage.getItem('cart') || '[]');
|
| 566 |
const product = products[selectedProductIndex];
|
| 567 |
const cartItemId = `${product.name}-${color}`;
|
|
|
|
| 604 |
${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/${item.photo}" alt="${item.name}">` : ''}
|
| 605 |
<div>
|
| 606 |
<strong>${item.name}</strong>
|
| 607 |
+
<p>${item.price} с × ${item.quantity.toFixed(2)} (Цвет: ${item.color})</p>
|
| 608 |
</div>
|
| 609 |
</div>
|
| 610 |
+
<span>${itemTotal.toFixed(2)} с</span>
|
| 611 |
</div>
|
| 612 |
`;
|
| 613 |
}).join('');
|
| 614 |
|
| 615 |
+
document.getElementById('cartTotal').textContent = total.toFixed(2);
|
| 616 |
document.getElementById('cartModal').style.display = 'block';
|
| 617 |
}
|
| 618 |
|
|
|
|
| 627 |
cart.forEach((item, index) => {
|
| 628 |
const itemTotal = item.price * item.quantity;
|
| 629 |
total += itemTotal;
|
| 630 |
+
orderText += `${index + 1}. ${item.name} - ${item.price} с × ${item.quantity.toFixed(2)} (Цвет: ${item.color})%0A`;
|
| 631 |
});
|
| 632 |
+
orderText += `Итого: ${total.toFixed(2)} с`;
|
| 633 |
window.open(`https://api.whatsapp.com/send?phone=996702588388&text=${orderText}`, '_blank');
|
| 634 |
}
|
| 635 |
|
|
|
|
| 749 |
photos_list = []
|
| 750 |
|
| 751 |
if photos_files:
|
| 752 |
+
for photo in photos_files[:10]:
|
| 753 |
if photo and photo.filename:
|
| 754 |
photo_filename = secure_filename(photo.filename)
|
| 755 |
uploads_dir = 'uploads'
|
|
|
|
| 779 |
'description': description,
|
| 780 |
'category': category if category in categories else 'Без категории',
|
| 781 |
'photos': photos_list,
|
| 782 |
+
'colors': [c for c in colors if c.strip()] if colors else []
|
| 783 |
}
|
| 784 |
products.append(new_product)
|
| 785 |
save_data(data)
|
|
|
|
| 796 |
|
| 797 |
if photos_files and any(photo.filename for photo in photos_files):
|
| 798 |
new_photos_list = []
|
| 799 |
+
for photo in photos_files[:10]:
|
| 800 |
if photo and photo.filename:
|
| 801 |
photo_filename = secure_filename(photo.filename)
|
| 802 |
uploads_dir = 'uploads'
|
|
|
|
| 821 |
products[index]['price'] = float(price.replace(',', '.'))
|
| 822 |
products[index]['description'] = description
|
| 823 |
products[index]['category'] = category if category in categories else 'Без категории'
|
| 824 |
+
products[index]['colors'] = [c for c in colors if c.strip()] if colors else []
|
| 825 |
save_data(data)
|
| 826 |
return redirect(url_for('admin'))
|
| 827 |
|
|
|
|
| 893 |
border-radius: 8px;
|
| 894 |
font-size: 1rem;
|
| 895 |
transition: all 0.3s ease;
|
| 896 |
+
box-sizing: border-box;
|
| 897 |
}
|
| 898 |
input:focus, textarea:focus, select:focus {
|
| 899 |
border-color: #3b82f6;
|
|
|
|
| 943 |
display: flex;
|
| 944 |
gap: 10px;
|
| 945 |
margin-top: 5px;
|
| 946 |
+
align-items: center;
|
| 947 |
+
}
|
| 948 |
+
.color-input-group input {
|
| 949 |
+
flex-grow: 1;
|
| 950 |
+
}
|
| 951 |
+
.remove-color-btn {
|
| 952 |
+
background-color: #ef4444;
|
| 953 |
+
padding: 8px 12px;
|
| 954 |
+
font-size: 0.8rem;
|
| 955 |
+
margin-top: 5px;
|
| 956 |
}
|
| 957 |
.add-color-btn {
|
| 958 |
background-color: #10b981;
|
| 959 |
+
margin-bottom: 10px;
|
| 960 |
}
|
| 961 |
.add-color-btn:hover {
|
| 962 |
background-color: #059669;
|
|
|
|
| 988 |
<label>Фотографии (до 10):</label>
|
| 989 |
<input type="file" name="photos" accept="image/*" multiple>
|
| 990 |
<label>Цвета:</label>
|
| 991 |
+
<div id="color-inputs-add">
|
| 992 |
<div class="color-input-group">
|
| 993 |
<input type="text" name="colors" placeholder="Например: Красный">
|
| 994 |
+
<button type="button" class="remove-color-btn" onclick="this.parentElement.remove()">Удалить</button>
|
| 995 |
</div>
|
| 996 |
</div>
|
| 997 |
+
<button type="button" class="add-color-btn" onclick="addColorInput('color-inputs-add')">Добавить цвет</button>
|
| 998 |
<button type="submit">Добавить товар</button>
|
| 999 |
</form>
|
| 1000 |
|
|
|
|
| 1038 |
<p><strong>Описание:</strong> {{ product['description'] }}</p>
|
| 1039 |
<p><strong>Цвета:</strong> {{ product.get('colors', ['Нет цветов'])|join(', ') }}</p>
|
| 1040 |
{% if product.get('photos') and product['photos']|length > 0 %}
|
| 1041 |
+
<div style="display: flex; flex-wrap: wrap; gap: 10px; margin-bottom:10px;">
|
| 1042 |
{% for photo in product['photos'] %}
|
| 1043 |
<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/photos/{{ photo }}"
|
| 1044 |
alt="{{ product['name'] }}"
|
|
|
|
| 1060 |
<label>Категория:</label>
|
| 1061 |
<select name="category">
|
| 1062 |
<option value="Без категории" {% if product.get('category', 'Без категории') == 'Без категории' %}selected{% endif %}>Без категории</option>
|
| 1063 |
+
{% for category_item in categories %}
|
| 1064 |
+
<option value="{{ category_item }}" {% if product.get('category') == category_item %}selected{% endif %}>{{ category_item }}</option>
|
| 1065 |
{% endfor %}
|
| 1066 |
</select>
|
| 1067 |
+
<label>Фотографии (до 10, выберите новые, чтобы заменить старые):</label>
|
| 1068 |
<input type="file" name="photos" accept="image/*" multiple>
|
| 1069 |
<label>Цвета:</label>
|
| 1070 |
<div id="edit-color-inputs-{{ loop.index0 }}">
|
| 1071 |
{% for color in product.get('colors', []) %}
|
| 1072 |
<div class="color-input-group">
|
| 1073 |
<input type="text" name="colors" value="{{ color }}">
|
| 1074 |
+
<button type="button" class="remove-color-btn" onclick="this.parentElement.remove()">Удалить</button>
|
| 1075 |
</div>
|
| 1076 |
{% endfor %}
|
| 1077 |
+
{% if not product.get('colors') %}
|
| 1078 |
+
<div class="color-input-group">
|
| 1079 |
+
<input type="text" name="colors" placeholder="Например: Синий">
|
| 1080 |
+
<button type="button" class="remove-color-btn" onclick="this.parentElement.remove()">Удалить</button>
|
| 1081 |
+
</div>
|
| 1082 |
+
{% endif %}
|
| 1083 |
</div>
|
| 1084 |
<button type="button" class="add-color-btn" onclick="addColorInput('edit-color-inputs-{{ loop.index0 }}')">Добавить цвет</button>
|
| 1085 |
<button type="submit">Сохранить</button>
|
| 1086 |
</form>
|
| 1087 |
</details>
|
| 1088 |
+
<form method="POST" style="display: inline;">
|
| 1089 |
<input type="hidden" name="action" value="delete">
|
| 1090 |
<input type="hidden" name="index" value="{{ loop.index0 }}">
|
| 1091 |
<button type="submit" class="delete-button">Удалить</button>
|
|
|
|
| 1095 |
</div>
|
| 1096 |
</div>
|
| 1097 |
<script>
|
| 1098 |
+
function addColorInput(containerId) {
|
| 1099 |
const container = document.getElementById(containerId);
|
| 1100 |
+
const newInputGroup = document.createElement('div');
|
| 1101 |
+
newInputGroup.className = 'color-input-group';
|
| 1102 |
+
|
| 1103 |
+
const newInput = document.createElement('input');
|
| 1104 |
+
newInput.type = 'text';
|
| 1105 |
+
newInput.name = 'colors';
|
| 1106 |
+
newInput.placeholder = 'Например: Красный';
|
| 1107 |
+
|
| 1108 |
+
const removeBtn = document.createElement('button');
|
| 1109 |
+
removeBtn.type = 'button';
|
| 1110 |
+
removeBtn.className = 'remove-color-btn';
|
| 1111 |
+
removeBtn.textContent = 'Удалить';
|
| 1112 |
+
removeBtn.onclick = function() {
|
| 1113 |
+
newInputGroup.remove();
|
| 1114 |
+
};
|
| 1115 |
+
|
| 1116 |
+
newInputGroup.appendChild(newInput);
|
| 1117 |
+
newInputGroup.appendChild(removeBtn);
|
| 1118 |
+
container.appendChild(newInputGroup);
|
| 1119 |
}
|
| 1120 |
</script>
|
| 1121 |
</body>
|
|
|
|
| 1134 |
return "База данных скачана.", 200
|
| 1135 |
|
| 1136 |
if __name__ == '__main__':
|
| 1137 |
+
if HF_TOKEN_WRITE and REPO_ID:
|
| 1138 |
+
backup_thread = threading.Thread(target=periodic_backup, daemon=True)
|
| 1139 |
+
backup_thread.start()
|
| 1140 |
try:
|
| 1141 |
load_data()
|
| 1142 |
except Exception as e:
|