spolszczenie wszystkiego i poprawki
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ venv/
|
||||
.env
|
||||
version.txt
|
||||
deploy/varnish/default.vcl
|
||||
*.tar.gz
|
76
alters.txt
76
alters.txt
@@ -1,68 +1,18 @@
|
||||
-- WŁĄCZ/wyłącz FK zależnie od etapu migracji
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- 1) Nowa tabela z właściwym FK (ON DELETE CASCADE)
|
||||
CREATE TABLE wplata_new (
|
||||
id INTEGER PRIMARY KEY,
|
||||
zbiorka_id INTEGER NOT NULL,
|
||||
kwota REAL NOT NULL,
|
||||
data DATETIME,
|
||||
opis TEXT,
|
||||
FOREIGN KEY(zbiorka_id) REFERENCES zbiorka(id) ON DELETE CASCADE
|
||||
);
|
||||
-- UŻYTKOWNIK
|
||||
ALTER TABLE user RENAME TO uzytkownik;
|
||||
ALTER TABLE uzytkownik RENAME COLUMN username TO uzytkownik;
|
||||
ALTER TABLE uzytkownik RENAME COLUMN password_hash TO haslo_hash;
|
||||
ALTER TABLE uzytkownik RENAME COLUMN is_admin TO czy_admin;
|
||||
|
||||
-- 2) (opcjonalnie) upewnij się, że nie ma „sierotek”
|
||||
-- SELECT w.* FROM wplata w LEFT JOIN zbiorka z ON z.id = w.zbiorka_id WHERE z.id IS NULL;
|
||||
|
||||
-- 3) Kopiowanie danych
|
||||
INSERT INTO wplata_new (id, zbiorka_id, kwota, data, opis)
|
||||
SELECT id, zbiorka_id, kwota, data, opis
|
||||
FROM wplata;
|
||||
|
||||
-- 4) Usunięcie starej tabeli
|
||||
DROP TABLE wplata;
|
||||
|
||||
-- 5) Zmiana nazwy nowej tabeli na właściwą
|
||||
ALTER TABLE wplata_new RENAME TO wplata;
|
||||
|
||||
-- 6) Odtwórz indeksy/trigger-y jeśli jakieś były (przykład indeksu po FK)
|
||||
-- CREATE INDEX idx_wplata_zbiorka_id ON wplata(zbiorka_id);
|
||||
-- USTAWIENIA GLOBALNE
|
||||
ALTER TABLE global_settings RENAME TO ustawienia_globalne;
|
||||
ALTER TABLE ustawienia_globalne RENAME COLUMN allowed_login_hosts TO dozwolone_hosty_logowania;
|
||||
ALTER TABLE ustawienia_globalne RENAME COLUMN site_title TO tytul_strony;
|
||||
ALTER TABLE ustawienia_globalne RENAME COLUMN show_logo_in_navbar TO pokaz_logo_w_navbar;
|
||||
ALTER TABLE ustawienia_globalne RENAME COLUMN navbar_brand_mode TO typ_navbar;
|
||||
ALTER TABLE ustawienia_globalne RENAME COLUMN footer_brand_mode TO typ_stopka;
|
||||
ALTER TABLE ustawienia_globalne RENAME COLUMN footer_text TO stopka_text;
|
||||
|
||||
COMMIT;
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
PRAGMA foreign_keys=OFF;
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE global_settings ADD COLUMN logo_url TEXT DEFAULT '';
|
||||
ALTER TABLE global_settings ADD COLUMN site_title TEXT DEFAULT '';
|
||||
ALTER TABLE global_settings ADD COLUMN show_logo_in_navbar BOOLEAN DEFAULT 0;
|
||||
|
||||
|
||||
|
||||
COMMIT;
|
||||
PRAGMA foreign_keys=ON;
|
||||
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
|
||||
-- 1) Dodajemy nowe kolumny (SQLite pozwala tylko na ADD COLUMN)
|
||||
ALTER TABLE global_settings ADD COLUMN navbar_brand_mode TEXT DEFAULT 'text';
|
||||
ALTER TABLE global_settings ADD COLUMN footer_brand_mode TEXT DEFAULT 'text';
|
||||
ALTER TABLE global_settings ADD COLUMN footer_text TEXT;
|
||||
|
||||
-- 2) Backfill: zgodność wsteczna z show_logo_in_navbar
|
||||
UPDATE global_settings
|
||||
SET navbar_brand_mode = 'logo'
|
||||
WHERE COALESCE(show_logo_in_navbar, 0) = 1;
|
||||
|
||||
-- 3) Upewnij się, że wartości są ustawione (na wypadek NULL-i)
|
||||
UPDATE global_settings
|
||||
SET navbar_brand_mode = COALESCE(navbar_brand_mode, 'text'),
|
||||
footer_brand_mode = COALESCE(footer_brand_mode, 'text');
|
232
app.py
232
app.py
@@ -65,18 +65,18 @@ APP_VERSION = f"{deploy_date}+{commit}"
|
||||
app.config["APP_VERSION"] = APP_VERSION
|
||||
|
||||
# MODELE
|
||||
class User(UserMixin, db.Model):
|
||||
class Uzytkownik(UserMixin, db.Model):
|
||||
__tablename__ = "uzytkownik"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(128), nullable=False)
|
||||
is_admin = db.Column(db.Boolean, default=False) # Flaga głównego administratora
|
||||
uzytkownik = db.Column(db.String(80), unique=True, nullable=False)
|
||||
haslo_hash = db.Column(db.String(128), nullable=False)
|
||||
czy_admin = db.Column(db.Boolean, default=False)
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
self.haslo_hash = generate_password_hash(password)
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
return check_password_hash(self.haslo_hash, password)
|
||||
|
||||
class Zbiorka(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
@@ -89,6 +89,9 @@ class Zbiorka(db.Model):
|
||||
ukryta = db.Column(db.Boolean, default=False)
|
||||
ukryj_kwote = db.Column(db.Boolean, default=False)
|
||||
zrealizowana = db.Column(db.Boolean, default=False)
|
||||
pokaz_postep_finanse = db.Column(db.Boolean, default=True, nullable=False)
|
||||
pokaz_postep_pozycje = db.Column(db.Boolean, default=True, nullable=False)
|
||||
pokaz_postep_kwotowo = db.Column(db.Boolean, default=True, nullable=False)
|
||||
|
||||
wplaty = db.relationship(
|
||||
"Wplata",
|
||||
@@ -139,10 +142,8 @@ class Wplata(db.Model):
|
||||
kwota = db.Column(Numeric(12, 2), nullable=False)
|
||||
data = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
opis = db.Column(db.Text, nullable=True)
|
||||
|
||||
zbiorka = db.relationship("Zbiorka", back_populates="wplaty")
|
||||
|
||||
|
||||
class Wydatek(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
zbiorka_id = db.Column(
|
||||
@@ -155,22 +156,24 @@ class Wydatek(db.Model):
|
||||
opis = db.Column(db.Text, nullable=True)
|
||||
|
||||
|
||||
class GlobalSettings(db.Model):
|
||||
class UstawieniaGlobalne(db.Model):
|
||||
__tablename__ = "ustawienia_globalne"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
numer_konta = db.Column(db.String(50), nullable=False)
|
||||
numer_telefonu_blik = db.Column(db.String(50), nullable=False)
|
||||
allowed_login_hosts = db.Column(db.Text, nullable=True)
|
||||
dozwolone_hosty_logowania = db.Column(db.Text, nullable=True)
|
||||
logo_url = db.Column(db.String(255), nullable=True)
|
||||
site_title = db.Column(db.String(120), nullable=True)
|
||||
show_logo_in_navbar = db.Column(db.Boolean, default=False)
|
||||
navbar_brand_mode = db.Column(db.String(10), default="text")
|
||||
footer_brand_mode = db.Column(db.String(10), default="text")
|
||||
footer_text = db.Column(db.String(200), nullable=True)
|
||||
tytul_strony = db.Column(db.String(120), nullable=True)
|
||||
pokaz_logo_w_navbar = db.Column(db.Boolean, default=False)
|
||||
typ_navbar = db.Column(db.String(10), default="text")
|
||||
typ_stopka = db.Column(db.String(10), default="text")
|
||||
stopka_text = db.Column(db.String(200), nullable=True)
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
return db.session.get(User, int(user_id))
|
||||
return db.session.get(Uzytkownik, int(user_id))
|
||||
|
||||
|
||||
@event.listens_for(Engine, "connect")
|
||||
@@ -249,11 +252,9 @@ def markdown_filter(text):
|
||||
|
||||
@app.context_processor
|
||||
def inject_globals():
|
||||
settings = GlobalSettings.query.first()
|
||||
settings = UstawieniaGlobalne.query.first()
|
||||
allowed_hosts_str = (
|
||||
settings.allowed_login_hosts
|
||||
if settings and settings.allowed_login_hosts
|
||||
else ""
|
||||
settings.dozwolone_hosty_logowania if settings and settings.dozwolone_hosty_logowania else ""
|
||||
)
|
||||
client_ip = get_real_ip()
|
||||
return {
|
||||
@@ -290,7 +291,7 @@ def zbiorka(zbiorka_id):
|
||||
zb = db.session.get(Zbiorka, zbiorka_id)
|
||||
if zb is None:
|
||||
abort(404)
|
||||
if zb.ukryta and (not current_user.is_authenticated or not current_user.is_admin):
|
||||
if zb.ukryta and (not current_user.is_authenticated or not current_user.czy_admin):
|
||||
abort(404)
|
||||
|
||||
# scalona oś czasu: wpłaty + wydatki
|
||||
@@ -311,34 +312,22 @@ def zbiorka(zbiorka_id):
|
||||
|
||||
@app.route("/zaloguj", methods=["GET", "POST"])
|
||||
def zaloguj():
|
||||
# Pobierz ustawienia globalne, w tym dozwolone hosty
|
||||
settings = GlobalSettings.query.first()
|
||||
allowed_hosts_str = ""
|
||||
if settings and settings.allowed_login_hosts:
|
||||
allowed_hosts_str = settings.allowed_login_hosts
|
||||
|
||||
# Sprawdzenie, czy adres IP klienta jest dozwolony
|
||||
settings = UstawieniaGlobalne.query.first()
|
||||
allowed_hosts_str = settings.dozwolone_hosty_logowania or "" if settings else ""
|
||||
client_ip = get_real_ip()
|
||||
if not is_allowed_ip(client_ip, allowed_hosts_str):
|
||||
flash(
|
||||
"Dostęp do tego systemu jest zablokowany dla Twojego adresu IP",
|
||||
"danger",
|
||||
)
|
||||
flash("Dostęp do tego systemu jest zablokowany dla Twojego adresu IP", "danger")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
if request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
user = User.query.filter_by(username=username).first()
|
||||
login = request.form["uzytkownik"]
|
||||
password = request.form["haslo"]
|
||||
user = Uzytkownik.query.filter_by(uzytkownik=login).first()
|
||||
if user and user.check_password(password):
|
||||
login_user(user)
|
||||
flash("Zalogowano pomyślnie", "success")
|
||||
next_page = request.args.get("next")
|
||||
return (
|
||||
redirect(next_page)
|
||||
if next_page
|
||||
else redirect(url_for("admin_dashboard"))
|
||||
)
|
||||
return redirect(next_page) if next_page else redirect(url_for("admin_dashboard"))
|
||||
else:
|
||||
flash("Nieprawidłowe dane logowania", "danger")
|
||||
return render_template("login.html")
|
||||
@@ -358,12 +347,12 @@ def zarejestruj():
|
||||
flash("Rejestracja została wyłączona przez administratora", "danger")
|
||||
return redirect(url_for("zaloguj"))
|
||||
if request.method == "POST":
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
if User.query.filter_by(username=username).first():
|
||||
login = request.form["uzytkownik"]
|
||||
password = request.form["haslo"]
|
||||
if Uzytkownik.query.filter_by(uzytkownik=login).first():
|
||||
flash("Użytkownik już istnieje", "danger")
|
||||
return redirect(url_for("register"))
|
||||
new_user = User(username=username)
|
||||
new_user = Uzytkownik(uzytkownik=login)
|
||||
new_user.set_password(password)
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
@@ -376,7 +365,7 @@ def zarejestruj():
|
||||
@app.route("/admin")
|
||||
@login_required
|
||||
def admin_dashboard():
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień do panelu administracyjnego", "danger")
|
||||
return redirect(url_for("index"))
|
||||
active_zbiorki = Zbiorka.query.filter_by(zrealizowana=False).all()
|
||||
@@ -392,30 +381,32 @@ def admin_dashboard():
|
||||
@app.route("/admin/zbiorka/edytuj/<int:zbiorka_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def formularz_zbiorek(zbiorka_id=None):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
# Tryb: dodawanie vs edycja
|
||||
is_edit = zbiorka_id is not None
|
||||
|
||||
# Obiekt zbiórki ładujemy TYLKO przy edycji
|
||||
zb = None
|
||||
if is_edit:
|
||||
zb = db.session.get(Zbiorka, zbiorka_id)
|
||||
if zb is None:
|
||||
abort(404)
|
||||
|
||||
global_settings = GlobalSettings.query.first()
|
||||
global_settings = UstawieniaGlobalne.query.first()
|
||||
|
||||
if request.method == "POST":
|
||||
# Pola wspólne
|
||||
nazwa = request.form.get("nazwa", "").strip()
|
||||
opis = request.form.get("opis", "").strip()
|
||||
|
||||
numer_konta = request.form.get("numer_konta", "").strip()
|
||||
numer_telefonu_blik = request.form.get("numer_telefonu_blik", "").strip()
|
||||
|
||||
# Widoczność kwot i poszczególnych pasków postępu
|
||||
ukryj_kwote = "ukryj_kwote" in request.form
|
||||
pokaz_postep_finanse = "pokaz_postep_finanse" in request.form
|
||||
pokaz_postep_pozycje = "pokaz_postep_pozycje" in request.form
|
||||
pokaz_postep_kwotowo = "pokaz_postep_kwotowo" in request.form
|
||||
|
||||
# Cel — Decimal, > 0
|
||||
try:
|
||||
cel_str = request.form.get("cel", "").replace(",", ".").strip()
|
||||
@@ -424,23 +415,38 @@ def formularz_zbiorek(zbiorka_id=None):
|
||||
raise InvalidOperation
|
||||
except (InvalidOperation, ValueError):
|
||||
flash("Podano nieprawidłową wartość dla celu zbiórki", "danger")
|
||||
# Utwórz tymczasowy obiekt do ponownego renderu formularza
|
||||
temp_zb = (zb or Zbiorka(
|
||||
|
||||
# Przy błędzie celu odtwórz stan formularza (również przełączniki)
|
||||
if is_edit and zb:
|
||||
zb.nazwa = nazwa
|
||||
zb.opis = opis
|
||||
zb.numer_konta = numer_konta
|
||||
zb.numer_telefonu_blik = numer_telefonu_blik
|
||||
zb.ukryj_kwote = ukryj_kwote
|
||||
zb.pokaz_postep_finanse = pokaz_postep_finanse
|
||||
zb.pokaz_postep_pozycje = pokaz_postep_pozycje
|
||||
zb.pokaz_postep_kwotowo = pokaz_postep_kwotowo
|
||||
temp_zb = zb
|
||||
else:
|
||||
temp_zb = Zbiorka(
|
||||
nazwa=nazwa,
|
||||
opis=opis,
|
||||
numer_konta=numer_konta,
|
||||
numer_telefonu_blik=numer_telefonu_blik,
|
||||
cel=None,
|
||||
ukryj_kwote=("ukryj_kwote" in request.form),
|
||||
))
|
||||
ukryj_kwote=ukryj_kwote,
|
||||
pokaz_postep_finanse=pokaz_postep_finanse,
|
||||
pokaz_postep_pozycje=pokaz_postep_pozycje,
|
||||
pokaz_postep_kwotowo=pokaz_postep_kwotowo,
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"admin/formularz_zbiorek.html",
|
||||
zbiorka=temp_zb,
|
||||
global_settings=global_settings,
|
||||
)
|
||||
|
||||
ukryj_kwote = "ukryj_kwote" in request.form
|
||||
|
||||
# Lista produktów
|
||||
names = request.form.getlist("item_nazwa[]")
|
||||
links = request.form.getlist("item_link[]")
|
||||
prices = request.form.getlist("item_cena[]")
|
||||
@@ -464,11 +470,14 @@ def formularz_zbiorek(zbiorka_id=None):
|
||||
zb.opis = opis
|
||||
zb.numer_konta = numer_konta
|
||||
zb.numer_telefonu_blik = numer_telefonu_blik
|
||||
zb.cel = cel # pozostaje Decimal
|
||||
zb.cel = cel
|
||||
zb.ukryj_kwote = ukryj_kwote
|
||||
zb.pokaz_postep_finanse = pokaz_postep_finanse
|
||||
zb.pokaz_postep_pozycje = pokaz_postep_pozycje
|
||||
zb.pokaz_postep_kwotowo = pokaz_postep_kwotowo
|
||||
db.session.commit() # zapisz bazowe pola
|
||||
|
||||
# Nadpisz listę produktów (czyść i dodaj od nowa dla prostoty)
|
||||
# Nadpisz listę produktów
|
||||
zb.przedmioty.clear()
|
||||
for i, raw_name in enumerate(names):
|
||||
name = (raw_name or "").strip()
|
||||
@@ -496,8 +505,11 @@ def formularz_zbiorek(zbiorka_id=None):
|
||||
opis=opis,
|
||||
numer_konta=numer_konta,
|
||||
numer_telefonu_blik=numer_telefonu_blik,
|
||||
cel=cel, # Decimal
|
||||
cel=cel,
|
||||
ukryj_kwote=ukryj_kwote,
|
||||
pokaz_postep_finanse=pokaz_postep_finanse,
|
||||
pokaz_postep_pozycje=pokaz_postep_pozycje,
|
||||
pokaz_postep_kwotowo=pokaz_postep_kwotowo,
|
||||
)
|
||||
db.session.add(nowa)
|
||||
db.session.commit() # potrzebne ID
|
||||
@@ -526,14 +538,16 @@ def formularz_zbiorek(zbiorka_id=None):
|
||||
|
||||
# GET
|
||||
return render_template(
|
||||
"admin/formularz_zbiorek.html", zbiorka=zb, global_settings=global_settings
|
||||
"admin/formularz_zbiorek.html",
|
||||
zbiorka=zb,
|
||||
global_settings=global_settings
|
||||
)
|
||||
|
||||
|
||||
@app.route("/admin/zbiorka/<int:zbiorka_id>/wplata/dodaj", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def dodaj_wplate(zbiorka_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@@ -565,7 +579,7 @@ def dodaj_wplate(zbiorka_id):
|
||||
@app.route("/admin/zbiorka/usun/<int:zbiorka_id>", methods=["POST"])
|
||||
@login_required
|
||||
def usun_zbiorka(zbiorka_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger")
|
||||
return redirect(url_for("index"))
|
||||
zb = db.session.get(Zbiorka, zbiorka_id)
|
||||
@@ -580,7 +594,7 @@ def usun_zbiorka(zbiorka_id):
|
||||
@app.route("/admin/zbiorka/edytuj_stan/<int:zbiorka_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def edytuj_stan(zbiorka_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger")
|
||||
return redirect(url_for("index"))
|
||||
zb = db.session.get(Zbiorka, zbiorka_id)
|
||||
@@ -602,7 +616,7 @@ def edytuj_stan(zbiorka_id):
|
||||
@app.route("/admin/zbiorka/zmien_widzialnosc/<int:zbiorka_id>", methods=["POST"])
|
||||
@login_required
|
||||
def zmien_widzialnosc(zbiorka_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger")
|
||||
return redirect(url_for("index"))
|
||||
zb = db.session.get(Zbiorka, zbiorka_id)
|
||||
@@ -615,15 +629,17 @@ def zmien_widzialnosc(zbiorka_id):
|
||||
|
||||
|
||||
def create_admin_account():
|
||||
admin = User.query.filter_by(is_admin=True).first()
|
||||
admin = Uzytkownik.query.filter_by(czy_admin=True).first()
|
||||
if not admin:
|
||||
main_admin = User(username=app.config["MAIN_ADMIN_USERNAME"], is_admin=True)
|
||||
main_admin = Uzytkownik(
|
||||
uzytkownik=app.config["MAIN_ADMIN_USERNAME"],
|
||||
czy_admin=True
|
||||
)
|
||||
main_admin.set_password(app.config["MAIN_ADMIN_PASSWORD"])
|
||||
db.session.add(main_admin)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
|
||||
@app.after_request
|
||||
def apply_headers(response):
|
||||
if request.path.startswith("/static/"):
|
||||
@@ -638,9 +654,9 @@ def apply_headers(response):
|
||||
return response
|
||||
|
||||
path_norm = request.path.lstrip("/")
|
||||
is_admin = path_norm.startswith("admin/") or path_norm == "admin"
|
||||
czy_admin = path_norm.startswith("admin/") or path_norm == "admin"
|
||||
|
||||
if is_admin:
|
||||
if czy_admin:
|
||||
if (response.mimetype or "").startswith("text/html"):
|
||||
response.headers["Cache-Control"] = "no-store, no-cache"
|
||||
response.headers.pop("ETag", None)
|
||||
@@ -667,7 +683,7 @@ def apply_headers(response):
|
||||
|
||||
if (
|
||||
app.config.get("BLOCK_BOTS", False)
|
||||
and not is_admin
|
||||
and not czy_admin
|
||||
and not request.path.startswith("/static/")
|
||||
):
|
||||
cc_override = app.config.get("CACHE_CONTROL_HEADER")
|
||||
@@ -683,60 +699,58 @@ def apply_headers(response):
|
||||
@app.route("/admin/ustawienia", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def admin_ustawienia():
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień do panelu administracyjnego", "danger")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
client_ip = get_real_ip()
|
||||
settings = GlobalSettings.query.first()
|
||||
settings = UstawieniaGlobalne.query.first()
|
||||
if request.method == "POST":
|
||||
numer_konta = request.form.get("numer_konta")
|
||||
numer_telefonu_blik = request.form.get("numer_telefonu_blik")
|
||||
allowed_login_hosts = request.form.get("allowed_login_hosts")
|
||||
dozwolone_hosty_logowania = request.form.get("dozwolone_hosty_logowania")
|
||||
logo_url = request.form.get("logo_url")
|
||||
site_title = request.form.get("site_title")
|
||||
navbar_brand_mode = request.form.get("navbar_brand_mode", "text")
|
||||
footer_brand_mode = request.form.get("footer_brand_mode", "text")
|
||||
footer_text = request.form.get("footer_text") or None
|
||||
show_logo_in_navbar = navbar_brand_mode == "logo"
|
||||
tytul_strony = request.form.get("tytul_strony")
|
||||
typ_navbar = request.form.get("typ_navbar", "text")
|
||||
typ_stopka = request.form.get("typ_stopka", "text")
|
||||
stopka_text = request.form.get("stopka_text") or None
|
||||
pokaz_logo_w_navbar = (typ_navbar == "logo")
|
||||
|
||||
if settings is None:
|
||||
settings = GlobalSettings(
|
||||
settings = UstawieniaGlobalne(
|
||||
numer_konta=numer_konta,
|
||||
numer_telefonu_blik=numer_telefonu_blik,
|
||||
allowed_login_hosts=allowed_login_hosts,
|
||||
dozwolone_hosty_logowania=dozwolone_hosty_logowania,
|
||||
logo_url=logo_url,
|
||||
site_title=site_title,
|
||||
show_logo_in_navbar=show_logo_in_navbar,
|
||||
navbar_brand_mode=navbar_brand_mode,
|
||||
footer_brand_mode=footer_brand_mode,
|
||||
footer_text=footer_text,
|
||||
tytul_strony=tytul_strony,
|
||||
pokaz_logo_w_navbar=pokaz_logo_w_navbar,
|
||||
typ_navbar=typ_navbar,
|
||||
typ_stopka=typ_stopka,
|
||||
stopka_text=stopka_text,
|
||||
)
|
||||
db.session.add(settings)
|
||||
else:
|
||||
settings.numer_konta = numer_konta
|
||||
settings.numer_telefonu_blik = numer_telefonu_blik
|
||||
settings.allowed_login_hosts = allowed_login_hosts
|
||||
settings.dozwolone_hosty_logowania = dozwolone_hosty_logowania
|
||||
settings.logo_url = logo_url
|
||||
settings.site_title = site_title
|
||||
settings.show_logo_in_navbar = show_logo_in_navbar
|
||||
settings.navbar_brand_mode = navbar_brand_mode
|
||||
settings.footer_brand_mode = footer_brand_mode
|
||||
settings.footer_text = footer_text
|
||||
settings.tytul_strony = tytul_strony
|
||||
settings.pokaz_logo_w_navbar = pokaz_logo_w_navbar
|
||||
settings.typ_navbar = typ_navbar
|
||||
settings.typ_stopka = typ_stopka
|
||||
settings.stopka_text = stopka_text
|
||||
|
||||
db.session.commit()
|
||||
flash("Ustawienia globalne zostały zaktualizowane", "success")
|
||||
return redirect(url_for("admin_dashboard"))
|
||||
|
||||
return render_template(
|
||||
"admin/ustawienia.html", settings=settings, client_ip=client_ip
|
||||
)
|
||||
return render_template("admin/ustawienia.html", settings=settings, client_ip=client_ip)
|
||||
|
||||
|
||||
@app.route("/admin/zbiorka/<int:zbiorka_id>/wydatek/dodaj", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def dodaj_wydatek(zbiorka_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@@ -778,7 +792,7 @@ def dodaj_wydatek(zbiorka_id):
|
||||
)
|
||||
@login_required
|
||||
def oznacz_zbiorka(zbiorka_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień do wykonania tej operacji", "danger")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@@ -810,7 +824,7 @@ def robots():
|
||||
@app.route("/admin/zbiorka/<int:zbiorka_id>/transakcje")
|
||||
@login_required
|
||||
def transakcje_zbiorki(zbiorka_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger"); return redirect(url_for("index"))
|
||||
zb = db.session.get(Zbiorka, zbiorka_id)
|
||||
if zb is None:
|
||||
@@ -826,7 +840,7 @@ def transakcje_zbiorki(zbiorka_id):
|
||||
@app.route("/admin/wplata/<int:wplata_id>/zapisz", methods=["POST"])
|
||||
@login_required
|
||||
def zapisz_wplate(wplata_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger"); return redirect(url_for("index"))
|
||||
w = db.session.get(Wplata, wplata_id)
|
||||
if w is None:
|
||||
@@ -852,7 +866,7 @@ def zapisz_wplate(wplata_id):
|
||||
@app.route("/admin/wplata/<int:wplata_id>/usun", methods=["POST"])
|
||||
@login_required
|
||||
def usun_wplate(wplata_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger"); return redirect(url_for("index"))
|
||||
w = db.session.get(Wplata, wplata_id)
|
||||
if w is None:
|
||||
@@ -868,7 +882,7 @@ def usun_wplate(wplata_id):
|
||||
@app.route("/admin/wydatek/<int:wydatek_id>/zapisz", methods=["POST"])
|
||||
@login_required
|
||||
def zapisz_wydatek(wydatek_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger"); return redirect(url_for("index"))
|
||||
x = db.session.get(Wydatek, wydatek_id)
|
||||
if x is None:
|
||||
@@ -895,7 +909,7 @@ def zapisz_wydatek(wydatek_id):
|
||||
@app.route("/admin/wydatek/<int:wydatek_id>/usun", methods=["POST"])
|
||||
@login_required
|
||||
def usun_wydatek(wydatek_id):
|
||||
if not current_user.is_admin:
|
||||
if not current_user.czy_admin:
|
||||
flash("Brak uprawnień", "danger"); return redirect(url_for("index"))
|
||||
x = db.session.get(Wydatek, wydatek_id)
|
||||
if x is None:
|
||||
@@ -926,14 +940,12 @@ def healthcheck():
|
||||
if __name__ == "__main__":
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
# Tworzenie konta głównego admina, jeśli nie istnieje
|
||||
stmt = select(User).filter_by(is_admin=True)
|
||||
stmt = select(Uzytkownik).filter_by(czy_admin=True)
|
||||
admin = db.session.execute(stmt).scalars().first()
|
||||
|
||||
if not admin:
|
||||
main_admin = User(
|
||||
username=app.config["MAIN_ADMIN_USERNAME"],
|
||||
is_admin=True
|
||||
main_admin = Uzytkownik(
|
||||
uzytkownik=app.config["MAIN_ADMIN_USERNAME"],
|
||||
czy_admin=True
|
||||
)
|
||||
main_admin.set_password(app.config["MAIN_ADMIN_PASSWORD"])
|
||||
db.session.add(main_admin)
|
||||
|
@@ -1,19 +1,20 @@
|
||||
(function () {
|
||||
// IBAN: tylko cyfry, auto-grupowanie co 4 (po prefiksie PL)
|
||||
// static/js/ustawienia.js
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Formatowanie IBAN (PL)
|
||||
const iban = document.getElementById('numer_konta');
|
||||
if (iban) {
|
||||
iban.addEventListener('input', () => {
|
||||
const digits = iban.value.replace(/\\D/g, '').slice(0, 26); // 26 cyfr po "PL"
|
||||
const digits = iban.value.replace(/\D/g, '').slice(0, 26);
|
||||
const chunked = digits.replace(/(.{4})/g, '$1 ').trim();
|
||||
iban.value = chunked;
|
||||
});
|
||||
}
|
||||
|
||||
// Telefon BLIK: tylko cyfry, format 3-3-3
|
||||
// Telefon BLIK 3-3-3
|
||||
const tel = document.getElementById('numer_telefonu_blik');
|
||||
if (tel) {
|
||||
tel.addEventListener('input', () => {
|
||||
const digits = tel.value.replace(/\\D/g, '').slice(0, 9);
|
||||
const digits = tel.value.replace(/\D/g, '').slice(0, 9);
|
||||
const parts = [];
|
||||
if (digits.length > 0) parts.push(digits.substring(0, 3));
|
||||
if (digits.length > 3) parts.push(digits.substring(3, 6));
|
||||
@@ -22,24 +23,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Biała lista IP/hostów — helpery
|
||||
const ta = document.getElementById('allowed_login_hosts');
|
||||
// Biała lista IP/hostów
|
||||
const ta = document.getElementById('dozwolone_hosty_logowania');
|
||||
const count = document.getElementById('hostsCount');
|
||||
const addBtn = document.getElementById('btn-add-host');
|
||||
const addMyBtn = document.getElementById('btn-add-my-ip');
|
||||
const input = document.getElementById('host_input');
|
||||
const dedupeBtn = document.getElementById('btn-dedupe');
|
||||
|
||||
function parseList(text) {
|
||||
// akceptuj przecinki, średniki i nowe linie; trimuj; usuń puste
|
||||
return text
|
||||
.split(/[\\n,;]+/)
|
||||
const parseList = (text) =>
|
||||
text
|
||||
.split(/[\r\n,;]+/) // \r?\n, przecinek, średnik
|
||||
.map(s => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
function formatList(arr) {
|
||||
return arr.join('\\n');
|
||||
}
|
||||
function dedupe(arr) {
|
||||
|
||||
const formatList = (arr) => arr.join('\n');
|
||||
|
||||
const dedupe = (arr) => {
|
||||
const seen = new Set();
|
||||
const out = [];
|
||||
for (const v of arr) {
|
||||
@@ -47,22 +47,23 @@
|
||||
if (!seen.has(k)) { seen.add(k); out.push(v); }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function updateCount() {
|
||||
};
|
||||
|
||||
const updateCount = () => {
|
||||
if (!ta || !count) return;
|
||||
count.textContent = parseList(ta.value).length.toString();
|
||||
}
|
||||
function addEntry(val) {
|
||||
count.textContent = String(parseList(ta.value).length);
|
||||
};
|
||||
|
||||
const addEntry = (val) => {
|
||||
if (!ta || !val) return;
|
||||
const list = dedupe([...parseList(ta.value), val]);
|
||||
ta.value = formatList(list);
|
||||
updateCount();
|
||||
}
|
||||
};
|
||||
|
||||
if (ta) {
|
||||
ta.addEventListener('input', updateCount);
|
||||
// inicjalny przelicznik
|
||||
updateCount();
|
||||
updateCount(); // inicjalne przeliczenie
|
||||
}
|
||||
|
||||
if (addBtn && input) {
|
||||
@@ -82,11 +83,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
const dedupeBtn = document.getElementById('btn-dedupe');
|
||||
if (dedupeBtn && ta) {
|
||||
dedupeBtn.addEventListener('click', () => {
|
||||
ta.value = formatList(dedupe(parseList(ta.value)));
|
||||
updateCount();
|
||||
});
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
@@ -9,11 +9,11 @@
|
||||
}, false);
|
||||
})();
|
||||
|
||||
const pw = document.getElementById('password');
|
||||
const pw = document.getElementById("haslo");
|
||||
const toggle = document.getElementById('togglePw');
|
||||
toggle.addEventListener('click', () => {
|
||||
const isText = pw.type === 'text';
|
||||
pw.type = isText ? 'password' : 'text';
|
||||
pw.type = isText ? "haslo" : 'text';
|
||||
toggle.textContent = isText ? 'Pokaż' : 'Ukryj';
|
||||
toggle.setAttribute('aria-pressed', (!isText).toString());
|
||||
pw.focus();
|
||||
|
@@ -5,7 +5,7 @@
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
const pw1 = document.getElementById('password');
|
||||
const pw1 = document.getElementById("haslo");
|
||||
const pw2 = document.getElementById('password2');
|
||||
if (pw1.value !== pw2.value) {
|
||||
e.preventDefault();
|
||||
@@ -19,11 +19,11 @@
|
||||
}, false);
|
||||
})();
|
||||
|
||||
const pw = document.getElementById('password');
|
||||
const pw = document.getElementById("haslo");
|
||||
const toggle = document.getElementById('togglePw');
|
||||
toggle.addEventListener('click', () => {
|
||||
const isText = pw.type === 'text';
|
||||
pw.type = isText ? 'password' : 'text';
|
||||
pw.type = isText ? "haslo" : 'text';
|
||||
toggle.textContent = isText ? 'Pokaż' : 'Ukryj';
|
||||
pw.focus();
|
||||
});
|
||||
|
@@ -269,7 +269,44 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="row g-3 mt-2">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="pokaz_postep_finanse"
|
||||
name="pokaz_postep_finanse" {% if is_edit and zbiorka.pokaz_postep_finanse or not
|
||||
is_edit %}checked{% endif %}>
|
||||
<label class="form-check-label" for="pokaz_postep_finanse">Pokaż postęp: Finanse</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="pokaz_postep_pozycje"
|
||||
name="pokaz_postep_pozycje" {% if is_edit and zbiorka.pokaz_postep_pozycje or not
|
||||
is_edit %}checked{% endif %}>
|
||||
<label class="form-check-label" for="pokaz_postep_pozycje">Pokaż postęp: Zakupy
|
||||
(liczba)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="pokaz_postep_kwotowo"
|
||||
name="pokaz_postep_kwotowo" {% if is_edit and zbiorka.pokaz_postep_kwotowo or not
|
||||
is_edit %}checked{% endif %}>
|
||||
<label class="form-check-label" for="pokaz_postep_kwotowo">Pokaż postęp: Zakupy
|
||||
(kwotowo)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- CTA -->
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
|
@@ -6,14 +6,24 @@
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3 class="mb-0">Transakcje: {{ zbiorka.nazwa }}</h3>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-sm btn-outline-light border" href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}">+
|
||||
Wpłata</a>
|
||||
<a class="btn btn-sm btn-outline-light border"
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=zbiorka.id) }}">+ Wydatek</a>
|
||||
<a class="btn btn-sm btn-outline-light border"
|
||||
href="{{ url_for('zbiorka', zbiorka_id=zbiorka.id) }}">Szczegóły zbiórki</a>
|
||||
<div class="btn-group" role="group" aria-label="Akcje zbiórki">
|
||||
<a class="btn btn-sm btn-outline-light border" href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}">
|
||||
<i class="fas fa-plus-circle"></i> Dodaj wpłatę
|
||||
</a>
|
||||
|
||||
<a class="btn btn-sm btn-outline-light border" href="{{ url_for('dodaj_wydatek', zbiorka_id=zbiorka.id) }}">
|
||||
Dodaj wydatek
|
||||
</a>
|
||||
|
||||
<a class="btn btn-sm btn-outline-light border" href="{{ url_for('edytuj_stan', zbiorka_id=zbiorka.id) }}">
|
||||
Edytuj stan
|
||||
</a>
|
||||
|
||||
<a class="btn btn-sm btn-outline-light border" href="{{ url_for('zbiorka', zbiorka_id=zbiorka.id) }}">
|
||||
Otwórz ↗
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
|
@@ -40,42 +40,60 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SEKCJA: Dostępy / biała lista IP -->
|
||||
<!-- SEKCJA: Dostępy / biała lista IP -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-secondary text-white d-flex align-items-center justify-content-between gap-2">
|
||||
<h3 class="card-title mb-0">Dostęp — dozwolone adresy IP / hosty</h3>
|
||||
<small class="opacity-75">Zależnie od konfiguracji, logowanie może wymagać dopasowania do białej listy</small>
|
||||
<small class="opacity-75">Zależnie od konfiguracji logowanie może wymagać dopasowania do białej listy</small>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<!-- Wiersz z inputem i przyciskiem dodawania -->
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-12 col-md-6">
|
||||
<label for="host_input" class="form-label">Dodaj pojedynczy IP/host</label>
|
||||
<input type="text" class="form-control" id="host_input" placeholder="np. 203.0.113.42 lub corp.example.com"
|
||||
aria-describedby="hostAddHelp">
|
||||
<div id="hostAddHelp" class="form-text">Po wpisaniu kliknij „Dodaj do listy”. Duplikaty są pomijane.</div>
|
||||
<div class="col-12 col-lg-8">
|
||||
<label for="host_input" class="form-label">Dodaj IP/host</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="host_input"
|
||||
placeholder="np. 203.0.113.42 lub corp.example.com" aria-describedby="hostAddHelp">
|
||||
<button type="button" class="btn btn-outline-primary" id="btn-add-host">
|
||||
<i class="fas fa-plus-circle"></i> Dodaj
|
||||
</button>
|
||||
</div>
|
||||
<div id="hostAddHelp" class="form-text">Po wpisaniu kliknij „Dodaj”. Duplikaty są pomijane.</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="d-flex flex-wrap gap-2 justify-content-lg-end">
|
||||
<button type="button" class="btn btn-light text-dark" id="btn-add-my-ip" data-my-ip="{{ client_ip }}">
|
||||
<i class="fas fa-location-arrow"></i> Dodaj moje IP ({{ client_ip }})
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="btn-dedupe">
|
||||
<i class="fas fa-broom"></i> Usuń duplikaty
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-light border" id="btn-add-host">Dodaj do listy</button>
|
||||
<button type="button" class="btn btn-light text-dark" id="btn-add-my-ip" data-my-ip="{{ client_ip }}">Dodaj
|
||||
moje IP ({{ client_ip }})</button>
|
||||
<button type="button" class="btn btn-outline-light border" id="btn-dedupe">Usuń duplikaty</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label for="allowed_login_hosts" class="form-label">Dozwolone hosty logowania (jeden na linię lub rozdzielone
|
||||
przecinkami)</label>
|
||||
<textarea class="form-control" id="allowed_login_hosts" name="allowed_login_hosts" rows="6"
|
||||
placeholder="Adresy IP lub nazwy domen — każdy w osobnej linii lub rozdzielony przecinkiem">{{ settings.allowed_login_hosts if settings and settings.allowed_login_hosts else '' }}</textarea>
|
||||
<div class="d-flex justify-content-between mt-1">
|
||||
<small class="text-muted">Akceptowane separatory: przecinek (`,`), średnik (`;`) i nowa linia.</small>
|
||||
<small class="text-muted">Pozycji na liście: <span id="hostsCount">0</span></small>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<label for="dozwolone_hosty_logowania" class="form-label mb-0">
|
||||
Dozwolone hosty logowania (jeden na linię lub rozdzielone przecinkami)
|
||||
</label>
|
||||
<span class="badge text-bg-secondary">Pozycji: <span id="hostsCount">0</span></span>
|
||||
</div>
|
||||
|
||||
<textarea class="form-control" id="dozwolone_hosty_logowania" name="dozwolone_hosty_logowania" rows="6"
|
||||
placeholder="Adresy IP lub nazwy domen — każdy w osobnej linii lub rozdzielony przecinkiem">{{ settings.dozwolone_hosty_logowania if settings and settings.dozwolone_hosty_logowania else '' }}</textarea>
|
||||
|
||||
<small class="text-muted d-block mt-1">
|
||||
Akceptowane separatory: przecinek (`,`), średnik (`;`) i nowa linia.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- SEKCJA: Branding -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-secondary text-white d-flex align-items-center justify-content-between gap-2">
|
||||
@@ -99,9 +117,9 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="site_title" class="form-label">Tytuł serwisu</label>
|
||||
<input type="text" class="form-control" id="site_title" name="site_title"
|
||||
value="{{ settings.site_title if settings else '' }}" placeholder="Np. Zbiórki unitraklub.pl">
|
||||
<label for="tytul_strony" class="form-label">Tytuł serwisu</label>
|
||||
<input type="text" class="form-control" id="tytul_strony" name="tytul_strony"
|
||||
value="{{ settings.tytul_strony if settings else '' }}" placeholder="Np. Zbiórki unitraklub.pl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -112,15 +130,15 @@
|
||||
<div class="col-md-6">
|
||||
<h6 class="mb-2">Menu (navbar)</h6>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="navbar_brand_mode" id="navbar_mode_logo" value="logo"
|
||||
{% if settings and settings.navbar_brand_mode=='logo' or (settings and settings.show_logo_in_navbar)
|
||||
%}checked{% endif %}>
|
||||
<input class="form-check-input" type="radio" name="typ_navbar" id="navbar_mode_logo" value="logo" {% if
|
||||
settings and settings.typ_navbar=='logo' or (settings and settings.pokaz_logo_w_navbar) %}checked{%
|
||||
endif %}>
|
||||
<label class="form-check-label" for="navbar_mode_logo">Pokaż logo</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="navbar_brand_mode" id="navbar_mode_text" value="text"
|
||||
{% if not settings or (settings and settings.navbar_brand_mode !='logo' and not
|
||||
settings.show_logo_in_navbar) %}checked{% endif %}>
|
||||
<input class="form-check-input" type="radio" name="typ_navbar" id="navbar_mode_text" value="text" {% if
|
||||
not settings or (settings and settings.typ_navbar !='logo' and not settings.pokaz_logo_w_navbar)
|
||||
%}checked{% endif %}>
|
||||
<label class="form-check-label" for="navbar_mode_text">Pokaż tekst</label>
|
||||
</div>
|
||||
<div class="form-text mt-1">Jeśli wybierzesz logo, użyjemy adresu z pola "Tytuł serwisu".</div>
|
||||
@@ -130,19 +148,19 @@
|
||||
<div class="col-md-6">
|
||||
<h6 class="mb-2">Stopka</h6>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="footer_brand_mode" id="footer_mode_logo" value="logo"
|
||||
{% if settings and settings.footer_brand_mode=='logo' %}checked{% endif %}>
|
||||
<input class="form-check-input" type="radio" name="typ_stopka" id="footer_mode_logo" value="logo" {% if
|
||||
settings and settings.typ_stopka=='logo' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="footer_mode_logo">Logo</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="footer_brand_mode" id="footer_mode_text" value="text"
|
||||
{% if not settings or (settings and settings.footer_brand_mode !='logo' ) %}checked{% endif %}>
|
||||
<input class="form-check-input" type="radio" name="typ_stopka" id="footer_mode_text" value="text" {% if
|
||||
not settings or (settings and settings.typ_stopka !='logo' ) %}checked{% endif %}>
|
||||
<label class="form-check-label" for="footer_mode_text">Tekst</label>
|
||||
</div>
|
||||
|
||||
<label for="footer_text" class="form-label mt-2">Tekst w stopce (gdy wybrano „Tekst”)</label>
|
||||
<input type="text" class="form-control" id="footer_text" name="footer_text"
|
||||
value="{{ settings.footer_text if settings and settings.footer_text else '' }}"
|
||||
<label for="stopka_text" class="form-label mt-2">Tekst w stopce (gdy wybrano „Tekst”)</label>
|
||||
<input type="text" class="form-control" id="stopka_text" name="stopka_text"
|
||||
value="{{ settings.stopka_text if settings and settings.stopka_text else '' }}"
|
||||
placeholder="Np. © {{ now().year if now else '2025' }} Zbiórki">
|
||||
<div class="form-text">Pozostaw pusty, by użyć domyślnego.</div>
|
||||
</div>
|
||||
|
@@ -14,13 +14,13 @@
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center gap-2" href="{{ url_for('index') }}">
|
||||
{% set nav_mode = (global_settings.navbar_brand_mode if global_settings and
|
||||
global_settings.navbar_brand_mode else ('logo' if global_settings and
|
||||
global_settings.show_logo_in_navbar else 'text')) %}
|
||||
{% set nav_mode = (global_settings.typ_navbar if global_settings and
|
||||
global_settings.typ_navbar else ('logo' if global_settings and
|
||||
global_settings.pokaz_logo_w_navbar else 'text')) %}
|
||||
{% if nav_mode == 'logo' and global_settings and global_settings.logo_url %}
|
||||
<img src="{{ global_settings.logo_url }}" alt="Logo" style="max-height:40px; vertical-align:middle;">
|
||||
{% else %}
|
||||
<span>{{ global_settings.site_title if global_settings and global_settings.site_title else "Zbiórki"
|
||||
<span>{{ global_settings.tytul_strony if global_settings and global_settings.tytul_strony else "Zbiórki"
|
||||
}}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
@@ -72,12 +72,12 @@
|
||||
|
||||
<!-- stopka -->
|
||||
<footer class="mt-auto text-center py-3 border-top" style="background: var(--surface-0);">
|
||||
{% set footer_mode = global_settings.footer_brand_mode if global_settings and global_settings.footer_brand_mode
|
||||
{% set footer_mode = global_settings.typ_stopka if global_settings and global_settings.typ_stopka
|
||||
else 'text' %}
|
||||
{% if footer_mode == 'logo' and global_settings and global_settings.logo_url %}
|
||||
<img src="{{ global_settings.logo_url }}" alt="Logo" style="max-height:28px;">
|
||||
{% else %}
|
||||
{{ global_settings.footer_text if global_settings and global_settings.footer_text else "© " ~ (now().year if now
|
||||
{{ global_settings.stopka_text if global_settings and global_settings.stopka_text else "© " ~ (now().year if now
|
||||
else '2025') ~ " linuxiarz.pl" }}
|
||||
{% endif %}
|
||||
<div class="small text-muted">v{{ APP_VERSION }}</div>
|
||||
|
@@ -105,7 +105,7 @@ zbiórki{% endif %}{% endblock %}
|
||||
{% else %}
|
||||
<h5 class="mb-2">Brak aktywnych zbiórek</h5>
|
||||
<p class="text-muted mb-4">Wygląda na to, że teraz nic nie zbieramy.</p>
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
{% if current_user.is_authenticated and current_user.czy_admin %}
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-primary">Utwórz nową zbiórkę</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('zbiorki_zrealizowane') }}" class="btn btn-primary">Zobacz zrealizowane</a>
|
||||
|
@@ -18,21 +18,21 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Nazwa użytkownika</label>
|
||||
<input type="text" class="form-control" id="username" name="username"
|
||||
<label for="uzytkownik" class="form-label">Nazwa użytkownika</label>
|
||||
<input type="text" class="form-control" id="uzytkownik" name="uzytkownik"
|
||||
autocomplete="username" autocapitalize="none" spellcheck="false" required autofocus>
|
||||
<div class="invalid-feedback">Podaj nazwę użytkownika.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label for="password" class="form-label d-flex justify-content-between align-items-center">
|
||||
<label for="haslo" class="form-label d-flex justify-content-between align-items-center">
|
||||
<span>Hasło</span>
|
||||
<small id="capsWarning" class="text-muted" style="display:none;">CAPS LOCK
|
||||
włączony</small>
|
||||
</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="password" name="password"
|
||||
<input type="password" class="form-control" id="haslo" name="haslo"
|
||||
autocomplete="current-password" required minlength="5">
|
||||
<button type="button" class="btn btn-secondary rounded-end" id="togglePw"
|
||||
aria-label="Pokaż/ukryj hasło">Pokaż</button>
|
||||
|
@@ -14,20 +14,20 @@
|
||||
<form method="post" class="needs-validation" novalidate>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Nazwa użytkownika</label>
|
||||
<input type="text" class="form-control" id="username" name="username"
|
||||
<label for="uzytkownik" class="form-label">Nazwa użytkownika</label>
|
||||
<input type="text" class="form-control" id="uzytkownik" name="uzytkownik"
|
||||
autocomplete="username" autocapitalize="none" spellcheck="false" required autofocus>
|
||||
<div class="invalid-feedback">Podaj nazwę użytkownika.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label d-flex justify-content-between align-items-center">
|
||||
<label for="haslo" class="form-label d-flex justify-content-between align-items-center">
|
||||
<span>Hasło</span>
|
||||
<small id="capsWarning" class="text-muted" style="display:none;">CAPS LOCK
|
||||
włączony</small>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="password" name="password"
|
||||
<input type="password" class="form-control" id="haslo" name="haslo"
|
||||
autocomplete="new-password" required minlength="6">
|
||||
<button type="button" class="btn btn-secondary" id="togglePw"
|
||||
aria-label="Pokaż/ukryj hasło">Pokaż</button>
|
||||
|
@@ -103,23 +103,28 @@
|
||||
<h5 class="mb-0">Postęp</h5>
|
||||
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
{% if has_cel and not zbiorka.ukryj_kwote %}
|
||||
{% if has_cel and not zbiorka.ukryj_kwote and zbiorka.pokaz_postep_finanse %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">
|
||||
Finanse: {{ zbiorka.stan|round(2) }} / {{ zbiorka.cel|round(2) }} PLN
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if has_items %}
|
||||
|
||||
{% if has_items and zbiorka.pokaz_postep_pozycje %}
|
||||
<span class="badge bg-secondary">Pozycje: {{ kupione_cnt }}/{{ total_cnt }}</span>
|
||||
{% if not zbiorka.ukryj_kwote and (suma_all or 0) > 0 %}
|
||||
<span class="badge bg-secondary">Zakupy (kwotowo):
|
||||
{{ (suma_kupione or 0)|round(2) }} / {{ (suma_all or 0)|round(2) }} PLN
|
||||
{% endif %}
|
||||
|
||||
{% if has_items and not zbiorka.ukryj_kwote and (suma_all or 0) > 0 and zbiorka.pokaz_postep_kwotowo %}
|
||||
<span class="badge bg-secondary">
|
||||
Zakupy (kwotowo): {{ (suma_kupione or 0)|round(2) }} / {{ (suma_all or 0)|round(2) }} PLN
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="hr-bw">
|
||||
<!-- Pasek: Finanse (zawsze) -->
|
||||
|
||||
{# Pasek: Finanse #}
|
||||
{% if zbiorka.pokaz_postep_finanse %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Finanse</small>
|
||||
<div class="progress" role="progressbar" aria-valuenow="{{ progress_clamped|round(2) }}" aria-valuemin="0"
|
||||
@@ -130,9 +135,10 @@
|
||||
{% if zbiorka.ukryj_kwote %}—{% else %}{{ progress|round(1) }}%{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if has_items %}
|
||||
<!-- Pasek: Zakupy sztukami -->
|
||||
{# Pasek: Zakupy sztukami #}
|
||||
{% if has_items and zbiorka.pokaz_postep_pozycje %}
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">Zakupy (liczba pozycji)</small>
|
||||
<div class="progress" role="progressbar" aria-valuenow="{{ items_pct|round(2) }}" aria-valuemin="0"
|
||||
@@ -141,9 +147,10 @@
|
||||
</div>
|
||||
<small class="text-muted">{{ items_pct|round(1) }}%</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not zbiorka.ukryj_kwote and (suma_all or 0) > 0 %}
|
||||
<!-- Pasek: Zakupy kwotowo -->
|
||||
{# Pasek: Zakupy kwotowo #}
|
||||
{% if has_items and not zbiorka.ukryj_kwote and (suma_all or 0) > 0 and zbiorka.pokaz_postep_kwotowo %}
|
||||
<div>
|
||||
<small class="text-muted">Zakupy (kwotowo)</small>
|
||||
<div class="progress" role="progressbar" aria-valuenow="{{ suma_pct|round(2) }}" aria-valuemin="0"
|
||||
@@ -153,7 +160,6 @@
|
||||
<small class="text-muted">{{ suma_pct|round(1) }}%</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -238,7 +244,7 @@
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
{% if current_user.is_authenticated and current_user.czy_admin %}
|
||||
<hr>
|
||||
<div class="d-grid gap-2 mt-2">
|
||||
<a href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Dodaj
|
||||
@@ -264,7 +270,7 @@
|
||||
{% if aktywnosci and aktywnosci|length > 0 %}
|
||||
<small class="text-muted">Łącznie pozycji: {{ aktywnosci|length }}</small>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
{% if current_user.is_authenticated and current_user.czy_admin %}
|
||||
<a href="{{ url_for('transakcje_zbiorki', zbiorka_id=zbiorka.id) }}"
|
||||
class="btn btn-sm btn-outline-light border">
|
||||
Zarządzaj
|
||||
|
Reference in New Issue
Block a user