Skip to content

Commit a747493

Browse files
committed
feat: expenses model, views and tests created and working
1 parent 69dd7ed commit a747493

File tree

8 files changed

+119
-39
lines changed

8 files changed

+119
-39
lines changed

Diff for: docker-compose.yml

-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ services:
1212
- "5432:5432"
1313
networks:
1414
- app_network
15-
environment:
16-
POSTGRES_HOST_AUTH_METHOD: trust
1715

1816
web:
1917
build:

Diff for: expenses/admin.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,22 @@
44

55
@admin.register(Expense)
66
class ExpenseAdmin(admin.ModelAdmin):
7-
list_display = ("username", "amount", "expense_type_id", "category_id", "bankcard_id", "created_at", "updated_at")
8-
search_fields = ("username", "expense_type_id", "category_id")
7+
list_display = (
8+
"username",
9+
"amount",
10+
"user_expense_type_name",
11+
"category_name",
12+
"bankcard_id",
13+
"created_at",
14+
"updated_at",
15+
)
16+
17+
def user_expense_type_name(self, obj):
18+
return obj.user_expense_type.name
19+
20+
user_expense_type_name.short_description = "User Expense Type"
21+
22+
def category_name(self, obj):
23+
return obj.category.name
24+
25+
category_name.short_description = "Category"

Diff for: expenses/migrations/0001_initial.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
1-
# Generated by Django 5.0.6 on 2024-06-10 23:28
1+
# Generated by Django 5.0.6 on 2024-06-17 13:41
22

3+
import django.db.models.deletion
34
from django.db import migrations, models
45

56

67
class Migration(migrations.Migration):
78

89
initial = True
910

10-
dependencies = []
11+
dependencies = [
12+
("categories", "0001_initial"),
13+
("user_expense_type", "0001_initial"),
14+
]
1115

1216
operations = [
1317
migrations.CreateModel(
1418
name="Expense",
1519
fields=[
1620
("id", models.AutoField(primary_key=True, serialize=False)),
1721
("username", models.UUIDField()),
18-
("expense_type_id", models.IntegerField()),
19-
("category_id", models.IntegerField()),
2022
("bankcard_id", models.IntegerField()),
2123
("amount", models.IntegerField()),
2224
("created_at", models.DateTimeField(auto_now_add=True)),
2325
("updated_at", models.DateTimeField(auto_now=True)),
26+
("category", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="categories.category")),
27+
(
28+
"user_expense_type",
29+
models.ForeignKey(
30+
on_delete=django.db.models.deletion.CASCADE, to="user_expense_type.userexpensetype"
31+
),
32+
),
2433
],
2534
),
2635
]

Diff for: expenses/models.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from django.db import models
2+
from user_expense_type.models import UserExpenseType
3+
from categories.models import Category
24

35

46
class Expense(models.Model):
57
id = models.AutoField(primary_key=True)
68
username = models.UUIDField()
7-
expense_type_id = models.IntegerField()
8-
category_id = models.IntegerField()
9+
user_expense_type = models.ForeignKey(UserExpenseType, on_delete=models.CASCADE)
10+
category = models.ForeignKey(Category, on_delete=models.CASCADE)
11+
# cambiar cuando exista el modelo de bankcard
912
bankcard_id = models.IntegerField()
1013
amount = models.IntegerField()
1114
created_at = models.DateTimeField(auto_now_add=True)

Diff for: expenses/serializers.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from rest_framework import serializers
22
from .models import Expense
3+
from user_expense_type.models import UserExpenseType
4+
from categories.models import Category
35

46

57
class ExpenseSerializer(serializers.Serializer):
68
id = serializers.IntegerField(read_only=True)
79

810
username = serializers.UUIDField()
9-
expense_type_id = serializers.IntegerField()
10-
category_id = serializers.IntegerField()
11+
user_expense_type = serializers.PrimaryKeyRelatedField(queryset=UserExpenseType.objects.all())
12+
category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all())
1113
bankcard_id = serializers.IntegerField()
1214
amount = serializers.IntegerField()
1315

Diff for: expenses/tests.py

+26-12
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ def setUp(self):
1212
self.client = APIClient()
1313
self.view = ExpenseViewSet()
1414
self.expense = {
15-
"expense_type_id": 1,
16-
"category_id": 1,
15+
"user_expense_type": 1,
16+
"category": 1,
1717
"bankcard_id": 1,
1818
"amount": 100,
1919
}
@@ -50,6 +50,22 @@ def test_list(self, mock_login_user, mock_list):
5050
self.assertEqual(response.status_code, status.HTTP_200_OK)
5151
self.assertEqual(response.data["amount"], 100)
5252

53+
@patch.object(ExpenseViewSet, "retrieve")
54+
@patch.object(CognitoService, "login_user")
55+
def test_retrieve(self, mock_login_user, mock_retrieve):
56+
mock_login_user.return_value = {
57+
"AuthenticationResult": {"AccessToken": "mock_access_token", "IdToken": "mock_id_token"}
58+
}
59+
mock_retrieve.return_value = Response(status=status.HTTP_200_OK, data=self.expense)
60+
61+
request = self.client.get("/expenses/1/")
62+
request.headers["Authorization"] = "Bearer mock_access_token"
63+
64+
response = self.client.get("/expenses/1/")
65+
66+
self.assertEqual(response.status_code, status.HTTP_200_OK)
67+
self.assertEqual(response.data["amount"], 100)
68+
5369
@patch.object(ExpenseViewSet, "destroy")
5470
@patch.object(CognitoService, "login_user")
5571
def test_destroy(self, mock_login_user, mock_destroy):
@@ -58,11 +74,10 @@ def test_destroy(self, mock_login_user, mock_destroy):
5874
}
5975
mock_destroy.return_value = Response(status=status.HTTP_204_NO_CONTENT)
6076

61-
request = self.client.delete("/expenses/")
77+
request = self.client.delete("/expenses/1/")
6278
request.headers["Authorization"] = "Bearer mock_access_token"
63-
request.body = {"id": 1}
6479

65-
response = self.client.delete("/expenses/")
80+
response = self.client.delete("/expenses/1/")
6681

6782
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
6883

@@ -73,18 +88,17 @@ def test_partial_update(self, mock_login_user, mock_partial_update):
7388
"AuthenticationResult": {"AccessToken": "mock_access_token", "IdToken": "mock_id_token"}
7489
}
7590
new_expense = {
76-
"expense_type_id": 1,
77-
"category_id": 1,
91+
"expense_type": 1,
92+
"category": 1,
7893
"bankcard_id": 1,
7994
"amount": 200,
8095
}
8196
mock_partial_update.return_value = Response(status=status.HTTP_200_OK, data=new_expense)
8297

83-
request = self.client.put("/expenses/", {"amount": 200})
98+
request = self.client.put("/expenses/1/", {"amount": 200})
8499
request.headers["Authorization"] = "Bearer mock_access_token"
85-
request.body = {"id": 1}
86100

87-
response = self.client.put("/expenses/", {"amount": 200})
101+
response = self.client.put("/expenses/1/", {"amount": 200})
88102

89103
self.assertEqual(response.status_code, status.HTTP_200_OK)
90104
self.assertEqual(response.data["amount"], 200)
@@ -95,8 +109,8 @@ def setUp(self):
95109
self.client = APIClient()
96110
self.view = ExpenseGroupedByTypeAndCategoryViewSet()
97111
self.expense = {
98-
"expense_type_id": 1,
99-
"category_id": 1,
112+
"expense_type": 1,
113+
"category": 1,
100114
"bankcard_id": 1,
101115
"amount": 100,
102116
}

Diff for: expenses/urls.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
urlpatterns = [
55
path(
66
"",
7-
ExpenseViewSet.as_view({"get": "list", "post": "create", "delete": "destroy", "put": "partial_update"}),
7+
ExpenseViewSet.as_view({"get": "list", "post": "create"}),
88
name="expenses",
99
),
10+
path(
11+
"<int:pk>/",
12+
ExpenseViewSet.as_view({"get": "retrieve", "put": "partial_update", "delete": "destroy"}),
13+
name="expense-detail",
14+
),
1015
path(
1116
"grouped/",
1217
ExpenseGroupedByTypeAndCategoryViewSet.as_view({"get": "list"}),

Diff for: expenses/views.py

+46-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from rest_framework import viewsets, status
22
from rest_framework.response import Response
3+
4+
# from .models import Expense
35
from .models import Expense
46
from user_expense_type.models import UserExpenseType
57
from categories.models import Category
@@ -36,12 +38,32 @@ def list(self, request):
3638
username = self.get_user_id_from_token(request)
3739
expenses = Expense.objects.filter(username=username)
3840
serializer = ExpenseSerializer(expenses, many=True)
41+
for ser in serializer.data:
42+
ser.pop("username")
3943
return Response(serializer.data)
4044
except Expense.DoesNotExist:
4145
return Response({"error": "Expenses not found"}, status=status.HTTP_404_NOT_FOUND)
4246
except Exception as e:
4347
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
4448

49+
@cognito_authenticated
50+
def retrieve(self, request, pk=None):
51+
try:
52+
username = self.get_user_id_from_token(request)
53+
expense = Expense.objects.get(id=pk, username=username)
54+
serializer = ExpenseSerializer(expense)
55+
response = {
56+
"user_expense_type": serializer.data["user_expense_type"],
57+
"category": serializer.data["category"],
58+
"bankcard_id": serializer.data["bankcard_id"],
59+
"amount": serializer.data["amount"],
60+
}
61+
return Response(data=response)
62+
except Expense.DoesNotExist:
63+
return Response({"error": "Expense not found"}, status=status.HTTP_404_NOT_FOUND)
64+
except Exception as e:
65+
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
66+
4567
@cognito_authenticated
4668
def create(self, request):
4769
try:
@@ -52,17 +74,22 @@ def create(self, request):
5274
serializer = ExpenseSerializer(data=data)
5375
if serializer.is_valid():
5476
serializer.save()
55-
return Response(serializer.data, status=status.HTTP_201_CREATED)
77+
response = {
78+
"user_expense_type": serializer.data["user_expense_type"],
79+
"category": serializer.data["category"],
80+
"bankcard_id": serializer.data["bankcard_id"],
81+
"amount": serializer.data["amount"],
82+
}
83+
return Response(data=response, status=status.HTTP_201_CREATED)
5684
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
5785
except Exception as e:
5886
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
5987

6088
@cognito_authenticated
61-
def destroy(self, request):
89+
def destroy(self, request, pk=None):
6290
try:
6391
username = self.get_user_id_from_token(request)
64-
id = request.data.get("id")
65-
expense = Expense.objects.get(id=id, username=username)
92+
expense = Expense.objects.get(id=pk, username=username)
6693
expense.delete()
6794
return Response(status=status.HTTP_204_NO_CONTENT)
6895
except Expense.DoesNotExist:
@@ -71,15 +98,20 @@ def destroy(self, request):
7198
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
7299

73100
@cognito_authenticated
74-
def partial_update(self, request):
101+
def partial_update(self, request, pk=None):
75102
try:
76103
username = self.get_user_id_from_token(request)
77-
id = request.data.get("id")
78-
expense = Expense.objects.get(id=id, username=username)
104+
expense = Expense.objects.get(id=pk, username=username)
79105
serializer = ExpenseSerializer(expense, data=request.data, partial=True)
80106
if serializer.is_valid():
81107
serializer.save()
82-
return Response(serializer.data)
108+
response = {
109+
"user_expense_type": serializer.data["user_expense_type"],
110+
"category": serializer.data["category"],
111+
"bankcard_id": serializer.data["bankcard_id"],
112+
"amount": serializer.data["amount"],
113+
}
114+
return Response(data=response)
83115
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
84116
except Expense.DoesNotExist:
85117
return Response({"error": "Expense not found"}, status=status.HTTP_404_NOT_FOUND)
@@ -115,19 +147,19 @@ def list(self, request):
115147
today = now()
116148
expenses = (
117149
Expense.objects.filter(username=username, created_at__year=today.year, created_at__month=today.month)
118-
.values("expense_type_id", "category_id")
150+
.values("user_expense_type_id", "category_id")
119151
.annotate(total_amount=Sum("amount"))
120152
)
121153

122154
for expense in expenses:
123-
expense_type_name = UserExpenseType.objects.get(id=expense["expense_type_id"]).name
124-
category_name = Category.objects.get(id=expense["category_id"]).name
155+
user_expense_type = UserExpenseType.objects.get(id=expense["user_expense_type_id"])
156+
category = Category.objects.get(id=expense["category_id"])
125157
total_amount = expense["total_amount"]
126158

127-
if expense_type_name not in expenses_grouped:
128-
expenses_grouped[expense_type_name] = {}
159+
if user_expense_type.name not in expenses_grouped:
160+
expenses_grouped[user_expense_type.name] = {}
129161

130-
expenses_grouped[expense_type_name][category_name] = total_amount
162+
expenses_grouped[user_expense_type.name][category.name] = total_amount
131163

132164
return Response(expenses_grouped)
133165
except Exception as e:

0 commit comments

Comments
 (0)