0👍
Even though I failed to implement an account name based on a given account number, I’m happy to share the way we can send money from one account to another.
The technical way to do so is by creating only two models, Account
and Transaction
models, and adding what is in the Wallet
model to an Account
model like this:
class Account(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
account_number = models.IntegerField()
account_balance = models.DecimalField(max_digits=12, decimal_places=6)
To send fund from one account to another, we have to create sender
and receiver
field and assign them to a CustomUser
model with different related_name
in the Transaction
model, just like this:
import random
def generate_random_number():
return random.randint(1, 30)
class Transaction(models.Model):
amount = models.DecimalField(max_digits=12, decimal_places=6)
sender = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='transfer_sents')
receiver = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='transfer_receives')
account_number = models.IntegerField()
name = models.CharField(max_length=50)
refrence_number = models.CharField(max_length=50, default=generate_random_number)
I’ve written a Django view function that’s designed to handle financial transactions. Let me break down how it works:
The view takes the amount
and name
values from the submitted form data, and it retrieves the sender
and receiver
account objects from the database using the Account
model. The sender account is associated with the currently logged-in user, while the receiver account is identified by the account_number
provided in the form data, and then ensure there are sufficient funds, the view checks if the sender account balance can cover the transaction amount. If it can, the view deducts the ‘amount’ from the sender account balance and increases the receiver account balance by the same ‘amount’. These changes are then saved to the database. In the event of insufficient funds in the sender account, the view generates an error message using Django messaging framework. The user is then redirected to that page.
views.py
from decimal import Decimal
from django.contrib import messages
def create_transfer(request):
if request.method == 'POST':
amount = Decimal(request.POST.get('amount'))
name = request.POST.get('name')
sender_account = Account.objects.get(user=request.user)
receiver_account = Account.objects.get(account_number=request.POST.get('account_number'))
if sender_account.account_balance >= amount:
sender_account.account_balance -= amount
sender_account.save()
receiver_account.account_balance += amount
receiver_account.save()
Transaction.objects.create(
sender=sender_account.user,
receiver=receiver_account.user,
amount=amount,
name=name,
account_number=receiver_account
)
else:
messages.error(request, 'Insufficient Funds')
return redirect('Transaction')
return render(request, 'create_transfer.html')
0👍
FAQ
Q: Why use UUID instead of RNG(random number generator in range) (1-10)?
- Universally Unique: UUIDs are designed to be globally unique across space and time.
- No Need for Centralized ID Generation: Generating random 10-digit numbers that are guaranteed to be unique can be challenging, especially in distributed systems. lets imagine you have 1 Billion users, then there is 1 in 10 chance that your generated id will collide with existing, if user count goes up to 9,999,999,999 , your system will run that many iterations, and will hang on next user. dead.
- Ease of Data Synchronization: If you ever need to synchronize data between different databases or systems, UUIDs make this process simpler. You don’t have to worry about conflicts due to duplicate IDs since UUIDs are globally unique.
Q: you seem to have atleast 3 models, but is it more manageble as you scale up?
while creating model must follow database normalization, but too many tables will lead to millions of joins for relatively few users, in worst case. and introducing a new change will be nightmare. so if you know that T1 has (ABC)
T2 has (AD) you can simply merge a new optimized T3 (ABCD) therefore eliminating any joins or slow queries! in other words, you can achieve same functionality with just 2 tables USER and TRANSACTIONS, i will explain subsequently.
Further Reading: AbstractUser
Setup
pip install uuid
Code
here we are making some assumptions, that a user may not have more than one wallet. if has more than one wallet can create another wallet model as already created by you, but if wallet is for specific purpose and countable like "earnings_wallet", "withdrawal_wallet", or "gifts_wallet", then just create one more attribute in model rather than creating new model, it will avoid unnecessary back refs to user’s (owner’s) primary key.
import uuid # Import the uuid library
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# here id is auto generated so no tension for manual random generation
account_balance = models.DecimalField(max_digits=10, decimal_places=2, default=0)
# wallet and user id are canonicalized so no question of synchronization as they are guranteed to be unique and reference only one entity.
# any other fields you may have, put here
class Transaction(models.Model):
# a vary basic model which banks use, may have other fields like, mode of payment (cc, bank transfer)
# or something related to tax or any other attribute of the country's financial system
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
sender = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='sent_transactions')
receiver = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='received_transactions')
amount = models.DecimalField(max_digits=5, decimal_places=2)
created_on = models.DateTimeField(auto_now_add=True)
comments = models.TextField(blank=True) # Add a comments field
def __str__(self):
return f"{self.sender} to {self.receiver}: {self.amount}"
now if you want to transfer money to someone, you just need 3 parameters sender, receiver and amount.
Notes
- don’t make this publicly available route, and keep some form of 2fa authentication or JWT, as some attacks like Session Hijacking can perform unwanted transaction without user’s notice. some naughty web extensions on browser can do this mischief.
- keep session expiry very low, to reduce attack surface