improvements
This commit is contained in:
5
app.py
5
app.py
@@ -1,6 +1,11 @@
|
||||
from shopping_app import app, socketio, APP_PORT, DEBUG_MODE
|
||||
from shopping_app.app_setup import logging
|
||||
|
||||
from shopping_app.startup_info import print_startup_info
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG if DEBUG_MODE else logging.INFO)
|
||||
|
||||
print_startup_info(app)
|
||||
|
||||
socketio.run(app, host="0.0.0.0", port=APP_PORT, debug=False)
|
||||
|
||||
BIN
shopping_app.zip
Normal file
BIN
shopping_app.zip
Normal file
Binary file not shown.
@@ -94,6 +94,18 @@ def read_commit(filename="version.txt", root_path=None):
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def get_file_md5(path):
|
||||
try:
|
||||
digest = hashlib.md5()
|
||||
with open(path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(8192), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()[:12]
|
||||
except Exception:
|
||||
return "dev"
|
||||
|
||||
|
||||
commit = read_commit("version.txt", root_path=os.path.dirname(os.path.dirname(__file__))) or "dev"
|
||||
APP_VERSION = commit
|
||||
app.config["APP_VERSION"] = APP_VERSION
|
||||
|
||||
95
shopping_app/startup_info.py
Normal file
95
shopping_app/startup_info.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import socket
|
||||
from datetime import datetime
|
||||
|
||||
import psutil
|
||||
|
||||
try:
|
||||
from sqlalchemy import text
|
||||
except Exception:
|
||||
text = None
|
||||
|
||||
def mb(x):
|
||||
return int(x / 1024 / 1024)
|
||||
|
||||
|
||||
def get_db_type(app):
|
||||
uri = app.config.get("SQLALCHEMY_DATABASE_URI") or app.config.get("DATABASE_URL", "")
|
||||
|
||||
if not uri:
|
||||
return "NONE"
|
||||
|
||||
if uri.startswith("sqlite"):
|
||||
return "SQLite"
|
||||
if uri.startswith("mysql"):
|
||||
return "MySQL"
|
||||
if uri.startswith("postgresql"):
|
||||
return "PostgreSQL"
|
||||
|
||||
return "OTHER"
|
||||
|
||||
def print_startup_info(app):
|
||||
host = os.getenv("HOST", "127.0.0.1")
|
||||
port = int(os.getenv("PORT", "8000"))
|
||||
|
||||
rules = list(app.url_map.iter_rules())
|
||||
|
||||
cpu = psutil.cpu_percent(interval=0.2)
|
||||
ram = psutil.virtual_memory()
|
||||
proc = psutil.Process(os.getpid())
|
||||
|
||||
db_type = get_db_type(app)
|
||||
|
||||
print("\n" + "="*52)
|
||||
print(" APP START")
|
||||
print("="*52)
|
||||
|
||||
# SYSTEM
|
||||
print("\n[ SYSTEM ]")
|
||||
print(f"Time : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"OS : {platform.system()} {platform.release()} ({platform.machine()})")
|
||||
print(f"Python : {sys.version.split()[0]}")
|
||||
print(f"Host : {socket.gethostname()}")
|
||||
|
||||
# SERVER
|
||||
print("\n[ SERVER ]")
|
||||
print(f"Bind : {host}:{port}")
|
||||
print(f"URL : http://127.0.0.1:{port}")
|
||||
|
||||
# APP
|
||||
print("\n[ APP ]")
|
||||
print(f"Name : {app.name}")
|
||||
print(f"Mode : {'DEV' if app.debug else 'PROD'}")
|
||||
print(f"Debug : {app.debug}")
|
||||
|
||||
# RESOURCES
|
||||
print("\n[ RESOURCES ]")
|
||||
print(f"CPU : {cpu:>5.1f}%")
|
||||
print(f"RAM : {ram.percent:>5.1f}% ({mb(ram.used)} / {mb(ram.total)} MB)")
|
||||
print(f"PROC : {mb(proc.memory_info().rss)} MB")
|
||||
|
||||
# DATABASE
|
||||
print("\n[ DATABASE ]")
|
||||
print(f"Type : {db_type}")
|
||||
|
||||
# SECURITY
|
||||
print("\n[ SECURITY ]")
|
||||
print(f"Secret : {'OK' if app.config.get('SECRET_KEY') else 'MISSING'}")
|
||||
print(f"Talis : {'OFF' if app.config.get('TALISMAN_DISABLED') else 'ON'}")
|
||||
|
||||
# HEALTH
|
||||
print("\n[ HEALTH ]")
|
||||
print(f"Uploads: {'OK' if os.path.exists('uploads') else 'MISS'}")
|
||||
print(f"Static : {'OK' if os.path.exists(app.static_folder) else 'MISS'}")
|
||||
|
||||
# ROUTES
|
||||
print("\n[ ROUTES ]")
|
||||
print(f"Total : {len(rules)}")
|
||||
|
||||
# STATUS
|
||||
print("\n[ STATUS ]")
|
||||
print("READY")
|
||||
|
||||
print("="*52 + "\n")
|
||||
@@ -5259,3 +5259,93 @@ body:not(.sorting-active) .drag-handle {
|
||||
min-width: 44px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* wyróżnienie pola dodawania produktu */
|
||||
.endpoint-list .shopping-entry-card,
|
||||
.endpoint-list_share .shopping-entry-card,
|
||||
.endpoint-shared_list .shopping-entry-card,
|
||||
.endpoint-view_list .shopping-entry-card {
|
||||
background: linear-gradient(180deg, rgba(25, 135, 84, 0.16), rgba(13, 17, 23, 0.92));
|
||||
border: 1px solid rgba(25, 135, 84, 0.42);
|
||||
border-radius: 1rem;
|
||||
padding: .9rem;
|
||||
box-shadow: 0 .5rem 1.2rem rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.endpoint-list .shopping-entry-card__label,
|
||||
.endpoint-list_share .shopping-entry-card__label,
|
||||
.endpoint-shared_list .shopping-entry-card__label,
|
||||
.endpoint-view_list .shopping-entry-card__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .4rem;
|
||||
margin-bottom: .2rem;
|
||||
font-size: .95rem;
|
||||
font-weight: 700;
|
||||
color: #d1f7df;
|
||||
}
|
||||
|
||||
.endpoint-list .shopping-entry-card__hint,
|
||||
.endpoint-list_share .shopping-entry-card__hint,
|
||||
.endpoint-shared_list .shopping-entry-card__hint,
|
||||
.endpoint-view_list .shopping-entry-card__hint {
|
||||
margin-bottom: .75rem;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
font-size: .82rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.endpoint-list .shopping-entry-card .shopping-product-input-group,
|
||||
.endpoint-list_share .shopping-entry-card .shopping-product-input-group,
|
||||
.endpoint-shared_list .shopping-entry-card .shopping-product-input-group,
|
||||
.endpoint-view_list .shopping-entry-card .shopping-product-input-group {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.endpoint-list .shopping-entry-card .shopping-product-input-group > .form-control,
|
||||
.endpoint-list_share .shopping-entry-card .shopping-product-input-group > .form-control,
|
||||
.endpoint-shared_list .shopping-entry-card .shopping-product-input-group > .form-control,
|
||||
.endpoint-view_list .shopping-entry-card .shopping-product-input-group > .form-control {
|
||||
border-color: rgba(25, 135, 84, 0.55) !important;
|
||||
background: rgba(17, 24, 39, 0.95) !important;
|
||||
}
|
||||
|
||||
.endpoint-list .shopping-entry-card .shopping-product-input-group > .form-control::placeholder,
|
||||
.endpoint-list_share .shopping-entry-card .shopping-product-input-group > .form-control::placeholder,
|
||||
.endpoint-shared_list .shopping-entry-card .shopping-product-input-group > .form-control::placeholder,
|
||||
.endpoint-view_list .shopping-entry-card .shopping-product-input-group > .form-control::placeholder {
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
}
|
||||
|
||||
.endpoint-list .shopping-entry-card .shopping-product-input-group > .shopping-product-name-input:focus,
|
||||
.endpoint-list_share .shopping-entry-card .shopping-product-input-group > .shopping-product-name-input:focus,
|
||||
.endpoint-shared_list .shopping-entry-card .shopping-product-input-group > .shopping-product-name-input:focus,
|
||||
.endpoint-view_list .shopping-entry-card .shopping-product-input-group > .shopping-product-name-input:focus {
|
||||
box-shadow: inset 0 0 0 1px rgba(25, 135, 84, 0.25), 0 0 0 .2rem rgba(25, 135, 84, 0.18);
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.endpoint-list .shopping-entry-card,
|
||||
.endpoint-list_share .shopping-entry-card,
|
||||
.endpoint-shared_list .shopping-entry-card,
|
||||
.endpoint-view_list .shopping-entry-card {
|
||||
padding: .8rem;
|
||||
border-radius: .95rem;
|
||||
}
|
||||
|
||||
.endpoint-list .shopping-entry-card__label,
|
||||
.endpoint-list_share .shopping-entry-card__label,
|
||||
.endpoint-shared_list .shopping-entry-card__label,
|
||||
.endpoint-view_list .shopping-entry-card__label {
|
||||
font-size: .92rem;
|
||||
}
|
||||
|
||||
.endpoint-list .shopping-entry-card__hint,
|
||||
.endpoint-list_share .shopping-entry-card__hint,
|
||||
.endpoint-shared_list .shopping-entry-card__hint,
|
||||
.endpoint-view_list .shopping-entry-card__hint {
|
||||
font-size: .78rem;
|
||||
margin-bottom: .65rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ function toggleEmptyPlaceholder() {
|
||||
const li = document.createElement('li');
|
||||
li.id = 'empty-placeholder';
|
||||
li.className = 'list-group-item bg-dark text-secondary text-center w-100';
|
||||
li.textContent = 'Brak produktów w tej liście.';
|
||||
li.textContent = 'Brak produktów w tej liście.';
|
||||
list.appendChild(li);
|
||||
} else if (hasRealItems && placeholder) {
|
||||
placeholder.remove();
|
||||
@@ -206,7 +206,7 @@ function setupList(listId, username) {
|
||||
|
||||
const progressTitle = document.getElementById('progress-title');
|
||||
if (progressTitle) {
|
||||
progressTitle.textContent = `📊 Postęp listy — ${data.purchased_count}/${data.total_count} kupionych (${Math.round(data.percent)}%)`;
|
||||
progressTitle.textContent = `Postęp listy — ${data.purchased_count}/${data.total_count} kupionych (${Math.round(data.percent)}%)`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@
|
||||
checkboxes.forEach(cb => cb.checked = this.checked);
|
||||
});
|
||||
</script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='preview_list_modal.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'preview_list_modal.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -146,6 +146,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='preview_list_modal.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='categories_select_admin.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'preview_list_modal.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'categories_select_admin.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -303,5 +303,5 @@
|
||||
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='select.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'select.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -170,8 +170,8 @@
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='product_suggestion.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='table_search.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'product_suggestion.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'table_search.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -181,7 +181,7 @@
|
||||
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='access_users.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='lists_access.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'access_users.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'lists_access.js') }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -224,8 +224,8 @@
|
||||
endpoint: "/admin/crop_receipt"
|
||||
};
|
||||
</script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_crop.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_crop_logic.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'receipt_crop.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'receipt_crop_logic.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -153,6 +153,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<link rel="stylesheet" href="{{ url_for('static_bp.serve_css', filename='admin_settings.css') }}?v={{ APP_VERSION }}">
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='admin_settings.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<link rel="stylesheet" href="{{ static_asset_url('static_bp.serve_css', 'admin_settings.css') }}">
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'admin_settings.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='user_management.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'user_management.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
<title>{% block title %}Live Lista Zakupów{% endblock %}</title>
|
||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('favicon') }}">
|
||||
|
||||
<link href="{{ url_for('static_bp.serve_css_lib', filename='bootstrap.min.css') }}?v={{ APP_VERSION }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static_bp.serve_css', filename='style.css') }}?v={{ APP_VERSION }}" rel="stylesheet">
|
||||
<link href="{{ static_asset_url('static_bp.serve_css_lib', 'bootstrap.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ static_asset_url('static_bp.serve_css', 'style.css') }}" rel="stylesheet">
|
||||
|
||||
{% set exclude_paths = ['/system-auth'] %}
|
||||
{% if (exclude_paths | select("in", request.path) | list | length == 0)
|
||||
and has_authorized_cookie
|
||||
and not is_blocked %}
|
||||
<link href="{{ url_for('static_bp.serve_css_lib', filename='glightbox.min.css') }}?v={{ APP_VERSION }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static_bp.serve_css_lib', filename='sort_table.min.css') }}?v={{ APP_VERSION }}" rel="stylesheet">
|
||||
<link href="{{ static_asset_url('static_bp.serve_css_lib', 'glightbox.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ static_asset_url('static_bp.serve_css_lib', 'sort_table.min.css') }}" rel="stylesheet">
|
||||
{% endif %}
|
||||
|
||||
{% set substrings_cropper = ['/admin/receipts', '/edit_my_list'] %}
|
||||
{% if substrings_cropper | select("in", request.path) | list | length > 0 %}
|
||||
<link href="{{ url_for('static_bp.serve_css_lib', filename='cropper.min.css') }}?v={{ APP_VERSION }}" rel="stylesheet">
|
||||
<link href="{{ static_asset_url('static_bp.serve_css_lib', 'cropper.min.css') }}" rel="stylesheet">
|
||||
{% endif %}
|
||||
|
||||
{% set substrings_tomselect = ['/edit_my_list', '/admin/edit_list', '/admin/edit_categories'] %}
|
||||
{% if substrings_tomselect | select("in", request.path) | list | length > 0 %}
|
||||
<link href="{{ url_for('static_bp.serve_css_lib', filename='tom-select.bootstrap5.min.css') }}?v={{ APP_VERSION }}" rel="stylesheet">
|
||||
<link href="{{ static_asset_url('static_bp.serve_css_lib', 'tom-select.bootstrap5.min.css') }}" rel="stylesheet">
|
||||
{% endif %}
|
||||
</head>
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='bootstrap.bundle.min.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'bootstrap.bundle.min.js') }}"></script>
|
||||
|
||||
{% if not is_blocked %}
|
||||
<script>
|
||||
@@ -147,16 +147,16 @@
|
||||
</script>
|
||||
|
||||
{% if request.endpoint != 'system_auth' %}
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='glightbox.min.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='socket.io.min.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='sort_table.min.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='functions.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='live.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='sockets.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'glightbox.min.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'socket.io.min.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'sort_table.min.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'functions.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'live.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'sockets.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='toasts.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='app_ui.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'toasts.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'app_ui.js') }}"></script>
|
||||
<script>
|
||||
if (typeof GLightbox === 'function') {
|
||||
let lightbox = GLightbox({ selector: '.glightbox' });
|
||||
@@ -165,12 +165,12 @@
|
||||
|
||||
{% set substrings = ['/admin/receipts', '/edit_my_list'] %}
|
||||
{% if substrings | select("in", request.path) | list | length > 0 %}
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='cropper.min.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'cropper.min.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
{% set substrings = ['/edit_my_list', '/admin/edit_list', '/admin/edit_categories'] %}
|
||||
{% if substrings | select("in", request.path) | list | length > 0 %}
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='tom-select.complete.min.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'tom-select.complete.min.js') }}"></script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -260,9 +260,9 @@
|
||||
endpoint: "/user_crop_receipt"
|
||||
};
|
||||
</script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='confirm_delete.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_crop.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_crop_logic.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='select.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='access_users.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'confirm_delete.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'receipt_crop.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'receipt_crop_logic.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'select.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'access_users.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -213,13 +213,13 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='chart.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='show_all_expense.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='expense_chart.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='expense_table.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='expense_tab.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='select_all_table.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='chart_controls.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='modal_chart.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='download_chart.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'chart.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'show_all_expense.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'expense_chart.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'expense_table.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'expense_tab.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'select_all_table.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'chart_controls.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'modal_chart.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'download_chart.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -196,21 +196,25 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-2 shopping-compact-input-group shopping-product-input-group">
|
||||
<input type="text" id="newItem" name="name"
|
||||
class="form-control bg-dark text-white border-secondary shopping-product-name-input"
|
||||
placeholder="Dodaj produkt" required>
|
||||
<div class="shopping-entry-card mb-3" aria-label="Sekcja dodawania produktu">
|
||||
<div class="shopping-entry-card__label">➕ Dodaj produkt</div>
|
||||
<div class="shopping-entry-card__hint">Wpisz nazwę produktu i ilość, potem kliknij Dodaj.</div>
|
||||
<div class="input-group mb-0 shopping-compact-input-group shopping-product-input-group">
|
||||
<input type="text" id="newItem" name="name"
|
||||
class="form-control bg-dark text-white border-secondary shopping-product-name-input"
|
||||
placeholder="Dodaj produkt" required>
|
||||
|
||||
<input type="number" id="newQuantity" name="quantity"
|
||||
class="form-control bg-dark text-white border-secondary shopping-qty-input"
|
||||
placeholder="Ilość" min="1" value="1">
|
||||
<input type="number" id="newQuantity" name="quantity"
|
||||
class="form-control bg-dark text-white border-secondary shopping-qty-input"
|
||||
placeholder="Ilość" min="1" value="1">
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-outline-success share-submit-btn shopping-compact-submit"
|
||||
onclick="addItem({{ list.id }})">
|
||||
<span class="shopping-btn-icon" aria-hidden="true">➕</span>
|
||||
<span class="shopping-btn-label">Dodaj</span>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-outline-success share-submit-btn shopping-compact-submit"
|
||||
onclick="addItem({{ list.id }})">
|
||||
<span class="shopping-btn-icon" aria-hidden="true">➕</span>
|
||||
<span class="shopping-btn-label">Dodaj</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -394,7 +398,7 @@
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='Sortable.min.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'Sortable.min.js') }}"></script>
|
||||
<script>
|
||||
const isShare = document.getElementById('items').dataset.isShare === 'true';
|
||||
window.IS_SHARE = isShare;
|
||||
@@ -402,11 +406,11 @@
|
||||
window.IS_ARCHIVED = {{ 'true' if list.is_archived else 'false' }};
|
||||
window.IS_OWNER = {{ 'true' if is_owner else 'false' }};
|
||||
</script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='mass_add.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_upload.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='sort_mode.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='access_users.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='category_modal.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'mass_add.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'receipt_upload.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'sort_mode.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'access_users.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'category_modal.js') }}"></script>
|
||||
<script>
|
||||
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
|
||||
|
||||
|
||||
@@ -94,13 +94,17 @@
|
||||
</ul>
|
||||
|
||||
{% if not list.is_archived %}
|
||||
<div class="input-group mb-2 shopping-compact-input-group shopping-product-input-group">
|
||||
<div class="shopping-entry-card mb-3" aria-label="Sekcja dodawania produktu">
|
||||
<div class="shopping-entry-card__label">➕ Dodaj produkt</div>
|
||||
<div class="shopping-entry-card__hint">Wpisz nazwę produktu i ilość, potem kliknij Dodaj.</div>
|
||||
<div class="input-group mb-0 shopping-compact-input-group shopping-product-input-group">
|
||||
<input id="newItem" class="form-control bg-dark text-white border-secondary shopping-product-name-input" placeholder="Dodaj produkt" {% if
|
||||
not current_user.is_authenticated %}disabled{% endif %}>
|
||||
<input id="newQuantity" type="number" class="form-control bg-dark text-white border-secondary shopping-qty-input" placeholder="Ilość"
|
||||
min="1" value="1" {% if not current_user.is_authenticated %}disabled{% endif %}>
|
||||
<button onclick="addItem({{ list.id }})" class="btn btn-outline-success share-submit-btn shopping-compact-submit" {% if not
|
||||
current_user.is_authenticated %}disabled{% endif %}><span class="shopping-btn-icon" aria-hidden="true">➕</span><span class="shopping-btn-label">Dodaj</span></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -236,12 +240,12 @@
|
||||
var isSorting = false;
|
||||
}
|
||||
</script>
|
||||
<script src="{{ url_for('static_bp.serve_js_lib', filename='Sortable.min.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='notes.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='clickable_row.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_section.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_upload.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='receipt_analysis.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'Sortable.min.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'notes.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'clickable_row.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'receipt_section.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'receipt_upload.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'receipt_analysis.js') }}"></script>
|
||||
<script>
|
||||
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
|
||||
</script>
|
||||
|
||||
@@ -289,8 +289,8 @@
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='toggle_button.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='select_month.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'toggle_button.js') }}"></script>
|
||||
<script src="{{ static_asset_url('static_bp.serve_js', 'select_month.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -10,7 +10,24 @@ def load_user(user_id):
|
||||
|
||||
@app.context_processor
|
||||
def inject_version():
|
||||
return {"APP_VERSION": app.config["APP_VERSION"]}
|
||||
def static_asset_url(endpoint, filename):
|
||||
directory_map = {
|
||||
"static_bp.serve_js": "static/js",
|
||||
"static_bp.serve_css": "static/css",
|
||||
"static_bp.serve_js_lib": "static/lib/js",
|
||||
"static_bp.serve_css_lib": "static/lib/css",
|
||||
}
|
||||
relative_dir = directory_map.get(endpoint)
|
||||
version = app.config["APP_VERSION"]
|
||||
if relative_dir:
|
||||
file_path = os.path.join(app.root_path, relative_dir, filename)
|
||||
version = get_file_md5(file_path)
|
||||
return url_for(endpoint, filename=filename, v=version)
|
||||
|
||||
return {
|
||||
"APP_VERSION": app.config["APP_VERSION"],
|
||||
"static_asset_url": static_asset_url,
|
||||
}
|
||||
|
||||
|
||||
@app.context_processor
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from shopping_app import app
|
||||
|
||||
|
||||
class RefactorSmokeTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
app.config.update(TESTING=True)
|
||||
cls.client = app.test_client()
|
||||
|
||||
def test_undefined_path_returns_not_500(self):
|
||||
response = self.client.get('/undefined')
|
||||
self.assertNotEqual(response.status_code, 500)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_login_page_renders(self):
|
||||
response = self.client.get('/login')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
html = response.get_data(as_text=True)
|
||||
self.assertIn('name="password"', html)
|
||||
self.assertIn('app_ui.js', html)
|
||||
|
||||
|
||||
class TemplateContractTests(unittest.TestCase):
|
||||
def test_main_template_uses_single_action_group_on_mobile(self):
|
||||
main_html = Path('shopping_app/templates/main.html').read_text(encoding='utf-8')
|
||||
self.assertIn('mobile-list-heading', main_html)
|
||||
self.assertIn('list-main-title__link', main_html)
|
||||
self.assertNotIn('d-flex d-sm-none" role="group"', main_html)
|
||||
|
||||
def test_list_templates_use_compact_mobile_action_layout(self):
|
||||
list_html = Path('shopping_app/templates/list.html').read_text(encoding='utf-8')
|
||||
shared_html = Path('shopping_app/templates/list_share.html').read_text(encoding='utf-8')
|
||||
for html in (list_html, shared_html):
|
||||
self.assertIn('shopping-item-row', html)
|
||||
self.assertIn('shopping-item-actions', html)
|
||||
self.assertIn('shopping-compact-input-group', html)
|
||||
self.assertIn('shopping-item-head', html)
|
||||
|
||||
def test_css_contains_mobile_ux_overrides(self):
|
||||
css = Path('shopping_app/static/css/style.css').read_text(encoding='utf-8')
|
||||
self.assertIn('.shopping-item-actions', css)
|
||||
self.assertIn('.shopping-compact-input-group', css)
|
||||
self.assertIn('.ui-password-group > .ui-password-toggle', css)
|
||||
self.assertIn('.hide-purchased-switch--minimal', css)
|
||||
self.assertIn('.shopping-item-head', css)
|
||||
self.assertIn('UX tweak 2026-03-14 c: hamburger with full labels', css)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
||||
class NavbarContractTests(unittest.TestCase):
|
||||
def test_base_template_uses_mobile_collapse_nav(self):
|
||||
base_html = Path('shopping_app/templates/base.html').read_text(encoding='utf-8')
|
||||
self.assertIn('navbar-toggler', base_html)
|
||||
self.assertIn('appNavbarMenu', base_html)
|
||||
|
||||
|
||||
def test_base_template_mobile_nav_has_full_labels(self):
|
||||
base_html = Path('shopping_app/templates/base.html').read_text(encoding='utf-8')
|
||||
self.assertIn('>📊 <span>Wydatki</span><', base_html)
|
||||
self.assertIn('>🚪 <span>Wyloguj</span><', base_html)
|
||||
|
||||
def test_main_template_temp_toggle_is_integrated(self):
|
||||
main_html = Path('shopping_app/templates/main.html').read_text(encoding='utf-8')
|
||||
self.assertIn('create-list-temp-toggle', main_html)
|
||||
Reference in New Issue
Block a user