optymalizacje_kodu #8
31
app.py
31
app.py
@@ -1143,7 +1143,8 @@ def main_page():
|
||||
# ostatnia kwota (w tym przypadku max = suma z ostatniego zapisu)
|
||||
latest_expenses_map = dict(
|
||||
db.session.query(
|
||||
Expense.list_id, func.coalesce(func.max(Expense.amount), 0)
|
||||
Expense.list_id,
|
||||
func.coalesce(func.sum(Expense.amount), 0)
|
||||
)
|
||||
.filter(Expense.list_id.in_(all_ids))
|
||||
.group_by(Expense.list_id)
|
||||
@@ -1907,21 +1908,21 @@ def admin_panel():
|
||||
.group_by(Item.list_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
stats_map = {
|
||||
s.list_id: (s.total_count or 0, s.purchased_count or 0) for s in stats
|
||||
}
|
||||
|
||||
# Pobranie ostatnich kwot dla wszystkich list w jednym zapytaniu
|
||||
latest_expenses_map = dict(
|
||||
db.session.query(
|
||||
Expense.list_id, func.coalesce(func.max(Expense.amount), 0)
|
||||
Expense.list_id,
|
||||
func.coalesce(func.sum(Expense.amount), 0)
|
||||
)
|
||||
.filter(Expense.list_id.in_(all_ids))
|
||||
.group_by(Expense.list_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
enriched_lists = []
|
||||
for l in all_lists:
|
||||
total_count, purchased_count = stats_map.get(l.id, (0, 0))
|
||||
@@ -2024,6 +2025,7 @@ def admin_panel():
|
||||
)
|
||||
|
||||
|
||||
|
||||
@app.route("/admin/delete_list/<int:list_id>")
|
||||
@login_required
|
||||
@admin_required
|
||||
@@ -2634,6 +2636,27 @@ def recalculate_filesizes_all():
|
||||
return redirect(url_for("admin_receipts", id="all"))
|
||||
|
||||
|
||||
@app.route("/admin/mass_edit_categories", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def admin_mass_edit_categories():
|
||||
lists = ShoppingList.query.options(joinedload(ShoppingList.categories)).order_by(ShoppingList.created_at.desc()).all()
|
||||
categories = Category.query.order_by(Category.name.asc()).all()
|
||||
|
||||
if request.method == "POST":
|
||||
for lst in lists:
|
||||
selected_ids = request.form.getlist(f"categories_{lst.id}")
|
||||
lst.categories.clear()
|
||||
if selected_ids:
|
||||
cats = Category.query.filter(Category.id.in_(selected_ids)).all()
|
||||
lst.categories.extend(cats)
|
||||
db.session.commit()
|
||||
flash("Zaktualizowano kategorie dla wybranych list", "success")
|
||||
return redirect(url_for("admin_mass_edit_categories"))
|
||||
|
||||
return render_template("admin/mass_edit_categories.html", lists=lists, categories=categories)
|
||||
|
||||
|
||||
@app.route("/healthcheck")
|
||||
def healthcheck():
|
||||
header_token = request.headers.get("X-Internal-Check")
|
||||
|
11
static/js/admin_mass_categories.js
Normal file
11
static/js/admin_mass_categories.js
Normal file
@@ -0,0 +1,11 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.querySelectorAll('select[multiple]').forEach(function (el) {
|
||||
new TomSelect(el, {
|
||||
plugins: ['remove_button'],
|
||||
placeholder: 'Wybierz kategorie...',
|
||||
create: false,
|
||||
sortField: { field: "text", direction: "asc" },
|
||||
dropdownParent: 'body'
|
||||
});
|
||||
});
|
||||
});
|
@@ -18,14 +18,18 @@
|
||||
<div class="collapse navbar-collapse" id="adminNavbar">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/users">👥 Zarządzanie użytkownikami</a>
|
||||
<a class="nav-link" href="{{ url_for('list_users') }}">👥 Zarządzanie użytkownikami</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/receipts/all">📸 Wszystkie paragony</a>
|
||||
<a class="nav-link" href="{{ url_for('admin_receipts', id='all') }}">📸 Wszystkie paragony</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/products">🛍️ Produkty i sugestie</a>
|
||||
<a class="nav-link" href="{{ url_for('list_products') }}">🛍️ Produkty i sugestie</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin_mass_edit_categories') }}">🗂 Masowa edycja kategorii</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,7 +199,7 @@
|
||||
});
|
||||
</script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='expenses.js') }}"></script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
<div class="info-bar-fixed">
|
||||
|
67
templates/admin/mass_edit_categories.html
Normal file
67
templates/admin/mass_edit_categories.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Masowa edycja kategorii{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap mb-4">
|
||||
<h2 class="mb-2">🗂 Masowa edycja kategorii list</h2>
|
||||
<div>
|
||||
<a href="{{ url_for('admin_panel') }}" class="btn btn-outline-secondary">← Powrót do panelu</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-striped table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Nazwa listy</th>
|
||||
<th scope="col">Właściciel</th>
|
||||
<th scope="col">Data utworzenia</th>
|
||||
<th scope="col">Kategorie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for lst in lists %}
|
||||
<tr>
|
||||
<td>{{ lst.id }}</td>
|
||||
<td>{{ lst.title }}</td>
|
||||
<td>{{ lst.owner.username if lst.owner else "?" }}</td>
|
||||
<td>{{ lst.created_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td style="min-width: 220px;">
|
||||
<select name="categories_{{ lst.id }}" multiple
|
||||
class="form-select bg-dark text-white border-secondary">
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat.id }}" {% if cat in lst.categories %}selected{% endif %}>
|
||||
{{ cat.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="submit" class="btn btn-success">💾 Zapisz zmiany</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/tom-select/dist/js/tom-select.complete.min.js"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='admin_mass_categories.js') }}"></script>
|
||||
|
||||
<style>
|
||||
.ts-dropdown {
|
||||
z-index: 9999 !important;
|
||||
/* 🔹 dropdown zawsze na wierzchu */
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
@@ -15,7 +15,7 @@
|
||||
{% if '/admin/receipts' in request.path or '/edit_my_list' in request.path %}
|
||||
<link href="{{ url_for('static_bp.serve_css_lib', filename='cropper.min.css') }}" rel="stylesheet">
|
||||
{% endif %}
|
||||
{% if '/edit_my_list' or '/admin/edit_list' in request.path %}
|
||||
{% if '/edit_my_list' or '/admin/edit_list' in request.path or '/admin/mass_edit_categories' %}
|
||||
<link href="{{ url_for('static_bp.serve_css_lib', filename='tom-select.bootstrap5.min.css') }}" rel="stylesheet">
|
||||
{% endif %}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
{% if '/admin/receipts' in request.path or '/edit_my_list' in request.path %}
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='cropper.min.js') }}"></script>
|
||||
{% endif %}
|
||||
{% if '/edit_my_list' or '/admin/edit_list' in request.path %}
|
||||
{% if '/edit_my_list' or '/admin/edit_list' or '/admin/mass_edit_categories' in request.path %}
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='tom-select.complete.min.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
|
@@ -100,7 +100,7 @@
|
||||
<a href="{{ url_for('rotate_receipt_user', receipt_id=r.id) }}"
|
||||
class="btn btn-sm btn-outline-warning w-100 mb-2">🔄 Obróć o 90°</a>
|
||||
|
||||
<a href="#" class="btn btn-sm btn-outline-secondary w-100 mb-2 disabled" data-bs-toggle="modal"
|
||||
<a href="#" class="btn btn-sm btn-outline-secondary w-100 mb-2" data-bs-toggle="modal"
|
||||
data-bs-target="#userCropModal" data-img-src="{{ url_for('uploaded_file', filename=r.filename) }}"
|
||||
data-receipt-id="{{ r.id }}" data-crop-endpoint="{{ url_for('crop_receipt_user') }}">
|
||||
✂️ Przytnij
|
||||
|
Reference in New Issue
Block a user