DocsPython estruturas
De Django para Nexy
No Nexy, uma visualização Django e sua linha urls.py tornam-se um arquivo em src/routes/, os modelos permanecem semelhantes ao Jinja2, e o ORM, formulários e administrador são sua escolha, em vez de integrados.
Cada seção abaixo mostra um hábito Django, então a mesma coisa no Nexy.
Uma visualização e seu URL
from django.http import JsonResponse def hello(request): return JsonResponse({"message": "Hello"})from django.urls import path from . import views urlpatterns = [path("api/hello/", views.hello)]Não há urls.py: o caminho do arquivo é a URL, o nome da função é o método e um dict retornado é JSON.
async def GET(): return {"message": "Hello"}Conversores de caminho
urlpatterns = [path("api/posts/<int:id>/", views.post_detail)]O conversor se torna uma anotação de tipo em um segmento [id]:
async def GET(id: int): return {"id": id}<path:rest> torna-se [...rest].py.
request.GET e request.POST
def search(request): q = request.GET.get("q", "") page = int(request.GET.get("page", 1)) return JsonResponse({"q": q, "page": page})Um parâmetro de consulta é um parâmetro de função, convertido e validado:
async def GET(q: str = "", page: int = 1): return {"q": q, "page": page}Visualizações baseadas em classe
from django.views import View class NoteView(View): def get(self, request, id): return JsonResponse({"id": id}) def delete(self, request, id): return JsonResponse({"deleted": id})Uma função por método, no arquivo da URL:
async def GET(id: int): return {"id": id} async def DELETE(id: int): return {"deleted": id}Modelos e render
from django.shortcuts import render def post_list(request): posts = [{"title": "Hello"}, {"title": "Second"}] return render(request, "blog/post_list.html", {"posts": posts}){% extends "base.html" %}{% block content %}<ul> {% for post in posts %} <li>{{ post.title }}</li> {% endfor %}</ul>{% endblock %}A visualização e o modelo são um arquivo .nexy. As tags são Jinja2, próximas de Django:
posts = [{"title": "Hello"}, {"title": "Second"}]<ul> {% for post in posts %} <li>{{ post.title }}</li> {% endfor %}</ul>base.html e {% block %}
<!DOCTYPE html><html> <body>{% block content %}{% endblock %}</body></html>Um layout.nexy envolve todas as páginas de sua pasta e abaixo, sem {% extends %} nas páginas:
children: prop[NexyElement]<!DOCTYPE html><html> <body>{{ children | safe }}</body></html>{% include %} e tags de inclusão
<article><h2>{{ title }}</h2>{{ body }}</article>{% include "card.html" with title="Latest" body="This week" %}
Um componente é um arquivo .nexy com adereços digitados e um slot para seus filhos:
title: prop[str]<article> <h2>{{ title }}</h2> <Slot /></article>from "@/components/Card.nexy" import Card<Card title="Latest"> <p>This week</p></Card>@ é src/: nx new escreve useAliases = {"@": "src"} em nexyconfig.py.
Filtros de modelo
| Django | Jinja2 em .nexy |
|---|---|
{{ name|default:"guest" }} |
{{ name or "guest" }} ou {{ name | default("guest") }} |
{{ title|upper }} |
{{ title | upper }} |
{{ body|safe }} |
{{ body | safe }} |
{{ items|length }} |
{{ items | length }} |
{% url "post" post.id %} |
/posts/{{ post.id }}: o URL é o caminho do arquivo |
Modelos e o ORM
from django.db import models class Post(models.Model): title = models.CharField(max_length=200) published = models.BooleanField(default=False)def published(request): posts = Post.objects.filter(published=True) return JsonResponse({"titles": [p.title for p in posts]})Nexy não possui ORM próprio. nx new pode criar andaime SQLModel, SQLAlchemy ou Tortoise-ORM; com SQLModel o modelo e a consulta são assim:
from sqlmodel import Field, SQLModel class Post(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) title: str published: bool = Falsefrom sqlmodel import Session, select from src.configs.db import enginefrom src.models.post_model import Post async def GET(): with Session(engine) as session: posts = session.exec(select(Post).where(Post.published)).all() return {"titles": [post.title for post in posts]}Consulte Bancos de dados.
Migrações
| Django | Nexy |
|---|---|
python manage.py makemigrations |
nx migrate -m "add posts" |
python manage.py migrate |
nx migrate --upgrade |
nx migrate unidades Alembic para SQLModel e SQLAlchemy, e Aerich para Tortoise-ORM.
Formulários e CSRF
from django.shortcuts import redirect def add_note(request): if request.method == "POST": NOTES.append(request.POST["title"]) return redirect("notes") return render(request, "notes.html", {"notes": NOTES})<form method="post"> {% csrf_token %} <input name="title"> <button type="submit">Add</button></form>Uma ação é a função para a qual o formulário é postado. Seus parâmetros são os campos do formulário, e o visitante é enviado de volta à página com um 303:
from nexy.decorators import Action NOTES: list[str] = [] @Actionasync def add_note(title: str) -> None: NOTES.append(title)from src.actions.notes import NOTES<form method="post" action="{{ __actions_path['add_note'] }}"> <input name="title"> <button type="submit">Add</button></form><ul> {% for note in NOTES %} <li>{{ note }}</li> {% endfor %}</ul>Não há {% csrf_token %}: veja Ações para saber o que protege o caminho da ação e o que você ainda verifica por si mesmo.
Django formulários e serializadores DRF
from rest_framework import serializers class PostSerializer(serializers.Serializer): title = serializers.CharField(max_length=200) published = serializers.BooleanField(default=False)Um modelo Pydantic valida o corpo, e um inválido responde 422 com o campo que falhou:
from pydantic import BaseModel, Field class NewPost(BaseModel): title: str = Field(max_length=200) published: bool = False async def POST(post: NewPost): return post@login_required
from django.contrib.auth.decorators import login_required @login_requireddef dashboard(request): return render(request, "dashboard.html")Um dependencies.py protege uma pasta inteira:
from nexy import unauthorizedfrom nexy.http import Request def require_login(request: Request) -> None: if not request.session.get("user"): unauthorized() dependencies = [require_login]from nexy import useSession user = useSession()["user"]<h1>Welcome back, {{ user }}</h1>As sessões são ativadas em nexyconfig.py:
from nexy.core.models import NexyConfigModel class NexyConfig(NexyConfigModel): useAliases = {"@": "src"} useSession = {"secret_key": "change-me"}Middleware
class AppHeaderMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): response = self.get_response(request) response["X-App"] = "demo" return responseMIDDLEWARE = [..., "blog.middleware.AppHeaderMiddleware"]Um middleware.py agrupa as rotas de sua pasta; aquele na raiz envolve todos eles:
from nexy.http import Request async def middleware(request: Request, call_next): response = await call_next(request) response.headers["x-app"] = "demo" return responseHttp404 e 404.html
from django.shortcuts import get_object_or_404 def post_detail(request, slug): post = get_object_or_404(Post, slug=slug) return render(request, "post.html", {"post": post})notFound() para a página e notfound.nexy na raiz da rota desempenha o papel de 404.html:
from nexy import notFound POSTS = {"hello": "Hello, world"} slug: prop[str]if slug not in POSTS: notFound()<h1>{{ POSTS[slug] }}</h1><h1>Not found</h1><p><a href="/">Back home</a></p>cache_page
from django.views.decorators.cache import cache_page @cache_page(60)def post_list(request): ...Uma linha no frontmatter armazena em cache a página por URL e a renderiza novamente em segundo plano:
revalidate = 60<h1>Archive</h1>settings.py
| Configuração Django | nexyconfig.py |
|---|---|
SECRET_KEY + SessionMiddleware |
useSession = {"secret_key": ...} |
ALLOWED_HOSTS |
useTrustedHost = {"allowed_hosts": [...]} |
SECURE_SSL_REDIRECT |
useHTTPSRedirect = True |
CORS_ALLOWED_ORIGINS (cabeçalhos django-cors) |
useCORS = {"allow_origins": [...]} |
MIDDLEWARE |
useMiddlewares = [...] ou middleware.py arquivos |
DATABASES |
DATABASE_URL em .env, lido por src/configs/db.py |
STATIC_URL, STATICFILES_DIRS |
public/, servido em / |
Aplicativos
Um projeto Django é dividido em aplicativos. No Nexy, uma pasta de src/routes/ é a unidade para URLs, e src/models/ e src/services/ contêm o código que eles compartilham. Para controladores e módulos com injeção de dependência, o roteador modular é a correspondência mais próxima.
O administrador
Nexy não tem site de administração. Monte um dos FastAPI pacotes de administração, como SQLAdmin, ou crie as páginas necessárias a partir de .nexy arquivos e ações.
Testes
from django.test import TestCase class HelloTests(TestCase): def test_hello(self): self.assertEqual(self.client.get("/api/hello/").json(), {"message": "Hello"})from fastapi.testclient import TestClient from nexy.app import app def test_hello(): assert TestClient(app).get("/api/hello").json() == {"message": "Hello"}Correndo
| Django | Nexy |
|---|---|
django-admin startproject |
nx new |
python manage.py runserver |
nx dev |
gunicorn project.wsgi |
nx build, então nx start |
python manage.py test |
nx test |
python manage.py collectstatic |
nx build |