BG
SchoolPythonТестове status

PGKNMA Advent Calendar

An advent calendar for PGKNMA “Prof. Minko Balkanski”, Stara Zagora, built with Django 5, Unfold and MySQL.

README

Адвент календар

Тестове

Уеб сайт с вратички, които се отварят по една на ден до Коледа, направен за ПГКНМА „Проф. Минко Балкански“ – Стара Загора. Django 5 + django-unfold (админ) + MySQL, фронтенд с чист HTML/CSS/JS (без build стъпка).

Кодът е под лиценз GNU AGPL-3.0 – виж раздел Лиценз.

Какво има

  • Решетка от вратички с 3D отваряне, модален прозорец, свой цвят/икона/стил за всеки тип: пожелание, предизвикателство, гатанка (с бутон „Покажи отговора“), цитат, шега, интересен факт, снимка.

  • Вратичка N се отключва на N-ия ден от избрания месец по часова зона Europe/Sofia. Съдържанието на бъдещите вратички никога не се изпраща към браузъра (API връща 403).

  • Без акаунти: отворените вратички се пазят в localStorage.

  • Днешната вратичка свети; заключената се разклаща с „Още не е време!“.

  • Сняг на canvas (бутон за изключване, спазва prefers-reduced-motion) и бутон за музика.

  • Съобщение след края: след последния ден над вратичките излиза „Календарът приключи – благодарим, че бяхте с нас!“ (текстът се сменя от настройките); всички вратички остават отворени.

  • Админ панел (/upravlenie/ по подразбиране – виж „Сигурност на админа“):

    • Вратички: списък с филтри и търсене, редакция на цялото съдържание, качване на снимки, бутон „Генерирай празни врати 1-24“, масово публикуване/скриване.
    • Настройки на календара: последен ден (24/25…), месец, година, заглавие и подзаглавие на сайта, име, град и сайт на училището и лого (ако не е качено, се ползва static/img/pgknma-logo.svg). Стойностите ADVENT_LAST_DAY, ADVENT_MONTH, ADVENT_YEAR в advent/settings.py са само началните стойности при първото създаване на настройките.
  • Обратно броене: преди първия ден страницата показва „Календарът се отваря след N дни“ с часовник (дни/часове/минути/секунди) по часа на сървъра; в 00:00 страницата се презарежда сама.

  • Реакции: ❤ за всяка вратичка и „Изпълних предизвикателството!“ за предизвикателствата.

  • Анонимна статистика в админа („Статистика → По дни / По вратички“, плюс колони в списъка с вратички): посетители на ден, отваряния, сърца, изпълнени предизвикателства. Броячите на вратичките са отделни за всяка година на календара, а браузърът пази отворените/харесаните вратички също по години (advent-2026-opened и т.н.), така че нищо не се пренася, когато смениш годината в настройките. Не се пазят IP адреси, бисквитки или идентификатори. Браузърът помни в localStorage дали вече е отчел посещение/отваряне, а сървърът пази само общи броячи. IP адресът се ползва единствено като хеш в паметта за ограничение до 60 заявки/минута и се забравя след минута.

API

Метод Адрес Отговор
GET /api/doors/ {last_day, today, doors: [{day, type, unlocked}]} – само публикуваните вратички, без съдържание
GET /api/doors/<day>/ {day, type, type_label, title, text, image, answer, hearts, completions} ако денят е настъпил; иначе 403, а за липсваща/непубликувана – 404
POST /api/doors/<day>/react/ тяло {"action": "open" | "heart" | "done", "active": true/false} → {hearts, completions}; само за отключени вратички (тестовият режим не важи)
POST /api/visit/ отчита едно посещение за днешния ден

Тестов режим: при DEBUG=True добави ?preview=1 към адреса (напр. http://127.0.0.1:8000/?preview=1) и всички вратички се отключват. При DEBUG=False параметърът се игнорира.

Локално пускане (Windows / PowerShell)

python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
copy .env.example .env      # и попълни стойностите

Създай MySQL база (в mysql клиента):

CREATE DATABASE advent CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'advent'@'localhost' IDENTIFIED BY 'силна-парола';
GRANT ALL PRIVILEGES ON advent.* TO 'advent'@'localhost';
-- за `manage.py test` върху MySQL потребителят трябва да може да създава база test_advent:
GRANT ALL PRIVILEGES ON test_advent.* TO 'advent'@'localhost';

После:

python manage.py migrate
python manage.py createsuperuser
python manage.py seed_doors          # 25 готови вратички, 8 със снимки (--force презаписва)
python manage.py runserver

Сигурност на админа

  • Адрес: админът не е на стандартния /admin/ (там връща 404), а на /upravlenie/. В продукция задай свой, трудно отгатваем адрес в .env, напр. ADMIN_URL=upravlenie-k7x2/.
  • Ограничение на опитите за вход (django-axes): след 5 грешни пароли за едно потребителско име от един IP адрес входът се блокира за 1 час (страница „Входът е временно блокиран“). Блокираните опити се виждат в админа („Потребители → Грешни опити за вход“); изтриването на запис отключва входа веднага. От командния ред: python manage.py axes_reset. Пазят се само неуспешните опити и само докато изтече блокирането. Ако сайтът е зад reverse proxy (напр. Cloudflare или nginx пред Apache), настрой AXES_IPWARE_PROXY_COUNT / AXES_IPWARE_META_PRECEDENCE_ORDER, за да се вижда истинският IP адрес.

Без MySQL (само за бърз опит): сложи DB_ENGINE=sqlite в .env.

Тестове: python manage.py test doors

Музика

static/music/theme.mp3 е празен placeholder. Замени го с MP3 файл със свободен лиценз (без авторски права или с лиценз, който позволява използване), например от:

Запази файла със същото име theme.mp3. Браузърите блокират автоматичното пускане на звук, затова музиката тръгва само след натискане на бутона „Музика“; изборът се помни в localStorage.

Пускане на сървър

Пълен гайд стъпка по стъпка (Cloudflare → Apache → Waitress → Django + MySQL, субдомейн koleda.pgknma.space): DEPLOY.md. Готовите конфигурации са в папка deploy/.

Структура

advent/            настройки на проекта, urls, wsgi
doors/             модели (Door, AdventSettings), API, админ, seed_doors, тестове
templates/         base.html, doors/index.html
static/            css/style.css, js/app.js, js/snow.js, music/theme.mp3, img/pgknma-logo.(svg|png)
media/             качени снимки (doors/… със случайни имена)
deploy/           Apache vhost, systemd услуга, update.sh
serve.py          production сървър (Waitress зад Apache)
DEPLOY.md         гайд за пускане на сървъра

Лиценз

Copyright (C) 2026 Krum (Krumqnkata)

Програмата е свободен софтуер: можеш да я разпространяваш и/или променяш при условията на GNU Affero General Public License, версия 3, публикуван от Free Software Foundation. Накратко: всеки може да ползва, променя и споделя кода, но ако пусне променена версия (включително само като уеб сайт), трябва да публикува изходния ѝ код под същия лиценз и да запази бележките за авторство. Пълните условия са в LICENSE.

Не са под този лиценз: логото и името на ПГКНМА „Проф. Минко Балкански“ (static/img/pgknma-logo.svg, static/img/pgknma-logo.png) принадлежат на училището и не са част от лиценза на кода. Ако ползваш проекта за друго училище или организация, замени ги със свои (логото и името се сменят и от админа).

Иконите са по мотиви от Lucide (ISC лиценз). Шрифтовете Manrope, Marck Script и Playfair Display са под SIL Open Font License.