Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -60,7 +60,7 @@ def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWN
|
|
| 60 |
if not os.path.exists(file_name):
|
| 61 |
try:
|
| 62 |
with open(file_name, 'w', encoding='utf-8') as f:
|
| 63 |
-
json.dump({'equipment': [], 'categories': [], 'services': [], 'projects': []}, f)
|
| 64 |
except Exception as create_e:
|
| 65 |
logging.error(f"Failed to create empty local file {file_name}: {create_e}")
|
| 66 |
success = True
|
|
@@ -106,7 +106,7 @@ def periodic_backup():
|
|
| 106 |
logging.info("Periodic backup finished.")
|
| 107 |
|
| 108 |
def load_data():
|
| 109 |
-
default_data = {'equipment': [], 'categories': [], 'services': [], 'projects': []}
|
| 110 |
try:
|
| 111 |
with open(DATA_FILE, 'r', encoding='utf-8') as file:
|
| 112 |
data = json.load(file)
|
|
@@ -116,6 +116,8 @@ def load_data():
|
|
| 116 |
if 'categories' not in data: data['categories'] = []
|
| 117 |
if 'services' not in data: data['services'] = []
|
| 118 |
if 'projects' not in data: data['projects'] = []
|
|
|
|
|
|
|
| 119 |
return data
|
| 120 |
except (FileNotFoundError, json.JSONDecodeError, ValueError):
|
| 121 |
logging.warning(f"Local file {DATA_FILE} not found or corrupt. Attempting download.")
|
|
@@ -358,7 +360,7 @@ LANDING_TEMPLATE = '''
|
|
| 358 |
.contact-info p { font-size: 1.3rem; margin-bottom: 0; color: var(--text-primary); }
|
| 359 |
.contact-info a { color: var(--accent-primary); text-decoration: none; font-weight: 600; transition: color 0.3s ease; }
|
| 360 |
.contact-info a:hover { color: var(--accent-secondary); }
|
| 361 |
-
.contact-info .btn { font-size: 1.1rem; color: #fff !important; }
|
| 362 |
.contact-info .btn i { margin-right: 10px; }
|
| 363 |
|
| 364 |
.footer { text-align: center; padding: 40px 0; background-color: #0c111d; border-top: 1px solid var(--border-color); }
|
|
@@ -524,6 +526,7 @@ LANDING_TEMPLATE = '''
|
|
| 524 |
<section id="equipment">
|
| 525 |
<div class="container">
|
| 526 |
<h2>Наше Оборудование</h2>
|
|
|
|
| 527 |
{% if equipment %}
|
| 528 |
<div class="equipment-filters">
|
| 529 |
<button class="filter-btn active" data-filter="all">Все</button>
|
|
@@ -540,8 +543,13 @@ LANDING_TEMPLATE = '''
|
|
| 540 |
<img src="https://via.placeholder.com/250x180.png?text=No+Image" alt="No Image" style="filter: grayscale(0.8) opacity(0.6);">
|
| 541 |
{% endif %}
|
| 542 |
<h3>{{ item.name }}</h3>
|
| 543 |
-
|
| 544 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
</div>
|
| 546 |
{% endfor %}
|
| 547 |
</div>
|
|
@@ -611,6 +619,10 @@ LANDING_TEMPLATE = '''
|
|
| 611 |
modalBody.innerHTML = '';
|
| 612 |
let content = '';
|
| 613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
if (currentType === 'service') {
|
| 615 |
content = `
|
| 616 |
${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/services/${item.photo}" alt="${item.title}">` : ''}
|
|
@@ -618,12 +630,28 @@ LANDING_TEMPLATE = '''
|
|
| 618 |
<p>${item.description || 'Описание отсутствует.'}</p>
|
| 619 |
`;
|
| 620 |
} else if (currentType === 'equipment') {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
content = `
|
| 622 |
${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/equipment/${item.photo}" alt="${item.name}">` : '<img src="https://via.placeholder.com/400x300.png?text=No+Image" alt="No Image Available">'}
|
| 623 |
-
<h3>${
|
| 624 |
<p><strong>Категория:</strong> ${item.category || 'Не указана'}</p>
|
| 625 |
-
|
| 626 |
-
<a href="
|
| 627 |
`;
|
| 628 |
} else if (currentType === 'project') {
|
| 629 |
content = `
|
|
@@ -654,8 +682,9 @@ LANDING_TEMPLATE = '''
|
|
| 654 |
} else {
|
| 655 |
prevBtn.style.display = 'inline-block';
|
| 656 |
nextBtn.style.display = 'inline-block';
|
| 657 |
-
|
| 658 |
-
|
|
|
|
| 659 |
}
|
| 660 |
}
|
| 661 |
|
|
@@ -759,6 +788,9 @@ ADMIN_TEMPLATE = '''
|
|
| 759 |
.message.success { background-color: #d4edda; color: #155724; }
|
| 760 |
.message.error { background-color: #f8d7da; color: #721c24; }
|
| 761 |
.message.warning { background-color: #fff3cd; color: #856404; }
|
|
|
|
|
|
|
|
|
|
| 762 |
</style>
|
| 763 |
</head>
|
| 764 |
<body>
|
|
@@ -767,7 +799,18 @@ ADMIN_TEMPLATE = '''
|
|
| 767 |
{% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}{% for category, message in messages %}<div class="message {{ category }}">{{ message }}</div>{% endfor %}{% endif %}{% endwith %}
|
| 768 |
|
| 769 |
<div class="section">
|
| 770 |
-
<h2><i class="fas fa-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
<form method="POST" action="{{ url_for('force_upload') }}" style="display: inline;"><button type="submit" class="button">Загрузить на сервер</button></form>
|
| 772 |
<form method="POST" action="{{ url_for('force_download') }}" style="display: inline;"><button type="submit" class="button">Скачать с сервера</button></form>
|
| 773 |
</div>
|
|
@@ -855,7 +898,7 @@ ADMIN_TEMPLATE = '''
|
|
| 855 |
<details style="margin-top:20px;"><summary>Добавить оборудование</summary>
|
| 856 |
<form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="add_equipment">
|
| 857 |
<label>Название*:</label><input type="text" name="name" required>
|
| 858 |
-
<label>Цена (KGS)
|
| 859 |
<label>Категория:</label><select name="category"><option value="Без категории">Без категории</option>{% for cat in categories %}<option value="{{ cat }}">{{ cat }}</option>{% endfor %}</select>
|
| 860 |
<label>Фото:</label><input type="file" name="photo" accept="image/*">
|
| 861 |
<button type="submit">Добавить</button>
|
|
@@ -873,7 +916,7 @@ ADMIN_TEMPLATE = '''
|
|
| 873 |
<div id="edit-eq-{{ loop.index0 }}" class="edit-form-container">
|
| 874 |
<form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="edit_equipment"><input type="hidden" name="index" value="{{ loop.index0 }}">
|
| 875 |
<label>Название*:</label><input type="text" name="name" value="{{ item.name }}" required>
|
| 876 |
-
<label>Цена (KGS)
|
| 877 |
<label>Категория:</label><select name="category">{% for cat in categories %}<option value="{{ cat }}" {% if item.category == cat %}selected{% endif %}>{{ cat }}</option>{% endfor %}</select>
|
| 878 |
<label>Заменить фото:</label><input type="file" name="photo" accept="image/*">
|
| 879 |
<button type="submit">Сохранить</button>
|
|
@@ -912,7 +955,11 @@ def admin():
|
|
| 912 |
action = request.form.get('action')
|
| 913 |
logging.info(f"Admin action: {action}")
|
| 914 |
try:
|
| 915 |
-
if action == '
|
|
|
|
|
|
|
|
|
|
|
|
|
| 916 |
name = request.form.get('category_name', '').strip()
|
| 917 |
if name and name not in data['categories']:
|
| 918 |
data['categories'].append(name)
|
|
@@ -927,11 +974,12 @@ def admin():
|
|
| 927 |
|
| 928 |
elif action in ['add_equipment', 'edit_equipment']:
|
| 929 |
name = request.form.get('name', '').strip()
|
| 930 |
-
price_str = request.form.get('price')
|
| 931 |
price = round(float(price_str), 2) if price_str else 0
|
| 932 |
category = request.form.get('category')
|
| 933 |
-
|
| 934 |
-
|
|
|
|
| 935 |
return redirect(url_for('admin'))
|
| 936 |
|
| 937 |
item_data = {'name': name, 'price': price, 'category': category}
|
|
@@ -1035,7 +1083,8 @@ def admin():
|
|
| 1035 |
categories=sorted(data.get('categories', [])),
|
| 1036 |
services=data.get('services', []),
|
| 1037 |
projects=data.get('projects', []),
|
| 1038 |
-
repo_id=REPO_ID
|
|
|
|
| 1039 |
)
|
| 1040 |
|
| 1041 |
def upload_photo_to_hf(photo, item_name, folder):
|
|
|
|
| 60 |
if not os.path.exists(file_name):
|
| 61 |
try:
|
| 62 |
with open(file_name, 'w', encoding='utf-8') as f:
|
| 63 |
+
json.dump({'equipment': [], 'categories': [], 'services': [], 'projects': [], 'settings': {'prices_enabled': True}}, f)
|
| 64 |
except Exception as create_e:
|
| 65 |
logging.error(f"Failed to create empty local file {file_name}: {create_e}")
|
| 66 |
success = True
|
|
|
|
| 106 |
logging.info("Periodic backup finished.")
|
| 107 |
|
| 108 |
def load_data():
|
| 109 |
+
default_data = {'equipment': [], 'categories': [], 'services': [], 'projects': [], 'settings': {'prices_enabled': True}}
|
| 110 |
try:
|
| 111 |
with open(DATA_FILE, 'r', encoding='utf-8') as file:
|
| 112 |
data = json.load(file)
|
|
|
|
| 116 |
if 'categories' not in data: data['categories'] = []
|
| 117 |
if 'services' not in data: data['services'] = []
|
| 118 |
if 'projects' not in data: data['projects'] = []
|
| 119 |
+
if 'settings' not in data: data['settings'] = {'prices_enabled': True}
|
| 120 |
+
if 'prices_enabled' not in data['settings']: data['settings']['prices_enabled'] = True
|
| 121 |
return data
|
| 122 |
except (FileNotFoundError, json.JSONDecodeError, ValueError):
|
| 123 |
logging.warning(f"Local file {DATA_FILE} not found or corrupt. Attempting download.")
|
|
|
|
| 360 |
.contact-info p { font-size: 1.3rem; margin-bottom: 0; color: var(--text-primary); }
|
| 361 |
.contact-info a { color: var(--accent-primary); text-decoration: none; font-weight: 600; transition: color 0.3s ease; }
|
| 362 |
.contact-info a:hover { color: var(--accent-secondary); }
|
| 363 |
+
.contact-info .btn { font-size: 1.1rem; color: #fff !important; }
|
| 364 |
.contact-info .btn i { margin-right: 10px; }
|
| 365 |
|
| 366 |
.footer { text-align: center; padding: 40px 0; background-color: #0c111d; border-top: 1px solid var(--border-color); }
|
|
|
|
| 526 |
<section id="equipment">
|
| 527 |
<div class="container">
|
| 528 |
<h2>Наше Оборудование</h2>
|
| 529 |
+
{% set prices_enabled = data.settings.prices_enabled %}
|
| 530 |
{% if equipment %}
|
| 531 |
<div class="equipment-filters">
|
| 532 |
<button class="filter-btn active" data-filter="all">Все</button>
|
|
|
|
| 543 |
<img src="https://via.placeholder.com/250x180.png?text=No+Image" alt="No Image" style="filter: grayscale(0.8) opacity(0.6);">
|
| 544 |
{% endif %}
|
| 545 |
<h3>{{ item.name }}</h3>
|
| 546 |
+
{% if prices_enabled and item.price > 0 %}
|
| 547 |
+
<p class="price">{{ "%.2f"|format(item.price) }} KGS</p>
|
| 548 |
+
<a href="https://api.whatsapp.com/send?phone={{ whatsapp_phone }}&text=Здравствуйте, интересует оборудование: {{ item.name|urlencode }}" target="_blank" class="btn">Запросить</a>
|
| 549 |
+
{% else %}
|
| 550 |
+
<p class="price">Уточнить цену</p>
|
| 551 |
+
<a href="https://api.whatsapp.com/send?phone={{ whatsapp_phone }}&text=Здравствуйте, хочу узнать цену {{ item.name|urlencode }}" target="_blank" class="btn">Уточнить цену</a>
|
| 552 |
+
{% endif %}
|
| 553 |
</div>
|
| 554 |
{% endfor %}
|
| 555 |
</div>
|
|
|
|
| 619 |
modalBody.innerHTML = '';
|
| 620 |
let content = '';
|
| 621 |
|
| 622 |
+
const dataSettings = {{ data.settings | tojson }};
|
| 623 |
+
const pricesEnabled = dataSettings.prices_enabled;
|
| 624 |
+
const whatsappPhone = '{{ whatsapp_phone }}';
|
| 625 |
+
|
| 626 |
if (currentType === 'service') {
|
| 627 |
content = `
|
| 628 |
${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/services/${item.photo}" alt="${item.title}">` : ''}
|
|
|
|
| 630 |
<p>${item.description || 'Описание отсутствует.'}</p>
|
| 631 |
`;
|
| 632 |
} else if (currentType === 'equipment') {
|
| 633 |
+
let priceHtml = '';
|
| 634 |
+
let buttonHref = '';
|
| 635 |
+
let buttonText = '';
|
| 636 |
+
const itemName = item.name || '';
|
| 637 |
+
const itemPrice = item.price || 0;
|
| 638 |
+
|
| 639 |
+
if (pricesEnabled && itemPrice > 0) {
|
| 640 |
+
priceHtml = `<p class="price" style="font-size: 1.8rem; color: var(--accent-primary); margin: 20px 0;">${itemPrice.toFixed(2)} KGS</p>`;
|
| 641 |
+
buttonText = 'Запросить';
|
| 642 |
+
buttonHref = `https://api.whatsapp.com/send?phone=${whatsappPhone}&text=Здравствуйте, интересует оборудование: ${encodeURIComponent(itemName)}`;
|
| 643 |
+
} else {
|
| 644 |
+
priceHtml = `<p class="price" style="font-size: 1.8rem; color: var(--accent-primary); margin: 20px 0;">Уточнить цену</p>`;
|
| 645 |
+
buttonText = 'Уточнить цену';
|
| 646 |
+
buttonHref = `https://api.whatsapp.com/send?phone=${whatsappPhone}&text=Здравствуйте, хочу узнать цену ${encodeURIComponent(itemName)}`;
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
content = `
|
| 650 |
${item.photo ? `<img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/equipment/${item.photo}" alt="${item.name}">` : '<img src="https://via.placeholder.com/400x300.png?text=No+Image" alt="No Image Available">'}
|
| 651 |
+
<h3>${itemName}</h3>
|
| 652 |
<p><strong>Категория:</strong> ${item.category || 'Не указана'}</p>
|
| 653 |
+
${priceHtml}
|
| 654 |
+
<a href="${buttonHref}" target="_blank" class="btn" style="padding: 14px 30px; font-size: 1.05rem;">${buttonText}</a>
|
| 655 |
`;
|
| 656 |
} else if (currentType === 'project') {
|
| 657 |
content = `
|
|
|
|
| 682 |
} else {
|
| 683 |
prevBtn.style.display = 'inline-block';
|
| 684 |
nextBtn.style.display = 'inline-block';
|
| 685 |
+
// Since we are wrapping around, navigation is always possible if length > 1
|
| 686 |
+
prevBtn.disabled = false; // currentIndex === 0;
|
| 687 |
+
nextBtn.disabled = false; // currentIndex === allItems.length - 1;
|
| 688 |
}
|
| 689 |
}
|
| 690 |
|
|
|
|
| 788 |
.message.success { background-color: #d4edda; color: #155724; }
|
| 789 |
.message.error { background-color: #f8d7da; color: #721c24; }
|
| 790 |
.message.warning { background-color: #fff3cd; color: #856404; }
|
| 791 |
+
.settings-input-group { display: flex; flex-direction: column; align-items: flex-start;}
|
| 792 |
+
.settings-input-group label { display: flex; align-items: center; gap: 10px; margin-top: 15px;}
|
| 793 |
+
.settings-input-group input[type="checkbox"] { width: auto; margin: 0; }
|
| 794 |
</style>
|
| 795 |
</head>
|
| 796 |
<body>
|
|
|
|
| 799 |
{% with messages = get_flashed_messages(with_categories=true) %}{% if messages %}{% for category, message in messages %}<div class="message {{ category }}">{{ message }}</div>{% endfor %}{% endif %}{% endwith %}
|
| 800 |
|
| 801 |
<div class="section">
|
| 802 |
+
<h2><i class="fas fa-cog"></i> Настройки сайта</h2>
|
| 803 |
+
<div class="settings-input-group">
|
| 804 |
+
<form method="POST"><input type="hidden" name="action" value="update_settings">
|
| 805 |
+
<label>
|
| 806 |
+
<input type="checkbox" name="prices_enabled" value="true" {% if settings.prices_enabled %}checked{% endif %}>
|
| 807 |
+
Цены включены (Снимите флажок, чтобы скрыть цены на сайте)
|
| 808 |
+
</label>
|
| 809 |
+
<button type="submit">Сохранить настройки</button>
|
| 810 |
+
</form>
|
| 811 |
+
</div>
|
| 812 |
+
|
| 813 |
+
<h2 style="margin-top: 20px;"><i class="fas fa-sync-alt"></i> Синхронизация</h2>
|
| 814 |
<form method="POST" action="{{ url_for('force_upload') }}" style="display: inline;"><button type="submit" class="button">Загрузить на сервер</button></form>
|
| 815 |
<form method="POST" action="{{ url_for('force_download') }}" style="display: inline;"><button type="submit" class="button">Скачать с сервера</button></form>
|
| 816 |
</div>
|
|
|
|
| 898 |
<details style="margin-top:20px;"><summary>Добавить оборудование</summary>
|
| 899 |
<form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="add_equipment">
|
| 900 |
<label>Название*:</label><input type="text" name="name" required>
|
| 901 |
+
<label>Цена (KGS):</label><input type="number" name="price" step="0.01" min="0">
|
| 902 |
<label>Категория:</label><select name="category"><option value="Без категории">Без категории</option>{% for cat in categories %}<option value="{{ cat }}">{{ cat }}</option>{% endfor %}</select>
|
| 903 |
<label>Фото:</label><input type="file" name="photo" accept="image/*">
|
| 904 |
<button type="submit">Добавить</button>
|
|
|
|
| 916 |
<div id="edit-eq-{{ loop.index0 }}" class="edit-form-container">
|
| 917 |
<form method="POST" enctype="multipart/form-data"><input type="hidden" name="action" value="edit_equipment"><input type="hidden" name="index" value="{{ loop.index0 }}">
|
| 918 |
<label>Название*:</label><input type="text" name="name" value="{{ item.name }}" required>
|
| 919 |
+
<label>Цена (KGS):</label><input type="number" name="price" value="{{ item.price if item.price else '' }}" step="0.01" min="0">
|
| 920 |
<label>Категория:</label><select name="category">{% for cat in categories %}<option value="{{ cat }}" {% if item.category == cat %}selected{% endif %}>{{ cat }}</option>{% endfor %}</select>
|
| 921 |
<label>Заменить фото:</label><input type="file" name="photo" accept="image/*">
|
| 922 |
<button type="submit">Сохранить</button>
|
|
|
|
| 955 |
action = request.form.get('action')
|
| 956 |
logging.info(f"Admin action: {action}")
|
| 957 |
try:
|
| 958 |
+
if action == 'update_settings':
|
| 959 |
+
data['settings']['prices_enabled'] = 'prices_enabled' in request.form
|
| 960 |
+
flash("Настройки сайта обновлены.", 'success')
|
| 961 |
+
|
| 962 |
+
elif action == 'add_category':
|
| 963 |
name = request.form.get('category_name', '').strip()
|
| 964 |
if name and name not in data['categories']:
|
| 965 |
data['categories'].append(name)
|
|
|
|
| 974 |
|
| 975 |
elif action in ['add_equipment', 'edit_equipment']:
|
| 976 |
name = request.form.get('name', '').strip()
|
| 977 |
+
price_str = request.form.get('price', '').strip()
|
| 978 |
price = round(float(price_str), 2) if price_str else 0
|
| 979 |
category = request.form.get('category')
|
| 980 |
+
|
| 981 |
+
if not name:
|
| 982 |
+
flash("Название оборудования обязательно.", 'error')
|
| 983 |
return redirect(url_for('admin'))
|
| 984 |
|
| 985 |
item_data = {'name': name, 'price': price, 'category': category}
|
|
|
|
| 1083 |
categories=sorted(data.get('categories', [])),
|
| 1084 |
services=data.get('services', []),
|
| 1085 |
projects=data.get('projects', []),
|
| 1086 |
+
repo_id=REPO_ID,
|
| 1087 |
+
settings=data.get('settings', {'prices_enabled': True})
|
| 1088 |
)
|
| 1089 |
|
| 1090 |
def upload_photo_to_hf(photo, item_name, folder):
|