ATM machine demo with Python and Django

This is a long post. The most important information is at the top and gradually progresses to less and less relevant information. So you don’t have to read all of it. Just start reading at the top and then when you get bored, simply discard the rest.

I’m trying to build a rudimentary ATM machine demo with Python and Django.

Here is a screenshot of my site in its current form.

The intended basic functionality I am trying to create right now is to have the web visitor (banking client) enter the amount of cash in the input field and then click: “Deposit” or ”Withdraw”. Say for example the client deposits $20, the amount should appear in the HTML table under the “Credits” column and the “Running Balance” column should reflect that as well, like anyone would expect a typical ATM and basic accounting ledger to behave. The problem is, when a web visitor enters an amount and clicks “Deposit”, nothing happens.

I know the main problem is clearly with my views.py:

from django.shortcuts import render
from telagents.models import Account
 
def index(request):
   balances = 0
   withdrawals = 0
   deposits = 0
   amounts = 0
   data = Account.objects.all().order_by('-inception_date')
   context = {'data':data,'withdrawals':withdrawals,'deposits':deposits,'amount':amounts, 'balances': balances,} # initialize context var to be updated after math operations
   if 'deposits' in request.POST:  
       balances = balances + amounts
       context.update({'balances': balances,})
   elif 'withdrawals' in request.POST:
       balances  = balances - amounts
       context.update({'balance': balances,})   
   return render(request, "telagents/home.html", context)

As you can see above, I declare (initialize) the numerical variables each to integer 0. Next I establish the context variable as a dictionary. I am then trying to use conditional logic to catch cases when the template receives a POST request from the input form from the web visitor. But instead, it’s as if the deposits antecedent isn’t getting triggered so Django is effectively ignoring it and continues on to the next operation ultimately skipping down to the bottom and just passing in the original context dictionary with the variables assigned to 0. That’s not how I want my app to behave. Instead I want Django to receive a deposit from the user as a POST request and then process the basic mathematical operation (add the amount for a Deposit) and then update the balance according to client input and serve the updated balance back into the HTML table in the template. To achieve that desired outcome, I am all out of ideas on what to try next. Can anyone provide some guidance or further insight here?

Here is my template: home.html (which has its own set of issues that I already have some insight into discussed afterwards):

 <body>
{% block content %}
 
<br><br>
 
{% for data_obj in data %}
   <center>
   Client Name : {{data_obj.first_name}} {{data_obj.last_name}}
       <br>
   Bank Account Number : {{data_obj.account_number}}
       <br>       
   Interest Rate : {{data_obj.interest}} %
   </center>
{% endfor%}
 
<br><br>
 
<center>
<form action="{% url 'index' %}" method="post"> {% csrf_token %}
  <!-- <label for="fname">Merchant Name:</label>
   <input type="text" id="fname" name="fname"><br><br> -->
  <label for="amount">Amount:</label>
  <input type="number" id="amount" name="amounts"><br><br>
 
 <input type="submit" value="Deposit" name="deposits">
  <input type="submit" value="Withdraw"name="withdrawals">
 </form>
</center>
<br>
 
<!-- HTML Code: Place this code in the document's body (between the 'body' tags) where the table should appear -->
<center>
<table class="GeneratedTable">
   <thead>
   <tr>
     <th>Type</th>
     <th>Timestamp</th>
     <th>Trans ID #</th>
     <th>Debits</th>
     <th>Credits</th>
     <th>Running Balance</th>
   </tr>
 </thead>
 <tbody>
   <tr>
  
     <td>Cell</td>
    
     {% for data_obj in data %}
     <td>{{ data_obj.transaction_time_stamp }}</td>
        {% endfor %}    
    
     <td>Cell</td>
    
     <td>{{ withdrawals }}</td>
 
     <td>{{ deposits }}</td>
    
     <td>{{ balances }} </td>
 
   </tr>
 </tbody>
</table>
<!-- Codes by Quackit.com -->
 
 
 {% endblock %}
</center>
 

The problem with the above template is the withdrawals, deposits, and balances. They are just empty variables. They are not dynamic. In an effort to make them dynamic, I tried wrapping them around with variations of Jinja for loops and conditionals but no matter which combination I tried, I couldn’t get the user input to reflect inside the right columns in the table.

I’ve got my models.py configured. For what it’s worth (and for general reference), here it is:

from django.db import models
from datetime import datetime
#from pytz import timezone
import decimal
from decimal import Decimal
from random import randint
 
 
class Account(models.Model):
   interest = models.DecimalField(max_digits=6, decimal_places=3) # Decimal('0.005') # Percent
   inception_date = models.DateTimeField('Client since (inception date)')
   first_name = models.CharField(max_length=30)
   last_name = models.CharField(max_length=30)
   account_number = models.BigIntegerField()
 
   def transaction_time_stamp(self):
       return self.inception_date.strftime("%A %d %B %Y @ %I:%M:%S %p")
  
   def __str__(self):
       return f'{self.first_name} {self.last_name}'

This project started out as an exercise for a hobby course on Udemy by Fred Baptiste that I am taking on Python OOP (working CLI script below) which I decided to port to Django:

from datetime import datetime
from pytz import timezone
import decimal
from decimal import Decimal
from random import randint
 
class TimeZone:
  
   def __init__(self):
       self.selection = int(input("Howdy! Make your time zone selection: \n 1: Los Angeles \n 2: London \n 3: Shanghai \n 4: Sydney \n 5: Rio de Janeiro \n"))
       self.locality = {
           1: "US/Pacific", # Los Angeles
           2: "Europe/London", # London
           3: "Asia/Shanghai", # Shanghai
           4: "Australia/Sydney", # Sydney
           5: "Brazil/East", # Rio de Janeiro
       }  
       self.tz = datetime.now(timezone(self.locality[self.selection]))
       self.readable_format = '%Y-%m-%d %H:%M:%S %Z%z'
       print(f'The date and time: {self.tz.strftime(self.readable_format)}')
       self.transaction_time_id_format = '%Y%m%d%H%M%S'
       print(f'The date and time condensed: {self.tz.strftime(self.transaction_time_id_format)}')
  
   def condensed(self):
      return f'{self.tz.strftime(self.transaction_time_id_format)}'
 
class Account:
  
   interest = Decimal('0.005') # Percen t
 
   def __init__(self, first_name, last_name, account_num=10000001, starting_balance=0.00):
       self.first_name = first_name
       self.last_name = last_name
       self.full_name = f'{first_name} {last_name}'
       self.account_num = randint(9999999,99999999)
       self.balance = round(Decimal(starting_balance),2)
       self.transaction_id = randint(101,999)
       self.tzone = TimeZone()
 
   def deposit(self, amount):
       self.balance += round(Decimal(amount),2)
       self.transaction_id += randint(101,999) - randint(101,999)
       return f'D-{self.account_num}-{self.tzone.condensed()}-{self.transaction_id}'
  
   def withdraw(self, amount):
       if amount > self.balance:        
           self.transaction_id += randint(101,999) - randint(101,999)
           print(f'X-{self.account_num}-{self.tzone.condensed()}-{self.transaction_id}')
           raise ValueError('Transaction declined. Insufficient funds. Please deposit some more $$$ first.')
          
       self.balance -= round(Decimal(amount),2)
       self.transaction_id += randint(101,999) - randint(101,999)
       return f'W-{self.account_num}-{self.tzone.condensed()}-{self.transaction_id}'
 
   def pay_interest(self):
       monthly_rate = self.interest/12
       monthly_sum = monthly_rate * self.balance
       self.transaction_id += randint(101,999) - randint(101,999)
       print(f'I-{self.account_num}-{self.tzone.condensed()}-{self.transaction_id}')
       return round(Decimal(monthly_sum, + self.balance), 2)
 
   def __repr__(self):
       """Return a string that represents the account."""
       return f"{self.__class__.__name__}({self.last_name}, {self.first_name}, balance={self.balance})"

Below are the original specs for the above completed exercise. My goal for my Django ATM machine is to reach feature parity.

SPECS

Here are the basic specs, functionality, and characteristics as outlined by course instructor Fred Baptiste:

  • accounts are uniquely identified by an account number (assume it will just be passed in the initializer)
  • account holders have a first and last name
  • accounts have an associated preferred time zone offset (e.g. -7 for MST)
  • balances need to be zero or higher, and should not be directly settable.
  • but, deposits and withdrawals can be made (given sufficient funds)
    • if a withdrawal is attempted that would result in negative funds, the transaction should be declined.
  • a monthly interest rate exists and is applicable to all accounts uniformly. There should be a method that can be called to calculate the interest on the current balance using the current interest rate, and add it to the balance.
  • each deposit and withdrawal must generate a confirmation number composed of:
  • the transaction type: D for deposit, and W for withdrawal, I for interest deposit, and X for declined (in which case the balance remains unaffected)
    • the account number
    • the time the transaction was made, using UTC
    • an incrementing number (that increments across all accounts and transactions)
    • for (extreme!) simplicity assume that the transaction id starts at zero (or - whatever number you choose) whenever the program starts
  • the confirmation number should be returned from any of the transaction methods (deposit, withdraw, etc)
  • create a method that, given a confirmation number, returns:
    • the account number, transaction code (D, W, etc), datetime (UTC format), date time (in whatever timezone is specified in te argument, but more human readable), the transaction ID
    • make it so it is a nicely structured object (so can use dotted notation to access these three attributes)
    • I purposefully made it so the desired timezone is passed as an argument. Can you figure out why? (hint: does this method require any information from any instance?)

So just looking at the index view, you’re not saving the entered data anywhere. Yes, you’re updating the context for the template, but that information is going to be lost on the next call to that view.

Also, you’re not retrieving the data from the request. You’re checking to see if the field is in the POST, but then you’re not accessing it.

My first suggestion is that you review the docs at Working with forms | Django documentation | Django and Creating forms from models | Django documentation | Django to refresh yourself on the interaction between views, forms, and templates.