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
adaptix_slides.pdf
sobolevn
0
11
Зачем программисту Twitch (что?) – Алексей Гладких, PythoNN
sobolevn
0
12
MQTT глазами BE для работы с embedded устройствами – Сергей Макаров, PythoNN
sobolevn
0
7
Я в СКРЫТОМ ПУЛЕ: выстраиваем процессы в команде без тильта – Максим Мельников, PythoNN
sobolevn
0
14
Я твой fastapi msgspec'ом ускорял – Александр Целуйко, PythoNN
sobolevn
0
12
Будущее локальных LLM – Алексей Голобурдин, PythoNN
sobolevn
0
75
Чего вы не знали о строках в Python – Василий Рябов, PythoNN
sobolevn
0
260
ИИ-Агенты в каждый дом – Алексей Порядин, PythoNN
sobolevn
0
230
Внутреннее устройство сборки мусора в CPython 3.14+ – Сергей Мирянов, PythoNN
sobolevn
0
150
Other Decks in Technology
See All in Technology
FinOps Agentが生んだ技術負債:コスト自動調査の落とし穴と解消までの道のり
oasis1994liveforever
3
300
AI駆動開発における開発環境のセキュリティの考え方
yamahiro
2
620
自宅サーバと脆弱性
masakiokuda
1
120
Claude Code をローカルとサーバー上で利用した際の Bedrock 利用料をユーザー単位で一本化してみる
ohorig
0
160
背中から、背中へ ー 社外コミュニティで学んだ「雑に背中を押す」という恩送り ー / easygoing encouragement and pay forward to community
naitosatoshi
0
320
reInvent2026stby LT00 "Change your life!"
shinichirokawano
1
300
QAエンジニアがSoutherを使ってみたの〜10分でわかるSouther〜
mhlyc
0
120
「社外イベント=意識高い」の壁を壊せ!元々無関心だった僕らが部内で社外イベントを民主化して15人を連れ出すまで
say_no_w
0
140
Obsidian Vault Architectures
x5gtrn
PRO
0
160
構造で守らせる データ・法務・セキュリティ・開発チームとつくる AI時代のデータガバナンスとデータ基盤のリアル
legalontechnologies
PRO
1
530
BacklogのRAG 今ならこう作る - S3 Vectorsと自前パイプラインの勘所
revsystem
0
140
契約で守るコンパウンドプロダクトのデザインシステム
yug1224
1
1.5k
Featured
See All Featured
Testing 201, or: Great Expectations
jmmastey
46
8.4k
The Limits of Empathy - UXLibs8
cassininazir
2
730
The agentic SEO stack - context over prompts
schlessera
0
980
4 Signs Your Business is Dying
shpigford
187
23k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
35
2.9k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
How to Ace a Technical Interview
jacobian
280
24k
Git: the NoSQL Database
bkeepers
PRO
433
67k
Designing for humans not robots
tammielis
254
26k
How to build a perfect <img>
jonoalderson
1
6.2k
First, design no harm
axbom
PRO
2
1.4k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.9k
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 ВЫВОДЫ
КОНТАКТЫ