Обезличенный шаблон персонального сайта-портфолио на FastAPI с серверным рендерингом через Jinja2, хранением контента в базе данных и собственной административной панелью.
Проект задуман не как статический лендинг, а как небольшое CMS-подобное приложение:
- публичная часть показывает профиль, навыки и проекты;
- контент хранится в базе данных;
- админка позволяет управлять основными блоками сайта без изменения кода;
- архитектура остается простой и понятной для дальнейшего сопровождения.
Репозиторий специально подготовлен для публичного размещения:
- без личных данных владельца;
- без реальных контактов;
- без стартовых реальных проектов в
seed.py.
На текущем этапе реализованы:
- публичная главная страница;
- список проектов;
- детальные страницы проектов;
- парольная авторизация в админке;
- редактирование профиля;
- CRUD для контактов;
- CRUD для категорий навыков и отдельных навыков;
- CRUD для проектов;
- ограничение до 3 избранных проектов на главной странице.
- Python
- FastAPI
- SQLAlchemy 2.x
- Jinja2
- SQLite
- Pydantic Settings
- Starlette SessionMiddleware
portfolio/
├─ app/
│ ├─ config.py
│ ├─ database.py
│ ├─ dependencies.py
│ ├─ main.py
│ ├─ seed.py
│ ├─ models/
│ │ ├─ contact.py
│ │ ├─ profile.py
│ │ ├─ project.py
│ │ └─ skill.py
│ ├─ routers/
│ │ ├─ public.py
│ │ ├─ admin_auth.py
│ │ ├─ admin_profile.py
│ │ ├─ admin_contacts.py
│ │ ├─ admin_skills.py
│ │ └─ admin_projects.py
│ ├─ services/
│ │ └─ site_content.py
│ ├─ static/
│ │ ├─ css/
│ │ └─ images/
│ └─ templates/
├─ requirements.txt
├─ PROJECT_CONTEXT.md
├─ ARCHITECTURE.md
├─ DEPLOY.md
└─ README.md
Файл app/main.py:
- создает экземпляр
FastAPI; - подключает middleware для сессий;
- монтирует статику;
- подключает все роутеры;
- на старте создает таблицы и при пустой базе выполняет начальное заполнение.
Файл app/config.py отвечает за настройки приложения:
database_urladmin_passwordsecret_key
Все значения могут браться из .env.
Файл app/database.py:
- создает
engine; - описывает базовый класс
Baseдля всех ORM-моделей; - создает
SessionLocal; - предоставляет dependency
get_db()для роутеров.
Одна запись с основной информацией о владельце сайта:
- ФИО
- должность
- подзаголовок
- текст "О себе"
- путь к фото
Контактная информация, связанная с профилем:
- тип
- название
- значение
- ссылка
- порядок отображения
Категория навыков, например:
- Backend
- AI
- Infrastructure
- Integrations
Отдельный навык внутри категории.
Карточка проекта для публичной части и админки.
app/services/site_content.py собирает данные для главной страницы:
- профиль;
- контакты;
- категории навыков;
- избранные проекты.
Это позволяет не дублировать одинаковые ORM-запросы между публичной и админской версиями главной страницы.
app/routers/public.py
//projects/projects/{slug}
Роутеры админки разделены по ответственности:
admin_auth.py— вход и выход;admin_profile.py— главная админки и редактирование профиля;admin_contacts.py— CRUD контактов;admin_skills.py— CRUD категорий навыков и навыков;admin_projects.py— CRUD проектов.
python -m pip install -r requirements.txtПример:
DATABASE_URL=sqlite:///./portfolio.db
ADMIN_PASSWORD=your-strong-password
SECRET_KEY=your-long-random-secretpython -m uvicorn app.main:app --reloadПосле запуска будут доступны:
http://127.0.0.1:8000/http://127.0.0.1:8000/projectshttp://127.0.0.1:8000/admin
Для серверного или production-like запуска проект можно поднимать через docker compose.
Пример:
DATABASE_URL=sqlite:////srv/portfolio/data/portfolio.db
ADMIN_PASSWORD=change-this-password
SECRET_KEY=change-this-secret-keydocker compose up -d --builddocker ps
docker logs portfolio_web --tail 50docker-compose.yml монтирует два каталога:
./data->/srv/portfolio/data./uploads->/srv/portfolio/app/static/uploads
Это значит, что:
- база SQLite хранится на хосте;
- загруженные фото и изображения проектов тоже хранятся на хосте;
- пересборка контейнера не удаляет контент.
После клонирования репозитория настрой проект под себя в таком порядке:
- Обнови
.env:ADMIN_PASSWORDSECRET_KEY- при необходимости
DATABASE_URL
- Запусти приложение.
- Зайди в
/admin/login. - В
/admin/profile/editзамени placeholder-данные:- имя;
- роль;
- подзаголовок;
- текст "О себе";
- фото профиля.
- Через
/admin/contactsдобавь свои контакты. - Через
/admin/skillsдобавь категории навыков и сами навыки. - Через
/admin/projectsдобавь реальные проекты и загрузки изображений.
Такой сценарий удобен для публичного GitHub-репозитория: код открыт, а реальный контент живет только в твоей локальной или серверной базе данных.
Если до этого ты уже запускал проект со старой сидированной базой, удали portfolio.db и запусти приложение снова, чтобы получить чистый обезличенный старт.
Файл app/seed.py заполняет пустую базу только базовым placeholder-профилем.
Важно:
- сидирование выполняется только если в базе еще нет профиля;
- если структура модели изменилась и нужны новые поля в SQLite, проще удалить
portfolio.dbи дать приложению создать базу заново; - реальный контент рекомендуется вносить уже через админку.
/— главная страница/projects— список всех проектов/projects/{slug}— детальная страница проекта
/admin/login— вход/admin/logout— выход/admin— админская версия главной страницы/admin/profile/edit— редактирование профиля/admin/contacts— управление контактами/admin/skills— управление навыками/admin/projects— управление проектами
На главной странице выводятся только 3 избранных проекта.
В админке это ограничение поддерживается на уровне серверной логики:
- нельзя сохранить четвертый проект как
is_featured=True; - пользователь получает понятную ошибку прямо в форме проекта.
Изображения проектов теперь можно загружать через админку.
- отдельное редактирование фото профиля;
- валидация slug и URL;
- более строгие сообщения об ошибках в формах;
- Dockerfile и docker-compose;
- деплой на VPS с Nginx;
- обновление SSL-сертификата для домена.
Чтобы быстро вспомнить логику проекта через месяц:
- начни с
README.md; - потом открой
app/main.py; - затем посмотри
app/services/site_content.py; - после этого роутеры в
app/routers/; - и уже потом модели в
app/models/.
Так проще всего восстановить общую картину: сначала вход в приложение, потом поток данных, потом конкретные CRUD-блоки.
.env— пароль админа,secret_key, строка подключения к БД;app/seed.py— только стартовый placeholder-профиль;app/static/uploads/profile/— загруженные фото профиля;app/static/uploads/projects/— загруженные изображения проектов;portfolio.db— весь контент, добавленный через админку.