6

I have the following models in my Django application:

class Transaction (models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    account_number = models.IntegerField()
    name = models.CharField(max_length=50)
    amount = models.DecimalField(max_digits=5, decimal_places=2)
    created_on = models.DateTimeField()

class Wallet(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    account_balance = models.DecimalField(max_digits=5, decimal_places=2, default=0)

class AccountNum(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    account_number = models.IntegerField()
    slug = models.SlugField(unique=True)

I want to implement a feature where the name field in the Transactions model gets synchronized with the account owner's name based on the provided account_number input. Additionally, I want to enable money transfers using the current user's wallet and the specified amount in the Transactions model.

To provide some context, I have a post-save signal generate_account_number which generates a random 10-digit account number.

What are some recommended techniques or approaches to achieve this synchronization of the name field with the account owner's name and enable money transfers using the wallet model and specified amount in the Transaction model?

This is what I have try.

I try to use django-channels for synchronization, But I have failed to do so. I tried to copy from some people by Googling how to do that, but I don't think I can do this alone. Here is what I have tried:

consumers.py

from channels.generic.websocket import AsyncWebsocketConsumer
import json
from .models import AccountNum

class TransferConsumers(AsyncWebsocketConsumer):
    async def connect(self):
        await self.connect()

    async def disconnect(self, close_data):
        pass

    async def receive(self, text_data):
        data = json.load(text_data)
        account_number = data['account_number']

        try:
            account = AccountNum.objects.get(account_number=account_number)
            access_name = account.user.username
        except AccountNum.DoesNotExist:
            access_name = 'Name Not found'

routing.py

from django.urls import re_path
from . consumers import TransferConsumers

websocket_urlpatters = [
    re_path('/ws/access/', TransferConsumers.as_asgi()
]

The reason I didn't add my template was due to the JavaScript code required to make that happen. I don't have experience in JavaScript, but I would like to learn more about it.

views.py:

def make_transfer(request):
    if request.method == 'POST':
        account_number = request.POST.get('account_number')
        name = request.POST.get('name')
        amount = request.POST.get('amount')
    return render(request, 'Profile/make_transfer.html')
  • 1
    Is there a reason you are not just using a `models.ForeignKey` from the `Transaction` to the `Account` model? then you would not need to track the `name` on the `Transaction` at all as you could follow the relation ship back to the owner of the account. – Matthaus Woolard Aug 25 '23 at 22:34
  • @MatthausWoolard If I use a `ForeignKey` within a `Transaction` model to establish a link with the `Account` model, it implies that every user can view other people's accounts. This situation could potentially compromise the platform's security, sir. To address this issue, we might consider implementing proper access controls and permissions to restrict user access only to their relevant account information. – Adamu Dee Azare Aug 26 '23 at 05:11
  • Yer you need a proper access controle to limit what users can see the DB FK relationships do not need to map one to one with what data is exposed to users. – Matthaus Woolard Aug 27 '23 at 02:00
  • @MatthausWoolard The solution you have seen here is the only way we can use to secure the app. Will you please help us? – Adamu Dee Azare Aug 27 '23 at 08:54
  • Attempting to correctly update related models at commit time and be sure these updates take place either requires you to register PG on commit hocks (so that the update is bound within the DB to update) but in effect what you are doing then is creating a relationship between these models. I still don't understand why having a FK will en dup breaking the security. Without your server you need to write epxliclty ACL code you cant just depend on there not being a relationship, regardless of there being a relationship to a user the access controle code you write need to manage – Matthaus Woolard Aug 27 '23 at 09:20
  • @MatthausWoolard What you might not understand is that the utilization of the `account_number` in the `Transaction` model, along with the name field within it, aims to validate that the `account_number` belongs to the name indicated in the `name` field within the `Transaction` model. While establishing a relationship with the `AccountNum` model would be beneficial, the validation of the `account_number` must take place prior to allowing the user to submit the form. – Adamu Dee Azare Aug 27 '23 at 09:37
  • @MatthausWoolard and this validation should occur in real-time. – Adamu Dee Azare Aug 27 '23 at 09:39
  • If you use a FK relationship to link the transition to the account then you do not need to do any validation. The DB records the relationship and thus when ever you need access to the information you have it. – Matthaus Woolard Aug 27 '23 at 11:35
  • @MatthausWoolard I'm open to see an examples, can you show us how we can do that to achieve this synchronization of the name field with the account owner's name? – Adamu Dee Azare Aug 27 '23 at 11:52
  • @MatthausWoolard Imagine you want to transfer money to someone else. To do this, you first need to input the person's account number into the "account_number" input. The name of the account owner associated with that account number will then appear in the "name" field in real-time within the Transaction model. – Adamu Dee Azare Aug 27 '23 at 12:07
  • Sure but that is UI not anything to do with the Database. Your DB should have a FK relationship. Then in your UI when a user enters an Account number you can do a request to loopup the name using the account number (I suggest adding rate limiting to this endpoint). Also make user your account nubmers are ransoms not sequential. – Matthaus Woolard Aug 28 '23 at 03:58

1 Answers1

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
General Grievance
  • 4,555
  • 31
  • 31
  • 45
nikhil swami
  • 2,360
  • 5
  • 15
  • User can have 2 wallet, `Personal` and `Business` wallet. – Adamu Dee Azare Sep 01 '23 at 10:47
  • @AdamuDeeAzare personal_account_balance, business_account_balance , need to be added in model, as simple as that. hope using uuid, the issue with synchronization is improved? – nikhil swami Sep 01 '23 at 10:54
  • The synchronization is needed in real time, Imagine you want to transfer money to someone else. To do this, you first need to input the person's account number into the "account_number" input in the Transaction model. The name of the account owner associated with that account number will then appear in the "name" field in real-time within the Transaction model. – Adamu Dee Azare Sep 01 '23 at 12:48
  • This include verifying the account format, checking the recipient's name, and confirming the account is belong to the recipient. – Adamu Dee Azare Sep 01 '23 at 12:53
  • Please stop with the icons. They don't belong in posts and just add noise. – General Grievance Sep 01 '23 at 15:27