Skip to content

Planning API Reference

planning.serializers

Serializers for the planning application.

Handles the logic for setting category spending limits (auto-mapping to the current active budget) and calculating savings targets based on deadlines.

BudgetCategoryLimitSerializer

Bases: ModelSerializer

Serializer for establishing a category spending limit for the current month.

This serializer is specialized for 'Quick Planning' where the system automatically finds or creates the current month's budget container.

Source code in planning/serializers.py
class BudgetCategoryLimitSerializer(serializers.ModelSerializer):
    """
    Serializer for establishing a category spending limit for the current month.

    This serializer is specialized for 'Quick Planning' where the system
    automatically finds or creates the current month's budget container.
    """

    class Meta:
        """Metadata for BudgetCategoryLimitSerializer."""
        model = BudgetCategoryLimit
        fields = ['category', 'limit']

    def validate_limit(self, value):
        """
        Ensure the limit provided is a positive number.

        Args:
            value (Decimal): The input limit value.

        Returns:
            Decimal: Validated limit.

        Raises:
            serializers.ValidationError: If the value is non-positive.
        """
        if value <= 0:
            raise serializers.ValidationError('Amount must be a positive number.')
        return value

    def create(self, validated_data):
        """
        Link the category limit to the current month's budget.

        Automatically finds or creates a Budget instance for the current
        month/year and attaches the new limit to it.

        Args:
            validated_data (dict): Validated input data.

        Returns:
            BudgetCategoryLimit: Created instance.

        Raises:
            serializers.ValidationError: If a limit for the category already exists for the month.
        """
        user = self.context['request'].user
        category = validated_data['category']
        now = timezone.now()

        budget_parent, _ = Budget.objects.get_or_create(
            user=user,
            month=now.month,
            year=now.year,
            defaults={'name': f'Budget {now.month}/{now.year}', 'total_limit': 0},
        )

        if BudgetCategoryLimit.objects.filter(budget=budget_parent, category=category).exists():
            raise serializers.ValidationError(
                'A budget for this category already exists for this month. '
                'Please edit the existing budget instead.'
            )

        return BudgetCategoryLimit.objects.create(budget=budget_parent, **validated_data)

Meta

Metadata for BudgetCategoryLimitSerializer.

Source code in planning/serializers.py
class Meta:
    """Metadata for BudgetCategoryLimitSerializer."""
    model = BudgetCategoryLimit
    fields = ['category', 'limit']

create(validated_data)

Link the category limit to the current month's budget.

Automatically finds or creates a Budget instance for the current month/year and attaches the new limit to it.

Parameters:

Name Type Description Default
validated_data dict

Validated input data.

required

Returns:

Name Type Description
BudgetCategoryLimit

Created instance.

Raises:

Type Description
ValidationError

If a limit for the category already exists for the month.

Source code in planning/serializers.py
def create(self, validated_data):
    """
    Link the category limit to the current month's budget.

    Automatically finds or creates a Budget instance for the current
    month/year and attaches the new limit to it.

    Args:
        validated_data (dict): Validated input data.

    Returns:
        BudgetCategoryLimit: Created instance.

    Raises:
        serializers.ValidationError: If a limit for the category already exists for the month.
    """
    user = self.context['request'].user
    category = validated_data['category']
    now = timezone.now()

    budget_parent, _ = Budget.objects.get_or_create(
        user=user,
        month=now.month,
        year=now.year,
        defaults={'name': f'Budget {now.month}/{now.year}', 'total_limit': 0},
    )

    if BudgetCategoryLimit.objects.filter(budget=budget_parent, category=category).exists():
        raise serializers.ValidationError(
            'A budget for this category already exists for this month. '
            'Please edit the existing budget instead.'
        )

    return BudgetCategoryLimit.objects.create(budget=budget_parent, **validated_data)

validate_limit(value)

Ensure the limit provided is a positive number.

Parameters:

Name Type Description Default
value Decimal

The input limit value.

required

Returns:

Name Type Description
Decimal

Validated limit.

Raises:

Type Description
ValidationError

If the value is non-positive.

Source code in planning/serializers.py
def validate_limit(self, value):
    """
    Ensure the limit provided is a positive number.

    Args:
        value (Decimal): The input limit value.

    Returns:
        Decimal: Validated limit.

    Raises:
        serializers.ValidationError: If the value is non-positive.
    """
    if value <= 0:
        raise serializers.ValidationError('Amount must be a positive number.')
    return value

SavingsGoalSerializer

Bases: ModelSerializer

Serializer for savings goals with advanced planning metrics.

Computes 'monthly_savings_needed' based on the distance to the deadline and the remaining target amount.

Source code in planning/serializers.py
class SavingsGoalSerializer(serializers.ModelSerializer):
    """
    Serializer for savings goals with advanced planning metrics.

    Computes 'monthly_savings_needed' based on the distance to the deadline
    and the remaining target amount.
    """

    monthly_savings_needed = serializers.SerializerMethodField()
    progress_percentage = serializers.SerializerMethodField()

    class Meta:
        """Metadata for SavingsGoalSerializer."""
        model = SavingsGoal
        fields = [
            'id', 'name', 'target_amount', 'current_amount',
            'deadline', 'completed', 'monthly_savings_needed', 'progress_percentage',
        ]
        read_only_fields = ['id', 'current_amount', 'completed']

    def get_monthly_savings_needed(self, obj):
        """
        Calculate the required monthly contribution to reach the goal by its deadline.

        Returns:
            float: Amount needed per month, or None if no deadline is set.
        """
        if not obj.deadline:
            return None
        today = date.today()
        remaining = obj.target_amount - obj.current_amount
        if remaining <= 0:
            return 0
        months = (obj.deadline.year - today.year) * 12 + (obj.deadline.month - today.month)
        return round(remaining / max(months, 1), 2)

    def get_progress_percentage(self, obj):
        """
        Calculate the percentage completion of the savings goal.

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

Meta

Metadata for SavingsGoalSerializer.

Source code in planning/serializers.py
class Meta:
    """Metadata for SavingsGoalSerializer."""
    model = SavingsGoal
    fields = [
        'id', 'name', 'target_amount', 'current_amount',
        'deadline', 'completed', 'monthly_savings_needed', 'progress_percentage',
    ]
    read_only_fields = ['id', 'current_amount', 'completed']

get_monthly_savings_needed(obj)

Calculate the required monthly contribution to reach the goal by its deadline.

Returns:

Name Type Description
float

Amount needed per month, or None if no deadline is set.

Source code in planning/serializers.py
def get_monthly_savings_needed(self, obj):
    """
    Calculate the required monthly contribution to reach the goal by its deadline.

    Returns:
        float: Amount needed per month, or None if no deadline is set.
    """
    if not obj.deadline:
        return None
    today = date.today()
    remaining = obj.target_amount - obj.current_amount
    if remaining <= 0:
        return 0
    months = (obj.deadline.year - today.year) * 12 + (obj.deadline.month - today.month)
    return round(remaining / max(months, 1), 2)

get_progress_percentage(obj)

Calculate the percentage completion of the savings goal.

Returns:

Name Type Description
float

Percentage value rounded to 2 decimal places.

Source code in planning/serializers.py
def get_progress_percentage(self, obj):
    """
    Calculate the percentage completion of the savings goal.

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

planning.views

Views for the planning application.

Provides simplified endpoints for setting quick budget limits and managing savings goals without navigating the full finance hierarchy.

BudgetLimitView

Bases: APIView

Shortcut view for creating a spending limit within the current month's budget.

This view simplifies the process by auto-calculating the budget period.

Source code in planning/views.py
class BudgetLimitView(APIView):
    """
    Shortcut view for creating a spending limit within the current month's budget.

    This view simplifies the process by auto-calculating the budget period.
    """

    permission_classes = [IsAuthenticated]

    @extend_schema(
        summary="Set Category Limit",
        description="Creates a spending limit for a specific category for the current month.",
        request=BudgetCategoryLimitSerializer, 
        responses=BudgetCategoryLimitSerializer
    )
    def post(self, request):
        """
        Create a new category limit.

        Args:
            request (Request): The HTTP request containing 'category' and 'limit'.

        Returns:
            Response: Success confirmation or validation errors.
        """
        serializer = BudgetCategoryLimitSerializer(
            data=request.data,
            context={'request': request},
        )
        if serializer.is_valid():
            serializer.save()
            return Response({'message': 'Budget created successfully'}, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

post(request)

Create a new category limit.

Parameters:

Name Type Description Default
request Request

The HTTP request containing 'category' and 'limit'.

required

Returns:

Name Type Description
Response

Success confirmation or validation errors.

Source code in planning/views.py
@extend_schema(
    summary="Set Category Limit",
    description="Creates a spending limit for a specific category for the current month.",
    request=BudgetCategoryLimitSerializer, 
    responses=BudgetCategoryLimitSerializer
)
def post(self, request):
    """
    Create a new category limit.

    Args:
        request (Request): The HTTP request containing 'category' and 'limit'.

    Returns:
        Response: Success confirmation or validation errors.
    """
    serializer = BudgetCategoryLimitSerializer(
        data=request.data,
        context={'request': request},
    )
    if serializer.is_valid():
        serializer.save()
        return Response({'message': 'Budget created successfully'}, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

SavingsGoalView

Bases: APIView

View for listing and creating personal savings goals.

Source code in planning/views.py
class SavingsGoalView(APIView):
    """
    View for listing and creating personal savings goals.
    """

    permission_classes = [IsAuthenticated]

    @extend_schema(
        summary="List Savings Goals",
        description="Return all savings goals belonging to the authenticated user.",
        responses=SavingsGoalSerializer(many=True)
    )
    def get(self, request):
        """
        Fetch all goals for the user.

        Returns:
            Response: List of serialized savings goals.
        """
        goals = SavingsGoal.objects.filter(user=request.user)
        serializer = SavingsGoalSerializer(goals, many=True)
        return Response(serializer.data)

    @extend_schema(
        summary="Create Savings Goal",
        description="Initializes a new savings target for the user.",
        request=SavingsGoalSerializer, 
        responses=SavingsGoalSerializer
    )
    def post(self, request):
        """
        Create a new goal.

        Args:
            request (Request): The HTTP request containing goal details.

        Returns:
            Response: Created goal data or validation errors.
        """
        serializer = SavingsGoalSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(user=request.user)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

get(request)

Fetch all goals for the user.

Returns:

Name Type Description
Response

List of serialized savings goals.

Source code in planning/views.py
@extend_schema(
    summary="List Savings Goals",
    description="Return all savings goals belonging to the authenticated user.",
    responses=SavingsGoalSerializer(many=True)
)
def get(self, request):
    """
    Fetch all goals for the user.

    Returns:
        Response: List of serialized savings goals.
    """
    goals = SavingsGoal.objects.filter(user=request.user)
    serializer = SavingsGoalSerializer(goals, many=True)
    return Response(serializer.data)

post(request)

Create a new goal.

Parameters:

Name Type Description Default
request Request

The HTTP request containing goal details.

required

Returns:

Name Type Description
Response

Created goal data or validation errors.

Source code in planning/views.py
@extend_schema(
    summary="Create Savings Goal",
    description="Initializes a new savings target for the user.",
    request=SavingsGoalSerializer, 
    responses=SavingsGoalSerializer
)
def post(self, request):
    """
    Create a new goal.

    Args:
        request (Request): The HTTP request containing goal details.

    Returns:
        Response: Created goal data or validation errors.
    """
    serializer = SavingsGoalSerializer(data=request.data)
    if serializer.is_valid():
        serializer.save(user=request.user)
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)