Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/models/categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ def save(self,*args,**kwargs):
self.code = generate_category_code()
super().save(*args,**kwargs)

@classmethod
def get_root_categories(cls):
"""Custom method to get root categories"""
return cls.objects.filter(parent__isnull=True)


37 changes: 33 additions & 4 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,45 @@ def create(self, validated_data):
return Customer.objects.create(**validated_data)

class CategorySerializer(serializers.ModelSerializer):
code = serializers.CharField(read_only=True)
children = serializers.SerializerMethodField()
products_count = serializers.IntegerField(read_only=True)
class Meta:
model = Category
fields = ['id','code', 'title', 'parent']
fields = ['id', 'code', 'title', 'parent', 'children', 'products_count']

def get_children(self, obj):
# Limit recursion depth
if self.context.get('depth', 0) >= 2:
return []

# Create a new context with increased depth
context = dict(self.context)
context['depth'] = context.get('depth', 0) + 1

# Get direct children
children = obj.get_children()
return CategorySerializer(children, many=True, context=context).data


class ProductSerializer(serializers.ModelSerializer):
category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all())
category = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
write_only=True
)
category_details = serializers.SerializerMethodField(read_only=True)

class Meta:
model = Product
fields = ['id','name', 'description', 'price', 'category']
fields = ['id', 'name', 'description', 'price', 'category','category_details']

def get_category_details(self, obj):
# Prevent recursive serialization
return {
'id': obj.category.id,
'code': obj.category.code,
'title': obj.category.title
}


class OrderItemSerializer(serializers.ModelSerializer):
price = serializers.ReadOnlyField(source='product.price')
Expand Down
90 changes: 83 additions & 7 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rest_framework import viewsets, status
from rest_framework.permissions import IsAuthenticated,AllowAny
from rest_framework.response import Response
from django.db.models import Avg
from django.db.models import Avg, Count
from django.db.utils import OperationalError

from api.auth import Auth0Authentication
Expand All @@ -23,22 +23,68 @@ class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
permission_classes = [AllowAny]

@action(detail=False, methods=['get'])
def by_category(self, request):
"""
Get products filtered by category, including products in subcategories
"""
category_id = request.query_params.get('category_id')
if not category_id:
return Response(
{"error": "Category ID is required"},
status=status.HTTP_400_BAD_REQUEST
)

try:
category = Category.objects.get(id=category_id)

subcategories = category.get_descendants(include_self=True)

products = Product.objects.filter(category__in=subcategories)

serializer = self.get_serializer(products, many=True)
return Response(serializer.data)

except Category.DoesNotExist:
return Response(
{"error": "Category not found"},
status=status.HTTP_404_NOT_FOUND
)

@action(detail=False, methods=['get'])
def average_price(self, request):
"""
Calculate average price for a category and its subcategories
"""
category_id = request.query_params.get('category_id')
if not category_id:
return Response({"error": "Category ID is required"},
status=status.HTTP_400_BAD_REQUEST)
try:
category = Category.objects.get(id=category_id)
subcategories = category.get_descendants(include_self=True)

avg_price = Product.objects.filter(
category__in=subcategories
).aggregate(avg_price=Avg('price'))['avg_price']

return Response({
'category_id': category_id,
'category_title': category.title,
'average_price': avg_price or 0,
'total_products': Product.objects.filter(category__in=subcategories).count()
})

except Category.DoesNotExist:
return Response({"error": "Category not found"}, status=status.HTTP_404_NOT_FOUND)

subcategories = category.get_descendants(include_self=True)
avg_price = Product.objects.filter(category__in=subcategories).aggregate(avg_price=Avg('price'))['avg_price']

return Response({'category_id': category_id, 'average_price': avg_price or 0})

except Exception as e:
return Response(
{"error": str(e)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)



class OrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all().prefetch_related('items__product')
Expand Down Expand Up @@ -79,6 +125,36 @@ class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer

def get_queryset(self):

return Category.objects.annotate(
products_count=Count('product', distinct=True)
)

@action(detail=False, methods=['get'])
def root_categories(self, request):
"""
Retrieve all root-level categories
"""
root_categories = Category.get_root_categories()
serializer = self.get_serializer(root_categories, many=True)
return Response(serializer.data)

@action(detail=True, methods=['get'])
def hierarchy(self, request, pk=None):
"""
Retrieve the full category hierarchy starting from a specific category
"""
try:
category = self.get_object()
context = {'depth': 0}
serializer = self.get_serializer(category, context=context)
return Response(serializer.data)
except Category.DoesNotExist:
return Response(
{"error": "Category not found"},
status=status.HTTP_404_NOT_FOUND
)

class HealthCheckView(viewsets.ViewSet):
"""
Expand All @@ -95,7 +171,7 @@ def get(self, request):
'message': 'Application is running and database is accessible'
})

#convert this to class

def health_check(request):
"""
Health check endpoint to verify application and database connectivity
Expand Down