Skip to content

Analytics API Reference

analytics.serializers

Serializers for the analytics application.

Provides data structures for financial reports, budget alerts, dashboard summaries, and categorical spending breakdowns.

BudgetAlertSerializer

Bases: ModelSerializer

Serializer for budget category limit alerts.

Computes visual cues like progress percentage, status color (green/orange/red), and human-readable alert messages.

Source code in analytics/serializers.py
class BudgetAlertSerializer(serializers.ModelSerializer):
    """
    Serializer for budget category limit alerts.

    Computes visual cues like progress percentage, status color (green/orange/red),
    and human-readable alert messages.
    """

    category_name = serializers.CharField(source='category.name', read_only=True)
    progress_percentage = serializers.SerializerMethodField()
    status_color = serializers.SerializerMethodField()
    alert_message = serializers.SerializerMethodField()

    class Meta:
        """Metadata for BudgetAlertSerializer."""
        model = BudgetCategoryLimit
        fields = ['category_name', 'limit', 'spent', 'progress_percentage', 'status_color', 'alert_message']

    def get_progress_percentage(self, obj):
        """
        Calculate spending as a percentage of the limit.

        Args:
            obj (BudgetCategoryLimit): The limit instance.

        Returns:
            float: Percentage value rounded to 2 decimal places.
        """
        if obj.limit > 0:
            return round((obj.spent / obj.limit) * 100, 2)
        return 0

    def get_status_color(self, obj):
        """
        Determine the visual status color based on consumption.

        - 100%+ -> red
        - 90%-100% -> orange
        - <90% -> green

        Returns:
            str: Color code.
        """
        percentage = self.get_progress_percentage(obj)
        if percentage >= 100:
            return 'red'
        elif percentage >= 90:
            return 'orange'
        return 'green'

    def get_alert_message(self, obj):
        """
        Generate a human-readable notification message for the budget status.

        Returns:
            str: Warning message or None.
        """
        percentage = self.get_progress_percentage(obj)
        if percentage >= 100:
            diff = obj.spent - obj.limit
            return (
                f"Budget Exceeded — {obj.category.name}! "
                f"You've exceeded your {obj.limit} budget by {diff}."
            )
        elif percentage >= 90:
            return (
                f"Budget Alert — {obj.category.name}: "
                f"You've used {percentage:.1f}% of your budget."
            )
        return None

Meta

Metadata for BudgetAlertSerializer.

Source code in analytics/serializers.py
class Meta:
    """Metadata for BudgetAlertSerializer."""
    model = BudgetCategoryLimit
    fields = ['category_name', 'limit', 'spent', 'progress_percentage', 'status_color', 'alert_message']

get_alert_message(obj)

Generate a human-readable notification message for the budget status.

Returns:

Name Type Description
str

Warning message or None.

Source code in analytics/serializers.py
def get_alert_message(self, obj):
    """
    Generate a human-readable notification message for the budget status.

    Returns:
        str: Warning message or None.
    """
    percentage = self.get_progress_percentage(obj)
    if percentage >= 100:
        diff = obj.spent - obj.limit
        return (
            f"Budget Exceeded — {obj.category.name}! "
            f"You've exceeded your {obj.limit} budget by {diff}."
        )
    elif percentage >= 90:
        return (
            f"Budget Alert — {obj.category.name}: "
            f"You've used {percentage:.1f}% of your budget."
        )
    return None

get_progress_percentage(obj)

Calculate spending as a percentage of the limit.

Parameters:

Name Type Description Default
obj BudgetCategoryLimit

The limit instance.

required

Returns:

Name Type Description
float

Percentage value rounded to 2 decimal places.

Source code in analytics/serializers.py
def get_progress_percentage(self, obj):
    """
    Calculate spending as a percentage of the limit.

    Args:
        obj (BudgetCategoryLimit): The limit instance.

    Returns:
        float: Percentage value rounded to 2 decimal places.
    """
    if obj.limit > 0:
        return round((obj.spent / obj.limit) * 100, 2)
    return 0

get_status_color(obj)

Determine the visual status color based on consumption.

  • 100%+ -> red
  • 90%-100% -> orange
  • <90% -> green

Returns:

Name Type Description
str

Color code.

Source code in analytics/serializers.py
def get_status_color(self, obj):
    """
    Determine the visual status color based on consumption.

    - 100%+ -> red
    - 90%-100% -> orange
    - <90% -> green

    Returns:
        str: Color code.
    """
    percentage = self.get_progress_percentage(obj)
    if percentage >= 100:
        return 'red'
    elif percentage >= 90:
        return 'orange'
    return 'green'

CategorySpendingSerializer

Bases: Serializer

Data structure for category-level spending breakdown.

Used primarily for pie charts and data visualization.

Source code in analytics/serializers.py
class CategorySpendingSerializer(serializers.Serializer):
    """
    Data structure for category-level spending breakdown.

    Used primarily for pie charts and data visualization.
    """

    category = serializers.CharField()
    total_spent = serializers.DecimalField(max_digits=10, decimal_places=2)
    percentage = serializers.FloatField()

DashboardSummarySerializer

Bases: Serializer

Serializer for the main dashboard home page.

Aggregates balance, monthly income/expenses, and lists recent transactions and budget warnings.

Source code in analytics/serializers.py
class DashboardSummarySerializer(serializers.Serializer):
    """
    Serializer for the main dashboard home page.

    Aggregates balance, monthly income/expenses, and lists recent transactions
    and budget warnings.
    """

    total_balance = serializers.DecimalField(max_digits=12, decimal_places=2)
    monthly_income = serializers.DecimalField(max_digits=12, decimal_places=2)
    monthly_expenses = serializers.DecimalField(max_digits=12, decimal_places=2)
    recent_transactions = serializers.SerializerMethodField()
    budget_warnings = serializers.SerializerMethodField()

    def get_recent_transactions(self, obj):
        """
        Fetch the 5 most recent transactions for the user.

        Args:
            obj (dict): Context object containing 'user'.

        Returns:
            list: List of serialized transactions.
        """
        txs = Transaction.objects.filter(user=obj['user']).order_by('-date')[:5]
        from finance.serializers import TransactionSerializer
        return TransactionSerializer(txs, many=True).data

    def get_budget_warnings(self, obj):
        """
        Identify budgets that are at or near their limit.

        Returns:
            list: List of warning strings.
        """
        warnings = BudgetCategoryLimit.objects.filter(
            budget__user=obj['user'],
        ).select_related('category')
        result = []
        for w in warnings:
            if w.limit > 0 and w.spent >= w.limit * 1:
                result.append(f"Warning: {w.category.name} budget exceeded!")
            elif w.limit > 0 and w.spent >= w.limit * Decimal('0.9'):
                result.append(f"Warning: {w.category.name} is near its limit!")
        return result

get_budget_warnings(obj)

Identify budgets that are at or near their limit.

Returns:

Name Type Description
list

List of warning strings.

Source code in analytics/serializers.py
def get_budget_warnings(self, obj):
    """
    Identify budgets that are at or near their limit.

    Returns:
        list: List of warning strings.
    """
    warnings = BudgetCategoryLimit.objects.filter(
        budget__user=obj['user'],
    ).select_related('category')
    result = []
    for w in warnings:
        if w.limit > 0 and w.spent >= w.limit * 1:
            result.append(f"Warning: {w.category.name} budget exceeded!")
        elif w.limit > 0 and w.spent >= w.limit * Decimal('0.9'):
            result.append(f"Warning: {w.category.name} is near its limit!")
    return result

get_recent_transactions(obj)

Fetch the 5 most recent transactions for the user.

Parameters:

Name Type Description Default
obj dict

Context object containing 'user'.

required

Returns:

Name Type Description
list

List of serialized transactions.

Source code in analytics/serializers.py
def get_recent_transactions(self, obj):
    """
    Fetch the 5 most recent transactions for the user.

    Args:
        obj (dict): Context object containing 'user'.

    Returns:
        list: List of serialized transactions.
    """
    txs = Transaction.objects.filter(user=obj['user']).order_by('-date')[:5]
    from finance.serializers import TransactionSerializer
    return TransactionSerializer(txs, many=True).data

TransactionReportSerializer

Bases: ModelSerializer

Simplified serializer for transaction data within analytical reports.

Source code in analytics/serializers.py
class TransactionReportSerializer(serializers.ModelSerializer):
    """
    Simplified serializer for transaction data within analytical reports.
    """

    category_name = serializers.ReadOnlyField(source='category.name')

    class Meta:
        """Metadata for TransactionReportSerializer."""
        model = Transaction
        fields = ['id', 'date', 'amount', 'type', 'category_name', 'description']

Meta

Metadata for TransactionReportSerializer.

Source code in analytics/serializers.py
class Meta:
    """Metadata for TransactionReportSerializer."""
    model = Transaction
    fields = ['id', 'date', 'amount', 'type', 'category_name', 'description']

analytics.views

Views for the analytics application.

Provides endpoints for tracking budget status alerts, generating spending reports with charts, and retrieving dashboard summaries.

BudgetStatusView

Bases: APIView

Retrieves the status of all budget category limits for the current month.

Returns a list of alerts including spending progress and warning messages.

Source code in analytics/views.py
class BudgetStatusView(APIView):
    """
    Retrieves the status of all budget category limits for the current month.

    Returns a list of alerts including spending progress and warning messages.
    """

    permission_classes = [IsAuthenticated]

    @extend_schema(
        summary="Current Budget Status Alerts",
        description="List all budget category limits for the authenticated user's current month.",
        responses=BudgetAlertSerializer(many=True)
    )
    def get(self, request):
        """
        Handle GET requests for budget alerts.

        Returns:
            Response: List of budget status alerts.
        """
        now = timezone.now()
        active_limits = BudgetCategoryLimit.objects.filter(
            budget__user=request.user,
            budget__month=now.month,
            budget__year=now.year,
        )
        serializer = BudgetAlertSerializer(active_limits, many=True)
        return Response(serializer.data)

get(request)

Handle GET requests for budget alerts.

Returns:

Name Type Description
Response

List of budget status alerts.

Source code in analytics/views.py
@extend_schema(
    summary="Current Budget Status Alerts",
    description="List all budget category limits for the authenticated user's current month.",
    responses=BudgetAlertSerializer(many=True)
)
def get(self, request):
    """
    Handle GET requests for budget alerts.

    Returns:
        Response: List of budget status alerts.
    """
    now = timezone.now()
    active_limits = BudgetCategoryLimit.objects.filter(
        budget__user=request.user,
        budget__month=now.month,
        budget__year=now.year,
    )
    serializer = BudgetAlertSerializer(active_limits, many=True)
    return Response(serializer.data)

DashboardHomeView

Bases: APIView

Provides a consolidated summary for the user's dashboard.

Includes balance, monthly income/expense totals, and recent activity.

Source code in analytics/views.py
class DashboardHomeView(APIView):
    """
    Provides a consolidated summary for the user's dashboard.

    Includes balance, monthly income/expense totals, and recent activity.
    """

    permission_classes = [IsAuthenticated]

    @extend_schema(
        summary="Dashboard Summary",
        description="Return high-level dashboard data: balance, income, expenses, recent activity.",
        responses=DashboardSummarySerializer
    )
    def get(self, request):
        """
        Handle GET requests for dashboard data.

        Returns:
            Response: Aggregated dashboard summary.
        """
        user = request.user

        income = (
            Transaction.objects.filter(user=user, type='income')
            .aggregate(Sum('amount'))['amount__sum'] or 0
        )
        expense = (
            Transaction.objects.filter(user=user, type='expense')
            .aggregate(Sum('amount'))['amount__sum'] or 0
        )

        data = {
            'user': user,
            'total_balance': income - expense,
            'monthly_income': income,
            'monthly_expenses': expense,
        }

        serializer = DashboardSummarySerializer(data)
        return Response(serializer.data)

get(request)

Handle GET requests for dashboard data.

Returns:

Name Type Description
Response

Aggregated dashboard summary.

Source code in analytics/views.py
@extend_schema(
    summary="Dashboard Summary",
    description="Return high-level dashboard data: balance, income, expenses, recent activity.",
    responses=DashboardSummarySerializer
)
def get(self, request):
    """
    Handle GET requests for dashboard data.

    Returns:
        Response: Aggregated dashboard summary.
    """
    user = request.user

    income = (
        Transaction.objects.filter(user=user, type='income')
        .aggregate(Sum('amount'))['amount__sum'] or 0
    )
    expense = (
        Transaction.objects.filter(user=user, type='expense')
        .aggregate(Sum('amount'))['amount__sum'] or 0
    )

    data = {
        'user': user,
        'total_balance': income - expense,
        'monthly_income': income,
        'monthly_expenses': expense,
    }

    serializer = DashboardSummarySerializer(data)
    return Response(serializer.data)

ReportsAnalyticsView

Bases: APIView

Generates detailed spending analytics for a specific date range.

Provides data suitable for pie charts (category distribution) and bar charts (income vs expense).

Source code in analytics/views.py
class ReportsAnalyticsView(APIView):
    """
    Generates detailed spending analytics for a specific date range.

    Provides data suitable for pie charts (category distribution) and
    bar charts (income vs expense).
    """

    permission_classes = [IsAuthenticated]

    @extend_schema(
        summary="Spending Analytics Reports",
        operation_id="get_reports_analytics",
        description="Return category spending breakdown and income vs expense totals."
    )
    def get(self, request):
        """
        Return category spending breakdown and income vs expense totals.

        Query parameters:
            start_date (str): Start of the date range (YYYY-MM-DD). Defaults to first of current month.
            end_date (str): End of the date range (YYYY-MM-DD). Defaults to today.

        Returns:
            Response: Analytics data including pie chart and bar chart aggregates.
        """
        start_date = request.query_params.get('start_date', timezone.now().date().replace(day=1))
        end_date = request.query_params.get('end_date', timezone.now().date())

        transactions = Transaction.objects.filter(
            user=request.user,
            date__range=[start_date, end_date],
        )

        if not transactions.exists():
            return Response({
                'message': 'No transaction data available for this period.',
                'pie_chart': [],
                'bar_chart': {},
                'insight': 'Start logging your expenses to see analytics!',
            })

        total_expenses = transactions.filter(
            type='expense'
        ).aggregate(Sum('amount'))['amount__sum'] or 1

        # Use raw SQL to get optimized category spending breakdown
        from django.db import connection
        with connection.cursor() as cursor:
            cursor.execute('''
                SELECT c.name, SUM(t.amount)
                FROM finance_transaction t
                JOIN finance_category c ON t.category_id = c.id
                WHERE t.user_id = %s AND t.type = %s AND t.date BETWEEN %s AND %s
                GROUP BY c.name
            ''', [request.user.id, 'expense', start_date, end_date])

            spending_by_category = [
                {'category__name': row[0], 'total_spent': row[1]}
                for row in cursor.fetchall()
            ]

        pie_data = [
            {
                'category': item['category__name'],
                'total_spent': item['total_spent'],
                'percentage': round((item['total_spent'] / total_expenses) * 100, 2),
            }
            for item in spending_by_category
        ]

        total_income = transactions.filter(
            type='income'
        ).aggregate(Sum('amount'))['amount__sum'] or 0

        # Simple financial health insight
        insight = (
            'Your spending is above 90% of your income — consider cutting back.'
            if total_expenses > (total_income * Decimal('0.9'))
            else 'Good job! Your spending is within a healthy range.'
        )

        return Response({
            'pie_chart': CategorySpendingSerializer(pie_data, many=True).data,
            'bar_chart': {
                'total_income': total_income,
                'total_expenses': total_expenses,
            },
            'insight': insight,
            'transactions_count': transactions.count(),
        })

get(request)

Return category spending breakdown and income vs expense totals.

Query parameters

start_date (str): Start of the date range (YYYY-MM-DD). Defaults to first of current month. end_date (str): End of the date range (YYYY-MM-DD). Defaults to today.

Returns:

Name Type Description
Response

Analytics data including pie chart and bar chart aggregates.

Source code in analytics/views.py
@extend_schema(
    summary="Spending Analytics Reports",
    operation_id="get_reports_analytics",
    description="Return category spending breakdown and income vs expense totals."
)
def get(self, request):
    """
    Return category spending breakdown and income vs expense totals.

    Query parameters:
        start_date (str): Start of the date range (YYYY-MM-DD). Defaults to first of current month.
        end_date (str): End of the date range (YYYY-MM-DD). Defaults to today.

    Returns:
        Response: Analytics data including pie chart and bar chart aggregates.
    """
    start_date = request.query_params.get('start_date', timezone.now().date().replace(day=1))
    end_date = request.query_params.get('end_date', timezone.now().date())

    transactions = Transaction.objects.filter(
        user=request.user,
        date__range=[start_date, end_date],
    )

    if not transactions.exists():
        return Response({
            'message': 'No transaction data available for this period.',
            'pie_chart': [],
            'bar_chart': {},
            'insight': 'Start logging your expenses to see analytics!',
        })

    total_expenses = transactions.filter(
        type='expense'
    ).aggregate(Sum('amount'))['amount__sum'] or 1

    # Use raw SQL to get optimized category spending breakdown
    from django.db import connection
    with connection.cursor() as cursor:
        cursor.execute('''
            SELECT c.name, SUM(t.amount)
            FROM finance_transaction t
            JOIN finance_category c ON t.category_id = c.id
            WHERE t.user_id = %s AND t.type = %s AND t.date BETWEEN %s AND %s
            GROUP BY c.name
        ''', [request.user.id, 'expense', start_date, end_date])

        spending_by_category = [
            {'category__name': row[0], 'total_spent': row[1]}
            for row in cursor.fetchall()
        ]

    pie_data = [
        {
            'category': item['category__name'],
            'total_spent': item['total_spent'],
            'percentage': round((item['total_spent'] / total_expenses) * 100, 2),
        }
        for item in spending_by_category
    ]

    total_income = transactions.filter(
        type='income'
    ).aggregate(Sum('amount'))['amount__sum'] or 0

    # Simple financial health insight
    insight = (
        'Your spending is above 90% of your income — consider cutting back.'
        if total_expenses > (total_income * Decimal('0.9'))
        else 'Good job! Your spending is within a healthy range.'
    )

    return Response({
        'pie_chart': CategorySpendingSerializer(pie_data, many=True).data,
        'bar_chart': {
            'total_income': total_income,
            'total_expenses': total_expenses,
        },
        'insight': insight,
        'transactions_count': transactions.count(),
    })