Framework-free Django form widgets: combobox and multiselect, with static and AJAX search.
Project description
django-select3
Widgets Django Forms para os componentes "Select3" — combobox e multiselect com busca AJAX. Sem dependências de front-end (nada de Alpine.js, jQuery ou Tailwind em runtime).
Use Select3 em qualquer forms.Form/forms.ModelForm apenas trocando o widget=....
O app registra CSS e JS próprios, inicializando via data-select3.
Nome de distribuição no PyPI:
django-select3. O pacote Python importável édjango_select3(pip install django-select3→import django_select3).
O CSS é autossuficiente e escopado (todas as classes têm prefixo s3- e ficam sob .s3-wrapper/.s3-panel), então não vaza reset/estilos para o resto da sua aplicação.
O que vem pronto
Widgets disponíveis (4 variações):
Select3ComboboxWidget: single + opções estáticasSelect3ComboboxAjaxWidget: single + busca AJAXSelect3MultiSelectWidget: multi + opções estáticasSelect3MultiSelectAjaxWidget: multi + busca AJAX
Arquivos importantes:
- CSS interno:
static/select3/select3-bundle.css - JS interno:
static/select3/select3-widgets.js(namespacewindow.select3Widgets) - Templates:
templates/select3/widgets/*.html
Instalação / ativação
Instalando via pip
pip install django-select3
Adicione django_select3 ao INSTALLED_APPS do seu projeto Django.
Durante o desenvolvimento local (a partir deste repo), instale em modo editável:
pip install -e .
Implementação em um projeto Django
O Select3 é uma biblioteca de widgets, basta adicionar o app, usar os widgets no form e renderizar o {{ form.media }}.
Durante o desenvolvimento local (a partir deste repo), use instalação editável:
python -m pip install -e .
-
Garanta que
django_select3esteja noINSTALLED_APPS. -
Garanta que seu template renderize os assets dos widgets.
O jeito recomendado é usar o {{ form.media }} (ou {{ form.media.css }} e {{ form.media.js }}), porque os widgets declaram Media.
Exemplo (em um template qualquer onde o form aparece):
<form method="post">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<button type="submit">Salvar</button>
</form>
Assets carregados pelos widgets
Os widgets carregam sempre os assets internos da biblioteca:
select3/select3-bundle.cssselect3/select3-widgets.js
Sobrescrever cores (tema)
O CSS do Select3 é themeável por CSS variables. A principal é --s3-primary.
Por padrão, ela é definida como:
--s3-primary: var(--color-primary, #3b82f6);
Ou seja: você pode definir --s3-primary diretamente, ou (se preferir) definir --color-primary no seu design system.
Exemplo (no seu CSS global da aplicação):
:root {
--s3-primary: #16a34a;
/* alternativa: --color-primary: #16a34a; */
}
Além da cor primária, outras variáveis podem ser sobrescritas (todas com valores padrão sensatos): --s3-bg, --s3-text, --s3-muted, --s3-border, --s3-border-hover, --s3-hover-bg, --s3-danger, --s3-radius, --s3-height, --s3-font-size.
Se quiser aplicar apenas em uma área da página (escopo), basta definir a variável em um contêiner ancestral:
.minha-area {
--s3-primary: #9333ea;
}
<div class="minha-area">
{{ form.media }}
{{ form.as_p }}
</div>
Traduções / textos da interface (i18n)
Os textos padrão dos widgets são em inglês.
- No lado Python, os placeholders padrão usam
gettext_lazy, então respeitam oLANGUAGE_CODE/traduções do Django. Você também pode passarplaceholder=...diretamente em cada widget. - No lado JS, os textos (mensagens de "sem resultados", "carregando", etc.) podem ser sobrescritos definindo
window.select3WidgetsConfig.i18nantes de carregar o script:
<script>
window.select3WidgetsConfig = {
i18n: {
noResults: "Nenhum resultado encontrado",
noOptions: "Nenhuma opção disponível",
searching: "Buscando...",
minChars: "Digite pelo menos {n} caracteres para buscar",
loadingMore: "Carregando mais...",
scrollForMore: "Role para carregar mais...",
remove: "Remover",
loading: "Carregando...",
selectPlaceholder: "Selecione uma opção",
searchPlaceholder: "Busque...",
multiPlaceholder: "Digite para buscar...",
},
};
</script>
Conteúdo dinâmico (HTMX / modais / swaps)
O JS dos widgets inicializa automaticamente qualquer elemento com data-select3:
- no carregamento da página (
DOMContentLoaded) - e também quando novos elementos são inseridos no DOM (via
MutationObserver)
Ou seja: se você renderiza forms via HTMX (ou injeta HTML via modal), os widgets devem “subir” sem precisar de snippet extra.
Opt-out do observer
Se você preferir controlar manualmente (por performance ou previsibilidade), desabilite o observer antes de carregar o JS:
<script>
window.select3WidgetsConfig = { observe: false };
</script>
E chame manualmente quando precisar:
window.select3Widgets.initAll(containerElement)
Cleanup (quando remover elementos)
O JS expõe helpers para limpar listeners e dropdowns criados no document.body:
window.select3Widgets.destroy(el) // um widget root
window.select3Widgets.destroyAll(scope) // scope/container
Uso (exemplos)
Exemplo completo com as 4 variações:
from django import forms
from django_select3.widgets import (
Select3ComboboxAjaxWidget,
Select3ComboboxWidget,
Select3MultiSelectAjaxWidget,
Select3MultiSelectWidget,
)
class ExampleForm(forms.Form):
status = forms.ChoiceField(
label="Status",
choices=[("A", "Ativo"), ("I", "Inativo")],
required=False,
widget=Select3ComboboxWidget(
placeholder="Selecione...",
allow_clear=True,
),
)
state = forms.CharField(
label="Estado",
required=False,
widget=Select3ComboboxAjaxWidget(
ajax_url="myapp:states_autocomplete", # ou "/api/states/"
placeholder="Busque estado...",
min_search_length=0,
allow_clear=True,
initial_label="",
),
)
city = forms.CharField(
label="Cidade",
required=False,
widget=Select3ComboboxAjaxWidget(
ajax_url="myapp:cities_autocomplete", # ou "/api/cities/"
placeholder="Busque cidade...",
min_search_length=2,
allow_clear=True,
forward={"state": "state"},
initial_label="",
),
)
tags = forms.MultipleChoiceField(
label="Tags",
choices=[("1", "VIP"), ("2", "Atraso")],
required=False,
widget=Select3MultiSelectWidget(
placeholder="Selecione...",
allow_clear=True,
),
)
services = forms.Field(
label="Serviços",
required=False,
widget=Select3MultiSelectAjaxWidget(
ajax_url="myapp:services_autocomplete", # ou "/api/services/"
placeholder="Digite para buscar...",
min_search_length=2,
forward={"city": "city"},
),
)
“Cláusulas” (args) dos widgets
Esta seção documenta os argumentos suportados no construtor de cada widget (os “kwargs” que você passa no widget=...).
Args comuns (todos os widgets)
-
label: str | None- Controla o label exibido no próprio template do widget.
- Se você já renderiza labels por fora (ou usa
{{ form.as_p }}/as_crispy_field), pode deixarNone.
-
placeholder: str | None- Texto do placeholder visível no input.
- Se omitido, cada widget usa um default (“Selecione…”, “Busque…”, etc.).
-
allow_clear: bool- Mostra/esconde o botão de limpar (ícone de “x”).
- Observação: hoje isso é puramente UX (front-end). Se o campo for obrigatório, considere
allow_clear=False.
-
required: bool | None- Se
None(padrão): o widget herdafield.required. - Se
True/False: força o estado “obrigatório” no template (exibe asterisco). - Observação: isso não faz validação. A validação de obrigatório continua sendo do
Field.
- Se
-
attrs: dict | None- Atributos HTML padrão do Django Widget.
- O
idvindo deattrs(ou gerado pelo Django) é usado nos inputs/labels do widget.
Select3ComboboxWidget (single + estático)
Construtor: Select3ComboboxWidget(..., options_element_id=None)
options_element_id: str | None- Alternativa para passar as opções via um elemento no HTML.
- Uso recomendado quando a lista de opções é grande, para evitar HTML com
data-options-json="..."muito pesado/escapado.
Formato esperado no DOM:
<script type="application/json" id="my_options">
[{"value": "A", "label": "Ativo"}, {"value": "I", "label": "Inativo"}]
</script>
Depois, no widget:
Select3ComboboxWidget(options_element_id="my_options")
Select3ComboboxAjaxWidget (single + AJAX)
Construtor: Select3ComboboxAjaxWidget(ajax_url=..., min_search_length=0, forward=None, initial_label=None)
-
ajax_url: str(obrigatório)- Pode ser:
- um nome de URL (o widget tenta
reverse(ajax_url)) - uma URL absoluta/relativa (quando contém
/ou começa comhttp:///https://)
- um nome de URL (o widget tenta
- Pode ser:
-
min_search_length: int(padrão0)0significa “carregar/mostrar dropdown mesmo sem digitar”.>0significa “só buscar quando tiver pelo menos N caracteres”.- UX: quando
qtem menos de N caracteres, o dropdown mostra a mensagem pedindo mais caracteres.
-
forward: dict[str, str] | None- Mapa
{chave_no_forward: nome_do_input_no_form}. - O JS lê o valor atual via
document.querySelector('input[name="<nome>"]'). - Exemplo:
forward={"state": "state"}envia{ "state": <valor do input name=state> }.
- Mapa
-
initial_label: str | None- Usado para “modo edição”: quando existe um
valueinicial, você também precisa passar o texto (label) para exibir na UI. - Sem isso, o widget sabe o “id” (valor) mas não sabe o “text” (label) até você buscar.
- Usado para “modo edição”: quando existe um
Select3MultiSelectWidget (multi + estático)
Construtor: Select3MultiSelectWidget(...)
Detalhe importante: o widget posta múltiplos valores repetindo inputs hidden com o mesmo name.
Por isso, ele implementa value_from_datadict() usando QueryDict.getlist(name).
Isso significa que ele funciona bem com MultipleChoiceField, ModelMultipleChoiceField, etc.
Select3MultiSelectAjaxWidget (multi + AJAX)
Construtor: Select3MultiSelectAjaxWidget(ajax_url=..., min_search_length=2, forward=None)
Args:
ajax_url: str(obrigatório): mesmo comportamento do combobox AJAX.min_search_length: int(padrão2): mesma regra do combobox AJAX.forward: dict[str, str] | None: mesma regra do combobox AJAX.
Observação: como as opções vêm dinamicamente do endpoint, é comum usar esse widget com forms.Field ou com uma limpeza/validação customizada no servidor. Se você usar MultipleChoiceField, precisa garantir que os choices válidos existam no momento da validação.
Contrato do endpoint AJAX
O JS envia requisições GET com estes parâmetros:
q: string digitadapage: número da página quando há paginação/infinite scrollforward: JSON url-encoded (opcional)
Resposta esperada (contrato JSON):
{
"results": [
{"id": "BR", "text": "Brasil"}
],
"pagination": {
"more": false
}
}
Para cada item em results, o JS usa id/text e também aceita value/label como alternativa.
Também é aceito retornar uma lista direta em vez de um objeto, por exemplo:
[
{"id": "BR", "text": "Brasil"}
]
Paginação (opcional):
pagination.more: booleannext_page: number | nullnext: string | null(URL pronta para a próxima página)page+total_pagescount+page+page_size
Se nenhum metadado de paginação vier, o JS usa um fallback por tamanho:
- Assume
page_size=20(ou usepage_sizeno JSON, se você retornar) - Continua buscando enquanto cada página vier “cheia”
- Para quando
resultsvier vazio ou com menos itens que opage_size
Esse fallback pode causar 1 request extra no final quando o total é múltiplo exato do page_size.
Exemplo de view simples:
from django.http import JsonResponse
def my_autocomplete(request):
q = (request.GET.get("q") or "").strip()
forward_raw = request.GET.get("forward") or ""
# forward_raw é JSON (string); se precisar, faça json.loads(forward_raw)
results = []
if q:
results = [
{"id": "1", "text": f"Resultado para: {q}"},
]
return JsonResponse({"results": results})
Forward (dependências/cascata)
O forward existe para encadear selects (ex.: País → Estado → Cidade).
Como funciona:
- Você configura
forward={"state": "state"}no widget “filho” (Cidade). - O JS inclui
forwardna querystring. - Seu endpoint usa isso para filtrar os resultados.
Múltiplos valores no “pai”:
- Se o “pai” tiver vários inputs com o mesmo
name(caso comum de multi), oforwardcoleta todos os valores. Quando há mais de um, o valor enviado para aquela chave vira um array JSON; com um único valor, vai como string. Trate os dois formatos no seu endpoint.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_select3-0.1.0.tar.gz.
File metadata
- Download URL: django_select3-0.1.0.tar.gz
- Upload date:
- Size: 26.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b90815dcb3bc8c240b17b15b6bc851c964fd7169217adf5ddd39fcbfa47a1da8
|
|
| MD5 |
aac7ccbff0c86bce616b521240cfda8c
|
|
| BLAKE2b-256 |
8e3c2b50ab8bddded77cfa54b24222f15ebcd8df5f2dc4d5d112d92df9514ec3
|
File details
Details for the file django_select3-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_select3-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed60542cb3ee03b2b42c1cc316172fe7486547062d114384be5440b972a1b753
|
|
| MD5 |
3f496a5f2ade104514e066e9025ad517
|
|
| BLAKE2b-256 |
ad77a25910ab675ffe22dbcd64ddf9d4b8c992dfe1272e1ba247fb50dbdd898d
|