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 (modais / swaps / HTMX)
Nota: o Select3 não depende de HTMX. As requisições de busca dos widgets AJAX usam a Fetch API (AJAX) nativa do browser — não há nenhuma dependência de HTMX. Esta seção trata de compatibilidade: os widgets funcionam bem quando o seu app injeta HTML dinamicamente, seja via HTMX, modais ou swaps de qualquer biblioteca.
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, ou faz swap por qualquer outra ferramenta), 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.querySelectorAll('[name="<nome>"]')— pega todos os elementos com aquelename(não sóinput), coletando todos os valores. - 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.
Metadata
Release files for django-select3 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_select3-0.1.1.tar.gz | 27.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_select3-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 52.6 kB
Release files / django_select3-0.1.1.tar.gz
| Download URL | django_select3-0.1.1.tar.gz |
|---|---|
| Size | 27.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2f0639a191952e589683db1d4514603b886147b23a47ad6d6c3392901dd69112
|
|
BLAKE2b-256 checksum How to use checksums |
d9c49c373d062900be2abcfb2a50dbb5752a437ea5179eff6e1557b65345cb6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.14
|
Release files / django_select3-0.1.1-py3-none-any.whl
| Download URL | django_select3-0.1.1-py3-none-any.whl |
|---|---|
| Size | 25.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
687d8bd6bf0bed970abf5e51a066199b3b8ac4f8937fe45d0192097e27a0d23b
|
|
BLAKE2b-256 checksum How to use checksums |
e18b0b8028f67a0677fc51dbaf4a51d55376342453b1d9f5698d62125b429b43
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.14
|