Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Алексей Гончарук – Современный веб с темлейтами...
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Sobolev Nikita
April 09, 2025
Technology
160
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Алексей Гончарук – Современный веб с темлейтами @ PythoNN
Sobolev Nikita
April 09, 2025
More Decks by Sobolev Nikita
See All by Sobolev Nikita
Чего вы не знали о строках в Python – Василий Рябов, PythoNN
sobolevn
0
260
ИИ-Агенты в каждый дом – Алексей Порядин, PythoNN
sobolevn
0
220
Внутреннее устройство сборки мусора в CPython 3.14+ – Сергей Мирянов, PythoNN
sobolevn
0
150
Генератор байткода и байткод генератора, Михаил Ефимов, PythoNN
sobolevn
0
150
Дотянуться до кремния. HighLoad Python: SIMD, GPU – Пётр Андреев, PythoNN
sobolevn
0
130
Проектирование — это когда чувствуешь, а не какие-то там циферки, Николай Хитров, PythoNN
sobolevn
0
150
Continuous profiling, Давид Джалаев, PythoNN
sobolevn
0
180
Михаил Гурбанов – Are you NATS? @ PythoNN
sobolevn
0
340
Дмитрий Бровкин – Почему исправление опечаток сложнее, чем кажется, и как мы с этим српавляемся @ PythoNN
sobolevn
0
80
Other Decks in Technology
See All in Technology
Databricksメトリクスビューはじめてのもくもく会
taka_aki
0
200
[2026 Oracle Technical Deep Dive] VMwareワークロードのOCI移行実践ガイド - OCVSでの継続利用からOCIコンピュートへの移行まで (2026年9月17日開催)
oracle4engineer
PRO
0
120
OSC2026on_the-world-is-waiting-for-your-voice.pdf
naruoga
0
350
生成AIを使って「人が」考える技術 ― AI時代の人機共想と実践ノウハウ|UNITT AC2026
ishiirikie
0
550
生成AIで高い生産性を求められて、 悩み、乗り越えた話
mot_techtalk
2
130
Incremental HTTP
kazuho
5
2k
IoTデバイスを繋げるプロトコルMatterをAndroidで使用する方法
harutiro
0
120
DAMOS - The Smart Cruise Control for RAM
ennael
PRO
1
130
メルペイ 会計システム概要と歴史
mewuto
0
150
調査タスクをAIと乗り切る 〜過去の調査をナレッジ化し 調査手順をスキルにする〜
mot_techtalk
1
110
KanaAI
shreyas1009
0
150
HolmesGPTで始めるSREエージェント入門!プラットフォームの障害調査はAIにお任せ 〜
leveragestech
PRO
0
150
Featured
See All Featured
Facilitating Awesome Meetings
lara
57
7.2k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
450
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
1
620
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1k
Tell your own story through comics
letsgokoyo
1
1.1k
Design in an AI World
tapps
1
350
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
1
550
Building the Perfect Custom Keyboard
takai
2
890
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.7k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
530
Ruling the World: When Life Gets Gamed
codingconduct
0
380
Transcript
СОВРЕМЕННЫЙ WEB С СОВРЕМЕННЫМИ ТЕМПЛЕЙТАМИ Алексей Гончарук Газ ИТ
ЧЕМ Я ЗАНИМАЮСЬ • Работаю в Газ ИТ • Делаю
апишки на python • Делаю SPAшки на Vue • Разгребаю хранимки Oracle на PL/SQL
ЧТО МЫ ХОТИМ ПОЛУЧИТЬ
ИСХОДНЫЙ КОД
Между Django и FastAPI
• Flask – слишком маленький • FastAPI – лучше, но
всё ещё мало • Django – В целом ОК, но с апишками после FastAPI грустно ПОЧЕМУ LITESTAR А НЕ …
• Готовые базовые модели • Настроенный DI • Миграции •
Репозитории и Сервисы ADVANCED ALCHEMY
Готовые базовые модели from datetime import UTC, datetime from advanced_alchemy.base
import UUIDBase from advanced_alchemy.types import DateTimeUTC from sqlalchemy.orm import Mapped, mapped_column class Task(UUIDBase): __tablename__ = 'task' name: Mapped[str] = mapped_column(unique=True) description: Mapped[str] is_done: Mapped[bool] = mapped_column(default=False) creation_date: Mapped[datetime] = mapped_column( DateTimeUTC(timezone=True), default=lambda: datetime.now(UTC), )
Интеграция с Litestar from advanced_alchemy.extensions.litestar import ( AsyncSessionConfig, SQLAlchemyAsyncConfig, SQLAlchemyPlugin,
) from litestar import Litestar session_config = AsyncSessionConfig( expire_on_commit=False ) sqlalchemy_config = SQLAlchemyAsyncConfig( connection_string='sqlite+aiosqlite:///db.sqlite3', session_config=session_config, create_all=True, ) sqlalchemy_plugin = SQLAlchemyPlugin( config=sqlalchemy_config ) app = Litestar( plugins=[ sqlalchemy_plugin, ], )
Интеграция с Litestar $ litestar database init $ litestar database
make-migration -m "add_todo_table“ $ litestar run
Паттерн репозиторий from advanced_alchemy.repository import ( SQLAlchemyAsyncRepository, ) from todoshnik.database
import models class TaskRepository( SQLAlchemyAsyncRepository[models.Task] ): model_type = models.Task uniquify = True
Паттерн сервис from advanced_alchemy.service import ( SQLAlchemyAsyncRepositoryService, ) from todoshnik.database.models
import Task from todoshnik.database.repository import TaskRepository class TaskService( SQLAlchemyAsyncRepositoryService[ Task, TaskRepository ] ): repository_type = TaskRepository uniquify = True
• Быстрый роутинг за счёт Radix Tree • Быстрый DI
но неудобный • Классы контроллеры РОУТИНГ
Контроллеры class PagesController(Controller): path = '/page' tags = ['Pages'] dependencies
= { 'task_service': Provide(provide_task_service), } @get('/tasks', name='Задачи') async def tasks_page( self, task_service: TaskService, ) -> Template: ...
Контроллеры class PagesController(Controller): path = '/page' tags = ['Pages'] dependencies
= { 'task_service': Provide(provide_task_service), } @get('/tasks', name='Задачи') async def tasks_page( self, task_service: TaskService, ) -> Template: ...
Контроллеры class PagesController(Controller): path = '/page' tags = ['Pages'] dependencies
= { 'task_service': Provide(provide_task_service), } @get('/tasks', name='Задачи') async def tasks_page( self, task_service: TaskService, ) -> Template: tasks = await task_service.list( models.Task.is_done == False, ) return HTMXTemplate( template_name='pages/tasks.html', context={'tasks': tasks}, )
DTO слой class BaseTask(BaseModel): name: str description: str | None
is_done: bool = False creation_date: datetime | None = Field( default_factory=lambda: datetime.now(tz=UTC) ) class IdMixin(BaseModel): id: UUID | None = None class TaskIn(BaseTask): pass class TaskOut(IdMixin, BaseTask): pass
DTO слой class Task(BaseModel): id: UUID | None = None
name: str description: str | None is_done: bool = False creation_date: datetime | None = Field( default_factory=lambda: datetime.now(tz=UTC) ) class TaskDTO(PydanticDTO[Task]): config = DTOConfig(exclude={'id'})
DTO слой @post( path='/api/task', dto=TaskDTO, dependencies={ 'task_service': Provide(provide_task_service), }, )
async def create_task( task_service: TaskService, data: Annotated[ schemas.Task, Body( media_type=RequestEncodingType.URL_ENCODED ), ], ) -> None: await task_service.create( data=data, auto_commit=True, )
DTO слой @post( path='/api/task', dto=TaskDTO, dependencies={ 'task_service': Provide(provide_task_service), }, )
async def create_task( task_service: TaskService, data: Annotated[ schemas.Task, Body( media_type=RequestEncodingType.URL_ENCODED ), ], ) -> None: await task_service.create( data=data, auto_commit=True, )
• Шаблонизаторы с поддержкой HTMX • Есть встроенное Observability •
По тестам TechEmpower быстрее FastAPI • Умеет в каналы • Поддерживается сообществом ЧТО ЕЩЁ ЕСТЬ
ГРУСТНЫЙ СЛАЙД
</>htmx Настоящий hypermedia
Бустинг HTML <div hx-boost="true"> <a href="/page1">Go To Page 1</a> <a
href="/page2">Go To Page 2</a> </div>
Бустинг HTML <form action="/task" method="post" hx-boost="true"> <input type="text" name="task" />
<button>Post Your Task</button> </form>
Больше методов <form action="/task/{{ task.id }}/delete" method="post"> <button>Delete Task</button> </form>
Больше методов <button hx-delete="/task/{{ task.id }}"> Delete Task </button>
Выбор цели <button hx-delete="/task/{{ task.id }}" hx-target="#task-{{ task.id }}" >
Delete Task </button>
Способ замены <button hx-delete="/task/{{ task.id }}" hx-target="#task-{{ task.id }}" hx-swap="outerHTML"
> Delete Task </button>
Способ замены <button hx-post="/task" hx-target="#tasks-list" hx-swap="beforeend" > Save Task </button>
• Хорошая событийная система • Подтверждения <button hx-delete="/task" hx-confirm="Delete all?">Delete<button>
• Индикаторы запросов • Тюнинг заголовками ответов litestar_htmx.HTMXTemplate() • Декларативное описание в самих HTML шаблонах ЧТО ЕЩЁ ЕСТЬ
Сделать красиво
Варианты утилит // Псевдо классы hover:bg-fuchsia-600 focus:text-red-500 invalid:border-pink-500 // Псевдо
элементы after:ml-0.5 before:-inset-1 backdrop:bg-gray-50 // Медиа запросы <div class="grid grid-cols-3 md:grid-cols-4"> <!-- ... --> </div>
Варианты утилит // Контейнерные запросы <div class="@container"> <div class="flex flex-col
@md:flex-row"> <!-- ... --> </div> </div> // Тёмная тема dark:bg-black
Динамические утилиты @import "tailwindcss"; @theme { --font-display: "Satoshi", "sans-serif"; --breakpoint-3xl:
1920px; --color-avocado-200: oklch(0.98 0.04 113.22); --color-avocado-300: oklch(0.94 0.11 115.03); --color-avocado-400: oklch(0.92 0.19 114.08); --color-avocado-500: oklch(0.84 0.18 117.33); } <div class="text-avocado-500 bg-avocado-300"> Avocado Shop </div>
@apply для шаблонов @layer components { .btn { @apply bg-action
cursor-pointer rounded-xl p-2 text-base text-white uppercase shadow-xs shadow-black duration-300; } .btn-flat { @apply text-action cursor-pointer rounded-xl bg-transparent p-2 text-base uppercase duration-300; } }
///_hyperscript Как JQuery, только нормальный
<button _="on click increment :x then put the result into
the next <output/>" > Click Me </button> <output>--</output> Простой синтаксис
Сахар для читаемости <button _="on click increment :x then put
it into the next <output/>" > Click Me </button> <output>--</output>
Сахар для читаемости <button _="on click increment :x then put
the result into the next <output/>" > Click Me </button> <output>--</output>
Декларативный подход document.querySelector('#example-btn') .addEventListener('click', e => { document .querySelectorAll(".elements-to-remove") .forEach(value
=> value.remove()); }) on click from #example-btn remove .elements-to-remove
• Небольшой фреймворк с актуальными батарейками - Litestar • Динамичный
UI с шаблонами – HTMX • Если вам нужна хорошая кастомизация – TailwindCSS • Немного скриптов для большей динамики - Hyperscript ВЫВОДЫ
КОНТАКТЫ