Problema Estou com dificuldade na implementação de um sistema de filtros em um catálogo de e-commerce com Django + AJAX + HTML (Jinja2). Os principais problemas são: Filtro por tamanho funciona mesmo em produtos que não possuem atributo de tamanho. Filtros cruzados (tipo + tamanho + ordenação) às vezes retornam errado ou nenhum item. Botão de reset nem sempre dá display: flex corretamente nos produtos. Alguns produtos aparecem sem corresponder aos filtros aplicados. Estrutura dos Arquivos models.py class Product(models.Model): name = models.CharField(max_length=100) description = models.TextField(max_length=200) price = models.DecimalField(max_digits=10, decimal_places=2) weight = models.FloatField() durability = models.IntegerField() image = models.ImageField(upload_to='products/') rating = models.IntegerField(choices=[(i, f'{i} Estrela(s)') for i in range(1, 6)], default=5) sales = models.IntegerField(default=0) class SizeMixin(models.Model): class Meta: abstract = True @property def tamanhos_disponiveis(self): if hasattr(self, 'product'): return ItemStock.objects.filter(product=self.product).values_list('size', flat=True) return [] class Katana(models.Model): product = models.OneToOneField(Product, on_delete=models.CASCADE, null=True, blank=True) image = models.ImageField(upload_to='products/swords/') types = models.CharField(max_length=10, choices=[('simples', 'Espada Simples'), ('lendarias', 'Espadas Lendárias')]) speed = models.IntegerField() damage = models.IntegerField() class Haori(SizeMixin, models.Model): product = models.OneToOneField(Product, on_delete=models.CASCADE, null=True, blank=True) size = models.CharField(max_length=10, choices=[('P', 'P'), ('M', 'M'), ('G', 'G'), ('GG', 'GG')]) image = models.ImageField(upload_to='products/haoris/') style = models.IntegerField() mesh = models.IntegerField() **utils.py** from .models import * def products_filter(request, tipo): models_map = { 'katanas': Katana, 'haoris': Haori, 'armors': Armor, 'kabutos': Kabuto, 'masks': Mask, 'kunais': Kunai, 'shurikens': Shuriken, } models_with_size = ['haoris','armors','kabutos','masks'] empty = False queryset = models_map[tipo].objects.select_related('product').all() if tipo else None if not queryset: empty = True return { 'tipo': tipo, 'products': queryset, 'empty': empty, } search = request.GET.get('search') if search: queryset = queryset.filter(product__name__icontains=search) if not queryset.exists(): empty = True allowed_filters = ['top-rated', 'price-asc', 'price-desc', 'best-selling'] sort_by_filter = request.GET.get('sort_by_filter') if sort_by_filter and sort_by_filter in allowed_filters: if sort_by_filter == 'top-rated': queryset = queryset.order_by('-product__rating') elif sort_by_filter == 'price-asc': queryset = queryset.order_by('product__price') elif sort_by_filter == 'price-desc': queryset = queryset.order_by('-product__price') elif sort_by_filter == 'best-selling': queryset = queryset.order_by('-product__sales') size = request.GET.get('size') if size and tipo in models_with_size: queryset = queryset.filter(product__item_stocks__size=size).distinct() if not queryset.exists(): empty = True price_range = request.GET.get('range') try: if price_range and price_range != '10000': price = float(price_range) queryset = queryset.filter(product__price__lte=price) if not queryset.exists(): empty = True else: queryset = queryset.filter(product__price__lte=10000) except ValueError: pass return { 'tipo': tipo, 'products': queryset, 'empty': empty, } **viwes.py** def catalog_filtered(request, tipo): result = products_filter(request, tipo) template_name = f'partials/{tipo[:-1]}.html' html = render_to_string(template_name, {'products': result['products'], 'empty': result['empty']}, request=request) return JsonResponse({'html': html}) def catalog(request): tipos = ['katanas', 'shurikens', 'kunais', 'armors', 'haoris', 'kabutos', 'masks'] result = {} total = 0 for tipo in tipos: data = products_filter(request, tipo) result[tipo] = data['products'] total += data['products'].count() result['empty'] = total == 0 return render(request, 'catalog.html', result) **urls.py** path('catalog/ajax/<str:tipo>', catalog_filtered, name='catalog_filtered'), path('catalog/', catalog, name='catalog'), **catalog.html(trecho)** <form method="get" id="filters-form"> <!-- filtros: search, tipo, sort_by, size, range --> <button type="submit">Aplicar Filtros</button> <button type="reset">Limpar Filtros</button> </form> {% if katanas %}<div class="katanas-container">{% include 'partials/katana.html' %}</div>{% endif %} {% if haoris %}<div class="haoris-container">{% include 'partials/haori.html' %}</div>{% endif %} <!-- demais categorias idem --> **partials/haori.html (exemplo de renderização)** {% for haori in products %} <div>{{ haori.product.name }} - R${{ haori.product.price }}</div> {% empty %} <p>Nenhum produto encontrado.</p> {% endfor %} **JS (ajax.js) - filtro dinâmico** formFilter.addEventListener('submit', (e) => { e.preventDefault(); const formData = new FormData(formFilter); const selectedType = formData.get('sort_by_product'); const tiposFilters = selectedType && tipos.includes(selectedType) ? [selectedType] : tipos; tipos.forEach(tipo => { const container = document.querySelector(`.${tipo}-container`); if (!container) return; container.style.display = !selectedType || selectedType === tipo ? 'flex' : 'none'; }); tiposFilters.forEach(tipo => { const tipoFormData = new FormData(formFilter); tipoFormData.delete('sort_by_product'); const queryString = new URLSearchParams(tipoFormData).toString(); fetch(`/catalog/ajax/${tipo}?${queryString}`) .then(res => res.json()) .then(data => { const container = document.querySelector(`.${tipo}-container`); if (!container) { console.log(`Container não encontrado: .${tipo}-container`); return }; console.log(`Html recebido:`,data.html) container.innerHTML = data.html; lucide.createIcons(); setupQuantityListeners(); }) .catch(error => { console.error(`Erro ao carregar ${tipo}:`, error); }); }); }); formFilter.addEventListener('reset', (e) => { e.preventDefault(); formFilter.reset(); suggestionsList.innerHTML = ''; tipos.forEach(tipo => { const container = document.querySelector(`.${tipo}-container`); if (!container)return; if (!selectedType) { container.style.display = 'flex'; } else { container.style.display = tipo === selectedType ? 'flex' : 'none'; } fetch(`/catalog/ajax/${tipo}`) .then(res => res.json()) .then(data => { container.innerHTML = data.html; lucide.createIcons(); setupQuantityListeners(); }) .catch(error => { console.error(`Erro ao resetar ${tipo}:`, error); }); }); }); **Ajuda que preciso** Como garantir que o filtro por size só funcione quando o tipo tiver esse atributo? Como limpar os filtros e restaurar corretamente o display de todos os produtos? Como garantir que queryset seja consistente nos filtros cruzados? Alguma sugestão para simplificar ou reestruturar esse sistema de filtros? Agradeço demais quem puder dar uma luz. Estou nesse projeto faz semanas e estou quase desistindo. Qualquer ajuda técnica ou dica de estrutura é bem-vinda. Continue reading...