Django Query two Models and Display Records in One HTML Table

Question:

I am working on a Django Daily Saving Project where I have Statement view and I want to display a Customer’s Deposits and Withdrawals (all his deposits and withdrawals) in one HTML Table. I am looking at the Best Performance (Constant Complexity for Big O Notation if possible in this case). I don’t know whether there is another way of displaying records in a table from a Model other than using a For Loop. If there is, then your kind answer is also welcome.
Here are my Models:

class Deposit(models.Model): 
    customer = models.ForeignKey(Profile, on_delete=models.CASCADE, null=True)
    transID = models.CharField(max_length=12, null=True)
    acct = models.CharField(max_length=6, null=True)
    staff = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    deposit_amount = models.PositiveIntegerField(null=True) 
    date = models.DateTimeField(auto_now_add=True) 



    def get_absolute_url(self):
        return reverse('create_account', args=[self.id])

    def __str__(self):
        return f'{self.customer} Deposited {self.deposit_amount} by {self.staff.username}'

class Witdrawal(models.Model): 
    account = models.ForeignKey(Profile, on_delete=models.CASCADE, null=True)
    transID = models.CharField(max_length=12, null=True)
    staff = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    withdrawal_amount = models.PositiveIntegerField(null=True)
    date = models.DateTimeField(auto_now_add=True)


    def __str__(self):
        return f'{self.account}- Withdrawn - {self.withdrawal_amount}'

Here is my view:

def account_statement(request, id):
    try:
            customer = Account.objects.get(id=id)
            #Get Customer ID
            customerID = customer.customer.id
        
    except Account.DoesNotExist:
            messages.error(request, 'Something Went Wrong')
            return redirect('create-customer')
    else:
       deposits = Deposit.objects.filter(customer__id=customerID).order_by('-date')[:5]
       #Get Customer Withdrawal by ID and order by Date minimum 5 records displayed
       withdrawals = Witdrawal.objects.filter(account__id=customerID).order_by('-date')[:5]
       context = {
            'deposits ':deposits ,
            'withdrawals ':withdrawals,
       }
       return render(request, 'dashboard/statement.html', context)

My HTML Template Code:

<table class="table bg-white">
                    <thead class="bg-info text-white">
                      <tr>
                        <th scope="col">#</th>
                        <th scope="col">Acct. No.</th>
                        <th scope="col">Phone</th>
                        <th scope="col">Amount</th>
                        <th scope="col">Date</th>
                        <th scope="col">Action</th>
                      </tr>
                    </thead>
                    {% if deposits %}
                    <tbody>
                    
                      
                      {% for deposit in deposits %}  
                      <tr>
                        <td>{{ forloop.counter }}</td>
                        <td>{{ deposit.acct }}</td>
                        <td>{{ deposit.customer.phone }}</td> 
                        <td>N{{ deposit.deposit_amount | intcomma }}</td>
                        <td>{{ deposit.date | naturaltime }}</td>
                        
                        <th scope="row"><a class="btn btn-success btn-sm" href="{% url 'deposit-slip' deposit.id %}">Slip</a></th>
                      </tr>
                      {% endfor %}  
                                 
                    </tbody>
                    {% else %}
                    <h3 style="text-align: center; color:red;">No Deposit Found for {{ customer.customer.profile.surname }} {{ customer.customer.profile.othernames }}</h3>
                    {% endif %}
                </table>

Please, understand that I am able to display only the customer’s Deposit in the above table but don’t know how to display both the Deposit and Withdrawal of the customer in this same table. Thanks

Asked By: apollos

||

Answers:

Change the context variable inside of your views to:

context = {"deposits_and_withdrawls": zip(deposits, withdrawls)}

Careful: This only works when depsits, as a list, is as long as widthdrawls, also a list.

Change your html table to the following syntax:

<tbody>
{% for deposit, withdrawl in deposits_and_withdrawls %}  
<tr>
  <td>{{ forloop.counter }}</td>
  <td>{{ deposit.acct }}</td>
  <td>{{ deposit.customer.phone }}</td> 
  <td>N{{ deposit.deposit_amount | intcomma }}</td>
  <td>{{ deposit.date | naturaltime }}</td>
  <th scope="row"><a class="btn btn-success btn-sm" href="{% url 'deposit-slip' deposit.id %}">Slip</a></th>
</tr>
<tr>
  <td>{{ forloop.counter }}</td>
  <td>withdrawl.account</td>
  <td>withdrawl.transID</td>
  <td>withdrawl.staff</td>
  <td>withdrawl.withdrawal_amount</td>
  <td>withdrawl.date</td>
</tr>
{% endfor %}  
</tbody>

Let me know if this works for you.

Edit:

When you have passed a list/queryset to your context variable, you can access each item individually avoiding a for-loop. But for sure this gets very tedious.

context = {
    "item_list": ["ape", "whale", "giraffe"],
    "deposits": deposits
}

html-template:

<p>I can access {{ item_list.2 }} before {{ item_list.0 }}. Same works for your deposits if you know the index. {{ deposits.4 }} is one single deposit in your deposits-query.</p>
Answered By: Tarquinius

In addition to using Python’s zip function as answered by @Tarquinius or zip_longest() as I mentioned in one of my comments above, we can use a conditional statement on the list items extracted from the zip function in the for loop to prevent any of them from displaying empty Table rows. This is because once any of the extracted list items in the loop happens not to contain any data them we would have a number of empty table rows equal to the number of the displayed table data. so in order to solve this challenge I have used an if statement to check whether the second item’s (withdrawal) TransactionID (transID) in the loop is available because Withdrawal depends on Deposit. i.e if there is no Deposit then there will be no Withdrawals because chances are that a customer could have Deposits without withdrawal yet. See code as shown below:

{% if deposits_and_withdrawals %}
                    <tbody>
                    
                      
                      {% for deposit, withdrawal in deposits_and_withdrawals %} 
                      
                      
                      <tr>
                        <td style="background-color:rgba(231, 232, 233, 0.919); color:blue;">Deposit - </td>
                        <td style="background-color:rgba(231, 232, 233, 0.919)">{{ deposit.acct }}</td>
                        <td style="background-color:rgba(231, 232, 233, 0.919)">{{ deposit.transID }}</td> 
                        <td style="background-color:rgba(231, 232, 233, 0.919)">N{{ deposit.deposit_amount | intcomma }}</td>
                        <td style="background-color:rgba(231, 232, 233, 0.919)">{{ deposit.date | naturaltime }}</td>
                        
                        <th scope="row" style="background-color:rgba(231, 232, 233, 0.919)"><a class="btn btn-success btn-sm" href="{% url 'deposit-slip' deposit.id %}">Slip</a></th>
                      </tr>
                      
#Check if there is a Withdrawal Transaction ID in the Withdrawal Model
                      {% if withdrawal.transID %}
                      <tr style="color: red;">
                        <td>Withdrawal - </td>
                        <td>{{ withdrawal.account.surname }}</td>
                        <td>{{ withdrawal.transID }}</td> 
                        <td>N{{ withdrawal.withdrawal_amount | intcomma }}</td>
                        <td>{{ withdrawal.date | naturaltime }}</td>
                        
                        <th scope="row">
                          
                          <a class="btn btn-success btn-sm" href="  ">Slip</a>
                          
                        </th>
                        
                      </tr>
                      {% endif %}

                      {% endfor %}  
                                 
                    </tbody>
                    {% else %}
                    <h3 style="text-align: center; color:red;">No Deposit/Withdrawal Found for {{ customer.customer.profile.surname }} {{ customer.customer.profile.othernames }}</h3>
                    {% endif %}

Please, let me know if there is any unseen shortcoming with my solution to this issue with Python zip or zip_longest function.

Answered By: apollos
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.