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
andlast
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
andwithdrawals
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 accountsuniformly
. There should be a method that can be called to calculate the interest on the current balance using the current interest rate, andadd it
to the balance. - each deposit and withdrawal must generate a
confirmation number
composed of: - the transaction type:
D
for deposit, andW
for withdrawal,I
for interest deposit, andX
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?)