I wanna be able to search a product with the category that isnt directly linked to it but the category is grandfather or great_great grandfather and so on above on the category tree of the category that is actually linked to the product itself. The only way i could implement was with the following which is ofcourse not scalable and proper way to do it:
def get_queryset(self, request):
queryset = super().get_queryset(request).select_related('brand', 'category', 'category__parent')
category_name = request.GET.get('category__name', None)
if category_name:
# Filter the queryset to include products with the specified category or its ancestors
queryset = queryset.filter(
Q(category__name=category_name) |
Q(category__parent__name=category_name) |
Q(category__parent__parent__name=category_name) |
# Add more levels as needed based on the depth of your category tree
)
return queryset
And here are the code as of now:
Product``models.py
class Product(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(blank=True, null=True)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
image = models.ImageField(upload_to='product_images/')
category = models.ForeignKey(Category, on_delete=models.SET_NULL,related_name='product_category', null=True, blank=True)
brand = models.ForeignKey(Brand, on_delete=models.SET_NULL, related_name='product_brand', null=True, blank=True)
tags = GenericRelation(TaggedItem, related_query_name='product')
ratings = GenericRelation(Rating, related_query_name='product')
active = models.BooleanField(default=True)
timestamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
state = models.CharField(max_length=2, choices=PublishStateOptions.choices, default=PublishStateOptions.DRAFT)
publish_timestamp = models.DateTimeField(
# means that the publish_timestamp field will not automatically be set to the current date and time when a new instance of this model is created.
auto_now_add=False,
# means that the publish_timestamp field will not automatically be updated to the current date and time every time the model is saved.
auto_now=False,
blank=True,
null=True
)
def __str__(self):
return self.name
Category``models.py
class Category(models.Model):
name = models.CharField(max_length=255)
parent = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='subcategories', null=True, blank=True)
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
Product``admin.py
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'brand', 'category']
inlines = [TaggedItemInline]
list_filter = [
'brand__name',
'category__name'
]
search_fields = ['name', 'price', 'brand__name', 'category__name', 'category__parent__name']
def get_queryset(self, request):
return super().get_queryset(request).select_related('brand', 'category', 'category__parent')
def get_brand_name(self, obj):
return obj.brand.name if obj.brand else None
def get_category_name(self, obj):
return obj.category.name if obj.category else None
get_brand_name.short_description = 'Brand'
get_category_name.short_description = 'Category'
# Define the labels for list filters
list_filter = (
('brand', admin.RelatedOnlyFieldListFilter),
('category', admin.RelatedOnlyFieldListFilter),
)
admin.site.register(Product, ProductAdmin)
After an hour, I figured it out.
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
# Search for products based on ascendant category names recursively
matching_categories = self.find_matching_categories(search_term)
ascendant_categories = self.get_all_ascendant_categories(matching_categories)
products_with_ascendant_categories = self.model.objects.filter(category__in=ascendant_categories)
queryset |= products_with_ascendant_categories
return queryset, use_distinct
def find_matching_categories(self, search_term):
# Find categories that match the search term
return Category.objects.filter(name__icontains=search_term)
def get_all_ascendant_categories(self, categories):
# Recursively retrieve all ascendant categories
ascendant_categories = list(categories)
new_ascendants = Category.objects.filter(parent__in=categories)
if new_ascendants:
ascendant_categories.extend(self.get_all_ascendant_categories(new_ascendants))
return ascendant_categories
When users search for products, they can now use the names of the product's category's ascendants. The get_search_results() method is customized to achieve this. When a user searches, the code identifies categories that match the search term and then recursively collects all ancestors of these categories. This ensures that products from categories and their ascendants are included in the search results.