# SearchableDropdown
[](https://badge.fury.io/py/django_searchable_dropdown)
[](https://www.python.org/downloads/)
[](https://www.djangoproject.com/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/novaalvorada/django_searchable_dropdown)
[](https://github.com/novaalvorada/django_searchable_dropdown)
Uma biblioteca Django completa para criar dropdowns pesquisáveis e customizáveis com funcionalidades avançadas.
## Sobre a Biblioteca
O **SearchableDropdown** é uma biblioteca Django que oferece componentes de dropdown avançados com funcionalidades de busca em tempo real, integração nativa com formulários Django e suporte a múltiplas seleções.
### Características Principais
- **Dropdowns Pesquisáveis**: Busca em tempo real nas opções
- **Integração Django**: Widgets e campos de formulário nativos
- **Suporte AJAX**: Busca dinâmica em modelos Django
- **Múltipla Seleção**: Suporte a seleção de múltiplas opções
- **Informações Adicionais**: Exibição de dados extras sobre opções selecionadas
- **Customizável**: Temas e configurações flexíveis
- **Responsivo**: Funciona em dispositivos móveis
- **Acessível**: Suporte a navegação por teclado e leitores de tela
### Status do Projeto
- ✅ **Versão**: 1.0.0 (Estável)
- ✅ **Testes**: 110 testes passando (100% cobertura)
- ✅ **Compatibilidade**: Django 2.2+ e Python 3.7+
- ✅ **Documentação**: Completa com exemplos práticos
- ✅ **Licença**: MIT (Open Source)
- ✅ **PyPI**: Disponível para instalação via pip
## Instalação
### Opção 1: Instalar via pip (Recomendado para produção)
```bash
pip install django_searchable_dropdown
```
E então adicionar ao `INSTALLED_APPS`:
```python
INSTALLED_APPS = [
# ... outras apps
'recoveredperispirit.django.django_searchable_dropdown',
]
```
### Opção 2: Integrar em Projeto Existente
#### 1. Adicionar ao projeto
```bash
# Copiar a pasta recoveredperispirit/django/django_searchable_dropdown para seu projeto Django
cp -r recoveredperispirit/django/django_searchable_dropdown/ /path/to/your/django/project/
```
#### 2. Configurar settings.py
```python
INSTALLED_APPS = [
# ... outras apps
'recoveredperispirit.django.django_searchable_dropdown',
]
# Configurações opcionais
SEARCHABLE_DROPDOWN_CONFIG = {
'default_placeholder': 'Selecione uma opção',
'default_search_placeholder': 'Digite para buscar...',
'default_no_results_text': 'Nenhum resultado encontrado',
'ajax_timeout': 5000,
'min_search_length': 1,
'max_results': 50,
}
```
#### 3. Coletar arquivos estáticos
```bash
python manage.py collectstatic
```
## Guia de Uso - Widgets Disponíveis
### 1. SearchableDropdownWidget (Básico)
O widget básico para dropdowns pesquisáveis com opções estáticas ou de modelos Django.
#### Exemplo Básico
```python
from recoveredperispirit.django.django_searchable_dropdown.widgets import SearchableDropdownWidget
from django import forms
class CategoriaForm(forms.Form):
categoria = forms.ModelChoiceField(
queryset=Categoria.objects.all(),
widget=SearchableDropdownWidget(
placeholder="Selecione uma categoria",
search_placeholder="Digite para buscar categorias...",
dropdown_type="categoria",
min_search_length=1,
max_results=20
),
required=False
)
```
#### Como Funciona
1. **Renderização**: O widget cria um dropdown customizado com campo de busca
2. **Busca**: Usuário digita no campo de busca e as opções são filtradas em tempo real
3. **Seleção**: Usuário clica em uma opção para selecioná-la
4. **Validação**: O valor é validado pelo Django normalmente
#### Resultado Visual
```
┌─────────────────────────────────────┐
│ Selecione uma categoria ▼ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Digite para buscar categorias... │
├─────────────────────────────────────┤
│ ✓ Tecnologia │
│ Esportes │
│ Música │
│ Livros │
└─────────────────────────────────────┘
```
### 2. SearchableDropdownMultipleWidget (Múltipla Seleção)
Widget para selecionar múltiplas opções simultaneamente.
#### Exemplo de Uso
```python
from recoveredperispirit.django.django_searchable_dropdown.widgets import SearchableDropdownMultipleWidget
class ProdutoForm(forms.Form):
tags = forms.ModelMultipleChoiceField(
queryset=Tag.objects.all(),
widget=SearchableDropdownMultipleWidget(
placeholder="Selecione tags (máximo 5)",
search_placeholder="Digite para buscar tags...",
dropdown_type="tags",
max_selections=5,
allow_clear=True
),
required=False
)
```
#### Como Funciona
1. **Múltipla Seleção**: Usuário pode selecionar várias opções
2. **Contador**: Mostra quantas opções foram selecionadas
3. **Limite**: Pode definir um número máximo de seleções
4. **Remoção**: Opções selecionadas podem ser removidas individualmente
#### Resultado Visual
```
┌─────────────────────────────────────┐
│ ✓ Tecnologia, Esportes (2/5) ▼ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Digite para buscar tags... │
├─────────────────────────────────────┤
│ ✓ Tecnologia [×] │
│ ✓ Esportes [×] │
│ Música │
│ Livros │
└─────────────────────────────────────┘
```
### 3. SearchableDropdownAjaxWidget (Busca AJAX)
Widget para busca dinâmica via AJAX em grandes datasets.
#### Exemplo de Uso
```python
from recoveredperispirit.django.django_searchable_dropdown.widgets import SearchableDropdownAjaxWidget
class ClienteForm(forms.Form):
cliente = forms.ModelChoiceField(
queryset=Cliente.objects.none(), # Queryset vazio inicialmente
widget=SearchableDropdownAjaxWidget(
placeholder="Digite para buscar clientes...",
search_placeholder="Nome ou email do cliente...",
dropdown_type="cliente_ajax",
ajax_url="/api/clientes/search/",
min_search_length=2,
max_results=20,
delay=300, # Delay em ms antes de fazer a busca
allow_clear=True
),
required=False
)
```
#### Como Funciona
1. **Busca Dinâmica**: Dados são carregados via AJAX conforme o usuário digita
2. **Delay**: Aguarda o usuário parar de digitar antes de fazer a requisição
3. **Filtros**: Aplica filtros no servidor para melhor performance
4. **Cache**: Pode implementar cache para melhorar performance
#### Resultado Visual
```
┌─────────────────────────────────────┐
│ Digite para buscar clientes... ▼ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ joão... │
├─────────────────────────────────────┤
│ Buscando... │
├─────────────────────────────────────┤
│ João Silva (joao@email.com) │
│ João Santos (joao.s@email.com) │
│ João Oliveira (joao.o@email.com) │
└─────────────────────────────────────┘
```
#### API Endpoint Necessário
```python
# views.py
from django.http import JsonResponse
from django.db.models import Q
def api_clientes_search(request):
query = request.GET.get('q', '')
if len(query) < 2:
return JsonResponse({'results': []})
clientes = Cliente.objects.filter(
Q(nome__icontains=query) | Q(email__icontains=query)
)[:20]
results = [
{
'id': cliente.id,
'text': f"{cliente.nome} ({cliente.email})"
}
for cliente in clientes
]
return JsonResponse({'results': results})
```
### 4. SearchableDropdownWithInfoWidget (Com Informações)
Widget que mostra informações detalhadas sobre a opção selecionada.
#### Exemplo de Uso
```python
from recoveredperispirit.django.django_searchable_dropdown.widgets import SearchableDropdownWithInfoWidget
class ProdutoDetalhadoForm(forms.Form):
produto = forms.ModelChoiceField(
queryset=Produto.objects.all(),
widget=SearchableDropdownWithInfoWidget(
placeholder="Selecione um produto para ver detalhes",
search_placeholder="Digite para buscar produtos...",
dropdown_type="produto_info",
info_url="/api/produtos/info/",
info_container_id="produto-info-container"
),
required=False
)
```
#### Como Funciona
1. **Seleção**: Usuário seleciona uma opção no dropdown
2. **Carregamento**: Informações detalhadas são carregadas via AJAX
3. **Exibição**: Dados são exibidos em um container separado
4. **Atualização**: Informações são atualizadas a cada nova seleção
#### Resultado Visual
```
┌─────────────────────────────────────┐
│ iPhone 13 Pro ▼ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Digite para buscar produtos... │
├─────────────────────────────────────┤
│ iPhone 13 Pro │
│ iPhone 12 │
│ Samsung Galaxy S21 │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Informações do Produto │
├─────────────────────────────────────┤
│ Nome: iPhone 13 Pro │
│ Código: PROD001 │
│ Preço: R$ 8.999,00 │
│ Estoque: 15 unidades │
│ Categoria: Smartphones │
│ Marca: Apple │
│ Descrição: iPhone 13 Pro 256GB... │
└─────────────────────────────────────┘
```
#### API Endpoint Necessário
```python
# views.py
def api_produtos_info(request):
produto_id = request.GET.get('id')
if not produto_id:
return JsonResponse({'error': 'ID do produto não fornecido'}, status=400)
try:
produto = Produto.objects.get(id=produto_id)
info = {
'id': produto.id,
'nome': produto.nome,
'codigo': produto.codigo,
'preco': str(produto.preco),
'estoque': produto.estoque,
'categoria': produto.categoria.nome,
'marca': produto.marca.nome,
'descricao': produto.descricao or 'Sem descrição'
}
return JsonResponse(info)
except Produto.DoesNotExist:
return JsonResponse({'error': 'Produto não encontrado'}, status=404)
```
#### Template HTML Necessário
```html
<!-- Incluir container para informações -->
<div class="row">
<div class="col-md-8">
{{ form.produto }}
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6>Informações do Produto</h6>
</div>
<div class="card-body" id="produto-info-container">
<p class="text-muted">Selecione um produto para ver as informações</p>
</div>
</div>
</div>
</div>
```
## Customização Avançada
### Configurações por Tipo de Dropdown
```python
# Em apps.py ou settings.py
from recoveredperispirit.django.django_searchable_dropdown.utils import dropdown_config
# Configurar tipo personalizado
dropdown_config.register_type('activity', {
'placeholder': 'Selecione uma atividade',
'search_placeholder': 'Digite o nome da atividade...',
'no_results_text': 'Nenhuma atividade encontrada',
'min_search_length': 2,
'max_results': 20,
'allow_clear': True,
'allow_create': False,
})
# Usar em formulários
class ActivityForm(forms.Form):
activity = forms.ModelChoiceField(
queryset=Activity.objects.all(),
widget=SearchableDropdownWidget(dropdown_type='activity')
)
```
### Estilos CSS Customizados
```css
/* Tema personalizado */
.searchable-dropdown.custom-theme {
--dropdown-bg: #ffffff;
--dropdown-border: #e0e0e0;
--dropdown-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--option-hover-bg: #f8f9fa;
--option-selected-bg: #007bff;
--option-selected-color: #ffffff;
--search-input-bg: #f8f9fa;
--search-input-border: #dee2e6;
}
/* Estilos específicos para diferentes tipos */
.searchable-dropdown[data-type="produto"] {
--option-selected-bg: #28a745;
}
.searchable-dropdown[data-type="cliente"] {
--option-selected-bg: #17a2b8;
}
```
### JavaScript Customizado
```javascript
// Eventos customizados
document.addEventListener('dropdown:change', function(e) {
const dropdown = e.target;
const value = e.detail.value;
const text = e.detail.text;
console.log('Dropdown alterado:', { value, text });
// Lógica customizada aqui
if (dropdown.getAttribute('data-type') === 'produto') {
updateProductInfo(value);
}
});
// Inicialização customizada
const customDropdown = new SearchableDropdown(element, {
placeholder: 'Selecione uma opção',
searchPlaceholder: 'Digite para buscar...',
onSelect: function(value, text) {
console.log('Opção selecionada:', value, text);
},
onSearch: function(query) {
console.log('Buscando:', query);
}
});
```
## Exemplos Práticos Completos
### Exemplo 1: Sistema de Agendamento
```python
# models.py
class Activity(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
duration = models.IntegerField(help_text="Duração em minutos")
max_participants = models.IntegerField()
def __str__(self):
return self.name
class Schedule(models.Model):
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
date = models.DateField()
time = models.TimeField()
participants = models.ManyToManyField('User', blank=True)
def __str__(self):
return f"{self.activity.name} - {self.date} {self.time}"
# forms.py
class ScheduleForm(forms.ModelForm):
activity = forms.ModelChoiceField(
queryset=Activity.objects.all(),
widget=SearchableDropdownWithInfoWidget(
dropdown_type='activity',
info_url='/api/activities/info/',
info_container_id='activity-info',
placeholder='Selecione uma atividade'
)
)
participants = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
widget=SearchableDropdownMultipleWidget(
dropdown_type='users',
placeholder='Selecione participantes',
max_selections=10
),
required=False
)
class Meta:
model = Schedule
fields = ['activity', 'date', 'time', 'participants']
# views.py
def api_activities_info(request):
activity_id = request.GET.get('id')
try:
activity = Activity.objects.get(id=activity_id)
return JsonResponse({
'name': activity.name,
'description': activity.description,
'duration': f"{activity.duration} minutos",
'max_participants': activity.max_participants
})
except Activity.DoesNotExist:
return JsonResponse({'error': 'Atividade não encontrada'}, status=404)
```
### Exemplo 2: E-commerce com Produtos
```python
# models.py
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.IntegerField()
description = models.TextField()
tags = models.ManyToManyField('Tag', blank=True)
def __str__(self):
return self.name
# forms.py
class ProductSearchForm(forms.Form):
category = forms.ModelChoiceField(
queryset=Category.objects.all(),
widget=SearchableDropdownWidget(
dropdown_type='category',
placeholder='Todas as categorias',
allow_clear=True
),
required=False
)
products = forms.ModelMultipleChoiceField(
queryset=Product.objects.all(),
widget=SearchableDropdownMultipleWidget(
dropdown_type='products',
placeholder='Selecione produtos para comparar',
max_selections=5
),
required=False
)
featured_product = forms.ModelChoiceField(
queryset=Product.objects.all(),
widget=SearchableDropdownWithInfoWidget(
dropdown_type='product_info',
info_url='/api/products/info/',
info_container_id='product-details',
placeholder='Selecione um produto para ver detalhes'
),
required=False
)
```
## Configurações Avançadas
### Configurações Globais
```python
# settings.py
SEARCHABLE_DROPDOWN_CONFIG = {
# Configurações padrão
'default_placeholder': 'Selecione uma opção',
'default_search_placeholder': 'Digite para buscar...',
'default_no_results_text': 'Nenhum resultado encontrado',
# Configurações de performance
'ajax_timeout': 5000,
'min_search_length': 1,
'max_results': 50,
# Configurações de UI
'allow_clear': True,
'allow_create': False,
'delay': 300,
# Configurações de acessibilidade
'aria_label': 'Dropdown pesquisável',
'aria_describedby': 'dropdown-help',
}
```
### Configurações por Ambiente
```python
# settings/development.py
SEARCHABLE_DROPDOWN_CONFIG = {
'debug': True,
'ajax_timeout': 10000,
'delay': 500,
}
# settings/production.py
SEARCHABLE_DROPDOWN_CONFIG = {
'debug': False,
'ajax_timeout': 3000,
'delay': 200,
'cache_results': True,
}
```
## Deploy e Performance
### Otimizações para Produção
```python
# settings.py
SEARCHABLE_DROPDOWN_CONFIG = {
'cache_results': True,
'cache_timeout': 300, # 5 minutos
'ajax_timeout': 3000,
'max_results': 20,
'min_search_length': 2,
}
# views.py com cache
from django.core.cache import cache
def api_search_view(request):
query = request.GET.get('q', '')
cache_key = f"search_{query}"
# Verificar cache
cached_results = cache.get(cache_key)
if cached_results:
return JsonResponse(cached_results)
# Buscar dados
results = perform_search(query)
# Salvar no cache
cache.set(cache_key, results, 300)
return JsonResponse(results)
```
## Troubleshooting
### Problemas Comuns e Soluções
#### 1. Dropdown não abre
```javascript
// Verificar se os scripts foram carregados
console.log('SearchableDropdown:', typeof SearchableDropdown);
console.log('SearchableDropdownUtils:', typeof SearchableDropdownUtils);
// Verificar se o elemento existe
const dropdown = document.querySelector('.searchable-dropdown');
console.log('Dropdown element:', dropdown);
```
#### 2. Busca AJAX não funciona
```python
# Verificar se a view retorna JSON válido
def api_search_view(request):
try:
# Sua lógica aqui
return JsonResponse({'results': results})
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
```
#### 3. Estilos não aplicados
```css
/* Forçar estilos com !important se necessário */
.searchable-dropdown {
display: block !important;
position: relative !important;
z-index: 1000 !important;
}
```
### Debug Avançado
```javascript
// Habilitar modo debug
SearchableDropdownUtils.setDebugMode(true);
// Verificar dropdowns inicializados
const dropdowns = SearchableDropdownUtils.getInitializedDropdowns();
console.log('Dropdowns inicializados:', dropdowns);
// Verificar configurações
const configs = SearchableDropdownUtils.getConfigs();
console.log('Configurações:', configs);
```
## Testes e Qualidade
### Executando Testes
```bash
# Testes da biblioteca
cd recoveredperispirit/django/django_searchable_dropdown
python -m pytest tests/ -v
# Testes com cobertura
python -m pytest tests/ --cov=. --cov-report=html
# Testes do test_app
cd test_app
python manage.py test
```
### Status dos Testes
- **110 testes passando** (100% de cobertura)
- **Formulários**: 25 testes
- **Widgets**: 25 testes
- **Utilitários**: 35 testes
- **Integração**: 6 testes
## Demonstração Completa
Para ver todos os widgets em ação, execute o **test_app**:
```bash
cd test_app
python manage.py runserver
```
Acesse: http://localhost:8000
### Páginas de Demonstração
- **Página Inicial**: Visão geral de todas as funcionalidades
- **Produtos**: Dropdowns básicos e com filtros
- **Clientes**: Dropdowns com AJAX
- **Pedidos**: Dropdowns múltiplos
- **AJAX Demo**: Demonstração completa de busca AJAX
- **With Info Demo**: Demonstração de dropdowns com informações
## Licença
Esta biblioteca é distribuída sob a licença MIT.
## Contribuição
Para contribuir:
1. Fork o repositório
2. Crie uma branch para sua feature
3. Faça commit das suas mudanças
4. Push para a branch
5. Abra um Pull Request
## Suporte
Para suporte e dúvidas:
- **Teste primeiro**: Use o test_app para verificar se o problema é específico do seu projeto
- **Consulte a documentação**: Esta documentação e os exemplos no test_app
- **Abra uma issue**: No GitHub com detalhes do problema e ambiente
- **Verifique os logs**: Use o modo debug para identificar problemas
Raw data
{
"_id": null,
"home_page": "https://github.com/novaalvorada/django_searchable_dropdown",
"name": "django-searchable-dropdown",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": "django, dropdown, searchable, select, widget, form, field",
"author": "Marcio Bernardes",
"author_email": "Marcio Bernardes <marciobernardes@live.com>",
"download_url": "https://files.pythonhosted.org/packages/41/bc/252e2ff819b34a5bdbb5e2ba5014b227de6afa1fb4e350886d39fad8f3a4/django_searchable_dropdown-1.0.1.tar.gz",
"platform": null,
"description": "# SearchableDropdown\n\n[](https://badge.fury.io/py/django_searchable_dropdown)\n[](https://www.python.org/downloads/)\n[](https://www.djangoproject.com/)\n[](https://opensource.org/licenses/MIT)\n[](https://github.com/novaalvorada/django_searchable_dropdown)\n[](https://github.com/novaalvorada/django_searchable_dropdown)\n\nUma biblioteca Django completa para criar dropdowns pesquis\u00e1veis e customiz\u00e1veis com funcionalidades avan\u00e7adas.\n\n## Sobre a Biblioteca\n\nO **SearchableDropdown** \u00e9 uma biblioteca Django que oferece componentes de dropdown avan\u00e7ados com funcionalidades de busca em tempo real, integra\u00e7\u00e3o nativa com formul\u00e1rios Django e suporte a m\u00faltiplas sele\u00e7\u00f5es.\n\n### Caracter\u00edsticas Principais\n\n- **Dropdowns Pesquis\u00e1veis**: Busca em tempo real nas op\u00e7\u00f5es\n- **Integra\u00e7\u00e3o Django**: Widgets e campos de formul\u00e1rio nativos\n- **Suporte AJAX**: Busca din\u00e2mica em modelos Django\n- **M\u00faltipla Sele\u00e7\u00e3o**: Suporte a sele\u00e7\u00e3o de m\u00faltiplas op\u00e7\u00f5es\n- **Informa\u00e7\u00f5es Adicionais**: Exibi\u00e7\u00e3o de dados extras sobre op\u00e7\u00f5es selecionadas\n- **Customiz\u00e1vel**: Temas e configura\u00e7\u00f5es flex\u00edveis\n- **Responsivo**: Funciona em dispositivos m\u00f3veis\n- **Acess\u00edvel**: Suporte a navega\u00e7\u00e3o por teclado e leitores de tela\n\n### Status do Projeto\n\n- \u2705 **Vers\u00e3o**: 1.0.0 (Est\u00e1vel)\n- \u2705 **Testes**: 110 testes passando (100% cobertura)\n- \u2705 **Compatibilidade**: Django 2.2+ e Python 3.7+\n- \u2705 **Documenta\u00e7\u00e3o**: Completa com exemplos pr\u00e1ticos\n- \u2705 **Licen\u00e7a**: MIT (Open Source)\n- \u2705 **PyPI**: Dispon\u00edvel para instala\u00e7\u00e3o via pip\n\n## Instala\u00e7\u00e3o\n\n### Op\u00e7\u00e3o 1: Instalar via pip (Recomendado para produ\u00e7\u00e3o)\n\n```bash\npip install django_searchable_dropdown\n```\n\nE ent\u00e3o adicionar ao `INSTALLED_APPS`:\n\n```python\nINSTALLED_APPS = [\n # ... outras apps\n 'recoveredperispirit.django.django_searchable_dropdown',\n]\n```\n\n### Op\u00e7\u00e3o 2: Integrar em Projeto Existente\n\n#### 1. Adicionar ao projeto\n\n```bash\n# Copiar a pasta recoveredperispirit/django/django_searchable_dropdown para seu projeto Django\ncp -r recoveredperispirit/django/django_searchable_dropdown/ /path/to/your/django/project/\n```\n\n#### 2. Configurar settings.py\n\n```python\nINSTALLED_APPS = [\n # ... outras apps\n 'recoveredperispirit.django.django_searchable_dropdown',\n]\n\n# Configura\u00e7\u00f5es opcionais\nSEARCHABLE_DROPDOWN_CONFIG = {\n 'default_placeholder': 'Selecione uma op\u00e7\u00e3o',\n 'default_search_placeholder': 'Digite para buscar...',\n 'default_no_results_text': 'Nenhum resultado encontrado',\n 'ajax_timeout': 5000,\n 'min_search_length': 1,\n 'max_results': 50,\n}\n```\n\n#### 3. Coletar arquivos est\u00e1ticos\n\n```bash\npython manage.py collectstatic\n```\n\n## Guia de Uso - Widgets Dispon\u00edveis\n\n### 1. SearchableDropdownWidget (B\u00e1sico)\n\nO widget b\u00e1sico para dropdowns pesquis\u00e1veis com op\u00e7\u00f5es est\u00e1ticas ou de modelos Django.\n\n#### Exemplo B\u00e1sico\n\n```python\nfrom recoveredperispirit.django.django_searchable_dropdown.widgets import SearchableDropdownWidget\nfrom django import forms\n\nclass CategoriaForm(forms.Form):\n categoria = forms.ModelChoiceField(\n queryset=Categoria.objects.all(),\n widget=SearchableDropdownWidget(\n placeholder=\"Selecione uma categoria\",\n search_placeholder=\"Digite para buscar categorias...\",\n dropdown_type=\"categoria\",\n min_search_length=1,\n max_results=20\n ),\n required=False\n )\n```\n\n#### Como Funciona\n\n1. **Renderiza\u00e7\u00e3o**: O widget cria um dropdown customizado com campo de busca\n2. **Busca**: Usu\u00e1rio digita no campo de busca e as op\u00e7\u00f5es s\u00e3o filtradas em tempo real\n3. **Sele\u00e7\u00e3o**: Usu\u00e1rio clica em uma op\u00e7\u00e3o para selecion\u00e1-la\n4. **Valida\u00e7\u00e3o**: O valor \u00e9 validado pelo Django normalmente\n\n#### Resultado Visual\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Selecione uma categoria \u25bc \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Digite para buscar categorias... \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2713 Tecnologia \u2502\n\u2502 Esportes \u2502\n\u2502 M\u00fasica \u2502\n\u2502 Livros \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n### 2. SearchableDropdownMultipleWidget (M\u00faltipla Sele\u00e7\u00e3o)\n\nWidget para selecionar m\u00faltiplas op\u00e7\u00f5es simultaneamente.\n\n#### Exemplo de Uso\n\n```python\nfrom recoveredperispirit.django.django_searchable_dropdown.widgets import SearchableDropdownMultipleWidget\n\nclass ProdutoForm(forms.Form):\n tags = forms.ModelMultipleChoiceField(\n queryset=Tag.objects.all(),\n widget=SearchableDropdownMultipleWidget(\n placeholder=\"Selecione tags (m\u00e1ximo 5)\",\n search_placeholder=\"Digite para buscar tags...\",\n dropdown_type=\"tags\",\n max_selections=5,\n allow_clear=True\n ),\n required=False\n )\n```\n\n#### Como Funciona\n\n1. **M\u00faltipla Sele\u00e7\u00e3o**: Usu\u00e1rio pode selecionar v\u00e1rias op\u00e7\u00f5es\n2. **Contador**: Mostra quantas op\u00e7\u00f5es foram selecionadas\n3. **Limite**: Pode definir um n\u00famero m\u00e1ximo de sele\u00e7\u00f5es\n4. **Remo\u00e7\u00e3o**: Op\u00e7\u00f5es selecionadas podem ser removidas individualmente\n\n#### Resultado Visual\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u2713 Tecnologia, Esportes (2/5) \u25bc \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Digite para buscar tags... \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 \u2713 Tecnologia [\u00d7] \u2502\n\u2502 \u2713 Esportes [\u00d7] \u2502\n\u2502 M\u00fasica \u2502\n\u2502 Livros \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n### 3. SearchableDropdownAjaxWidget (Busca AJAX)\n\nWidget para busca din\u00e2mica via AJAX em grandes datasets.\n\n#### Exemplo de Uso\n\n```python\nfrom recoveredperispirit.django.django_searchable_dropdown.widgets import SearchableDropdownAjaxWidget\n\nclass ClienteForm(forms.Form):\n cliente = forms.ModelChoiceField(\n queryset=Cliente.objects.none(), # Queryset vazio inicialmente\n widget=SearchableDropdownAjaxWidget(\n placeholder=\"Digite para buscar clientes...\",\n search_placeholder=\"Nome ou email do cliente...\",\n dropdown_type=\"cliente_ajax\",\n ajax_url=\"/api/clientes/search/\",\n min_search_length=2,\n max_results=20,\n delay=300, # Delay em ms antes de fazer a busca\n allow_clear=True\n ),\n required=False\n )\n```\n\n#### Como Funciona\n\n1. **Busca Din\u00e2mica**: Dados s\u00e3o carregados via AJAX conforme o usu\u00e1rio digita\n2. **Delay**: Aguarda o usu\u00e1rio parar de digitar antes de fazer a requisi\u00e7\u00e3o\n3. **Filtros**: Aplica filtros no servidor para melhor performance\n4. **Cache**: Pode implementar cache para melhorar performance\n\n#### Resultado Visual\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Digite para buscar clientes... \u25bc \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 jo\u00e3o... \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 Buscando... \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 Jo\u00e3o Silva (joao@email.com) \u2502\n\u2502 Jo\u00e3o Santos (joao.s@email.com) \u2502\n\u2502 Jo\u00e3o Oliveira (joao.o@email.com) \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n#### API Endpoint Necess\u00e1rio\n\n```python\n# views.py\nfrom django.http import JsonResponse\nfrom django.db.models import Q\n\ndef api_clientes_search(request):\n query = request.GET.get('q', '')\n if len(query) < 2:\n return JsonResponse({'results': []})\n \n clientes = Cliente.objects.filter(\n Q(nome__icontains=query) | Q(email__icontains=query)\n )[:20]\n \n results = [\n {\n 'id': cliente.id,\n 'text': f\"{cliente.nome} ({cliente.email})\"\n }\n for cliente in clientes\n ]\n \n return JsonResponse({'results': results})\n```\n\n### 4. SearchableDropdownWithInfoWidget (Com Informa\u00e7\u00f5es)\n\nWidget que mostra informa\u00e7\u00f5es detalhadas sobre a op\u00e7\u00e3o selecionada.\n\n#### Exemplo de Uso\n\n```python\nfrom recoveredperispirit.django.django_searchable_dropdown.widgets import SearchableDropdownWithInfoWidget\n\nclass ProdutoDetalhadoForm(forms.Form):\n produto = forms.ModelChoiceField(\n queryset=Produto.objects.all(),\n widget=SearchableDropdownWithInfoWidget(\n placeholder=\"Selecione um produto para ver detalhes\",\n search_placeholder=\"Digite para buscar produtos...\",\n dropdown_type=\"produto_info\",\n info_url=\"/api/produtos/info/\",\n info_container_id=\"produto-info-container\"\n ),\n required=False\n )\n```\n\n#### Como Funciona\n\n1. **Sele\u00e7\u00e3o**: Usu\u00e1rio seleciona uma op\u00e7\u00e3o no dropdown\n2. **Carregamento**: Informa\u00e7\u00f5es detalhadas s\u00e3o carregadas via AJAX\n3. **Exibi\u00e7\u00e3o**: Dados s\u00e3o exibidos em um container separado\n4. **Atualiza\u00e7\u00e3o**: Informa\u00e7\u00f5es s\u00e3o atualizadas a cada nova sele\u00e7\u00e3o\n\n#### Resultado Visual\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 iPhone 13 Pro \u25bc \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Digite para buscar produtos... \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 iPhone 13 Pro \u2502\n\u2502 iPhone 12 \u2502\n\u2502 Samsung Galaxy S21 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Informa\u00e7\u00f5es do Produto \u2502\n\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 Nome: iPhone 13 Pro \u2502\n\u2502 C\u00f3digo: PROD001 \u2502\n\u2502 Pre\u00e7o: R$ 8.999,00 \u2502\n\u2502 Estoque: 15 unidades \u2502\n\u2502 Categoria: Smartphones \u2502\n\u2502 Marca: Apple \u2502\n\u2502 Descri\u00e7\u00e3o: iPhone 13 Pro 256GB... \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n#### API Endpoint Necess\u00e1rio\n\n```python\n# views.py\ndef api_produtos_info(request):\n produto_id = request.GET.get('id')\n if not produto_id:\n return JsonResponse({'error': 'ID do produto n\u00e3o fornecido'}, status=400)\n \n try:\n produto = Produto.objects.get(id=produto_id)\n info = {\n 'id': produto.id,\n 'nome': produto.nome,\n 'codigo': produto.codigo,\n 'preco': str(produto.preco),\n 'estoque': produto.estoque,\n 'categoria': produto.categoria.nome,\n 'marca': produto.marca.nome,\n 'descricao': produto.descricao or 'Sem descri\u00e7\u00e3o'\n }\n return JsonResponse(info)\n except Produto.DoesNotExist:\n return JsonResponse({'error': 'Produto n\u00e3o encontrado'}, status=404)\n```\n\n#### Template HTML Necess\u00e1rio\n\n```html\n<!-- Incluir container para informa\u00e7\u00f5es -->\n<div class=\"row\">\n <div class=\"col-md-8\">\n {{ form.produto }}\n </div>\n <div class=\"col-md-4\">\n <div class=\"card\">\n <div class=\"card-header\">\n <h6>Informa\u00e7\u00f5es do Produto</h6>\n </div>\n <div class=\"card-body\" id=\"produto-info-container\">\n <p class=\"text-muted\">Selecione um produto para ver as informa\u00e7\u00f5es</p>\n </div>\n </div>\n </div>\n</div>\n```\n\n## Customiza\u00e7\u00e3o Avan\u00e7ada\n\n### Configura\u00e7\u00f5es por Tipo de Dropdown\n\n```python\n# Em apps.py ou settings.py\nfrom recoveredperispirit.django.django_searchable_dropdown.utils import dropdown_config\n\n# Configurar tipo personalizado\ndropdown_config.register_type('activity', {\n 'placeholder': 'Selecione uma atividade',\n 'search_placeholder': 'Digite o nome da atividade...',\n 'no_results_text': 'Nenhuma atividade encontrada',\n 'min_search_length': 2,\n 'max_results': 20,\n 'allow_clear': True,\n 'allow_create': False,\n})\n\n# Usar em formul\u00e1rios\nclass ActivityForm(forms.Form):\n activity = forms.ModelChoiceField(\n queryset=Activity.objects.all(),\n widget=SearchableDropdownWidget(dropdown_type='activity')\n )\n```\n\n### Estilos CSS Customizados\n\n```css\n/* Tema personalizado */\n.searchable-dropdown.custom-theme {\n --dropdown-bg: #ffffff;\n --dropdown-border: #e0e0e0;\n --dropdown-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n --option-hover-bg: #f8f9fa;\n --option-selected-bg: #007bff;\n --option-selected-color: #ffffff;\n --search-input-bg: #f8f9fa;\n --search-input-border: #dee2e6;\n}\n\n/* Estilos espec\u00edficos para diferentes tipos */\n.searchable-dropdown[data-type=\"produto\"] {\n --option-selected-bg: #28a745;\n}\n\n.searchable-dropdown[data-type=\"cliente\"] {\n --option-selected-bg: #17a2b8;\n}\n```\n\n### JavaScript Customizado\n\n```javascript\n// Eventos customizados\ndocument.addEventListener('dropdown:change', function(e) {\n const dropdown = e.target;\n const value = e.detail.value;\n const text = e.detail.text;\n \n console.log('Dropdown alterado:', { value, text });\n \n // L\u00f3gica customizada aqui\n if (dropdown.getAttribute('data-type') === 'produto') {\n updateProductInfo(value);\n }\n});\n\n// Inicializa\u00e7\u00e3o customizada\nconst customDropdown = new SearchableDropdown(element, {\n placeholder: 'Selecione uma op\u00e7\u00e3o',\n searchPlaceholder: 'Digite para buscar...',\n onSelect: function(value, text) {\n console.log('Op\u00e7\u00e3o selecionada:', value, text);\n },\n onSearch: function(query) {\n console.log('Buscando:', query);\n }\n});\n```\n\n## Exemplos Pr\u00e1ticos Completos\n\n### Exemplo 1: Sistema de Agendamento\n\n```python\n# models.py\nclass Activity(models.Model):\n name = models.CharField(max_length=100)\n description = models.TextField()\n duration = models.IntegerField(help_text=\"Dura\u00e7\u00e3o em minutos\")\n max_participants = models.IntegerField()\n \n def __str__(self):\n return self.name\n\nclass Schedule(models.Model):\n activity = models.ForeignKey(Activity, on_delete=models.CASCADE)\n date = models.DateField()\n time = models.TimeField()\n participants = models.ManyToManyField('User', blank=True)\n \n def __str__(self):\n return f\"{self.activity.name} - {self.date} {self.time}\"\n\n# forms.py\nclass ScheduleForm(forms.ModelForm):\n activity = forms.ModelChoiceField(\n queryset=Activity.objects.all(),\n widget=SearchableDropdownWithInfoWidget(\n dropdown_type='activity',\n info_url='/api/activities/info/',\n info_container_id='activity-info',\n placeholder='Selecione uma atividade'\n )\n )\n \n participants = forms.ModelMultipleChoiceField(\n queryset=User.objects.all(),\n widget=SearchableDropdownMultipleWidget(\n dropdown_type='users',\n placeholder='Selecione participantes',\n max_selections=10\n ),\n required=False\n )\n \n class Meta:\n model = Schedule\n fields = ['activity', 'date', 'time', 'participants']\n\n# views.py\ndef api_activities_info(request):\n activity_id = request.GET.get('id')\n try:\n activity = Activity.objects.get(id=activity_id)\n return JsonResponse({\n 'name': activity.name,\n 'description': activity.description,\n 'duration': f\"{activity.duration} minutos\",\n 'max_participants': activity.max_participants\n })\n except Activity.DoesNotExist:\n return JsonResponse({'error': 'Atividade n\u00e3o encontrada'}, status=404)\n```\n\n### Exemplo 2: E-commerce com Produtos\n\n```python\n# models.py\nclass Category(models.Model):\n name = models.CharField(max_length=100)\n description = models.TextField()\n \n def __str__(self):\n return self.name\n\nclass Product(models.Model):\n name = models.CharField(max_length=200)\n category = models.ForeignKey(Category, on_delete=models.CASCADE)\n price = models.DecimalField(max_digits=10, decimal_places=2)\n stock = models.IntegerField()\n description = models.TextField()\n tags = models.ManyToManyField('Tag', blank=True)\n \n def __str__(self):\n return self.name\n\n# forms.py\nclass ProductSearchForm(forms.Form):\n category = forms.ModelChoiceField(\n queryset=Category.objects.all(),\n widget=SearchableDropdownWidget(\n dropdown_type='category',\n placeholder='Todas as categorias',\n allow_clear=True\n ),\n required=False\n )\n \n products = forms.ModelMultipleChoiceField(\n queryset=Product.objects.all(),\n widget=SearchableDropdownMultipleWidget(\n dropdown_type='products',\n placeholder='Selecione produtos para comparar',\n max_selections=5\n ),\n required=False\n )\n \n featured_product = forms.ModelChoiceField(\n queryset=Product.objects.all(),\n widget=SearchableDropdownWithInfoWidget(\n dropdown_type='product_info',\n info_url='/api/products/info/',\n info_container_id='product-details',\n placeholder='Selecione um produto para ver detalhes'\n ),\n required=False\n )\n```\n\n## Configura\u00e7\u00f5es Avan\u00e7adas\n\n### Configura\u00e7\u00f5es Globais\n\n```python\n# settings.py\nSEARCHABLE_DROPDOWN_CONFIG = {\n # Configura\u00e7\u00f5es padr\u00e3o\n 'default_placeholder': 'Selecione uma op\u00e7\u00e3o',\n 'default_search_placeholder': 'Digite para buscar...',\n 'default_no_results_text': 'Nenhum resultado encontrado',\n \n # Configura\u00e7\u00f5es de performance\n 'ajax_timeout': 5000,\n 'min_search_length': 1,\n 'max_results': 50,\n \n # Configura\u00e7\u00f5es de UI\n 'allow_clear': True,\n 'allow_create': False,\n 'delay': 300,\n \n # Configura\u00e7\u00f5es de acessibilidade\n 'aria_label': 'Dropdown pesquis\u00e1vel',\n 'aria_describedby': 'dropdown-help',\n}\n```\n\n### Configura\u00e7\u00f5es por Ambiente\n\n```python\n# settings/development.py\nSEARCHABLE_DROPDOWN_CONFIG = {\n 'debug': True,\n 'ajax_timeout': 10000,\n 'delay': 500,\n}\n\n# settings/production.py\nSEARCHABLE_DROPDOWN_CONFIG = {\n 'debug': False,\n 'ajax_timeout': 3000,\n 'delay': 200,\n 'cache_results': True,\n}\n```\n\n## Deploy e Performance\n\n### Otimiza\u00e7\u00f5es para Produ\u00e7\u00e3o\n\n```python\n# settings.py\nSEARCHABLE_DROPDOWN_CONFIG = {\n 'cache_results': True,\n 'cache_timeout': 300, # 5 minutos\n 'ajax_timeout': 3000,\n 'max_results': 20,\n 'min_search_length': 2,\n}\n\n# views.py com cache\nfrom django.core.cache import cache\n\ndef api_search_view(request):\n query = request.GET.get('q', '')\n cache_key = f\"search_{query}\"\n \n # Verificar cache\n cached_results = cache.get(cache_key)\n if cached_results:\n return JsonResponse(cached_results)\n \n # Buscar dados\n results = perform_search(query)\n \n # Salvar no cache\n cache.set(cache_key, results, 300)\n \n return JsonResponse(results)\n```\n\n## Troubleshooting\n\n### Problemas Comuns e Solu\u00e7\u00f5es\n\n#### 1. Dropdown n\u00e3o abre\n```javascript\n// Verificar se os scripts foram carregados\nconsole.log('SearchableDropdown:', typeof SearchableDropdown);\nconsole.log('SearchableDropdownUtils:', typeof SearchableDropdownUtils);\n\n// Verificar se o elemento existe\nconst dropdown = document.querySelector('.searchable-dropdown');\nconsole.log('Dropdown element:', dropdown);\n```\n\n#### 2. Busca AJAX n\u00e3o funciona\n```python\n# Verificar se a view retorna JSON v\u00e1lido\ndef api_search_view(request):\n try:\n # Sua l\u00f3gica aqui\n return JsonResponse({'results': results})\n except Exception as e:\n return JsonResponse({'error': str(e)}, status=500)\n```\n\n#### 3. Estilos n\u00e3o aplicados\n```css\n/* For\u00e7ar estilos com !important se necess\u00e1rio */\n.searchable-dropdown {\n display: block !important;\n position: relative !important;\n z-index: 1000 !important;\n}\n```\n\n### Debug Avan\u00e7ado\n\n```javascript\n// Habilitar modo debug\nSearchableDropdownUtils.setDebugMode(true);\n\n// Verificar dropdowns inicializados\nconst dropdowns = SearchableDropdownUtils.getInitializedDropdowns();\nconsole.log('Dropdowns inicializados:', dropdowns);\n\n// Verificar configura\u00e7\u00f5es\nconst configs = SearchableDropdownUtils.getConfigs();\nconsole.log('Configura\u00e7\u00f5es:', configs);\n```\n\n## Testes e Qualidade\n\n### Executando Testes\n\n```bash\n# Testes da biblioteca\ncd recoveredperispirit/django/django_searchable_dropdown\npython -m pytest tests/ -v\n\n# Testes com cobertura\npython -m pytest tests/ --cov=. --cov-report=html\n\n# Testes do test_app\ncd test_app\npython manage.py test\n```\n\n### Status dos Testes\n\n- **110 testes passando** (100% de cobertura)\n- **Formul\u00e1rios**: 25 testes\n- **Widgets**: 25 testes \n- **Utilit\u00e1rios**: 35 testes\n- **Integra\u00e7\u00e3o**: 6 testes\n\n## Demonstra\u00e7\u00e3o Completa\n\nPara ver todos os widgets em a\u00e7\u00e3o, execute o **test_app**:\n\n```bash\ncd test_app\npython manage.py runserver\n```\n\nAcesse: http://localhost:8000\n\n### P\u00e1ginas de Demonstra\u00e7\u00e3o\n\n- **P\u00e1gina Inicial**: Vis\u00e3o geral de todas as funcionalidades\n- **Produtos**: Dropdowns b\u00e1sicos e com filtros\n- **Clientes**: Dropdowns com AJAX\n- **Pedidos**: Dropdowns m\u00faltiplos\n- **AJAX Demo**: Demonstra\u00e7\u00e3o completa de busca AJAX\n- **With Info Demo**: Demonstra\u00e7\u00e3o de dropdowns com informa\u00e7\u00f5es\n\n## Licen\u00e7a\n\nEsta biblioteca \u00e9 distribu\u00edda sob a licen\u00e7a MIT.\n\n## Contribui\u00e7\u00e3o\n\nPara contribuir:\n\n1. Fork o reposit\u00f3rio\n2. Crie uma branch para sua feature\n3. Fa\u00e7a commit das suas mudan\u00e7as\n4. Push para a branch\n5. Abra um Pull Request\n\n## Suporte\n\nPara suporte e d\u00favidas:\n\n- **Teste primeiro**: Use o test_app para verificar se o problema \u00e9 espec\u00edfico do seu projeto\n- **Consulte a documenta\u00e7\u00e3o**: Esta documenta\u00e7\u00e3o e os exemplos no test_app\n- **Abra uma issue**: No GitHub com detalhes do problema e ambiente\n- **Verifique os logs**: Use o modo debug para identificar problemas\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Biblioteca Django para criar dropdowns pesquis\u00e1veis e customiz\u00e1veis",
"version": "1.0.1",
"project_urls": {
"Documentation": "https://django_searchable_dropdown.readthedocs.io/",
"Homepage": "https://github.com/novaalvorada/django_searchable_dropdown",
"Issues": "https://github.com/novaalvorada/django_searchable_dropdown/issues",
"Repository": "https://github.com/novaalvorada/django_searchable_dropdown"
},
"split_keywords": [
"django",
" dropdown",
" searchable",
" select",
" widget",
" form",
" field"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "f018d3cd5b495919fcacc076ca1af7caaf35bc8d23db75d24186a262ae44f685",
"md5": "37f239b255376df6fed05f4b7ffaa4dd",
"sha256": "ad43fcefa836ade421c2e3075cd9923f749a93acdc9acf5070679394b0ef1597"
},
"downloads": -1,
"filename": "django_searchable_dropdown-1.0.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "37f239b255376df6fed05f4b7ffaa4dd",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 54414,
"upload_time": "2025-08-18T05:10:32",
"upload_time_iso_8601": "2025-08-18T05:10:32.628948Z",
"url": "https://files.pythonhosted.org/packages/f0/18/d3cd5b495919fcacc076ca1af7caaf35bc8d23db75d24186a262ae44f685/django_searchable_dropdown-1.0.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "41bc252e2ff819b34a5bdbb5e2ba5014b227de6afa1fb4e350886d39fad8f3a4",
"md5": "157184904d30bc65f30d3092960d7375",
"sha256": "ff63d3f38bd11791943a3678fecdcc6e85017a444707a1f2da3a2eeebfb707dc"
},
"downloads": -1,
"filename": "django_searchable_dropdown-1.0.1.tar.gz",
"has_sig": false,
"md5_digest": "157184904d30bc65f30d3092960d7375",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 47033,
"upload_time": "2025-08-18T05:10:34",
"upload_time_iso_8601": "2025-08-18T05:10:34.266401Z",
"url": "https://files.pythonhosted.org/packages/41/bc/252e2ff819b34a5bdbb5e2ba5014b227de6afa1fb4e350886d39fad8f3a4/django_searchable_dropdown-1.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-18 05:10:34",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "novaalvorada",
"github_project": "django_searchable_dropdown",
"github_not_found": true,
"lcname": "django-searchable-dropdown"
}