<НА ГЛАВНУЮ

Создайте многостраничное приложение Reflex с реальным временем и реактивным UI на Python

Пошаговый туториал по созданию доски заметок на Reflex с реальной базой, фильтрами и аналитикой.

Зачем Reflex и что мы собираем

В этом руководстве показано, как создать продвинутое веб-приложение на Reflex полностью на Python и запустить его в Google Colab. Проект демонстрирует, как Reflex позволяет делать full-stack разработку без JavaScript, используя реактивный Python для фронтенда и бэкенда. Итоговый проект — многостраничная доска заметок с постоянным SQLite-хранилищем, живыми обновлениями, фильтрацией, сортировкой, аналитикой и персонализацией пользователя.

Настройка в Colab

Создайте папку проекта и установите Reflex. Этот фрагмент кода подготавливает окружение и устанавливает конкретную версию Reflex:

import os, subprocess, sys, pathlib
APP = "reflex_colab_advanced"
os.makedirs(APP, exist_ok=True)
os.chdir(APP)
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "reflex==0.5.9"])

Он создаёт директорию проекта, переходит в неё и устанавливает Reflex, чтобы всё могло корректно работать в Colab.

Конфигурация

Опишите минимальный файл конфигурации, который задаёт имя приложения и соединение с базой данных. Пример записывает rxconfig.py с подключением к локальному SQLite:

rxconfig = """
import reflex as rx
class Config(rx.Config):
   app_name = "reflex_colab_advanced"
   db_url = "sqlite:///reflex.db"
config = Config()
"""
pathlib.Path("rxconfig.py").write_text(rxconfig)

Минимальная конфигурация упрощает работу и сохраняет постоянные данные заметок.

Модель данных и реактивное состояние

Определите модель Note и реактивный класс State, который управляет вводом пользователя, фильтрами, сортировкой и операциями с базой. State содержит асинхронные методы для CRUD и вычисление статистики.

app_py = """
import reflex as rx
 
 
class Note(rx.Model, table=True):
   content: str
   tag: str = "general"
   done: bool = False
 
 
class State(rx.State):
   user: str = ""
   search: str = ""
   tag_filter: str = "all"
   sort_desc: bool = True
   new_content: str = ""
   new_tag: str = "general"
   toast_msg: str = ""
 
 
   def set_user(self, v: str): self.user = v
   def set_search(self, v: str): self.search = v
   def set_tag_filter(self, v: str): self.tag_filter = v
   def set_new_content(self, v: str): self.new_content = v
   def set_new_tag(self, v: str): self.new_tag = v
   def toggle_sort(self): self.sort_desc = not self.sort_desc
 
 
   async def add_note(self):
       if self.new_content.strip():
           await Note.create(content=self.new_content.strip(), tag=self.new_tag.strip() or "general")
           self.new_content = ""; self.toast_msg = "Note added"
 
 
   async def toggle_done(self, note_id: int):
       note = await Note.get(id=note_id)
       if note:
           await note.update(done=not note.done)
 
 
   async def delete_note(self, note_id: int):
       await Note.delete(id=note_id)
       self.toast_msg = "Deleted"
 
 
   async def clear_done(self):
       items = await Note.all()
       for n in items:
           if n.done:
               await Note.delete(id=n.id)
       self.toast_msg = "Cleared done notes"
 
 
   async def notes_filtered(self):
       items = await Note.all()
       q = self.search.lower()
       if q:
           items = [n for n in items if q in n.content.lower() or q in n.tag.lower()]
       if self.tag_filter != "all":
           items = [n for n in items if n.tag == self.tag_filter]
       items.sort(key=lambda n: n.id, reverse=self.sort_desc)
       return items
 
 
   async def stats(self):
       items = await Note.all()
       total = len(items)
       done = len([n for n in items if n.done])
       tags = {}
       for n in items:
           tags[n.tag] = tags.get(n.tag, 0) + 1
       top_tags = sorted(tags.items(), key=lambda x: x[1], reverse=True)[:5]
       return {"total": total, "done": done, "pending": total - done, "tags": top_tags}
"""

Этот код реализует асинхронные операции, логику фильтрации и подсчёта статистики, которые автоматически отражаются в UI.

UI-компоненты и компоновка

Создайте переиспользуемые компоненты: боковую панель, карточки статистики, фильтры по тегам, список заметок и строку заметки. Каждый компонент привязан к реактивному состоянию.

app_py += """
def sidebar():
   return rx.vstack(
       rx.heading("RC Advanced", size="6"),
       rx.link("Dashboard", href="/"),
       rx.link("Notes Board", href="/board"),
       rx.text("User"),
       rx.input(placeholder="your name", value=State.user, on_change=State.set_user),
       spacing="3", width="15rem", padding="1rem", border_right="1px solid #eee"
   )
 
 
async def stats_cards():
   s = await State.stats()
   return rx.hstack(
       rx.box(rx.text("Total"), rx.heading(str(s["total"]), size="5"), padding="1rem", border="1px solid #eee", border_radius="0.5rem"),
       rx.box(rx.text("Done"), rx.heading(str(s["done"]), size="5"), padding="1rem", border="1px solid #eee", border_radius="0.5rem"),
       rx.box(rx.text("Pending"), rx.heading(str(s["pending"]), size="5"), padding="1rem", border="1px solid #eee", border_radius="0.5rem"),
       spacing="4"
   )
 
 
def tag_pill(tag: str, count: int = 0):
   return rx.badge(
       f"{tag} ({count})" if count else tag,
       on_click=State.set_tag_filter(tag),
       cursor="pointer",
       color_scheme="blue" if tag == State.tag_filter else "gray"
   )
 
 
async def tags_bar():
   s = await State.stats()
   tags = [("all", s["total"])] + s["tags"]
   return rx.hstack(*[tag_pill(t[0], t[1]) for t in tags], spacing="2", wrap="wrap")
 
 
def note_row(note: Note):
   return rx.hstack(
       rx.hstack(
           rx.checkbox(is_checked=note.done, on_change=State.toggle_done(note.id)),
           rx.text(note.content, text_decoration="line-through" if note.done else "none"),
       ),
       rx.badge(note.tag, color_scheme="green"),
       rx.button("", on_click=State.delete_note(note.id), color_scheme="red", size="1"),
       justify="between", width="100%"
   )
 
 
async def notes_list():
   items = await State.notes_filtered()
   return rx.vstack(*[note_row(n) for n in items], spacing="2", width="100%")
"""

Такой подход упрощает поддержку и расширение интерфейса.

Страницы, маршруты и запуск

Соберите страницы Dashboard и Notes Board, зарегистрируйте маршруты, скомпилируйте и запустите бэкенд:

app_py += """
def dashboard_page():
   return rx.hstack(
       sidebar(),
       rx.box(
           rx.heading("Dashboard", size="8"),
           rx.cond(State.user != "", rx.text(f"Hi {State.user}, here is your activity")),
           rx.vstack(
               rx.suspense(stats_cards, fallback=rx.text("Loading stats...")),
               rx.suspense(tags_bar, fallback=rx.text("Loading tags...")),
               spacing="4"
           ),
           padding="2rem", width="100%"
       ),
       width="100%"
   )
 
 
def board_page():
   return rx.hstack(
       sidebar(),
       rx.box(
           rx.heading("Notes Board", size="8"),
           rx.hstack(
               rx.input(placeholder="search...", value=State.search, on_change=State.set_search, width="50%"),
               rx.button("Toggle sort", on_click=State.toggle_sort),
               rx.button("Clear done", on_click=State.clear_done, color_scheme="red"),
               spacing="2"
           ),
           rx.hstack(
               rx.input(placeholder="note content", value=State.new_content, on_change=State.set_new_content, width="60%"),
               rx.input(placeholder="tag", value=State.new_tag, on_change=State.set_new_tag, width="20%"),
               rx.button("Add", on_click=State.add_note),
               spacing="2"
           ),
           rx.cond(State.toast_msg != "", rx.callout(State.toast_msg, icon="info")),
           rx.suspense(notes_list, fallback=rx.text("Loading notes...")),
           padding="2rem", width="100%"
       ),
       width="100%"
   )
 
 
app = rx.App()
app.add_page(dashboard_page, route="/", title="RC Dashboard")
app.add_page(board_page, route="/board", title="Notes Board")
app.compile()
"""
pathlib.Path("app.py").write_text(app_py)
subprocess.run(["reflex", "run", "--env", "prod", "--backend-only"], check=False)

Эта последовательность компилирует приложение Reflex, добавляет страницы и запускает бэкенд, делая интерфейс интерактивным.

Что вы получите и как развивать

  • Полностью определённый на Python фронтенд и бэкенд с реактивностью Reflex.
  • Постоянное хранение данных через SQLite и модельные CRUD-операции.
  • Реактивную фильтрацию, сортировку, теговые группы и аналитические карточки.
  • Чистую структуру для расширения: можно добавить аутентификацию, внешнюю БД или деплой на сервер.

Код и структура удобны для прототипирования и изучения концепции реактивных full-stack приложений на Python.

🇬🇧

Switch Language

Read this article in English

Switch to English