1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
from django.conf import settings
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.views import View
from django.http import HttpRequest, JsonResponse, HttpResponse
from datetime import date, datetime, timedelta
from .models import Transaction, Recurrence
import json
from .utils import prev_month_range, add_months
BLUE = "#3788d8"
YELLOW = "#d4a574"
GREEN = "#a8d5ba"
WHITE = "#fff"
BLACK = "#000"
GRAY = "#6a7282"
class IndexView(LoginRequiredMixin, View):
def get(self, request: HttpRequest) -> HttpResponse:
return render(request, "budget/index.html", {
"json_ctx": {
"debug": settings.DEBUG,
"adminIndex": reverse("admin:index"),
"showAdmin": request.user.is_staff,
}
})
class EventsView(LoginRequiredMixin, View):
def get(self, request: HttpRequest) -> JsonResponse:
data = request.GET
# 2025-07-27T00:00:00-05:00
start = datetime.fromisoformat(data["start"]) if "start" in data else date.min
end = datetime.fromisoformat(data["end"]) if "end" in data else date.max
transactions = Transaction.objects.filter(date__range=[start, end])
events = []
today = datetime.now().date()
for t in transactions:
bg, border, text = BLUE, BLUE, WHITE
if t.is_income:
bg, border, text = GREEN, GREEN, BLACK
if t.recurrence == "":
border = YELLOW
if t.date < today:
bg, border, text = GRAY, GREEN if t.is_income else BLUE, WHITE
events.append(
{
"id": t.id,
"allDay": True,
"start": t.date,
"title": t.display,
"extendedProps": {
"title": t.title,
"amount": t.cents,
"recurrence": t.recurrence,
"week": t.week,
"income": t.is_income,
},
"backgroundColor": bg,
"borderColor": border,
"textColor": text,
}
)
return JsonResponse(events, safe=False)
class TransactionView(LoginRequiredMixin, View):
def post(self, request: HttpRequest) -> HttpResponse:
data = json.loads(request.body)
props = data["extendedProps"]
t = Transaction()
t.title = props["title"]
t.cents = props["amount"]
t.date = datetime.fromisoformat(data["start"]).date()
t.recurrence = props["recurrence"]
t.week = int(props["week"])
t.is_income = props["income"]
t.save()
if t.recurrence == Recurrence.WEEK:
while True:
month = t.date.month
t.date += timedelta(weeks=t.week)
if t.date.month == month:
t.id = None
t.save()
else:
break
return HttpResponse()
def patch(self, request: HttpRequest) -> HttpResponse:
data = json.loads(request.body)
if "id" in data:
props = data["extendedProps"]
t = Transaction.objects.get(id=data["id"])
t.title = props["title"]
t.cents = props["amount"]
t.date = datetime.fromisoformat(data["start"])
t.recurrence = props["recurrence"]
t.week = props["week"]
t.is_income = props["income"]
t.save()
return HttpResponse()
def delete(self, request: HttpRequest) -> HttpResponse:
data = json.loads(request.body)
if "id" in data:
Transaction.objects.get(id=data["id"]).delete()
return HttpResponse()
class CopyView(View):
def post(self, request: HttpRequest):
month = json.loads(request.body)["month"]
first, last = prev_month_range(month)
transactions = Transaction.objects.filter(date__range=[first, last], recurrence__in=[Recurrence.WEEK, Recurrence.MONTH])
for transaction in transactions:
if transaction.recurrence == Recurrence.MONTH:
transaction.id = None
transaction.date = add_months(transaction.date, 1)
transaction.save()
else:
check = transaction.date + timedelta(weeks=transaction.week)
if check.month != month:
continue
while True:
transaction.date += timedelta(weeks=transaction.week)
if transaction.date.month == month:
transaction.id = None
transaction.save()
else:
break
return HttpResponse()
|