- Remove user ForeignKey from all 7 models (single-user app) - Update all views, handlers, forms, admin, API, seed, and tests - Add MonthlyGoal model with goals and promises sections - Goals/promises: add, toggle (strikethrough), delete via LiveView
118 lines
3.0 KiB
Python
118 lines
3.0 KiB
Python
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from app.expenses.models import Category, Subcategory
|
|
|
|
from .authentication import HasValidToken, TokenEnvAuthentication
|
|
from .serializers import (
|
|
CategorySerializer,
|
|
ExpenseReadSerializer,
|
|
ExpenseWriteSerializer,
|
|
SubcategorySerializer,
|
|
)
|
|
|
|
|
|
class RootView(APIView):
|
|
authentication_classes = [TokenEnvAuthentication]
|
|
permission_classes = [HasValidToken]
|
|
|
|
def get(self, request):
|
|
return Response(
|
|
{
|
|
"type": "Success",
|
|
"_links": {
|
|
"categories": {"href": "/api/categories/", "method": "GET"},
|
|
"expenses": {"href": "/api/expenses/", "method": "POST"},
|
|
},
|
|
}
|
|
)
|
|
|
|
|
|
class BaseAPIView(APIView):
|
|
authentication_classes = [TokenEnvAuthentication]
|
|
permission_classes = [HasValidToken]
|
|
|
|
def _success(self, data, meta=None, links=None, status_code=status.HTTP_200_OK):
|
|
response = {"type": "Success", "data": data}
|
|
if meta:
|
|
response["meta"] = meta
|
|
if links:
|
|
response["_links"] = links
|
|
return Response(response, status=status_code)
|
|
|
|
def _error(
|
|
self,
|
|
errors,
|
|
error_type="ParametersError",
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
):
|
|
formatted = []
|
|
if isinstance(errors, dict):
|
|
for field, messages in errors.items():
|
|
for msg in messages if isinstance(messages, list) else [messages]:
|
|
formatted.append({"field": field, "message": str(msg)})
|
|
else:
|
|
formatted.append({"field": None, "message": str(errors)})
|
|
return Response(
|
|
{"type": error_type, "errors": formatted},
|
|
status=status_code,
|
|
)
|
|
|
|
|
|
class CategoriesView(BaseAPIView):
|
|
def get(self, request):
|
|
categories = Category.objects.prefetch_related("subcategories").all()
|
|
serializer = CategorySerializer(categories, many=True)
|
|
return self._success(
|
|
serializer.data,
|
|
links={
|
|
"self": {"href": "/api/categories/", "method": "GET"},
|
|
},
|
|
)
|
|
|
|
|
|
class SubcategoriesView(BaseAPIView):
|
|
def get(self, request, category_id):
|
|
try:
|
|
category = Category.objects.get(id=category_id)
|
|
except Category.DoesNotExist:
|
|
return self._error(
|
|
"Category not found.",
|
|
error_type="ResourceError",
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
)
|
|
subcategories = Subcategory.objects.filter(category=category)
|
|
serializer = SubcategorySerializer(subcategories, many=True)
|
|
return self._success(
|
|
serializer.data,
|
|
links={
|
|
"self": {
|
|
"href": f"/api/categories/{category_id}/subcategories/",
|
|
"method": "GET",
|
|
},
|
|
"category": {
|
|
"href": "/api/categories/",
|
|
"method": "GET",
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
class ExpensesView(BaseAPIView):
|
|
def post(self, request):
|
|
serializer = ExpenseWriteSerializer(data=request.data)
|
|
if not serializer.is_valid():
|
|
return self._error(serializer.errors)
|
|
|
|
expense = serializer.save()
|
|
read_serializer = ExpenseReadSerializer(expense)
|
|
return self._success(
|
|
read_serializer.data,
|
|
links={
|
|
"self": {"href": "/api/expenses/", "method": "POST"},
|
|
"categories": {"href": "/api/categories/", "method": "GET"},
|
|
},
|
|
status_code=status.HTTP_201_CREATED,
|
|
)
|