Skip to content

Commit

Permalink
Implement balance as prefix-sum
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmurilo75 committed Mar 18, 2024
1 parent 3c28eca commit 5c9a9a9
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 48 deletions.
35 changes: 35 additions & 0 deletions development_notebook/2024-03-17.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Current State

See [[2024-03-06]] for previous log.

Deployed to production at [negligentoctopus.pythonanywhere.com](negligentoctopus.pythonanywhere.com).

Project has been fully configured from cookiecutter-django using MySQL.

Core models are being defined and added to the django-admin interface to be tested.

## Update

Account and transactions are implemented with balance as prefix-sum.

# Today

Define automatic balance tracking in account.

Prefix-sum: This requires a strict ordering. When adding, deleting or changing the amount: Just add it according to the ordering, then update balance on all transactions that follow. The problem here is that we'll change many transactions, so having such a big lock is not a good ideia, and without having a lock we might access a transaction before it is updated.
- Strict Ordering: is implemented by standard on django model Meta.order\_by

## Work Log
__InProgress__

__ToDo__
* Implement changeable to amt
* Implement changeable to timestamp
* Implement initial balance to accounts - change balance on transation to be relative and set a balance property | requires change to update_after and prev to use new field

__Done__
* Implement balance as prefix-sum.
* Migrate.
* Test on admin.

# To Do
50 changes: 41 additions & 9 deletions development_notebook/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,65 @@

## tmux

prefix: `Ctrl + a` or `Ctrl + b`

### Session
tmux list-sessions
tmux attach-session -t \<session name\>
tmux ps
? list sessions

tmux at -t \<session name\>
? Enter session

### Windows
`Ctrl + b` then \<arrow-key\>
? Change window
`prefix` then `[hjkl]
? Change window using VIM keys

`Ctrl + b` then `%`
`prefix` then `%`
? Split horizontal

`Ctrl + b` then `"`
`prefix` then `"`
? Split vertical

`Ctrl + b` then `Alt+[Arrow UDLR]`
`Ctrl + b` then `:resize-pane -[UDLR] N`
`prefix` then `[HJKL]`
? Resize window by `N` to [Up, Down, Left, Right]

## NVim

`Ctrl + n` or `Ctrl + \<space\>`
? Cycle autocomplete options

`,` then `d`
? Jump to definition
- Hint: duplicate window using `:sp` before jump

:sp \<filepath\>
? Split vertical

:vsp \<filepath\>
? Split horizontal

### TODO -> Include plugin commands
### Windows
`Ctrl + W` then `[hjkl]`
? Move cursor to window

`Ctrl + W` then `[HJKL]`
? Move window

`Ctrl + W` then `[+ -]`
? Resize window vertically

`Ctrl + W` then `[\< \>]`
? Resize window horizontally

`Ctrl + W` then `\_`
? Resize to near full-screen

`Ctrl + W` then `\=`
? Resize to equal parts

### NERDTree
`:NERDTree`


## Bash

Expand Down
11 changes: 10 additions & 1 deletion negligent_octopus/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ class AccountAdmin(admin.ModelAdmin):
"is_removed",
"modified",
] # TODO: Check modified catches transaction changes
readonly_fields = ["balance"]


@admin.register(Transaction)
class TransactionAdmin(admin.ModelAdmin):
list_display = ["title", "account", "get_account_owner", "amount", "timestamp"]
list_display = [
"title",
"account",
"get_account_owner",
"amount",
"timestamp",
"balance",
]
search_fields = ["account__owner__username", "account__name", "title"]
list_filter = ["account__owner", "account", "timestamp"]
readonly_fields = ["balance"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.2.10 on 2024-03-18 00:12

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

dependencies = [
("core", "0001_initial"),
]

operations = [
migrations.AlterModelOptions(
name="transaction",
options={
"ordering": ["-timestamp", "-created"],
"verbose_name": "Transaction",
"verbose_name_plural": "Transactions",
},
),
migrations.RemoveField(
model_name="account",
name="balance",
),
migrations.AddField(
model_name="transaction",
name="balance",
field=models.FloatField(editable=False, null=True),
),
migrations.AddField(
model_name="transaction",
name="timestamp",
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AlterUniqueTogether(
name="transaction",
unique_together={("timestamp", "created")},
),
migrations.RemoveField(
model_name="transaction",
name="date",
),
]
77 changes: 39 additions & 38 deletions negligent_octopus/core/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from django.db import models
from django.db import transaction as db_transaction
from django.utils import timezone
from django.utils.functional import cached_property
from model_utils.models import SoftDeletableModel
from model_utils.models import TimeStampedModel
from model_utils.tracker import FieldTracker

from negligent_octopus.users.models import User

Expand All @@ -12,9 +12,9 @@ class Account(TimeStampedModel, SoftDeletableModel):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)

@property
@cached_property
def balance(self):
return self.transaction_set.last().balance
return self.transaction_set.first().balance

def __str__(self):
return str(self.name)
Expand All @@ -26,56 +26,57 @@ class Meta(TimeStampedModel.Meta, SoftDeletableModel.Meta):


class Transaction(TimeStampedModel):
account = models.ForeignKey(Account, on_delete=models.CASCADE)
amount = models.FloatField()
timestamp = models.DateTimeField(default=timezone.now)
account = models.ForeignKey(Account, on_delete=models.CASCADE)
balance = models.FloatField(default=0.0, editable=False)
balance = models.FloatField(editable=False, null=True)
title = models.CharField(max_length=127)
description = models.TextField(blank=True)

tracker = FieldTracker(
fields=["amount", "timestamp"],
) # We need to consider changes to the timestamp
# and how to update balances based on these

def get_account_owner(self):
return str(self.account.owner)

@db_transaction.atomic
def save(self, *args, **kwargs):
universe = self.account.transaction_set

last_transaction = (
universe.filter(
timestamp__lte=self.timestamp,
)
.order_by("-created")
.first()
)
self.balance = last_transaction.balance + self.amount
old_model = self.__class__.objects.get(pk=self.pk)
if old_model.timestamp != self.timestamp or old_model.account != self.account:
msg = "Cannot change fields 'account' or 'timestamp'."
raise ValueError(msg) # TODO: Change fields account or timestamp

if old_model.amount != self.amount:
msg = "Cannot change field 'amount'."
raise ValueError(msg)
# TODO: Id what trans is (del, change, create) and calculate for approp amt

if self.balance is None:
self.balance = self.amount
last_transaction = self.account.transaction_set.filter(
models.Q(timestamp__lte=self.timestamp)
& models.Q(created__lt=self.created),
).first()
if last_transaction is not None:
self.balance += last_transaction.balance

universe = self.account.transaction_set.filter(
models.Q(timestamp__gte=self.timestamp)
& models.Q(created__gt=self.created),
).reverse()

previous = universe.first()
for transaction in universe:
if transaction == universe.first():
continue
transaction.balance = transaction.amount + previous.balance
transaction.save()
previous = transaction

super().save(*args, **kwargs)
last_transaction = self

to_update = universe.filter(
timestamp__gte=self.timestamp,
).exclude(
created__lt=self.created,
)
for transaction in to_update:
transaction.balance = last_transaction.balance + transaction.amount
transaction.save()
last_transaction = transaction

def delete(self, *args, **kwargs):
self.amount = -self.amount
self.save()
super().delete(*args, **kwargs)

def __str__(self):
return self.title
return str(self.title)

class Meta(TimeStampedModel.Meta):
verbose_name = "Transaction"
verbose_name_plural = "Transactions"
ordering = ["timestamp", "-modified"]
ordering = ["-timestamp", "-created"]
unique_together = ["timestamp", "created"]

0 comments on commit 5c9a9a9

Please sign in to comment.