A tutorial for creating fast and reliable web apps with python and django.

Be aware because is on free tier of heroku maybe will be need on first visit about 20–30 sec to load, then anything is fine! And if you want to add data

username: guest

password: mypassword1

Image for post
Image for post

What we will do?

We will create a webapp, which we will add our expenses.The expenses will be split up in three categories Bills, Payroll and Generic Expenses. Then we will create a database to store them and we will learn to make easy reports of the data stored and visualize them with charts.

What we are gonna learn?

We will learn who to create Abstract Models and how to use them on an app, we will configure the admin page, create actions, add features on save method, create costume managers and queries, creating views and urls, and making some examples on annotation and aggregate on database. On front-end we will use some basic bootstrap framework and we will integrate chart.js and use an example how django can handle it. And final we will go live using heroku and github.

What will we use?

We will use these libraries and frameworks, django 2.0, bootstrap 4, chart.js, github and heroku. Django is a framework in python which we can create fast and reliable web apps, bootstrap is a front-end framework which will handle our html and css , final chart.js the name say its all, will handle the charts! Github is a version control, will be used to store on the cloud our code and detect easy the changes and commits them and finally heroku is a PaaS service which has a nice free tier thats stores our webapp.

Sources?

Enough with the small-talk let’s create our first app. This example is made using windows , so i assume you have windows and you have installed python 3.6 (its works with previous version too you just need to edit some code like get rid of f’{test}’ etc) and django 2.0 or newer too, if not then follow this guide.

Now we are ready! Open the cmd on any folder you want and add this (ps. same commands works on linux etc so dont worry).

$ django-admin startproject my_expenses

and then cd my_expenses and create our first app

$ python manage.py startapp expenses

your project file tree will be like that

Image for post
Image for post

Now find the settings.py file and add ‘expenses’ in INSTALLED_APPS.

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

'expenses',
]
CURRENCY = '€'

Thats it, django handled anything smooth and fast and our app is ready to run. But before do the testing lets populate our models.py first. When you find the models.py (details on image below) add the code below or copy it from github.

Image for post
Image for post
from django.db import models
from django.db.models import Sum, F

from .managers import GeneralManager

CURRENCY = '€'


class
PaymentMethod(models.Model):
title = models.CharField(unique=True, max_length=150)

def __str__(self):
return self.title

class Meta:
verbose_name_plural = '0. Payment Method'


class
DefaultExpenseModel(models.Model):
title = models.CharField(max_length=100, blank=True, null=True)
date_expired = models.DateField()
final_value = models.DecimalField(default=0, decimal_places=2, max_digits=20)
paid_value = models.DecimalField(default=0, decimal_places=2, max_digits=20)
is_paid = models.BooleanField(default=False)
payment_method = models.ForeignKey(PaymentMethod, null=True, on_delete=models.SET_NULL)
objects = models.Manager()
my_query = GeneralManager()

class Meta:
abstract = True

def
save(self, *args, **kwargs):
if self.is_paid:
self.paid_value = self.final_value
else:
self.paid_value = 0
super(DefaultExpenseModel, self).save(*args, **kwargs)

def __str__(self):
return self.title

def tag_final_value(self):
return f'{self.final_value} {CURRENCY}'

tag_final_value.short_description = 'Value'

def
tag_is_paid(self):
return 'Is Paid' if self.is_paid else 'Not Paid'

tag_is_paid.short_description = 'Paid'

@staticmethod
def analysis(queryset):
total_value = queryset.aggregate(Sum('final_value'))['final_value__sum'] if queryset else 0
paid_value = queryset.filter(is_paid=False).aggregate(Sum('final_value'))['final_value__sum']\
if queryset.filter(is_paid=False) else 0
diff = total_value - paid_value
category_analysis = queryset.values('category__title').annotate(total_value=Sum('final_value'),
remaining=Sum(F('final_value')-F('paid_value'))
).order_by('remaining')
return [total_value, paid_value, diff, category_analysis]

@staticmethod
def filters_data(request, queryset):
search_name = request.GET.get('search_name', None)
cate_name = request.GET.getlist('cate_name', None)
paid_name = request.GET.getlist('paid_name', None)
person_name = request.GET.getlist('person_name', None)

queryset = queryset.filter(title__icontains=search_name) if search_name else queryset
queryset = queryset.filter(category__id__in=cate_name) if cate_name else queryset
queryset = queryset.filter(is_paid=True) if 'paid' == paid_name else queryset.filter(is_paid=False)\
if 'not_paid' == paid_name else queryset
if person_name:
try:
queryset = queryset.filter(person__id__in=person_name)
except:
queryset = queryset
return queryset

Code analysis!

Here we will use the CURRENCY to easily change currency like dollar or euro. On PaymentMethod model will we store the payment options like PayPal, Cash, Visa etc. And final the DefaultExpenseModel is our Abstract Model which will use it as a base for the Bill, Payroll and Expense models. Using on our DefaultExpense Model…

class Meta:
abstract = True

ensures the inheritance models will get all the fields, functions etc from this model and django will not create a separate table on our database for this model. The next code snippet is for the admin page, with that setting we can change the head value on the admin table.

def tag_final_value(self):
return f'{self.final_value} {CURRENCY}'

tag_final_value.short_description = 'Value'

After that, we will override the save method adding a little of custom code. So the logic is we say to our models to check if user have paid the instance and if is true then the model save method will make paid value equal to final value if no will ensure paid value is 0. We will not use more option for simplicity of the app.

def save(self, *args, **kwargs):
if self.is_paid:
self.paid_value = self.final_value
else:
self.paid_value = 0
super(DefaultExpenseModel, self).save(*args, **kwargs)

The super… on last line ensures that anything default happens on save method will continue to happen, the analysis and filter_data method we will explain them later. Now our Bill and BillCategory model

class BillCategory(models.Model):
title = models.CharField(unique=True, max_length=150)
balance = models.DecimalField(default=0, max_digits=20, decimal_places=2)

class Meta:
verbose_name_plural = '1. Bill Category'

def
__str__(self):
return self.title

def tag_balance(self):
return f'{self.balance} {CURRENCY}'

tag_balance.short_description = 'Value'

def
update_category(self):
queryset = self.bills.all()
total_value = queryset.aggregate(Sum('final_value'))['final_value__sum'] if queryset else 0
paid_value = queryset.filter(is_paid=True).aggregate(Sum('final_value'))['final_value__sum'] \
if queryset.filter(is_paid=True) else 0
self.balance = total_value - paid_value
self.save()


class Bill(DefaultExpenseModel):
category = models.ForeignKey(BillCategory, null=True, on_delete=models.SET_NULL, related_name='bills')

class Meta:
verbose_name_plural = '2. Bills'
ordering = ['-date_expired']

def save(self, *args, **kwargs):
if not self.title:
self.title = f'{self.category.title} - {self.id}'
super(Bill, self).save(*args, **kwargs)
self.category.update_category()

def tag_category(self):
return f'{self.category}'

On BillCategory model we will define our bill categories like Netflix, AmazonPrime, CellPhone monthly payment.. Using this function update_category, we ensure the balance on bill category will be up to date. (Probably we be ineffiecient if have alot of records but is just a example).

How works? After a Bill instance is saved, on save method we call the update_category which belongs on BillCategory model and then the update_category create two queries in database. The first return the total value of all bills which is related on this category. The related_name on ForeignKey is responsible for this. The second query does the same only for the paid instances. And after that we just do the maths and update the balance on our model!

def update_category(self):
queryset = self.bills.all()
total_value = queryset.aggregate(Sum('final_value'))['final_value__sum'] if queryset else 0
paid_value = queryset.filter(is_paid=True).aggregate(Sum('final_value'))['final_value__sum'] \
if queryset.filter(is_paid=True) else 0
self.balance = total_value - paid_value
self.save()

With use the same logic on the next models

class PayrollCategory(models.Model):
title = models.CharField(unique=True, max_length=150)
balance = models.DecimalField(default=0, max_digits=20, decimal_places=2)

class Meta:
verbose_name_plural = '3. Payroll Category'

def
__str__(self):
return self.title

def tag_balance(self):
return f'{self.balance} {CURRENCY}'

tag_balance.short_description = 'Value'

def
update_category(self):
queryset = self.category_payroll.all()
total_value = queryset.aggregate(Sum('final_value'))['final_value__sum'] if queryset else 0
paid_value = queryset.filter(is_paid=True).aggregate(Sum('final_value'))['final_value__sum'] \
if queryset.filter(is_paid=True) else 0
self.balance = total_value - paid_value
self.save()


class Person(models.Model):
title = models.CharField(unique=True, max_length=150)
occupation = models.CharField(max_length=100, blank=True, null=True)
phone = models.CharField(max_length=10, blank=True, null=True)
balance = models.DecimalField(default=0, max_digits=20, decimal_places=2)

class Meta:
verbose_name_plural = '4. Persons'

def
__str__(self):
return self.title

def tag_balance(self):
return f'{self.balance} {CURRENCY}'

tag_balance.short_description = 'Value'

def
update_person(self):
queryset = self.person_payroll.all()
total_value = queryset.aggregate(Sum('final_value'))['final_value__sum'] if queryset else 0
paid_value = queryset.filter(is_paid=True).aggregate(Sum('final_value'))['final_value__sum'] \
if queryset.filter(is_paid=True) else 0
self.balance = total_value - paid_value
self.save()


class Payroll(DefaultExpenseModel):
person = models.ForeignKey(Person, null=True, on_delete=models.SET_NULL, related_name='person_payroll')
category = models.ForeignKey(PayrollCategory, null=True, on_delete=models.SET_NULL, related_name='category_payroll')

class Meta:
verbose_name_plural = '5. Payroll'
ordering = ['-date_expired']

def save(self, *args, **kwargs):
if not self.title:
self.title = f'{self.person.title} - {self.id}'
super(Payroll, self).save(*args, **kwargs)
self.person.update_person()
self.category.update_category()

def tag_category(self):
return f'{self.person} - {self.category}'


class
GenericExpenseCategory(models.Model):
title = models.CharField(unique=True, max_length=150)
balance = models.DecimalField(default=0, max_digits=20, decimal_places=2)

class Meta:
verbose_name_plural = '6. Expense Category'

def
__str__(self):
return self.title

def tag_balance(self):
return f'{self.balance} {CURRENCY}'

tag_balance.short_description = 'Value'

def
update_category(self):
queryset = self.category_expenses.all()
total_value = queryset.aggregate(Sum('final_value'))['final_value__sum'] if queryset else 0
paid_value = queryset.filter(is_paid=True).aggregate(Sum('final_value'))['final_value__sum'] \
if queryset.filter(is_paid=True) else 0
self.balance = total_value - paid_value
self.save()


class GenericExpense(DefaultExpenseModel):
category = models.ForeignKey(GenericExpenseCategory, null=True, on_delete=models.SET_NULL,
related_name='category_expenses')

class Meta:
verbose_name_plural = '7. Generic Expenses'
ordering = ['-date_expired']

def save(self, *args, **kwargs):
if not self.title:
self.title = f'{self.title}'
super(GenericExpense, self).save(*args, **kwargs)
self.category.update_category()

def tag_category(self):
return f'{self.category}'

The only difference on Payroll model we update except category and the Person model.

Now lets create the managers.py.

Will we use this file to handle our custom queries. Django provide us his own ORM, thats means a system to ask data from database easy and fast. So we will take that system and will add some code like we did on save method. Add this file next to previous models.py. So lets see the code or grab it from github.

from django.db.models import QuerySet, Manager
import datetime


class GenericQuerySet(QuerySet):
def filter_by_date(self, date_start, date_end):
return self.filter(date_expired__range=[date_start, date_end])

def unpaid(self):
return self.filter(is_paid=False)


class GeneralManager(Manager):
def get_queryset(self):
return GenericQuerySet(self.model, using=self._db)

So after the import from django we will define the first QuerySet. Propably is overkill to use it that way in this example, but is a good moment to use reusable code for managers. Creating the QuerySet and not writing the code directly on manager, give use the freedom to use the same code on as many managers we want. So when we use the

class GeneralManager(Manager):
def get_queryset(self):
return GenericQuerySet(self.model, using=self._db)

from models we saying to django, get the query we created before and filter the model which is related. We bound the manager on models with a line like that my_query = GeneralManager(). We can use here objects =models.Manager() but that will overwrite the default manager and we dont want that on this project. On next code we just filter the queryset with the django ORM.

return self.filter(date_expired__range=[date_start, date_end])

Our code on models.py is allready ready for the managers. So now next move is the admin page!

Admin Page

Now lets create the admin.py. If you django dont have created this file you can create it and add this code or grab it from here .

from django.contrib import admin
from .models import *


def action_paid(modeladmin, request, queryset):
for ele in queryset:
ele.is_paid=True
ele.save()
action_paid.short_description = 'Multiple Paid'


@admin.register(PaymentMethod)
class PaymentMethodAdmin(admin.ModelAdmin):
pass


@admin.register(BillCategory)
class BillCategoryAdmin(admin.ModelAdmin):
list_display = ['title', 'tag_balance']
fields = ['title', ]
search_fields = ['title', ]


@admin.register(Bill)
class BillAdmin(admin.ModelAdmin):
save_as = True
save_on_top = True
list_display = ['date_expired', 'category', 'tag_final_value','payment_method', 'is_paid']
list_filter = ['category', 'is_paid', 'date_expired', 'payment_method']
search_fields = ['title', 'category__title']
readonly_fields = ['paid_value']
fields = ['category', 'payment_method', 'date_expired', 'is_paid', 'final_value', 'title', 'paid_value']
actions = [action_paid, ]


@admin.register(PayrollCategory)
class PayrollCategoryAdmin(admin.ModelAdmin):
list_display = ['title', 'tag_balance']
fields = ['title', ]
search_fields = ['title', ]


@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
list_display = ['title', 'phone', 'tag_balance']
fields = ['title', 'phone']
search_fields = ['title', 'phone']


@admin.register(Payroll)
class PayrollAdmin(admin.ModelAdmin):
list_display = ['person', 'category', 'date_expired', 'tag_final_value', 'payment_method', 'is_paid']
list_filter = ['category', 'person', 'is_paid', 'date_expired', 'payment_method']
search_fields = ['title', 'person__title', 'category__title']
fields = ['person', 'category', 'date_expired', 'is_paid', 'final_value', 'title', 'payment_method']
actions = [action_paid, ]


@admin.register(GenericExpenseCategory)
class GenericExpenseCategoryCategoryAdmin(admin.ModelAdmin):
list_display = ['title', 'tag_balance']
fields = ['title', ]
search_fields = ['title', ]


@admin.register(GenericExpense)
class GenericExpenseAdmin(admin.ModelAdmin):
list_display = ['category', 'date_expired', 'tag_final_value', 'is_paid', 'payment_method']
list_filter = ['category', 'is_paid', 'date_expired', 'payment_method']
search_fields = ['title', 'category__title']
fields = ['category', 'date_expired', 'is_paid', 'final_value', 'title', 'payment_method']
actions = [action_paid, ]

Code analysis

First we created our first action. Whats is action look the mage below! This enable us on admin page to multi checkbox our instance and change the status is_paid with one click. When you check the instance its create a queryset and then the rest is easy! Mind you can update the queryset and not do the loop and save, but we want the instance to be saved so the custom code we added will work.

Image for post
Image for post
def action_paid(modeladmin, request, queryset):
for ele in queryset:
ele.is_paid=True
ele.save()
action_paid.short_description = 'Multiple Paid'

Next how to register and why! When we register a model on admin using this

@admin.register(PaymentMethod)

or

admin.site.register(PaymentMethod)

the model is show up with the default settings on our admin page.

So next move is to edit that page, lets get this for example

Image for post
Image for post
@admin.register(Bill)
class BillAdmin(admin.ModelAdmin):
save_as = True
save_on_top = True
list_display = ['date_expired', 'category', 'tag_final_value','payment_method', 'is_paid']
list_filter = ['category', 'is_paid', 'date_expired', 'payment_method']
search_fields = ['title', 'category__title']
readonly_fields = ['paid_value']
fields = ['category', 'payment_method', 'date_expired', 'is_paid', 'final_value', 'title', 'paid_value']
actions = [action_paid, ]

The save_as will allow us to create the same instance with different id, the copy paste method. On list_display we define what fields or function will be show on main table, list_filter defines our filters, preferably use here all the ForeignKey models you want, and when we use the search_fields we create a search bar and we define where to search and final on actions we register which action we want to use. The fields or fieldsets which will not use, we define the fields on specific instance for editing, and the read_only we specific the values we want to show but not to edit!

Thats its, django can create and handle easy admin pages like that. There is many options will not cover like inlines or custom form for admin page, but we will not use at all django forms on this app. Now the first test.

And now our manager.py

First let’s create the shema on our database. We will do that with two simply commands makemigration and migrate. The first read all our models on registered apps and create the migrations. The second command applies that migrations to database.

$ python manage.py makemigrations
$ python manage.py migrate

After that lets create a superuser, use any username and password you want.

$ python manage.py createsuperuser

Now let’s start our development server with the command below and then open a broswer and go to http://127.0.0.1:8000/admin/ and explore a little our admin dashboard.

$ python manage.py runserver

Now to front-end!

Front -end!

On this part we will create our urls, templates, and views. First on views.py in our app, we will create functions and classes that will control all the front-end. We will add the templates for every view, and in urls.py we bound the views with the path we want on the broswer.

But before that we need to do some changes on settings.py so django knows where will find the templates.

'DIRS': [os.path.join(BASE_DIR, 'templates'),],

Ok full code… or github.

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'),],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

Then on same folder on urls.py we replace the code with this. Its our urls, but before anything work we need to add our views first. No much explain here, with django 2.0 we have access to path which does easier our job and the

name='bills_view'),

is for dynamic use call on the template when we need this url.

from django.contrib import admin
from django.urls import path
from expenses.views import (HomepageView, BillListView,
PayrollListView, ExpensesListView,
report_view
)

urlpatterns = [
path('admin/', admin.site.urls),
path('', HomepageView.as_view(), name='homepage'),

path('bills/', BillListView.as_view(), name='bills_view'),
path('payroll/', PayrollListView.as_view(), name='payroll_view'),
path('expenses/', ExpensesListView.as_view(), name='expenses_view'),
path('reports/', report_view, name='reports_view')

]

Now lets handle the views.py!

We assume here that the templates are ready on template folder in our app. If not then go here and grab the template folder and add it on same place. So after the initial imports we will create our first view for the homepage.

from django.shortcuts import render, get_list_or_404
from django.views.generic import TemplateView, ListView
from django.conf import settings

from .models import *

from itertools import chain
from dateutil.relativedelta import relativedelta
import datetime
CURRENCY = settings.CURRENCY


class HomepageView(TemplateView):
template_name = 'index.html'

def
get_context_data(self, **kwargs):
context = super(HomepageView, self).get_context_data(**kwargs)
bills = Bill.my_query.get_queryset().unpaid()[:10]
payrolls = Payroll.my_query.get_queryset().unpaid()[:10]
expenses = GenericExpense.my_query.get_queryset().unpaid()[:10]
context.update({'bills': bills,
'payroll': payrolls,
'expenses': expenses
})
return context

We will use most ClassViews and not functions for our views. If the view you want to create is simply is better to use class, maybe you eill need more time to create it but is more readable if you want to change something later. And we used our custom manager as you can see too.

Now lets add more views on views.py. Grab the code from here Github too.

class BillListView(ListView):
model = Bill
template_name = 'page_list.html'
paginate_by = 100

def get_queryset(self):
queryset = Bill.objects.all()
queryset = Bill.filters_data(self.request, queryset)
return queryset

def get_context_data(self, **kwargs):
context = super(BillListView, self).get_context_data(**kwargs)
page_title = 'Bills List'
categories = BillCategory.objects.all()
search_name, cate_name, paid_name = [self.request.GET.get('search_name', None),
self.request.GET.getlist('cate_name', None),
self.request.GET.getlist('paid_name')
]
total_value, paid_value, diff, category_analysis = Bill.analysis(self.object_list)
currency = CURRENCY
context.update(locals())
return context


class PayrollListView(ListView):
model = Payroll
template_name = 'page_list.html'
paginate_by = 100

def get_queryset(self):
queryset = Payroll.objects.all()
queryset = Payroll.filters_data(self.request, queryset)
return queryset

def get_context_data(self, **kwargs):
context = super(PayrollListView, self).get_context_data(**kwargs)
page_title = 'Payroll List'
categories = PayrollCategory.objects.all()
persons = Person.objects.all()
search_name, cate_name, paid_name, person_name = [self.request.GET.get('search_name', None),
self.request.GET.getlist('cate_name', None),
self.request.GET.getlist('paid_name', None),
self.request.GET.getlist('person_name', None)
]
total_value, paid_value, diff, category_analysis = Payroll.analysis(self.object_list)
currency = CURRENCY
context.update(locals())
return context


class ExpensesListView(ListView):
model = GenericExpense
template_name = 'page_list.html'
paginate_by = 100

def get_queryset(self):
queryset = GenericExpense.objects.all()
queryset = GenericExpense.filters_data(self.request, queryset)
return queryset

def get_context_data(self, **kwargs):
context = super(ExpensesListView, self).get_context_data(**kwargs)
page_title = 'Expenses List'
categories = GenericExpenseCategory.objects.all()
search_name, cate_name, paid_name = [self.request.GET.get('search_name', None),
self.request.GET.getlist('cate_name', None),
self.request.GET.getlist('paid_name')
]
total_value, paid_value, diff, category_analysis = GenericExpense.analysis(self.object_list)
currency = CURRENCY
context.update(locals())
return context

Here will only explain one view cause they are similar.

def get_queryset(self):
queryset = Bill.objects.all()
queryset = Bill.filters_data(self.request, queryset)
return queryset

How works? When we created our models we added a function called filters_data. The logic is simple, get all the request parameters and query from view, then look if there is request.GET we want and if there is, filter the query with the data the user provided. Its better to store that type of code on models so it’s reusable and makes our views thinner.

Here we get again the request.GET data, we will use it later on template to populate the values on forms..

search_name,cate_name,paid_name=[
self.request.GET.get('search_name',None),
self.request.GET.getlist('cate_name', None),
self.request.GET.getlist('paid_name', None)
]

And after

total_value, paid_value, diff, category_analysis = Bill.analysis(self.object_list)

And that line lead us on the

@staticmethod
def analysis(queryset):
total_value = queryset.aggregate(Sum('final_value'))['final_value__sum'] if queryset else 0
paid_value = queryset.filter(is_paid=False).aggregate(Sum('final_value'))['final_value__sum']\
if queryset.filter(is_paid=False) else 0
diff = total_value - paid_value
category_analysis = queryset.values('category__title').annotate(total_value=Sum('final_value'),
remaining=Sum(F('final_value')-F('paid_value'))
).order_by('remaining')
return [total_value, paid_value, diff, category_analysis]

Here we do this, we get the filtered queryset from before and we calculate the values we want. The aggregate and annotate methods comes from django ORM and SQL. Django ORM connects our app with the database, and comes with some tools. When we using for example the aggregate, we tell to our app to do the maths on database level which is always faster than python. With the @staticmethod, we tell to our class we want to use this function which is related to the class but not to the specific object, so using it we get rid from the self.

And finally the last and bigger and more complicated view.

def report_view(request):
startDate = request.GET.get('startDate', '2018-01-01')
endDate = request.GET.get('endDate', '2018-12-31')
if startDate > endDate:
startDate , endDate = '2018-01-01', '2018-12-31'
date_start = datetime.datetime.strptime(startDate, '%Y-%m-%d').date()
date_end = datetime.datetime.strptime(endDate, '%Y-%m-%d').date()
bills = Bill.my_query.get_queryset().filter_by_date(date_start, date_end)
payrolls = Payroll.my_query.get_queryset().filter_by_date(date_start, date_end)
expenses = GenericExpense.my_query.get_queryset().filter_by_date(date_start, date_end)
queryset = sorted(chain(bills, payrolls, expenses),
key=lambda instance: instance.date_expired
)
bill_total_value, bill_paid_value, bill_diff, bill_category_analysis = DefaultExpenseModel.analysis(bills)
payroll_total_value, payroll_paid_value, payroll_diff, bill_category_analysis = DefaultExpenseModel.analysis(payrolls)
expense_total_value, expense_paid_value, expense_diff, expense_category_analysis = DefaultExpenseModel.analysis(expenses)

bill_by_month, payroll_by_month, expenses_by_month, totals_by_month =[], [], [] ,[]
months_list = []
while date_start < date_end:
months_list.append(date_start)
date_start += relativedelta(months=1)

for date in months_list:
start = date.replace(day=1)
end = date.replace(day=28)
this_month_bill_queryset = bills.filter(date_expired__range=[start, end])
this_month_bills = DefaultExpenseModel.analysis(this_month_bill_queryset)
this_month_payroll_queryset = payrolls.filter(date_expired__range=[start, end])
this_month_payroll = DefaultExpenseModel.analysis(this_month_payroll_queryset)
this_month_expense_queryset = expenses.filter(date_expired__range=[start, end])
this_month_expense = DefaultExpenseModel.analysis(this_month_expense_queryset)
bill_by_month.append(this_month_bills)
payroll_by_month.append(this_month_expense)
expenses_by_month.append(this_month_payroll)
totals_by_month.append([this_month_bills[0]+this_month_expense[0]+ this_month_payroll[0],
this_month_bills[1] + this_month_expense[1] + this_month_payroll[1],
this_month_bills[2] + this_month_expense[2] + this_month_payroll[2]
])

totals = [payroll_total_value + bill_total_value + expense_total_value,
bill_paid_value + payroll_paid_value + expense_paid_value,
bill_diff + payroll_diff + expense_diff
]
currency = CURRENCY
context = locals()
return render(request, 'report.html', context=context)

So many things here…. Ok lets start from the begin….

startDate = request.GET.get('startDate', '2018-01-01')
endDate = request.GET.get('endDate', '2018-12-31')
if startDate > endDate:
startDate , endDate = '2018-01-01', '2018-12-31'
date_start = datetime.datetime.strptime(startDate, '%Y-%m-%d').date()
date_end = datetime.datetime.strptime(endDate, '%Y-%m-%d').date()

First we get the user data from the form. If the user filtered the date fields we get the date range he wants else we have a initial date range. Second we check if endDate is bigger than startDate so the app don’t crash when we filter it,(this is better to do it with javascript in front-end too but we learn python here! And we should to check if both values is date. Forgot to added it hehe.) and if anything all right we convert it on a format our models can understand.

bills = Bill.my_query.get_queryset().filter_by_date(date_start, date_end)
payrolls = Payroll.my_query.get_queryset().filter_by_date(date_start, date_end)
expenses = GenericExpense.my_query.get_queryset().filter_by_date(date_start, date_end)
queryset = sorted(chain(bills, payrolls, expenses),
key=lambda instance: instance.date_expired
)

We filter our models and then we do the trick! We use the chain function python provide us and then we connect all three queries and create a new query. After that we say to be sorted on date_expired.

Next lines are easy and then we add some brutal coding for our chart population.

bill_by_month, payroll_by_month, expenses_by_month, totals_by_month = [], [], [] ,[]
months_list = []
while date_start < date_end:
months_list.append(date_start)
date_start += relativedelta(months=1)

Here first we create our lists to store our results and the months, after that we do a loop to calculate the months.

for date in months_list:
start = date.replace(day=1)
next_month = date.replace(day=28) + datetime.timedelta(days=4)
end = next_month - datetime.timedelta(days=next_month.days)
this_month_bill_queryset = bills.filter(date_expired__range=[start, end])
this_month_bills = DefaultExpenseModel.analysis(this_month_bill_queryset)
this_month_payroll_queryset = payrolls.filter(date_expired__range=[start, end])
this_month_payroll = DefaultExpenseModel.analysis(this_month_payroll_queryset)
this_month_expense_queryset = expenses.filter(date_expired__range=[start, end])
this_month_expense = DefaultExpenseModel.analysis(this_month_expense_queryset)
bill_by_month.append(this_month_bills)
payroll_by_month.append(this_month_expense)
expenses_by_month.append(this_month_payroll)
totals_by_month.append([this_month_bills[0]+this_month_expense[0]+ this_month_payroll[0],
this_month_bills[1] + this_month_expense[1] + this_month_payroll[1],
this_month_bills[2] + this_month_expense[2] + this_month_payroll[2]
])

totals = [payroll_total_value + bill_total_value + expense_total_value,
bill_paid_value + payroll_paid_value + expense_paid_value,
bill_diff + payroll_diff + expense_diff
]

The object start is easy we just replace the day with one, the last day is tricky tho, because here can be 28, 30 or 31. Thats why we wrote this

next_month = date.replace(day=28) + datetime.timedelta(days=4)
end = next_month - datetime.timedelta(days=next_month.days)

After that is the same, we do our queryset analysis which we created before, we add the data to our lists and the app is ready.

On this point you can try again to open a server and test it.

Templates

The last part now the templates, if you don’t already did it, create a folder inside the expenses app called templates and then create our first file index.html. We will not use our own static or collect static, we will user the cdn for bootstrap, chart.js and jquery. The code for the template is below or github .

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Demo Expense Django App</title>
</head>
<body>
{% include 'navbar.html' %}
<br>
<div class="container-fluid">
{% block container %}
<div class="row">
<h4>No Paid Invoices</h4>
</div>
<div class="row">
<div class="col">
<h4>Bills</h4>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Date</th>
<th scope="col">Bill</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
{% for bill in bills %}
<tr>
<th scope="row">1</th>
<td>{{ bill.date_expired|date }}</td>
<td>{{ bill.category }}</td>
<td>{{ bill.tag_final_value }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4">No data</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="/admin/expenses/bill/" class="btn btn-primary">Edit</a>
</div>
<div class="col">
<h4>Payroll</h4>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Date</th>
<th scope="col">Person - Category</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
{% for bill in payroll %}
<tr>
<th scope="row">1</th>
<td>{{ bill.date_expired|date }}</td>
<td>{{ bill.tag_category }}</td>
<td>{{ bill.tag_final_value }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4">No data</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="/admin/expenses/payroll/" class="btn btn-primary">Edit</a>
</div>
<div class="col">
<h4>Expense</h4>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Date</th>
<th scope="col">Category</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
{% for expense in expenses %}
<tr>
<th scope="row">1</th>
<td>{{ expense.date_expired|date }}</td>
<td>{{ expense.category }}</td>
<td>{{ expense.tag_final_value }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4">No data</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="/admin/expenses/genericexpense/" class="btn btn-primary">Edit</a>
</div>
</div>
{% endblock %}
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script>
$('.datepicker').datepicker();
</script>
{% block extra_js %} {% endblock %}
</body>
</html>

So probably if you watching this tutorial i pressume you are familiar with css, html and javascript.

On index html we added the static files with the cdn links

like <link rel=”stylesheet” href=” ……"> . Lets go to the body part now and see the next code…

{% include 'navbar.html' %}

That’s is a template tag for django. With this we inform our template to include a second template on the current spot. This is nice because if we have a big template we can break into parts link them back on the main template. And breaking it into parts make them reusable too. We will create later the navbar.html, lets focus on this one for now. When you want to use python on a template you use template tags. There is some build ups come with django or you can create too, depends on situation. But is better to keep as much code and logic on views. When you want to use a template tag you include it like that {% for bill in bills %}, if you want to use the variables from the view you use {{ test }}. That was just a reminder, if you are newcomer on django google it!.

{% for bill in bills %}
<tr>
<th scope="row">1</th>
<td>{{ bill.date_expired|date }}</td>
<td>{{ bill.category }}</td>
<td>{{ bill.tag_final_value }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4">No data</td>
</tr>
{% endfor %}

One last note, when we using the {% block container %} {% endblock %}, the code inside that tag is dynamic. We can create a second template, which can extend from the first template and get all the static, code etc and add the new code inside that block. Like the next templates! Now go grab the code from github for the next templates!

On report.html we will use the chart.js. So we will add a canva element and give it any id name we want

<div class="row">
<div class="col-8"><canvas id="myChart" width="400" height="150"></canvas></div>
<div class="col-4"><canvas id="myChart0" width="400" height="300"></canvas></div>
</div>

That’s the easy part, with the help of bootstrap we specify how much space charts will get, and and now with javascript will add the charts.

var ctx = document.getElementById("myChart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: [
{% for month in months_list %}
{% if forloop.last %}
"{{ month }}"
{% else %}
"{{ month }}",
{% endif %}
{% endfor %}
],
datasets: [{
label: 'Bills',
fill:false,
borderColor: 'blue',
data: [{% for month in bill_by_month %}
{% if forloop.last %}
"{{ month.0 }}"
{% else %}
"{{ month.0 }}",
{% endif %}
{% endfor %}],

borderWidth: 1
},

On first line we got the div and the we send our chart with the new Chart command. But we didn’t done, we have to specify what type of chart is, the labels the dataset even the color of the dataset how fat will be the line etc . Here we only use the python to populate the data, and the javascript for the cosmetic changes.

Thats its, run again the runserver command and the app is ready!

Heroku

Optional step if you want the app go live on web.

Before we do anything lets make our app ready for heroku. We will use a version control program called github and we will create some files to guide heroku.

Note before continue, when you develop a app for production level, you must never send sensitive data like django secret key, email passwords etc to your server application via github. You must use alternative ways like a python library called decouple, which reads the data from environment. More details here.

Another note, again on production environment never use sqlite3! Expect if you knows what are you doing. Sqlite3 is a fine database but have a huge minus. Can’t be used for more than one user on same time, because its get locked, so is not that good for our app.

Now lets create this files heroku needed…

  • First file is called runtime.txt, is used to specify which version of python our app will use. We added the python-3.6.0
  • Second file is requirements.txt. It’s a common practice in python to add this file on every project to remember which libraries and version you used. On this file you will see two new libraries except django, the whitenoise and the gunicorn. Whitenoise allows our web app to serve its own static files, and gunicorn connects our app to Nginx server or something like that.
  • And the last file is the Procfile and his job is to tell gunicorn where our app wsgi is and we add this code… web: gunicorn my_expenses.wsgi — log-file —

Now we need to do more changes on our wsgi file

import os
from whitenoise.django import DjangoWhiteNoise
from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_expenses.settings")

application = get_wsgi_application()
application = DjangoWhiteNoise(application)

And on settings.py we need to replace a line with that

ALLOWED_HOSTS = [ '127.0.0.1', 'example.com',]

Where example is your domain on heroku. Now our code is ready. Lets create a github repository. Go to github and create a new account, and after that create a new repository with any name you want and follow exactly the steps on the guide. I mean the git init, git add ., git commit -m…

If you want you can ignore this step and use my repository. But is better to get familiar with git. will help you on future projects too.

Now lets go live on heroku, go to heroku and create a account, after all is settled create an new app. You have to use different name probable because that is reserved now

Image for post
Image for post

after that go here

Image for post
Image for post

If you created your own git repository or you fork mine use your account name and repository name or just use mine. After pressing done you will see the next page just press deploy Branch wait 1–2 min and your site is ready!

Image for post
Image for post

If you see that error

Image for post
Image for post

you can fix it like this.

Image for post
Image for post

That’s all folks! Have fun!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store