Movatterモバイル変換


[0]ホーム

URL:


How to Make an Accounting App with Django in Python

Learn how to build an accounting application using the Django framework and Bootstrap in Python.
  · 22 min read · Updated feb 2023 ·Finance ·Web Programming

Struggling with multiple programming languages? No worries. OurCode Converter has got you covered. Give it a go!

Financial accounting is mainly all about recording an organization or individual's overall monetary transactions. Each recording is made within each month and will be repeated the following month. This process of financial accounting helps people or organizations to track their spending, on what gains increase or decrease whether it is assets, liabilities, or capital. In this tutorial, I will show you how to build an accounting app and how to use it, by focusing on the important parts which are:

Django has built-in class views to help us make Django views easier. We can also make functional views but in this situation, the Django class-based views is the best option for making this app.

Django Installation and Setup

Before coding, let's have our environment first, best to find a perfect path for it on your terminal.

Let's start by making our app directory folder. I name mine asaccounting-app. Inside theaccounting-app dir, let's start a Python virtual environment using our terminal:

$ python -m venv env

To start the environment on Windows, type:

.\env\Scripts\activate

And for Linux and Mac:

$ source env/bin/activate

Next, let's install Django

$ pip install django

To start a Django project and app in the terminal, type:

# starting a project$ djangoadmin startproject base .# starting an app$ python manage.py startapp app

As you see here, the name of our project isbase and our app isapp. To connect ourapp withbase, we need to configure it. Openbase/settings.py and inINSTALLED_APPS, listapp.apps.AppConfig:

And to the lowermost part ofbase/settings.py, add:

LOGIN_URL = 'login'DATE_INPUT_FORMATS = ['%d-%m-%Y']

This will prevent us from getting errors in our authentication page later and give our app a date format of "Day/Month/Year" instead of the default "Year/Month/Date".

Next, let's connect thebase/urls.py to ourapp/urls.py. Inbase/urls.py, importinclude and list ourapp/urls.py inurlspattern:

# base/urls.pyfrom django.urls import includeurlpatterns [    ...,    path('', include('app.urls')),]

Now, createurls.py inapp directory and type:

# base/urls.pyfrom django.urls import pathurlpatterns = []

We'll use it later to give our Django Views links for accessing them.

Setting up the Templates and Design

For the template directories, create two other directories inside the app directory. The first one should name templates and inside it, create another directory with the same name given to our app name directory. The directory should look like this:

accounting-app/--app/  --templates/    --app/--base/

Head toapp/templates/app/ and create abase.html file. It will serve as the mother file of our templates, so it would be easy for us to do CSS and separate different sections that we also use for the rest of our templates. Inbase.html, type:

<!-- app/templates/app/base.html --><!DOCTYPE html><html><head>    <title>{% block title %}{% endblock %}</title>        <meta charset="utf-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous"></head><body>    <div>        <button>Home</button>        <button>Add Portfolio</button>        <button>Log out</button>    </div>  {% if request.user.is_authenticated %}      <h1>{{request.user}}</h1>      <img src="https://cdn-icons-png.flaticon.com/512/552/552721.png">  {% else %}      <a href="{% url 'login' %}">Login</a>  {% endif %}  {% block content %}{% endblock %}</body>

Next, create another file in the same directory and name itauth_base.html. Then type:

<!-- app/templates/app/auth_base.html --><!DOCTYPE html><html><head>    <title>{% block title %}{% endblock %}</title>    <meta charset="utf-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous"></head><body>    <div>        {% block content %}{% endblock %}    </div></body></html>

For the designs of our templates, we're gonna use Bootstrap for the styling.

Creating Models

The first thing we need for our app is the model classes. We need two model classes for this app (one for making a portfolio and another one for making transactions for portfolios).

# app/models.pyfrom django.db import modelsfrom django.contrib.auth.models import User# Create your models here.class Portfolio(models.Model):    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)    name = models.CharField(max_length=30)        def __str__(self):        return self.nameclass Transaction(models.Model):    journal_list = models.ForeignKey(Portfolio,on_delete=models.CASCADE)    trans_name = models.CharField(max_length=30)    trans_type = models.CharField(max_length=3)    amount = models.IntegerField()    date = models.DateField()    def __str__(self):        return self.trans_name

We have auser fields in ourPortfolio to give each portfolio we make an owner. TheTransaction model has fields such as;journal_list to identify which portfolio it belongs,trans_name or the formal transaction name of each transaction,trans_type to figure out whether our transaction is'dbt' (debit) or'cdt' (credit),amount of the transaction, anddate.

Now let's register our models inapp/admin.py so we are able to see their content on our admin page:

# app/admin.pyfrom django.contrib import adminfrom .models import Portfolio, Transaction# Register your models here.admin.site.register(Portfolio)admin.site.register(Transaction)

Sign In, Sign Up, and Sign out

In this section, we'll makeDjango authentication. We want each user to have their own set of portfolios so it's easy for them to find theirs and also to provide privacy for their transactions. So the first two class views we're gonna make our views for Login and Signup. In app/views.py importLoginView and createUserLogin class:

# app/views.pyfrom django.contrib.auth.views import LoginViewclass UserLogin(LoginView):    template_name = 'app/signin.html'    fields = '__all__'    redirect_authenticated_user = True

TheLoginView provides us with built-in user authentication. As you notice, thefields are set to '__all__'. It only accesses the'username' fields and'password' fields since in our signup-views, we only give it two fields which are the 'username' and 'password' fields.

Theredirect_authenticated_user disallows users to access the login page once an account is signed in already. Now that our login view is ready, let's create a template for it:

<!-- app/templates/app/signin.html -->{% load static %}<!DOCTYPE html><html><head>    <title>Sign in</title>    <link rel="stylesheet" type="text/css" href="{% static 'app/account-auth.css' %}"></head><body>    <h1>Log In</h1>    {% for message in messages %}        <p>{{message}}</p>    {% endfor %}    <form method="post">        {% csrf_token %}        <fieldset>            {{form.as_p}}        </fieldset>        <button type="submit" name="signin">Sign in</button>        <button>Create your Account</button>    </form></body></html>

As you noticed oursignin.html andbase.html had different CSS files (index.css forbase.html andaccount-auth.css forsignin.html. I choose to separate designs for sign-in/sign-up and our app's main contents. We currently don't have a CSS file for this template but it will work.

Now that we have views for log-in, we also have to make a view for the sign-up page. Back to ourapp/views.py type:

# app/views.pyfrom django.views.generic.edit import FormViewfrom django.contrib.auth.forms import UserCreationFormfrom django.contrib.auth import loginclass UserSignup(FormView):    template_name = 'app/signup.html'    form_class = UserCreationForm    redirect_authenticated_user = True    success_url = reverse_lazy('pfl-list')    def form_valid(self, form):        user = form.save()        if user is not None:            login(self.request, user)        return super(UserSignup, self).form_valid(form)    def get(self, *args, **kwargs):        return super(UserSignup, self).get(*args, **kwargs)

Ourform_class with the value ofUserCreationForm has fields ofusername andpassword by default. Thesuccess_url sends us directly to a landing page.

Theform_valid function saves our user and logging in us into our app automatically once we finished making an account.

For ourUserSignup templates:

<!-- app/templates/app/signup.html -->{% load static %}<!DOCTYPE html><html><head>    <title>Sign Up</title>    <link rel="stylesheet" type="text/css" href="{% static 'app/account-auth.css' %}"></head><body>    <h1>Sign Up</h1>     {% for message in messages %}        <p>{{message}}</p>    {% endfor %}    <form method="post">        {% csrf_token %}        <fieldset>            {{form.as_table}}        </fieldset>        <button type="submit" name="signup">Sign Up</button>        <button>Login your Account</button>    </form></body></html>

Now it's time to make link URLs forUserSignup andUserLogin so we can access them in our browser:

# app/urls.pyfrom .views import UserSignup, UserLoginurlpatterns = [    path('signup/', UserSignup.as_view(), name='signup'),    path('login/', UserLogin.as_view(), name='login'),]

Now that we have class views for sign-in and sign-up, let's create another view for log-out. The log-out views are way easier to create. In ourapp/urls.py, importLogOutView and give log-out a URL inurlspattern and on the next page it will redirect.

from django.contrib.auth.views import LogoutViewurlpatterns = [    ...,    path('logout/', LogoutView.as_view(next_page='login'), name='logout'),]

Related:How to Build an Authentication System in Django.

Making the Home Page

# app/views.pyfrom django.views.generic import Viewfrom django.contrib.auth.models import Userfrom django.contrib.auth.mixins import LoginRequiredMixinclass PortfolioList(LoginRequiredMixin,View):    def get(self,request):        account = User.objects.get(username=request.user)        context = {'user':account}        return render(request,'app/home.html',context)

As you noticed, we give our app two arguments which are theView (to use the built-in class-views) and theLoginReqiredMixin (to stop our app from accessing the home page if there's no logged-in account).

Our home page will show us the list of portfolios our user has. Theaccount variable handles accessing the currently authenticated user's objects and thencontext converts these objects into dictionaries so our HTML template will able to read them. To finish this class, we'll returnrender(request, 'app/home.html', context) to connect this class with its template and send objects to it.

For ourPortfolioList's templates, createhome.html file insideapp/templates/app/:

<!-- app/templates/app/home.html -->{% extends "app/base.html" %}{% block title %}Home{% endblock %}{% block content %}    <br><br><h2>MY PORTFOLIO LIST</h2><br>    <table>        {% for pfl in user.portfolio_set.all %}            <tr>                <td><h3><a href="{% url 'pfl-detail' pfl.id %}">{{pfl.name}}</a><h3></td>                <td><button>Delete</button></td>            </tr>        {% endfor %}    </table>{% endblock %}

Thehome.html uses ourbase.html by importing it:

{% extends "app/base.html" %}

And give its fields respected value of their own, just like what we did in{% block title %}Home{% endblock %}, it will set its page title to 'Home'.

For theblock content, we give it the main content of our page. If take a closer look, you can notice we can now iterate the objectuser which we configure from ourPortfolioList view.

For every portfolio name, we coupled them with ana tag which leads us to that portfolio's details, or better we call journal entries. The delete buttons will delete the said portfolio base on its portfolio's id.

And to complete it, let's give it a URL inurls.py:

# app/urls.pyfrom .views import UserSignup, UserLogin, PortfolioListurlpatterns = [    ...,    path('', PortfolioList.as_view(), name='pfl-list'),]

Now that we have our landing page, let's add another function inUserSignup andUserLogin so the app will be automatically connected to the home page after logging in or signing up.

Theget_success_url will send us to the Home page ('pfl-list') once we're authenticated:

# app/views.pyclass UserLogin(LoginView):    template_name = 'app/signin.html'    fields = '__all__'    redirect_authenticated_user = True    def get_success_url(self):        return reverse_lazy('pfl-list')

Let's modify theget function inUserSignup to let it send us to the home page once we finished making an account:

# app/views.pyclass UserSignup(FormView):    ...    def form_valid(self, form):        ...    def get(self, *args, **kwargs):        if self.request.user.is_authenticated:            return redirect('pfl-list')        return super(UserSignup, self).get(*args, **kwargs)

Let's try to run our server in the terminal by typingpython manage.py runserver in our app directory, and in our browser, type127.0.0.1:8000 orlocalhost:8000. This URL will send us to our home page but since there's no user or account authenticated yet, it will send us to the login page.

The Login page should look like this:

The Signup page should look like this:

And the home page should look like this:

There's nothing here on this page since we currently don't have any portfolio. Now, let's make another class-view for making portfolios.

Creating Portfolios

Inapp/views.py, let's make a class, we callPortfolioCreate for creating portfolios:

class PortfolioCreate(LoginRequiredMixin,View):    def get(self,request):        return render(request,'app/portfolio_create_form.html')    def post(self,request):        user = User.objects.get(username=request.user)        pfl_name = request.POST.get('portfolio_name')        user.portfolio_set.create(name=pfl_name)        my_object = user.portfolio_set.get(name=pfl_name).id        return redirect('pfl-detail', my_object)

As you see in thepost() function inlineuser.portfolio_set.create(name=pfl_name), we create each portfolio through the user instead of doing it directly with thePortfolio model we made. Why? Because if we make a portfolio directly, we're not able to give it auser field and this portfolio will show on other users' accounts.

After we create a portfolio, it will automatically send us to this portfolio's detail by redirecting to the class view with the URL name of'pfl-detail', and takingmy_object as its argument. We currently don't have a class view for that but we'll get to write it along in this tutorial.

Make another HTML file namedhome.html insideapp/templates/app/:

<!-- app/templates/app/portfolio_create_form.html -->{% extends "app/base.html" %}{% block title %}Person Create{% endblock %}{% block content %}    <br><br><h2>Create New Portfolio</h2><br>    <form method="post" action="">        {% csrf_token %}        <input type="textbox" name="portfolio_name" placeholder="Portfolio Name">        <input type="submit" value="Create">    </form>{% endblock %}

And for a final touch inPortfolioCreate, let's give it its own URL inapp/urls.py:

# app/urls.pyfrom .views import PortfolioCreateurlpatterns = [    ...,    path('pfl-create/', PortfolioCreate.as_view(), name='pfl-create'),]

Now if we go tolocalhost:8000/pfl-create/, we can see:

Each portfolio serves as a transaction container for every business/organization/person's transaction. I suggest naming each portfolio after the organization we select to track transactions.

Deleting Portfolios

Back in ourapp/views.py, create another class-view namedPortfolioDelete which we'll use to delete selected portfolio/s. We're gonna use the Django built-inDeleteView for this one:

# app/views.pyfrom django.views.generic.edit import DeleteViewclass PortfolioDelete(LoginRequiredMixin,DeleteView):    template = 'app/portfolio_confirm_delete.html'    model = Portfolio    success_url = reverse_lazy('pfl-list')

Just like other views except for views for authentication, we add aLoginRequiredMixin to this view so we can only access it if we are signed in.

Inapp/templates/app, create an HTML templateportfolio_confirm_delete.html that extends ourbase.html:

<!-- app/templates/app/portfolio_confirm_delete.html -->{% extends 'app/base.html' %} {% block title %}Delete Confirmation{% endblock %}{% block content %}    <br><br><h2>Delete Confirmation</h2><br>    <form method="post">        {% csrf_token %}        <p>Are you sure you want to delete "{{object}}"?</p>        <input type="submit" value="Confirm">    </form>{% endblock %}

And now forurls.py:

# app/urls.pyfrom .views import PortfolioDeleteurlpatterns = [    ...,    path('pfl-delete/pk=<int:pk>', PortfolioDelete.as_view(), name='pfl-delete'),]

The main content of this page is the input tag inside the form tag inblock content. It serves as confirming button if we really want to delete the chosen portfolio or not. If you hit the confirm button and head back to the Home page, you'll notice it worked:

How to Journal

Increases in assets (such as cash, equipment, furniture, etc) and receivables should be listed on the debit side or debt while increases in liabilities, payables, and capital should be listed on the credit side and vise-versa. Decreases in assets and receivables should be listed on the credit side while decreases in liabilities, payables, and capital should be listed on the cebit side or debt.

In making journal entries, there's always a debit and credit in each transaction to balance it. Let's say you spent $300 for equipment (asset) in a transaction. Making an increase in assets is also an increase in capital/revenue so it will list in Journal as:

If you pay a debt from accounts payable worth $430 through cash, the transaction will be:

And if you earned $700 from your work and received $400 cash and the $300 will be paid via account, the transaction will be:

To make this transaction in our app, you can do:

Note: If you find financial accounting pretty confusing, it's normal.Basics may help you understand more about financial accounting.

Making Portfolio Journal

As I said earlier, a portfolio is just a folder for each transaction we do. Now that we have that folder, let's make a view for making transactions in our portfolio. Create a class namedJournal inapp/views.py. We're gonna use the built-inDetailView for this one so we can access objects inside our portfolio:

# app/views.pyfrom django.views.generic.detail import DetailViewclass Journal(LoginRequiredMixin,DetailView):    model = Portfolio    template_name = 'app/journal.html'    context_object_name = 'pfl'    def get(self,*args,**kwargs):        return super(Journal, self).get(*args,**kwargs)    def post(self,*args,**kwargs):        return super(Journal, self).get(*args,**kwargs)

This will access our portfolio transaction contents but does not allow us to add transactions. Let's add a new function in theJournal class nameddispatch. We name our class-view functions as request calls by syntax. What thedispatch calls do is allow us to access the data sent in our app (data from post calls), so we can not just access it but also modify it.

    def dispatch(self,request,pk,*args,**kwargs):        dbt_trans, dbt_amt = request.POST.get('dbt'), request.POST.get('dbt-amt')        cdt_trans, cdt_amt = request.POST.get('cdt'), request.POST.get('cdt-amt')        trans_date = request.POST.get('trans-date')        pfl = self.model.objects.get(id=pk)        if self.request.POST.get('save'):            try:                if dbt_trans and dbt_amt and cdt_trans and cdt_amt != None:                    dbt_whole_trans = pfl.transaction_set.create(trans_name=dbt_trans, trans_type='dbt', amount=dbt_amt, date=trans_date)                    cdt_whole_trans = pfl.transaction_set.create(trans_name=cdt_trans, trans_type='cdt', amount=cdt_amt, date=trans_date)                    dbt_whole_trans.save()                    cdt_whole_trans.save()                    print(True)            except:                return super(Journal, self).dispatch(request,*args,**kwargs)        return super(Journal, self).dispatch(request,*args,**kwargs)

Now that we have adispatch() function, we can finally add transactions inside our journal portfolios. The next thing we have to do is to make a template for it so we can render the transactions we have as journal entries:

<!-- app/templates/app/journal.html -->{% extends 'app/base.html' %}{% load app_extras %}{% block title %}Details{% endblock %}{% block content %}    <br><br><h2>{{pfl.name}}'s Portfolio</h2><br>    <br>    <form method="post" action="">        {% csrf_token %}        <select name="dbt">            <option disabled="" selected="" value="">Select Debit Transaction type</option>            <option value="asset">Asset</option>            <option value="liability">Liability</option>            <option value="capital">Capital</option>            <option value="accounts receivable">Accounts Receivable</option>            <option value="accounts payable">Accounts Payable</option>            <option value="notes receivable">Notes Receivable</option>            <option value="notes payable">Notes Payable</option>        </select>        <input type="number" name="dbt-amt" placeholder="Enter Debit amount...">        <br>        <select name="cdt">            <option disabled="" selected="" value="">Select Credit Transaction type</option>            <option value="asset">Asset</option>            <option value="liability">Liability</option>            <option value="capital">Capital</option>            <option value="accounts receivable">Accounts Receivable</option>            <option value="accounts payable">Accounts Payable</option>            <option value="notes receivable">Notes Receivable</option>            <option value="notes payable">Notes Payable</option>        </select>        <input type="number" name="cdt-amt" placeholder="Enter Credit amount...">        <br>        <input type="date" name="trans-date">        <br>        <input type="submit" name="save" value="Save">    </form>    <button>Trial Balance</button>{% endblock %}

For every debit we add, there should be a credit to be added as a counterbalance to the transaction. We give both debit and credit a field of transaction types.

Let's add it inurls.py:

# app/urls.pyfrom .views import Journalurlpatterns = [    ...,    path('pfl-journal/pk=<int:pk>', Journal.as_view(), name='pfl-detail'),]

If we check it in our browser, our journal page looks like this:

We currently don't have a visual representation for our Journal entries. We can directly make a table that represents our Journal entries by accessing our objects but it's a bit of a hustle to do. Instead, let's can create a template extension of Django, a separate function which able to access our objects. This template extension of Django is calledtemplatetags.

What ourtemplatetag do in this app is to access the object and pass its values to our page's table.

Inside theapp/ directory, create atemplatetags/ directory, and inside it, create a.py file namedapp_extras.py.

from django import templateregister = template.Library()

As you see above, the first thing we should do is importtemplate from Django and register each listed extension we do inregister. After that, we can start coding extensions. Let's call our tagjournal_table:

@register.simple_tag(takes_context=True)def journal_table(context, pfl):    request = context.get('request')        trans_table = []    debit_total, credit_total = 0, 0    for trans in pfl.transaction_set.all():        if trans.trans_type == 'dbt':            trans_table.append((trans.date, trans.trans_name, trans.amount, ''))            debit_total += trans.amount        else:            trans_table.append((trans.date, trans.trans_name, '', trans.amount))            credit_total += trans.amount    context = {'tbl': trans_table, 'dt': debit_total, 'ct': credit_total}    return context

Thepfl argument we need stands for "Portfolio". What thejournal_table() function does is parse thepfl data and deliver it tojournal.html, fit the table and prepare for it. The decorator above the journal table,@register.simple_tag(takes_context=True) registers our function to theregister variable.

Back tojournal.html, create a table insideblock content below the Page title:

{% block content %}    <br><br><h2>{{pfl.name}}'s Portfolio</h2><br>    {% journal_table pfl as jt %}    <table>        <thead>            <tr>                <td>Date</td>                <td>Transaction</td>                <td>Debit</td>                <td>Credit</td>            </tr>        </thead>        <tbody>            {% for transaction in jt.tbl %}                <tr>                    {% for items in transaction %}                        <td>{{items}}</td>                    {% endfor %}                </tr>            {% endfor %}            <tr>                <td></td>                <td>Total:</td>                <td>{{jt.dt}}</td>                <td>{{jt.ct}}</td>            <tr>        </tbody>    </table>    ...

Now if we reload the page, we can finally see a table of Journal Entries.

Trial Balance

Let's start by making two main functions outside theTrialBalance class; thetrial_balance_computer function and thet_accounts function:

def trial_balance_computer(pk):    pfl = Portfolio.objects.get(id=pk)    trans_total = {}    tb_table = []    tb_total = [0, 0]    for trans in pfl.transaction_set.all():        if trans.trans_name not in trans_total:            trans_total[trans.trans_name] = 0        if trans.trans_type == 'dbt':            trans_total[trans.trans_name] += trans.amount        else:            trans_total[trans.trans_name] -= trans.amount    for x in trans_total:        if trans_total[x] > 0:            tb_table.append((x, trans_total[x], ''))            tb_total[0] += trans_total[x]        elif trans_total[x] < 0:            tb_table.append((x, '', trans_total[x]))            tb_total[1] += trans_total[x]    tb_table.append(('Total:', tb_total[0], tb_total[1]))    return pfl.name, tb_tabledef t_accounts(pk):    pfl = Portfolio.objects.get(id=pk)    ledger = {}    for trans in pfl.transaction_set.all():        if trans.trans_name not in ledger:            ledger[trans.trans_name] = []        if trans.trans_type == 'dbt':            ledger[trans.trans_name].append(trans.amount)        else:            ledger[trans.trans_name].append(-trans.amount)    return ledger

The trial balance is the accounting representation in which all of the debits (positive value, increases) are added and credits (negative values, decreases) are subtracted. In simple words, it's like the adjusted or computed Journal entries.

The T-accounts add all the debit transactions and subtract all the credit transactions for each transaction name. For example, we have:

We will have T-accounts computation for assets, capital, and notes payable but let's focus on capital's T-accounts instead of doing all:

To compute it, sum first each column and then, subtract D (debit) to C (credit). If the answer is positive, it will be listed in the Debit column of our Trial balance and if negative, it will be listed in the Credit column.

What it looks like in Trial Balance:

And yes, the Trial balance's contents are all from T-accounts.

Thetrial_balance_computer() simply accesses the portfolio contents from the database through the models and calculates it. Meanwhile, thet_accounts is a visual representation of the computation of how we end up having that Trial balance. But computing it up straight will do it easier so we just do thet_accounts() function for computation representation and havetrial_balance_computer() to compute it up.

For ourTrial_Balance class, we are using the defaultView so we can easily access the functions we just create:

class TrialBalance(LoginRequiredMixin, View):    def get(self, request, pk):        tb = trial_balance_computer(pk)        ta = t_accounts(pk)        ta_JSON = dumps(ta)        context = {'pk':pk, 'name':tb[0], 'tb':tb[1], 'ta':ta_JSON}        return render(request, 'app/trialbalance.html', context)

Moving to our templates, createtrialbalance.html inside theapp/templates/app/ folder.

{% extends "app/base.html" %}{% load app_extras %}{% block title %}Trial Balance{% endblock %}{% block sidenav %}    <button>Home</button>    <button>Add Portfolio</button>    <button>Journal</button>    <button>Log out</button>{% endblock %}{% block content %}    <br><br>    <div>        <h2>{{name}}'s Trial Balance</h2>        <table>            <tr>                <th>Transaction</th>                <th>Debit</th>                <th>Credit</th>            </tr>            {% for trans in tb %}                <tr>                    {% for item in trans %}                        <td>{{item}}</td>                    {% endfor %}                </tr>            {% endfor %}        </table>    </div>    <br><br>    <div>        <h2>{{name}}'s T Accounts</h2>    </div>    <script>        var tAccount = JSON.parse("{{ta|escapejs}}");        dataNode = document.getElementById('t-account');        for (const trans in tAccount) {            var sum = 0;            const transTable = document.createElement("table");            const transCaption = document.createElement("caption");            const transRow = document.createElement("tr");            const transDb = document.createElement("th");            const transCd = document.createElement("th");            transCaption.innerText = trans;            transDb.innerText = 'Debit';            transCd.innerText = 'Credit';            transRow.appendChild(transDb);            transRow.appendChild(transCd);            transTable.appendChild(transCaption);            transTable.appendChild(transRow);            tAccount[trans].forEach (data => {                sum = sum + data;                const tableRow = document.createElement("tr");                const tableDb = document.createElement("td");                tableDb.className = "debit"                const tableCd = document.createElement("td");                tableCd.className = "credit"                if (data > 0 ) {                    tableDb.innerText = data;                    tableCd.innerText = '';                }                else if (data < 0 ) {                    tableDb.innerText = '';                    tableCd.innerText = data;                }                tableRow.appendChild(tableDb);                tableRow.appendChild(tableCd);                transTable.append(tableRow);            });              dataNode.appendChild(transTable);            ttl = document.createElement("p");            ttl.innerText = `Total = ${sum}`;            dataNode.appendChild(ttl);            spc = document.createElement("br");            dataNode.appendChild(spc);        }    </script>{% endblock %}

This time, I put some Javascript code for dealing with T-accounts representation instead of having another decorator show you another way of parsing objects.

A few more steps to do and were close to finishing this app. Let's add a URL for it:

# app/urls.pyfrom .views import TrialBalanceurlpatterns = [    ...,    path('pfl-tb/pk=<int:pk>', TrialBalance.as_view(), name='trial-balance')]

Let's check our browser to see our work.

Template Designing

Our app is finished and it's time now to style it. As you remember, we linked the Bootstrap styles in ourbase.html andauth_base.html which is connected to our templates. Styling them is easier now since some designs are also present in others, as are other elements such as the navbar. To use the Bootstrap styles, use their class name for a specific design. Let's update our templates with proper class designs.

base.html:

<!DOCTYPE html><html><head>    <title>{% block title %}{% endblock %}</title>    <meta charset="utf-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous"></head><body>    <nav>        <ul>            <li>                <a href="{% url 'pfl-list' %}">Home</a>            </li>            <li>                <a href="{% url 'pfl-create' %}">Add Portfolio</a>            </li>            <li>                <a href="{% url 'logout' %}">Log out</a>            </li>        </ul>    </nav>    <div>        <img src="https://cdn-icons-png.flaticon.com/512/552/552721.png">        <span>{{request.user}}</span>    </div>    <div>        {% block content %}{% endblock %}    </div></body>

home.html:

{% extends "app/base.html" %}{% block title %}Home{% endblock %}{% block content %}    <div>        <h2>MY PORTFOLIO LIST</h2>        <table>            {% for pfl in portfolio.portfolio_set.all %}                <tr>                    <td>                        <h3>                            <a href="{% url 'pfl-detail' pfl.id %}">{{pfl.name}}</a>                        </h3>                    </td>                    <td>                        <button>Delete</button>                    </td>                </tr>            {% endfor %}        </table>    </div>{% endblock %}

portfolio_create_form.html:

{% extends "app/base.html" %}{% block title %}Portfolio Create{% endblock %}{% block content %}    <div>        <h2>Create New Portfolio</h2>        <form method="post" action="">            {% csrf_token %}            <div>                <input type="textbox" name="portfolio_name" placeholder="Portfolio Name">                <input type="submit" value="Create">            </div>        </form>    </div>{% endblock %}

portfolio_confirm_delete.html:

{% extends 'app/base.html' %} {% block title %}Delete Confirmation{% endblock %}{% block content %}    <div>        <h2>Delete Confirmation</h2>        <form method="post">            {% csrf_token %}            <p>Are you sure you want to delete "{{object}}"?</p>            <input type="submit" value="Confirm">        </form>    </div>{% endblock %}

journal.html:

{% extends 'app/base.html' %}{% load app_extras %}{% block title %}Details{% endblock %}{% block content %}<div>    <h2>{{pfl.name}}'s Portfolio</h2>    {% journal_table pfl as jt %}    <div>        <table>            <thead>                <tr>                    <td>Date</td>                    <td>Transaction</td>                    <td>Debit</td>                    <td>Credit</td>                </tr>            </thead>            <tbody>                {% for transaction in jt.tbl %}                    <tr>                        {% for items in transaction %}                            <td>{{items}}</td>                        {% endfor %}                    </tr>                {% endfor %}                <tr>                    <td></td>                    <td>Total:</td>                    <td>{{jt.dt}}</td>                    <td>{{jt.ct}}</td>                </tr>            </tbody>        </table>    </div>    <form method="post" action="">        <div>            {% csrf_token %}            <div>                <label for="inputGroupSelect01">Debit Options</label>                <select name="dbt">                    <option disabled="" selected="" value="">Select Debit Transaction type</option>                    <option value="asset">Asset</option>                    <option value="liability">Liability</option>                    <option value="capital">Capital</option>                    <option value="accounts receivable">Accounts Receivable</option>                    <option value="accounts payable">Accounts Payable</option>                    <option value="notes receivable">Notes Receivable</option>                    <option value="notes payable">Notes Payable</option>                </select>            </div>            <div>                <span>$</span>            <input type="number" name="dbt-amt" placeholder="Enter Debit amount...">                <span>.00</span>            </div>            <div>                <label for="inputGroupSelect02">Credit Options</label>                <select name="cdt">                    <option disabled="" selected="" value="">Select Credit Transaction type</option>                    <option value="asset">Asset</option>                    <option value="liability">Liability</option>                    <option value="capital">Capital</option>                    <option value="accounts receivable">Accounts Receivable</option>                    <option value="accounts payable">Accounts Payable</option>                    <option value="notes receivable">Notes Receivable</option>                    <option value="notes payable">Notes Payable</option>                </select>            </div>            <div>                <span>$</span>            <input type="number" name="cdt-amt" placeholder="Enter Credit amount...">                <span>.00</span>            </div>            <div>            <input type="date" name="trans-date">            </div>            <div>                <input type="submit" name="save" value="Save">            </div>        </div>    </form>    <div>        <button>Trial Balance</button>    </div></div>{% endblock %}

trialbalance.html:

{% extends "app/base.html" %}{% load app_extras %}{% block title %}Trial Balance{% endblock %}{% block content %}    <div>        <h2>{{name}}'s Trial Balance</h2>        <table>            <tr>                <th>Transaction</th>                <th>Debit</th>                <th>Credit</th>            </tr>            {% for trans in tb %}                <tr>                    {% for item in trans %}                        <td>{{item}}</td>                    {% endfor %}                </tr>            {% endfor %}        </table>        <div>            <h2>{{name}}'s T Accounts</h2>        </div>          <div>            <button>Back to Journal</button>        </div>    </div>    <script>        var tAccount = JSON.parse("{{ta|escapejs}}");        dataNode = document.getElementById('t-account');        for (const trans in tAccount) {            var sum = 0;            const transTable = document.createElement("table");            const transCaption = document.createElement("caption");            const transRow = document.createElement("tr");            const transDb = document.createElement("th");            const transCd = document.createElement("th");            transTable.classList.add("mx-auto");            transTable.classList.add("w-50");            transTable.classList.add("border");            transTable.classList.add("mb-4");            transRow.classList.add("bg-info");            transRow.classList.add("text-center");            transCaption.innerText = trans;            transDb.innerText = 'Debit';            transCd.innerText = 'Credit';            transRow.appendChild(transDb);            transRow.appendChild(transCd);            transTable.appendChild(transCaption);            transTable.appendChild(transRow);            tAccount[trans].forEach (data => {                sum = sum + data;                const tableRow = document.createElement("tr");                tableRow.classList.add("text-center");                const tableDb = document.createElement("td");                tableDb.className = "debit"                const tableCd = document.createElement("td");                tableCd.className = "credit"                if (data > 0 ) {                    tableDb.innerText = data;                    tableCd.innerText = '';                }                else if (data < 0 ) {                    tableDb.innerText = '';                    tableCd.innerText = data;                }                tableRow.appendChild(tableDb);                tableRow.appendChild(tableCd);                transTable.append(tableRow);            });            const tableEndRow = document.createElement("tr");            tableEndRow.classList.add("bg-info-subtle");            const tableEndDb = document.createElement("td");            tableEndDb.innerText = "Total"            const tableEndCd = document.createElement("td");            tableEndCd.innerText = `${sum}`            tableEndRow.appendChild(tableEndDb);            tableEndRow.appendChild(tableEndCd);            transTable.append(tableEndRow);            dataNode.appendChild(transTable);        }    </script>{% endblock %}

auth_base.html:

<!DOCTYPE html><html><head>    <title>{% block title %}{% endblock %}</title>    <meta charset="utf-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous"></head><body>    <div>        {% block content %}{% endblock %}    </div></body></html>

signin.html:

{% extends 'app/auth_base.html' %}{% block title %}Sign in{% endblock %}{% block content %}    <h1>Log In</h1>    {% for message in messages %}        <p>{{message}}</p>    {% endfor %}    <form method="post">        {% csrf_token %}        {% for field in form %}            <div>                <label for="signupField{{forloop.counter}}">{{field.label}}</label>                <p>{{field}}</p>            </div>        {% endfor %}        <div>            <button type="submit" name="signin">Sign in</button>        </div>    </form>    <div>        <button>Create your Account</button>    </div>{% endblock %}

signup.html:

{% extends 'app/auth_base.html' %}{% block title %}Sign up{% endblock %}{% block content %}    <h1>Sign Up</h1>     {% for message in messages %}        <p>{{message}}</p>    {% endfor %}    <form method="post">        {% csrf_token %}        {% for field in form %}            <div>                <label for="signupField{{forloop.counter}}">{{field.label}}</label>                <p>{{field}}</p>            </div>        {% endfor %}        <div>            Your password must be 8-20 characters long, contain letters, numbers, and special characters.        </div>        <div>            <button type="submit" name="signup">Sign Up</button>        </div>    </form>    <div>        <button >Login your Account</button></div>{% endblock %}

Conclusion

And we're done coding! Here's a video showing the app demo:

This app is useful in helping you to keep track of your financial transactions like spending, earnings, and more. I hope you learned a lot following this tutorial!

You can get the complete projecthere.

Learn also:How to Build a CRUD Application using Django in Python.

Happy coding ♥

Just finished the article? Why not take your Python skills a notch higher with ourPython Code Assistant? Check it out!

View Full Code Improve My Code
Sharing is caring!



Read Also


How to Build a Web Assistant Using Django and ChatGPT API in Python
How to Make a Todo App using Django in Python
How to Build a Weather App using Django in Python

Comment panel

    Got a coding query or need some guidance before you comment? Check out thisPython Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!





    Ethical Hacking with Python EBook - Topic - Top


    Join 50,000+ Python Programmers & Enthusiasts like you!



    Tags


    New Tutorials

    Popular Tutorials


    Ethical Hacking with Python EBook - Topic - Bottom

    CodingFleet - Topic - Bottom






    Claim your Free Chapter!

    Download a Completely Free Ethical hacking with Python from Scratch Chapter.

    See how the book can help you build awesome hacking tools with Python!



    [8]ページ先頭

    ©2009-2025 Movatter.jp