← Indeks

Co potrafi ten blog: przewodnik po funkcjach i o tym, co jest zablokowane w demo

Wyszukiwanie, tagi kontra znaczki, harmonogram publikacji, wersjonowanie treści, kosz i panel admina - oraz dlaczego część z tego jest wyłączona w wersji pokazowej.

Ten wpis nie opisuje żadnego zlecenia ani konkretnej realizacji. Opisuje sam ten blog, jako produkt: co potrafi, jakie decyzje stoją za poszczególnymi mechanizmami i - co ważne dla kogoś oglądającego wersję demo pod blog-demo.ronim.com.pl - co jest w tej wersji celowo zablokowane i dlaczego. Jeśli klikasz teraz po panelu admina na koncie demo, to najlepszy moment na tę lekturę.

Wyszukiwanie: dokładne, z awaryjnym trybem rozmytym

Wyszukiwarka pod /szukaj działa dwuetapowo. Najpierw zwykłe dopasowanie podciągu w tytule, opisie i tagach wpisu - szybkie i przewidywalne. Dopiero gdy to nie zwróci żadnego wyniku, uruchamia się tryb awaryjny: podobieństwo trigramowe (Jaccard na trójznakowych fragmentach słów), które wybacza literówkę. Zapytanie "wyceniejsz" zamiast "wycenisz" wciąż trafi do właściwego wpisu, zamiast pokazać pustą stronę wyników.

Liczone jest to w Pythonie, nie w bazie danych - dla skali tego bloga (kilkanaście, może kilkadziesiąt wpisów) skan w pamięci jest wystarczająco szybki i nie wymaga rozszerzenia PostgreSQL-a takiego jak pg_trgm. Przy realnie dużej bazie wpisów warto by to przenieść do zapytań SQL, ale to inny próg skali niż mamy teraz.

Do tego dochodzi /szukaj/live - podpowiedzi pojawiające się pod polem wyszukiwania w trakcie pisania, z niewielkim opóźnieniem (debounce), żeby nie odpytywać serwera przy każdym naciśniętym klawiszu. Reużywa dokładnie tej samej funkcji wyszukującej co strona wyników, więc podpowiedzi i pełne wyniki nigdy się nie rozjeżdżają.

Osobna strona, /tagi, pokazuje chmurę wszystkich tagów z filtrem na żywo - można zawęzić widoczne tagi wpisując fragment nazwy, bez przeładowania strony.

Tagi kontra znaczki - dwa niezależne mechanizmy

To częste źródło nieporozumienia, więc osobny nagłówek. Wpis może mieć jednocześnie tagi i znaczki, i są to dwie zupełnie oddzielne rzeczy w bazie danych, zarządzane w dwóch różnych miejscach panelu.

TagZnaczek (Label)
Opisuje temat wpisu (np. "flask", "wycena") Sygnalizuje coś o samym wpisie (np. "w budowie", "do aktualizacji")
Dowolna liczba na wpis, bez limitu Dowolna liczba na wpis, bez limitu
Widoczny na liście wpisów i filtruje po kliknięciu Widoczny jako kolorowa plakietka przy tytule
Zarządzanie: /admin/tags Zarządzanie: /admin/labels
Ma własny kolor przypisany ręcznie: nie Ma, do wyboru z ustalonej palety (niebieski, fioletowy, zielony, bursztynowy, czerwony, szary)

W obu panelach dostępne są te same trzy operacje: zmiana nazwy, scalanie duplikatów (np. gdy przypadkiem powstały "flask" i "Flask" jako dwa osobne wpisy) i usunięcie. Scalanie przepina wszystkie wpisy z jednego tagu/znaczka na drugi, a potem usuwa ten zbędny - nic nie znika po drodze.

W formularzu edycji wpisu pole tagów ma autouzupełnianie: podczas wpisywania pojawia się lista pasujących, istniejących już tagów, żeby nie tworzyć przypadkiem drugiego "case-study" obok "case study".

Osobno od obu tych mechanizmów istnieje jeszcze pole is_concept na wpisie realizacji - to nie jest tag ani znaczek, tylko stała flaga uczciwości: czy opisywany projekt jest prawdziwym wdrożeniem, czy fikcyjnym przykładem koncepcyjnym (jak "Kwiaciarnia" czy "Szkoła językowa" w tym blogu). Widoczna wprost w interfejsie, nie ukryta.

Harmonogram publikacji

Wpis można zaplanować na przyszłość zamiast publikować od razu. W formularzu edycji wystarczy ustawić datę i godzinę w polu harmonogramu - wpis dostaje wtedy status "zaplanowany" i pozostaje niewidoczny publicznie, dopóki ta chwila nie nadejdzie.

Ciekawszy jest mechanizm, który za tym stoi: nie ma tu żadnego osobnego procesu w tle (Celery, cron), który by o określonej godzinie "obudził się" i przełączył status. Zamiast tego wpis dojrzewa leniwie - przy każdym publicznym odczycie listy wpisów aplikacja sprawdza, czy któryś zaplanowany wpis ma już minięty termin, i jeśli tak, przełącza go na opublikowany w tym samym momencie. Dla odwiedzającego różnicy nie widać: wpis po prostu pojawia się na stronie po zaplanowanej dacie, bez żadnej dodatkowej akcji z mojej strony. Dla hostingu, który i tak nie udźwignąłby dodatkowego procesu w tle, to rozwiązanie tańsze niż prawdziwy harmonogram.

Najprostszy harmonogram to taki, który nie wymaga osobnego procesu do pilnowania zegarka - wystarczy, że każde zapytanie o listę wpisów samo sprawdza, czy coś "dojrzało".

Wersjonowanie treści

Przed każdą zmianą treści wpisu (tytułu, zajawki albo samej zawartości) powstaje migawka poprzedniej wersji. Migawki nie tworzą się przy drobnych zmianach klasyfikacji (np. dodanie tagu) bez zmiany treści - inaczej tabela wersji rosłaby przy każdym kliknięciu "Zapisz" niezależnie od tego, co realnie się zmieniło.

Historię wersji danego wpisu widać pod /admin/post/<id>/historia, razem z logiem działań (kto i kiedy co zrobił z wpisem - opublikował, zaplanował, usunął, zduplikował). Każdą starszą wersję można przywrócić jednym kliknięciem. Przywrócenie samo w sobie staje się nową wersją w historii - jest więc odwracalne, można cofnąć się o kilka wersji, a potem "cofnąć cofnięcie", bez utraty żadnego stanu po drodze. Adres wpisu (slug) nie jest przeliczany przy przywracaniu, więc linki do niego prowadzące z zewnątrz nie psują się.

Kosz zamiast trwałego usuwania

Usunięcie wpisu z listy w panelu nie kasuje go od razu z bazy danych - wpis trafia do kosza (/admin/kosz), z którego można go przywrócić (zawsze jako szkic, żeby nic nie wróciło od razu na żywo bez świadomej decyzji) albo usunąć trwale. Te dwie akcje są rozdzielone celowo: trwałe usunięcie jest osobnym, dodatkowym krokiem tylko z widoku kosza, żeby nie dało się tego zrobić przez pomyłkę jednym kliknięciem z listy głównej.

Panel admina - reszta

  • Dashboard ze statystykami: liczba wpisów w podziale na status, najstarszy nietknięty szkic, szybkie filtrowanie po typie i branży wpisu wprost z listy.
  • Szybki toggle statusu - przełączenie szkic/opublikowany bezpośrednio z listy wpisów, bez wchodzenia w pełną edycję.
  • Duplikowanie wpisu - kopia zawsze powstaje jako szkic, nigdy nie publikuje się sama z siebie.
  • Podgląd mobilny - w widoku podglądu wpisu przełącznik zwężający podgląd do szerokości telefonu, bez fizycznego zmieniania rozmiaru okna przeglądarki.
  • Diff "co zmieniłem?" - przy edycji istniejącego wpisu przycisk pokazujący różnicę słowo po słowie między treścią sprzed otwarcia formularza a bieżącym stanem edytora, zanim jeszcze cokolwiek zostanie zapisane. To osobny, lżejszy mechanizm niż pełne wersjonowanie opisane wyżej - działa wyłącznie po stronie przeglądarki i niczego nie zapisuje, jest tylko podglądem przed decyzją o zapisie.

Popover z definicjami pojęć

Niektóre wpisy (na przykład ten o superpozycji) zawierają specjalistyczne terminy, które nie każdy czytelnik zna. Zamiast tłumaczyć je w nawiasach w środku zdania, pierwsze wystąpienie takiego terminu w treści jest owinięte w podpowiedź: najechanie kursorem albo zaznaczenie klawiaturą (Tab) pokazuje krótką definicję w tooltipie. Zrobione czystym CSS, bez ani jednej linijki JavaScriptu - działa na pseudoklasach :hover i :focus. Definicje są osobnym, deklaratywnym słownikiem w kodzie, nie są częścią treści wpisu, więc poprawienie definicji nie wymaga edycji samego wpisu przez panel.

Bezpieczeństwo treści z edytora

Edytor w panelu (Quill) zwraca gotowy HTML, który trzeba bezpiecznie zapisać i wyświetlić. Cała treść przechodzi przez sanityzację (biblioteka bleach) z jawną whitelistą dozwolonych tagów - żadnego <script>, żadnych atrybutów on*, żadnego javascript: w linkach. Do tego dochodzi restrykcyjna polityka bezpieczeństwa treści (CSP) w nagłówkach odpowiedzi. Reguły sanityzacji mają swoją wersję (sanitizer_version na każdym wpisie) - po zmianie whitelisty komenda flask resanitize przelicza starą treść nowymi regułami, bez ręcznego przechodzenia po każdym wpisie z osobna. Więcej o tym w osobnym wpisie, "Jak zbudowany jest ten serwis".

Czego nie da się przetestować w wersji demo

Wersja demo pod blog-demo.ronim.com.pl daje prawdziwy dostęp do panelu - logujesz się jako konto pokazowe i naprawdę edytujesz treść w prawdziwej bazie danych, nie w symulacji. Baza resetuje się co godzinę do stanu początkowego, więc żadne zmiany nie zostają na stałe. Kilka akcji jest mimo to celowo zablokowanych (odpowiedź serwera: 403), niezależnie od tego resetu:

  • Upload obrazków - obrazki trafiają na Cloudinary, zewnętrzny serwis, którego reset bazy demo w ogóle nie dotyka. Bez tej blokady konto pokazowe mogłoby w nieskończoność zapychać cudzy, współdzielony limit miejsca.
  • Embed YouTube - z tego samego powodu co obrazki: skutek (wywołanie zewnętrznego serwisu) nie znika wraz z resetem bazy.
  • Zarządzanie tagami i znaczkami (zmiana nazwy, scalanie, usuwanie) - to są operacje na strukturze wspólnej dla wszystkich wpisów, nie na jednym wpisie. Usunięcie albo scalenie tagu zepsułoby doświadczenie następnej osobie, która zajrzy na demo przed najbliższym resetem, nawet jeśli sam reset i tak by to później naprawił.
  • Trwałe usunięcie wpisu (purge z kosza) - zwykłe, odwracalne usunięcie (do kosza) jest dozwolone, bo reset bazy je i tak naprawia. Purge jest zablokowany, bo w połączeniu z niskim priorytetem sensowności tego akurat kliku w środowisku współdzielonym, nie wnosi nic ponad zwykłe usunięcie, a ryzykuje nieporozumienie.

Dodatkowo każda próba zapisania treści zawierającej wulgaryzmy (prosta lista słów, dopasowanie bez rozróżniania wielkości liter) jest odrzucana z komunikatem walidacji - nie po fakcie, przy najbliższym resecie, tylko od razu. To nie jest ogólna moderacja treści bloga (prawdziwe konto administratora nie ma tego ograniczenia), tylko doraźne zabezpieczenie publicznego demo przed oczywistym spamem.

Wszystko inne - tworzenie i edycja wpisów, zwykłe usuwanie do kosza, zmiana statusu, harmonogram publikacji, duplikowanie, przeglądanie historii wersji i logu działań - działa dokładnie tak samo jak na prawdziwym koncie administratora.