[Django]-Implementing Name Synchronization and Money Transfers in Transactions Model with Account Number Input

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

Leave a comment