Pilne
Nowa obwodnica Banina — prace ruszają w październiku ◆ Dożynki Gminne 2026 — wielkie święto żniw przyciągnie tłumy ◆ Rozbudowa SP w Baninie — przetarg ogłoszony do 20 sierpnia ◆ Linia 183 zmienia trasę — nowy rozkład od 1 września ◆ Nowe kamery monitoringu w centrum Banina — projekt z funduszy UE ◆ Nowa obwodnica Banina — prace ruszają w październiku ◆ Dożynki Gminne 2026 — wielkie święto żniw przyciągnie tłumy ◆ Rozbudowa SP w Baninie — przetarg ogłoszony do 20 sierpnia ◆ Linia 183 zmienia trasę — nowy rozkład od 1 września ◆ Nowe kamery monitoringu w centrum Banina — projekt z funduszy UE ◆
📋

Dokumentacja techniczna

Jak portal jest zbudowany, gdzie mieszkają dane i co przełącza się z panelu

⚠️ Środowisko lokalne — dane demonstracyjne

Portal działa na maszynie deweloperskiej pod adresem naszebanino.syn.test, na bazie SQLite wypełnionej danymi startowymi z seedera. Konta, ogłoszenia, oceny i wpisy na forum są przykładowe — służą do sprawdzenia wyglądu i działania, nie są prawdziwą treścią portalu.

Przed uruchomieniem publicznym manage.py seed_portal — Trzeba zmienić hasła kont startowych, wpisać prawdziwe dane bankowe i klucze bramki płatności, a dane demonstracyjne usunąć.

Architektura

Nasze Banino to aplikacja wielostronicowa (MPA) w Django. Każda treść ma własny adres, jest renderowana na serwerze i zapisana w bazie danych. Poprzednia wersja była szablonem frontendowym bez backendu, w którym konta, oceny i ogłoszenia leżały w pamięci przeglądarki.

Warstwa serwera Django 6

Widoki klasowe, szablony serwerowe, sesje w ciasteczku HttpOnly.

Baza danych SQLite → PostgreSQL

Przełączenie zmienną DB_ENGINE, bez zmian w kodzie.

Warstwa przeglądarki HTMX + Alpine + TypeScript

Bez luźnych skryptów, bez typu any, strażniki typów na każdej odpowiedzi.

Wygląd Tailwind CSS 4

Motyw i klasy przeniesione 1:1 z szablonu.

Gdzie mieszkają dane

Nic nie jest trzymane w przeglądarce. Wszystko, co dawniej leżało w pamięci przeglądarki, ma teraz model w bazie i migrację.

accounts.User konta i sesje hasła: PBKDF2
Schemat danych
User
  username        CharField, unikalne
  email           EmailField
  account_type    'client' | 'staff'
  display_name    CharField, nazwa publiczna
  is_staff        wymuszone False dla klienta
  is_superuser    wymuszone False dla klienta
Uwagi / ograniczenia

Hasła hashuje Django. Sesja siedzi w ciasteczku HttpOnly, więc kod w przeglądarce jej nie odczyta.

O tym trzeba pamiętać

Rodzaj konta ustawia wyłącznie serwer. Ograniczenie CHECK „klient_bez_dostepu_do_panelu” odrzuca w bazie każdą próbę nadania klientowi uprawnień panelu — także zapytaniem UPDATE.

listings.Listing ogłoszenia czas życia z pakietu
Schemat danych
Listing
  owner           FK → User
  package         FK → ListingPackage
  categories      M2M → ListingCategory
  status          draft | awaiting_payment | published | expired
  published_at    DateTimeField
  expires_at      published_at + package.duration_days
  highlight_until published_at + package.highlight_days
Uwagi / ograniczenia

Widoczność liczy zapytanie `Listing.objects.visible()`: status opublikowany oraz data wygaśnięcia w przyszłości. Wygasanie dzieje się po stronie serwera, nie przy odczycie w przeglądarce.

O tym trzeba pamiętać

Limity pakietu (kategorie, zdjęcia, znaki opisu) sprawdza formularz na serwerze. Obejście podpowiedzi w przeglądarce kończy się błędem walidacji, nie opublikowanym ogłoszeniem.

payments.Payment zamówienia płatności numer nieprzewidywalny
Schemat danych
Payment
  reference       NB-RRRRMMDD-HEX, unikalny
  user            FK → User
  listing         FK → Listing
  package         FK → ListingPackage
  method          tpay | bank_transfer
  status          new | pending | paid | refunded | cancelled
  amount          Decimal
Uwagi / ograniczenia

Numer zamówienia zawiera losowy fragment, więc nie da się odgadnąć cudzego. Dodatkowo widok pokazuje zamówienie tylko jego właścicielowi.

O tym trzeba pamiętać

Powrót przeglądarki z bramki nie jest dowodem zapłaty. Dowodem jest powiadomienie serwerowe ze zgodną sumą md5sum i podpisem X-JWS-Signature. Zwrot pieniędzy zdejmuje ogłoszenie z portalu.

core.FeatureFlag przełączniki funkcji źródło: features.py
Schemat danych
FeatureFlag
  key             klucz z rejestru w kodzie
  is_enabled      BooleanField
Uwagi / ograniczenia

Panel przełącza istniejące klucze, ale nie wymyśla nowych — rejestr jest w kodzie, więc nie powstanie flaga, której nikt nie obsługuje.

O tym trzeba pamiętać

Wyłączona sekcja zwraca 404, znika z menu i z wyników wyszukiwania. Świadomie nie 403 — z zewnątrz ma wyglądać, jakby jej nie było.

Rozdział panelu administratora i panelu klienta

Dwa rozłączne światy. Konto klienta nigdy nie wejdzie do panelu administratora, a konto administracyjne nie ma panelu klienta. Ten niezmiennik jest wymuszony w trzech miejscach naraz.

🛠️

Panel administratora

/panel/

Wymaga konta z rodzajem „administracyjne”. Pełna szerokość ekranu.

👤

Panel klienta

/konto/

Wymaga konta z rodzajem „klient”. W pełni responsywny.

🔒

Wymuszenie w modelu

User.save()

Konto klienta ma zerowane uprawnienia panelu przy każdym zapisie.

🧱

Wymuszenie w bazie

klient_bez_dostepu_do_panelu

Ograniczenie CHECK odrzuca eskalację omijającą model.

🚧

Wymuszenie w widokach

ClientRequiredMixin

Konto administracyjne dostaje odmowę w panelu klienta.

🕵️

Logowanie bez wyroczni

ClientLoginForm

Poprawne hasło konta administracyjnego daje w panelu klienta ten sam komunikat co złe hasło, a po dziesięciu próbach wchodzi blokada na kwadrans.

Pakiety ogłoszeń i płatności

Pakiety są danymi, nie kodem. Administrator zakłada własne, ustala ceny i limity. Wyłączenie jednego przełącznika sprawia, że portal działa w całości bezpłatnie.

Mechanizm Gdzie Jak działa O tym pamiętać
Przełącznik modułu płatnego premium_packages Wyłączony — każde ogłoszenie dostaje pakiet bezpłatny. portal w 100% darmowy
Limity pakietu ListingPackage Liczba kategorii, zdjęć, znaków opisu, dni publikacji i wyróżnienia. sprawdzane na serwerze
Wyróżnienie highlight_days Ile dni ogłoszenie trzyma się na górze listy wyników. promoted_first()
Płatność online Tpay Open API Autoryzacja POST /oauth/auth, transakcja POST /transactions. czeka na klucze
Powiadomienie o zapłacie md5sum + X-JWS-Signature Suma kontrolna z kodu bezpieczeństwa i podpis odłączony RFC 7515. powtórki odrzucane
Przelew tradycyjny PaymentSettings Odbiorca, rachunek i tytuł ustawiane w konfiguracji płatności. dane z panelu
Dane karty formularz Tpay Numeru karty nie przyjmujemy na własnym serwerze. poza zakresem PCI

Kontrakt między serwerem a przeglądarką

Front nigdy nie dostaje pola, którego nie zna, i nie traci pola, którego oczekuje. Kontrakty są opisane raz, w Pythonie, i z nich generowane są interfejsy TypeScript.

Element Miejsce Rola
Źródło prawdy apps/core/api_contracts.py Dataklasy opisujące każdą odpowiedź i każdy zestaw danych dla przeglądarki.
Generowanie typów manage.py export_api_types Zapisuje frontend/src/types/api.ts. Uruchamiane przed budową frontu.
Strażnicy typów isRecord, hasExactKeys Odrzucają odpowiedź z brakującym albo nadmiarowym polem.
Skutek rozjazdu błąd sprawdzania typów Niezgodność wychodzi w budowie, nie w przeglądarce użytkownika.

Testy

Każda funkcjonalność ma testy jednostkowe i penetracyjne, z asercjami pozytywnymi i negatywnymi.

🐍

Testy backendu

manage.py test

Modele, formularze, usługi, widoki, kontrola dostępu, komendy.

🧩

Testy frontu

npx vitest run

Strażniki typów i logika modułów przeglądarki.

🛡️

Testy penetracyjne

tests/test_security.py

ACL, eskalacja uprawnień, IDOR, XSS, CSRF, weryfikacja powiadomień bramki.

🚫

Zakaz typu any

npx oxlint

Linter wywala budowę, jeśli w kodzie pojawi się typ any.

Nasze Banino — portal na Django

Django 6 · SQLite (docelowo PostgreSQL) · HTMX · Alpine · TypeScript · Tailwind CSS 4

Ta strona jest treścią z bazy danych. Rozdziały, wpisy i ich układ zmienia się w panelu administratora, bez dotykania kodu.

Pliki cookie w Nasze Banino

Używamy plików cookie, żeby portal działał poprawnie i żeby wiedzieć, co czytacie najchętniej. Niezbędne są konieczne do działania serwisu. Pozostałe włączasz sam. Polityka cookies.