zmiany w produktach i sugestoach / panel
This commit is contained in:
@@ -1,31 +1,87 @@
|
||||
# add_products_from_openfoodfacts.py
|
||||
# Uruchom w katalogu z app.py
|
||||
|
||||
import urllib.request
|
||||
import json
|
||||
from app import db, SuggestedProduct, app
|
||||
|
||||
# URL do pobrania polskich produktów (tylko nazwy)
|
||||
url = 'https://pl.openfoodfacts.org/cgi/search.pl?search_simple=1&action=process&json=1&fields=product_name&page_size=1000'
|
||||
|
||||
with urllib.request.urlopen(url) as response:
|
||||
data = json.loads(response.read())
|
||||
CATEGORIES = {
|
||||
"Przyprawa": [
|
||||
"przyprawa", "pieprz", "sól", "bazylia", "oregano", "papryka", "majeranek", "czosnek",
|
||||
"tymianek", "rozmaryn", "kolendra", "curry", "imbir", "goździki", "chili", "koper",
|
||||
"kminek", "liść laurowy", "ziele angielskie", "kurkuma", "musztarda", "chrzan"
|
||||
],
|
||||
"Mięso": [
|
||||
"kurczak", "piersi z kurczaka", "udka z kurczaka", "wołowina", "mielona wołowina",
|
||||
"wieprzowina", "schab", "łopatka", "szynka", "boczek", "indyk", "filet z indyka",
|
||||
"gulasz", "pasztet", "karkówka", "żeberka", "kiełbasa", "parówki", "salami", "kabanos"
|
||||
],
|
||||
"Ryba i owoce morza": [
|
||||
"łosoś", "dorsz", "mintaj", "makrela", "pstrąg", "karp", "śledź", "tuńczyk",
|
||||
"morszczuk", "sardynka", "szproty", "anchois", "tilapia", "sandacz", "halibut",
|
||||
"sum", "flądra", "ostrobok", "paluszki rybne", "konserwa rybna"
|
||||
],
|
||||
"Nabiał": [
|
||||
"mleko", "jogurt", "ser żółty", "ser biały", "twaróg", "śmietana", "masło",
|
||||
"kefir", "maślanka", "serek wiejski", "serek topiony", "mozzarella", "feta",
|
||||
"parmezan", "gouda", "emmental", "ser pleśniowy", "ser homogenizowany",
|
||||
"serek mascarpone", "ser ricotta"
|
||||
],
|
||||
"Warzywo": [
|
||||
"pomidor", "ogórek", "marchew", "cebula", "sałata", "papryka", "ziemniak",
|
||||
"kapusta", "brokuł", "kalafior", "cukinia", "bakłażan", "szpinak", "rukola",
|
||||
"seler", "por", "burak", "dynia", "rzodkiewka", "fasola"
|
||||
],
|
||||
"Owoc": [
|
||||
"jabłko", "banan", "gruszka", "truskawka", "winogrono", "malina", "borówka",
|
||||
"czereśnia", "wiśnia", "brzoskwinia", "nektaryna", "śliwka", "ananas",
|
||||
"mango", "kiwi", "cytryna", "limonka", "pomarańcza", "mandarynka", "grejpfrut"
|
||||
],
|
||||
"Pieczywo i zboża": [
|
||||
"chleb", "bułka", "bagietka", "kajzerka", "pumpernikiel", "chleb razowy",
|
||||
"chleb żytni", "tost", "grahamka", "croissant", "tortilla", "pizza",
|
||||
"pierogi", "ryż", "makaron", "kasza jaglana", "kasza gryczana", "owsianka",
|
||||
"płatki kukurydziane", "musli"
|
||||
],
|
||||
"Słodycze i przekąski": [
|
||||
"czekolada", "baton", "ciastko", "wafel", "lody", "cukierek", "żelki",
|
||||
"herbatnik", "paluszki", "chipsy", "orzeszki", "popcorn", "krakersy",
|
||||
"ciasto", "muffin", "pączek", "drożdżówka", "babeczka", "piernik", "beza"
|
||||
],
|
||||
"Napoje": [
|
||||
"woda", "sok jabłkowy", "sok pomarańczowy", "sok multiwitamina", "cola",
|
||||
"pepsi", "napój gazowany", "kawa", "herbata", "piwo", "wino czerwone",
|
||||
"wino białe", "tonik", "lemoniada", "napój izotoniczny", "kompot",
|
||||
"napój mleczny", "maślanka pitna", "koktajl owocowy", "nektar"
|
||||
],
|
||||
"Tłuszcze i oleje": [
|
||||
"oliwa", "olej rzepakowy", "olej słonecznikowy", "masło klarowane",
|
||||
"margaryna", "smalec", "masło orzechowe", "tłuszcz kokosowy",
|
||||
"olej lniany", "olej z pestek winogron", "olej sezamowy",
|
||||
"olej ryżowy", "olej z awokado", "olej kukurydziany", "olej arachidowy",
|
||||
"olej palmowy", "olej konopny", "olej sojowy", "olej dyniowy", "olej z orzechów włoskich"
|
||||
],
|
||||
"Dania gotowe": [
|
||||
"pizza", "hamburger", "hot dog", "zupa", "gulasz", "pierogi ruskie",
|
||||
"pierogi z mięsem", "lasagne", "sałatka warzywna", "kanapka",
|
||||
"wrap", "tortilla", "zapiekanka", "sushi", "falafel", "kebab",
|
||||
"pyzy", "kluski śląskie", "kotlet schabowy", "gołąbki"
|
||||
]
|
||||
}
|
||||
|
||||
produkty = []
|
||||
for prod in data.get('products', []):
|
||||
name = prod.get('product_name', '').strip()
|
||||
if name:
|
||||
produkty.append(name)
|
||||
|
||||
print(f"Znaleziono {len(produkty)} produktów do dodania.")
|
||||
for category, names in CATEGORIES.items():
|
||||
for name in names:
|
||||
produkty.append((category, name.lower().strip()))
|
||||
|
||||
print(f"Przygotowano {len(produkty)} produktów do dodania.")
|
||||
|
||||
dodane = 0
|
||||
with app.app_context():
|
||||
for name in produkty:
|
||||
if not SuggestedProduct.query.filter_by(name=name).first():
|
||||
prod = SuggestedProduct(name=name)
|
||||
dodane = 0
|
||||
for category, name in produkty:
|
||||
full_name = f"{category}: {name}"
|
||||
if not SuggestedProduct.query.filter_by(name=full_name).first():
|
||||
prod = SuggestedProduct(name=full_name)
|
||||
db.session.add(prod)
|
||||
dodane += 1
|
||||
db.session.commit()
|
||||
|
||||
print(f'Dodano {dodane} produktów do bazy.')
|
||||
print(f"Dodano {dodane} produktów do bazy.")
|
||||
|
5
app.py
5
app.py
@@ -945,8 +945,8 @@ def list_products():
|
||||
users = User.query.all()
|
||||
users_dict = {user.id: user.username for user in users}
|
||||
|
||||
# Wszystkie sugestie do słownika
|
||||
suggestions = SuggestedProduct.query.all()
|
||||
# Stabilne sortowanie sugestii
|
||||
suggestions = SuggestedProduct.query.order_by(SuggestedProduct.name.asc()).all()
|
||||
suggestions_dict = {s.name.lower(): s for s in suggestions}
|
||||
|
||||
return render_template(
|
||||
@@ -956,6 +956,7 @@ def list_products():
|
||||
suggestions_dict=suggestions_dict
|
||||
)
|
||||
|
||||
|
||||
@app.route('/admin/sync_suggestion/<int:item_id>', methods=['POST'])
|
||||
@login_required
|
||||
def sync_suggestion_ajax(item_id):
|
||||
|
@@ -1,5 +1,5 @@
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Usuń stare eventy
|
||||
// Odśwież eventy
|
||||
document.querySelectorAll('.sync-btn').forEach(btn => {
|
||||
btn.replaceWith(btn.cloneNode(true));
|
||||
});
|
||||
@@ -8,14 +8,12 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
});
|
||||
|
||||
// Synchronizacja sugestii
|
||||
const buttons = document.querySelectorAll('.sync-btn');
|
||||
buttons.forEach(btn => {
|
||||
document.querySelectorAll('.sync-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const itemId = this.getAttribute('data-item-id');
|
||||
const button = this;
|
||||
|
||||
button.disabled = true;
|
||||
|
||||
fetch(`/admin/sync_suggestion/${itemId}`, {
|
||||
@@ -36,7 +34,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
button.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
.catch(() => {
|
||||
showToast('Błąd synchronizacji', 'danger');
|
||||
button.disabled = false;
|
||||
});
|
||||
@@ -44,14 +42,12 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
});
|
||||
|
||||
// Usuwanie sugestii
|
||||
const deleteButtons = document.querySelectorAll('.delete-suggestion-btn');
|
||||
deleteButtons.forEach(btn => {
|
||||
document.querySelectorAll('.delete-suggestion-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const suggestionId = this.getAttribute('data-suggestion-id');
|
||||
const button = this;
|
||||
|
||||
button.disabled = true;
|
||||
|
||||
fetch(`/admin/delete_suggestion/${suggestionId}`, {
|
||||
@@ -65,41 +61,16 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
showToast(data.message, data.success ? 'success' : 'danger');
|
||||
|
||||
if (data.success) {
|
||||
// Możesz usunąć cały wiersz lub np. odświeżyć kolumnę
|
||||
const row = button.closest('tr');
|
||||
if (row) {
|
||||
row.remove();
|
||||
}
|
||||
if (row) row.remove();
|
||||
} else {
|
||||
button.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
.catch(() => {
|
||||
showToast('Błąd usuwania sugestii', 'danger');
|
||||
button.disabled = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Funkcja do wyświetlania toast
|
||||
function showToast(message, type = 'success') {
|
||||
const toastContainer = document.getElementById('toast-container');
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast align-items-center text-white bg-${type} border-0 show`;
|
||||
toast.role = 'alert';
|
||||
toast.style.minWidth = '250px';
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
`;
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
// Automatyczne usunięcie po 1.75 sekundy
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 1750);
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<a href="/admin" class="btn btn-outline-secondary">← Powrót do panelu</a>
|
||||
</div>
|
||||
|
||||
<div class="card bg-dark text-white">
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h4 class="m-0">📦 Produkty (z synchronizacją sugestii)</h4>
|
||||
<span class="badge bg-secondary">{{ items|length }} produktów</span>
|
||||
@@ -39,8 +39,7 @@
|
||||
{% set suggestion = suggestions_dict.get(item.name.lower()) %}
|
||||
{% if suggestion %}
|
||||
✅ Istnieje (ID: {{ suggestion.id }})
|
||||
<button class="btn btn-sm btn-outline-danger ms-1 delete-suggestion-btn" data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
|
||||
|
||||
<button class="btn btn-sm btn-outline-danger ms-1 delete-suggestion-btn" data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-outline-primary sync-btn" data-item-id="{{ item.id }}">🔄 Synchronizuj</button>
|
||||
{% endif %}
|
||||
@@ -60,8 +59,45 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
<!-- Tabela z samymi sugestiami -->
|
||||
<div class="card bg-dark text-white">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h4 class="m-0">💡 Wszystkie sugestie (poza powiązanymi)</h4>
|
||||
<span class="badge bg-secondary">{{ suggestions_dict|length }} sugestii</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% set item_names = items | map(attribute='name') | map('lower') | list %}
|
||||
<table class="table table-dark table-striped align-middle m-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Nazwa</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for suggestion in suggestions_dict.values() %}
|
||||
{% if suggestion.name.lower() not in item_names %}
|
||||
<tr>
|
||||
<td>{{ suggestion.id }}</td>
|
||||
<td class="fw-bold">{{ suggestion.name }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-danger delete-suggestion-btn" data-suggestion-id="{{ suggestion.id }}">🗑️ Usuń</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if suggestions_dict|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center text-muted">Brak sugestii do wyświetlenia.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='product_suggestion.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
Reference in New Issue
Block a user