diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b0274513300b5926dd7205a546c3911aa7c0a08f..56f6aab06777e903315ebc7d6074bd129edba46f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,7 @@
 stages:
-  - test
+  - unit-test
   - deploy
+  - functional-test
 
 before_script:
   - apt-get install -f
@@ -27,19 +28,12 @@ before_script:
   - python manage.py runserver 8000 &
 
 UnitTest:
-  stage: test
+  stage: unit-test
   when: on_success
   script:
     - coverage run --include='tutorial_*/*' manage.py test -p "unit_test*.py"
     - coverage report -m
 
-FunctionalTest:
-  stage: test
-  when: on_success
-  script:
-    - coverage run --include='tutorial_*/*' manage.py test -p "functional_test*.py"
-    - coverage report -m
-
 DeployToHeroku:
   image: ruby:2.4
   stage: deploy
@@ -53,3 +47,10 @@ DeployToHeroku:
   environment:
     name: production
     url: $HEROKU_APP_HOST
+
+FunctionalTest:
+  stage: functional-test
+  when: on_success
+  script:
+    - coverage run --include='tutorial_*/*' manage.py test -p "functional_test*.py"
+    - coverage report -m
diff --git a/requirements.txt b/requirements.txt
index edd331552a3c9012967e4769320066c0be9b7a20..efa3c33df46f42de747a63fcc0836a6a03dc2677 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
+pytz
 appdirs==1.4.0
 Django==1.11.17
 django-webpack-loader==0.4.1
diff --git a/tutorial/static/css/tutorial_2.366cb34e0003.css b/tutorial/static/css/tutorial_2.366cb34e0003.css
new file mode 100644
index 0000000000000000000000000000000000000000..abc49e145e42f2ae80560eaa29eb6369736a02a0
--- /dev/null
+++ b/tutorial/static/css/tutorial_2.366cb34e0003.css
@@ -0,0 +1,11 @@
+body {
+  min-height: 2000px;
+}
+
+.navbar-static-top {
+  margin-bottom: 19px;
+}
+/*Textarea not resizeable*/
+textarea {
+    resize:none
+}
diff --git a/tutorial/static/css/tutorial_2.366cb34e0003.css.gz b/tutorial/static/css/tutorial_2.366cb34e0003.css.gz
new file mode 100644
index 0000000000000000000000000000000000000000..7f63373e8538dac1edc79e9a31eb2f4891ee80cc
Binary files /dev/null and b/tutorial/static/css/tutorial_2.366cb34e0003.css.gz differ
diff --git a/tutorial/static/css/tutorial_2.css b/tutorial/static/css/tutorial_2.css
new file mode 100644
index 0000000000000000000000000000000000000000..abc49e145e42f2ae80560eaa29eb6369736a02a0
--- /dev/null
+++ b/tutorial/static/css/tutorial_2.css
@@ -0,0 +1,11 @@
+body {
+  min-height: 2000px;
+}
+
+.navbar-static-top {
+  margin-bottom: 19px;
+}
+/*Textarea not resizeable*/
+textarea {
+    resize:none
+}
diff --git a/tutorial/static/css/tutorial_2.css.gz b/tutorial/static/css/tutorial_2.css.gz
new file mode 100644
index 0000000000000000000000000000000000000000..7f63373e8538dac1edc79e9a31eb2f4891ee80cc
Binary files /dev/null and b/tutorial/static/css/tutorial_2.css.gz differ
diff --git a/tutorial/static/staticfiles.json b/tutorial/static/staticfiles.json
index 5e58c0caf874b4499c9a27ab807736108c083b34..c423aa219b1f36db2c2734799eda7ea0c736049e 100644
--- a/tutorial/static/staticfiles.json
+++ b/tutorial/static/staticfiles.json
@@ -1 +1 @@
-{"paths": {"admin/js/vendor/jquery/LICENSE-JQUERY.txt": "admin/js/vendor/jquery/LICENSE-JQUERY.a158210a2737.txt", "admin/js/vendor/jquery/jquery.min.js": "admin/js/vendor/jquery/jquery.min.33cabfa15c10.js", "admin/js/vendor/jquery/jquery.js": "admin/js/vendor/jquery/jquery.aacc43d6f308.js", "admin/js/vendor/xregexp/xregexp.min.js": "admin/js/vendor/xregexp/xregexp.min.c95393b8ca4d.js", "admin/js/vendor/xregexp/xregexp.js": "admin/js/vendor/xregexp/xregexp.1865b1cf5085.js", "admin/js/vendor/xregexp/LICENSE-XREGEXP.txt": "admin/js/vendor/xregexp/LICENSE-XREGEXP.d64cecf4f157.txt", "admin/js/admin/RelatedObjectLookups.js": "admin/js/admin/RelatedObjectLookups.d2bdb1018963.js", "admin/js/admin/DateTimeShortcuts.js": "admin/js/admin/DateTimeShortcuts.1536fb6bea1d.js", "admin/img/gis/move_vertex_on.svg": "admin/img/gis/move_vertex_on.0047eba25b67.svg", "admin/img/gis/move_vertex_off.svg": "admin/img/gis/move_vertex_off.7a23bf31ef8a.svg", "admin/css/widgets.css": "admin/css/widgets.5e372b41c483.css", "admin/css/login.css": "admin/css/login.a846c0e2ef65.css", "admin/css/dashboard.css": "admin/css/dashboard.7ac78187c567.css", "admin/css/forms.css": "admin/css/forms.2003a066ae02.css", "admin/css/fonts.css": "admin/css/fonts.494e4ec545c9.css", "admin/css/rtl.css": "admin/css/rtl.4c867197b256.css", "admin/css/base.css": "admin/css/base.6b517d0d5813.css", "admin/css/changelists.css": "admin/css/changelists.f6dc691f8d62.css", "admin/js/urlify.js": "admin/js/urlify.411bc3bb651c.js", "admin/js/inlines.min.js": "admin/js/inlines.min.d75b9ed03975.js", "admin/js/core.js": "admin/js/core.fd861d43f0f5.js", "admin/js/collapse.js": "admin/js/collapse.17d715df2104.js", "admin/js/actions.js": "admin/js/actions.32861b6cbdf9.js", "admin/js/prepopulate.js": "admin/js/prepopulate.ff9208865444.js", "admin/js/cancel.js": "admin/js/cancel.1d69cba4b4bf.js", "admin/js/inlines.js": "admin/js/inlines.eda404ee376d.js", "admin/js/change_form.js": "admin/js/change_form.9e85003a1a38.js", "admin/js/SelectFilter2.js": "admin/js/SelectFilter2.17009b6d4428.js", "admin/js/jquery.init.js": "admin/js/jquery.init.95b62fa19378.js", "admin/js/popup_response.js": "admin/js/popup_response.6ce3197f8fc8.js", "admin/js/SelectBox.js": "admin/js/SelectBox.b49f008d186b.js", "admin/js/actions.min.js": "admin/js/actions.min.f51f04edab28.js", "admin/js/calendar.js": "admin/js/calendar.9ac94d055fbd.js", "admin/js/timeparse.js": "admin/js/timeparse.51258861a46a.js", "admin/js/prepopulate.min.js": "admin/js/prepopulate.min.f4057ebb9b62.js", "admin/js/collapse.min.js": "admin/js/collapse.min.dc930adb2821.js", "admin/js/prepopulate_init.js": "admin/js/prepopulate_init.0d3b53c37074.js", "admin/img/search.svg": "admin/img/search.7cf54ff789c6.svg", "admin/img/icon-calendar.svg": "admin/img/icon-calendar.ac7aea671bea.svg", "admin/img/icon-clock.svg": "admin/img/icon-clock.e1d4dfac3f2b.svg", "admin/img/icon-no.svg": "admin/img/icon-no.439e821418cd.svg", "admin/img/tooltag-add.svg": "admin/img/tooltag-add.e59d620a9742.svg", "admin/img/inline-delete.svg": "admin/img/inline-delete.fec1b761f254.svg", "admin/img/LICENSE": "admin/img/LICENSE.2c54f4e1ca1c", "admin/img/icon-changelink.svg": "admin/img/icon-changelink.18d2fd706348.svg", "admin/img/icon-unknown.svg": "admin/img/icon-unknown.a18cb4398978.svg", "admin/img/sorting-icons.svg": "admin/img/sorting-icons.3a097b59f104.svg", "admin/img/icon-yes.svg": "admin/img/icon-yes.d2f9f035226a.svg", "admin/img/icon-addlink.svg": "admin/img/icon-addlink.d519b3bab011.svg", "admin/img/icon-unknown-alt.svg": "admin/img/icon-unknown-alt.81536e128bb6.svg", "admin/img/icon-deletelink.svg": "admin/img/icon-deletelink.564ef9dc3854.svg", "admin/img/README.txt": "admin/img/README.837277fa1908.txt", "admin/img/selector-icons.svg": "admin/img/selector-icons.b4555096cea2.svg", "admin/img/calendar-icons.svg": "admin/img/calendar-icons.39b290681a8b.svg", "admin/img/tooltag-arrowright.svg": "admin/img/tooltag-arrowright.bbfb788a849e.svg", "admin/img/icon-alert.svg": "admin/img/icon-alert.034cc7d8a67f.svg", "admin/fonts/Roboto-Light-webfont.woff": "admin/fonts/Roboto-Light-webfont.b446c2399bb6.woff", "admin/fonts/Roboto-Bold-webfont.woff": "admin/fonts/Roboto-Bold-webfont.2ad99072841e.woff", "admin/fonts/Roboto-Regular-webfont.woff": "admin/fonts/Roboto-Regular-webfont.ec39515ae8c6.woff", "admin/fonts/README.txt": "admin/fonts/README.2c3d0bcdede2.txt", "admin/fonts/LICENSE.txt": "admin/fonts/LICENSE.d273d63619c9.txt"}, "version": "1.0"}
\ No newline at end of file
+{"paths": {"admin/js/vendor/jquery/LICENSE-JQUERY.txt": "admin/js/vendor/jquery/LICENSE-JQUERY.a158210a2737.txt", "admin/js/vendor/jquery/jquery.min.js": "admin/js/vendor/jquery/jquery.min.33cabfa15c10.js", "admin/js/vendor/jquery/jquery.js": "admin/js/vendor/jquery/jquery.aacc43d6f308.js", "admin/js/vendor/xregexp/xregexp.min.js": "admin/js/vendor/xregexp/xregexp.min.c95393b8ca4d.js", "admin/js/vendor/xregexp/xregexp.js": "admin/js/vendor/xregexp/xregexp.1865b1cf5085.js", "admin/js/vendor/xregexp/LICENSE-XREGEXP.txt": "admin/js/vendor/xregexp/LICENSE-XREGEXP.d64cecf4f157.txt", "admin/js/admin/RelatedObjectLookups.js": "admin/js/admin/RelatedObjectLookups.d2bdb1018963.js", "admin/js/admin/DateTimeShortcuts.js": "admin/js/admin/DateTimeShortcuts.1536fb6bea1d.js", "admin/img/gis/move_vertex_on.svg": "admin/img/gis/move_vertex_on.0047eba25b67.svg", "admin/img/gis/move_vertex_off.svg": "admin/img/gis/move_vertex_off.7a23bf31ef8a.svg", "admin/css/widgets.css": "admin/css/widgets.5e372b41c483.css", "admin/css/login.css": "admin/css/login.a846c0e2ef65.css", "admin/css/dashboard.css": "admin/css/dashboard.7ac78187c567.css", "admin/css/forms.css": "admin/css/forms.2003a066ae02.css", "admin/css/fonts.css": "admin/css/fonts.494e4ec545c9.css", "admin/css/rtl.css": "admin/css/rtl.4c867197b256.css", "admin/css/base.css": "admin/css/base.6b517d0d5813.css", "admin/css/changelists.css": "admin/css/changelists.f6dc691f8d62.css", "admin/js/urlify.js": "admin/js/urlify.411bc3bb651c.js", "admin/js/inlines.min.js": "admin/js/inlines.min.d75b9ed03975.js", "admin/js/core.js": "admin/js/core.fd861d43f0f5.js", "admin/js/collapse.js": "admin/js/collapse.17d715df2104.js", "admin/js/actions.js": "admin/js/actions.32861b6cbdf9.js", "admin/js/prepopulate.js": "admin/js/prepopulate.ff9208865444.js", "admin/js/cancel.js": "admin/js/cancel.1d69cba4b4bf.js", "admin/js/inlines.js": "admin/js/inlines.eda404ee376d.js", "admin/js/change_form.js": "admin/js/change_form.9e85003a1a38.js", "admin/js/SelectFilter2.js": "admin/js/SelectFilter2.17009b6d4428.js", "admin/js/jquery.init.js": "admin/js/jquery.init.95b62fa19378.js", "admin/js/popup_response.js": "admin/js/popup_response.6ce3197f8fc8.js", "admin/js/SelectBox.js": "admin/js/SelectBox.b49f008d186b.js", "admin/js/actions.min.js": "admin/js/actions.min.f51f04edab28.js", "admin/js/calendar.js": "admin/js/calendar.9ac94d055fbd.js", "admin/js/timeparse.js": "admin/js/timeparse.51258861a46a.js", "admin/js/prepopulate.min.js": "admin/js/prepopulate.min.f4057ebb9b62.js", "admin/js/collapse.min.js": "admin/js/collapse.min.dc930adb2821.js", "admin/js/prepopulate_init.js": "admin/js/prepopulate_init.0d3b53c37074.js", "admin/img/search.svg": "admin/img/search.7cf54ff789c6.svg", "admin/img/icon-calendar.svg": "admin/img/icon-calendar.ac7aea671bea.svg", "admin/img/icon-clock.svg": "admin/img/icon-clock.e1d4dfac3f2b.svg", "admin/img/icon-no.svg": "admin/img/icon-no.439e821418cd.svg", "admin/img/tooltag-add.svg": "admin/img/tooltag-add.e59d620a9742.svg", "admin/img/inline-delete.svg": "admin/img/inline-delete.fec1b761f254.svg", "admin/img/LICENSE": "admin/img/LICENSE.2c54f4e1ca1c", "admin/img/icon-changelink.svg": "admin/img/icon-changelink.18d2fd706348.svg", "admin/img/icon-unknown.svg": "admin/img/icon-unknown.a18cb4398978.svg", "admin/img/sorting-icons.svg": "admin/img/sorting-icons.3a097b59f104.svg", "admin/img/icon-yes.svg": "admin/img/icon-yes.d2f9f035226a.svg", "admin/img/icon-addlink.svg": "admin/img/icon-addlink.d519b3bab011.svg", "admin/img/icon-unknown-alt.svg": "admin/img/icon-unknown-alt.81536e128bb6.svg", "admin/img/icon-deletelink.svg": "admin/img/icon-deletelink.564ef9dc3854.svg", "admin/img/README.txt": "admin/img/README.837277fa1908.txt", "admin/img/selector-icons.svg": "admin/img/selector-icons.b4555096cea2.svg", "admin/img/calendar-icons.svg": "admin/img/calendar-icons.39b290681a8b.svg", "admin/img/tooltag-arrowright.svg": "admin/img/tooltag-arrowright.bbfb788a849e.svg", "admin/img/icon-alert.svg": "admin/img/icon-alert.034cc7d8a67f.svg", "admin/fonts/Roboto-Light-webfont.woff": "admin/fonts/Roboto-Light-webfont.b446c2399bb6.woff", "admin/fonts/Roboto-Bold-webfont.woff": "admin/fonts/Roboto-Bold-webfont.2ad99072841e.woff", "admin/fonts/Roboto-Regular-webfont.woff": "admin/fonts/Roboto-Regular-webfont.ec39515ae8c6.woff", "admin/fonts/README.txt": "admin/fonts/README.2c3d0bcdede2.txt", "admin/fonts/LICENSE.txt": "admin/fonts/LICENSE.d273d63619c9.txt", "css/tutorial_2.css": "css/tutorial_2.366cb34e0003.css"}, "version": "1.0"}
\ No newline at end of file
diff --git a/tutorial/urls.py b/tutorial/urls.py
index 5d9b2be7b9b094fddcfb2ab86880b239d58a6448..e39dabaa310c3fb0d7b05a1b19bc160f0d0fb6aa 100644
--- a/tutorial/urls.py
+++ b/tutorial/urls.py
@@ -17,8 +17,10 @@ from django.conf.urls import url, include
 from django.contrib import admin
 from django.views.generic.base import RedirectView
 import tutorial_1.urls as tutorial_1
+import tutorial_2.urls as tutorial_2
 
 urlpatterns = [
     url(r'^admin/', admin.site.urls),
     url(r'^tutorial-1/', include(tutorial_1, namespace='tutorial-1')),
+    url(r'^tutorial-2/', include(tutorial_2, namespace='tutorial-2')),
 ]
diff --git a/tutorial_2/functional_tests.py b/tutorial_2/functional_tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..c75061f29258c7bb8a1e42e00f0bc1e2d98dcdc0
--- /dev/null
+++ b/tutorial_2/functional_tests.py
@@ -0,0 +1,100 @@
+import os
+from django.test import TestCase
+
+from selenium import webdriver
+from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.common.keys import Keys
+
+GITLAB_ENV = os.environ.get("HEROKU_APP_HOST") != None
+
+
+class Tutorial2FunctionalTest(TestCase):
+
+    def setUp(self):
+        chrome_options = Options()
+        chrome_options.add_argument('--dns-prefatch-disable')
+        chrome_options.add_argument('--no-sandbox')
+        chrome_options.add_argument('--headless')
+        chrome_options.add_argument('--disable-gpu')
+        self.selenium = webdriver.Chrome('./chromedriver', chrome_options=chrome_options)
+
+        self.localhost = "http://127.0.0.1:8000/tutorial-2/"
+
+        self.cases = [
+            (5, "oh, tidak"),
+            (2, "sibuk tapi santai"),
+            (0, "yey, waktunya berlibur"),
+        ]
+        print("GITLAB ENV:", GITLAB_ENV)
+        if GITLAB_ENV:
+            self.localhost = "https://pmpl-izzan.herokuapp.com/tutorial-2/"
+
+        super(Tutorial2FunctionalTest, self).setUp()
+
+    def tearDown(self):
+        self.selenium.quit()
+        super(Tutorial2FunctionalTest, self).tearDown()
+
+    def test_find_name(self):
+        selenium = self.selenium
+        selenium.get(self.localhost)
+        self.assertIn('Izzan Fakhril Islam', selenium.page_source)
+
+    def test_input_todo_item(self):
+        selenium = self.selenium
+        selenium.get(self.localhost)
+
+        todo_date = selenium.find_element_by_id('todo_date')
+        todo_date.send_keys('23052019')
+        todo_date.send_keys(Keys.TAB)
+        todo_date.send_keys('1245')
+
+        todo_text = selenium.find_element_by_id('activity')
+        todo_text.send_keys('Dummy')
+
+        selenium.find_element_by_id('todo_submit').click()
+        self.assertIn('Dummy', selenium.page_source)
+
+    def test_input_todo_commentary_item(self):
+        print("LOCALHOST:", self.localhost)
+        selenium = self.selenium
+        selenium.get(self.localhost)
+
+        todo_date = selenium.find_element_by_id('comment_date')
+        todo_date.send_keys('23122019')
+
+        todo_comment = selenium.find_element_by_id('comment')
+        todo_comment.send_keys('Dummy comment')
+
+        selenium.find_element_by_id('comment_submit').click()
+        self.assertIn('Dummy comment', selenium.page_source)
+
+    def test_input_todo_on_the_same_date_and_generate_comment(self):
+        selenium = self.selenium
+        selenium.get(self.localhost)
+        date = 24
+        month_and_year = '112019'
+
+        for n, comment in self.cases:
+            time = 1045
+            for i in range(n):
+                todo_date = selenium.find_element_by_id('todo_date')
+                todo_date.send_keys(str(date) + month_and_year)
+                todo_date.send_keys(Keys.TAB)
+                todo_date.send_keys(str(time))
+
+                todo_text = selenium.find_element_by_id('activity')
+                todo_text.send_keys('Dummy Activity' + str(n))
+
+                selenium.find_element_by_id('todo_submit').click()
+                time += 1
+
+            todo_date = selenium.find_element_by_id('comment_date')
+            todo_date.send_keys(str(date) + month_and_year)
+
+            todo_comment = selenium.find_element_by_id('comment')
+            todo_comment.send_keys(comment)
+            selenium.find_element_by_id('comment_submit').click()
+
+            self.assertIn(comment, selenium.page_source)
+            date += 1
diff --git a/tutorial_2/migrations/0001_initial.py b/tutorial_2/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..c34415b8d724781da70853433e231ef228459d5e
--- /dev/null
+++ b/tutorial_2/migrations/0001_initial.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.17 on 2019-09-23 12:42
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='TodoList',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateTimeField()),
+                ('todo_list', models.TextField(max_length=100)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='TodoListCommentary',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date', models.DateTimeField()),
+                ('comment', models.TextField(max_length=100)),
+            ],
+        ),
+    ]
diff --git a/tutorial_2/migrations/0002_auto_20190924_1309.py b/tutorial_2/migrations/0002_auto_20190924_1309.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2b0bf813f77fff1e8e9255ebc4bd75244e9b7fb
--- /dev/null
+++ b/tutorial_2/migrations/0002_auto_20190924_1309.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.17 on 2019-09-24 06:09
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tutorial_2', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='todolistcommentary',
+            name='date',
+            field=models.DateField(),
+        ),
+    ]
diff --git a/tutorial_2/models.py b/tutorial_2/models.py
index 71a836239075aa6e6e4ecb700e9c42c95c022d91..1110cb7f93828cf4eb037f7360ee940030a5c088 100644
--- a/tutorial_2/models.py
+++ b/tutorial_2/models.py
@@ -1,3 +1,18 @@
 from django.db import models
 
+
 # Create your models here.
+class TodoList(models.Model):
+    date = models.DateTimeField()
+    todo_list = models.TextField(max_length=100)
+
+    def __str__(self):
+        return "Todo at %s: %s" % (self.date.timestamp(), self.todo_list)
+
+
+class TodoListCommentary(models.Model):
+    date = models.DateField()
+    comment = models.TextField(max_length=100)
+
+    def __str__(self):
+        return "Commentary at %s: %s" % (self.date.__str__, self.comment)
diff --git a/tutorial_2/static/css/tutorial_2.css b/tutorial_2/static/css/tutorial_2.css
new file mode 100644
index 0000000000000000000000000000000000000000..abc49e145e42f2ae80560eaa29eb6369736a02a0
--- /dev/null
+++ b/tutorial_2/static/css/tutorial_2.css
@@ -0,0 +1,11 @@
+body {
+  min-height: 2000px;
+}
+
+.navbar-static-top {
+  margin-bottom: 19px;
+}
+/*Textarea not resizeable*/
+textarea {
+    resize:none
+}
diff --git a/tutorial_2/templates/layout/base.html b/tutorial_2/templates/layout/base.html
new file mode 100644
index 0000000000000000000000000000000000000000..5c3539c1d846fcda55c243b6c7f26aa570eaf88d
--- /dev/null
+++ b/tutorial_2/templates/layout/base.html
@@ -0,0 +1,46 @@
+{% load staticfiles %}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="description" content="Tutorial 2 PMPL">
+    <meta name="author" content="{{ author }}">
+    <!-- bootstrap csss -->
+    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
+
+    <title>
+        {% block title %} Tutorial 2 PMPL by {{ author }} - {{ npm }}{% endblock %}
+    </title>
+</head>
+<style>
+    @import url(https://fonts.googleapis.com/css?family=Roboto|Roboto+Slab);
+    .container, .footer-down, .header-up{
+        font-family: "Roboto", "sans-serif";
+        font-weight: normal;
+    }
+
+</style>
+<body style="background-color: darkseagreen">
+    <header class="header-up">
+        {% include "tutorial_2/../partials/header.html" %}
+    </header>
+    <content>
+        <div class="container">
+            {% block content %}
+
+            {% endblock %}
+        </div>
+
+    </content>
+    <footer class="footer-down">
+        <!-- TODO Block Footer dan include footer.html -->
+        {% block footer %}
+        {% include "tutorial_2/../partials/footer.html" %}
+        {% endblock %}
+    </footer>
+
+    <!-- Jquery n Bootstrap Script -->
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
+    <script type="application/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
+</body>
+</html>
diff --git a/tutorial_2/templates/partials/footer.html b/tutorial_2/templates/partials/footer.html
new file mode 100644
index 0000000000000000000000000000000000000000..7b444475c27afc22cce7bca2e845897c6f714a18
--- /dev/null
+++ b/tutorial_2/templates/partials/footer.html
@@ -0,0 +1 @@
+<p align="center">&copy; {{ author }}</p>
diff --git a/tutorial_2/templates/partials/header.html b/tutorial_2/templates/partials/header.html
new file mode 100644
index 0000000000000000000000000000000000000000..094848a450432daeb4b8902f13474a458b16fa07
--- /dev/null
+++ b/tutorial_2/templates/partials/header.html
@@ -0,0 +1,23 @@
+<!-- Fixed navbar -->
+<style type = text/css>
+    .navbar-static-top{
+        background-color: darkgray;
+        font-size: 17px;
+    }
+    .navbar-brand{
+        font-size: 17px;
+    }
+</style>
+<nav class="navbar navbar-default navbar-static-top">
+    <div class="container">
+        <div class="navbar-header">
+            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+                <span class="sr-only">Toggle navigation</span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </button>
+            <a class="navbar-brand" href="{% url 'tutorial-2:index' %}" style="color: black">Tutorial 2 PMPL</a>
+        </div>
+    </div>
+</nav>
diff --git a/tutorial_2/templates/tutorial_2.html b/tutorial_2/templates/tutorial_2.html
new file mode 100644
index 0000000000000000000000000000000000000000..693c35bfb1b6c8afb5cdf2e93799ddbc02bdbdba
--- /dev/null
+++ b/tutorial_2/templates/tutorial_2.html
@@ -0,0 +1,111 @@
+{% extends "tutorial_2/../layout/base.html" %}
+
+{% block content %}
+    <style>
+    .form-control {
+        @import url(https://fonts.googleapis.com/css?family=Droid+Sans+Mono);
+        margin-top: 3px;
+        margin-bottom: 3px;
+        font-family: "Droid Sans Mono";
+    }
+    table {
+        text-align: left;
+        padding: 8px;
+        width: 100%;
+    }
+    </style>
+    <h1 align="center" style="font-weight: bold">{{ author }}'s Todo List</h1>
+    <br><br>
+    <div class="row">
+        <div class="col-md-6 col-xs-6">
+            <h4>Todo List Table</h4>
+            <br>
+            <table>
+                <tr>
+                    <th><h5><b>Date/Time</b></h5></th>
+                    <th><h5><b>Todo</b></h5></th>
+                </tr>
+                {% for todos in todos_dict %}
+                    <tr>
+                        <td>{{ todos.date }}</td>
+                        <td>{{ todos.todo_list }}</td>
+                    </tr>
+                {% endfor %}
+            </table>
+            <br>
+            <h4>Daily Todo List Comments</h4>
+            <br>
+            <table>
+                <tr>
+                    <th><h5><b>Date</b></h5></th>
+                    <th><h5><b>Comment</b></h5></th>
+                </tr>
+                {% for commentary in todos_commentary_dict %}
+                    <tr>
+                        <td>{{ commentary.date }}</td>
+                        <td>{{ commentary.comment }}</td>
+                    </tr>
+                {% endfor %}
+            </table>
+            <br>
+        </div>
+        <div class="col-md-6 col-xs-6">
+            <h4><b>Add Todo</b></h4>
+            <form method="POST" action="{% url 'tutorial-2:add_todo' %}">
+                {% csrf_token %}
+                <table>
+                    <tr>
+                        <td>
+                            Date/Time
+                        </td>
+                        <td>
+                            <input type="datetime-local" name="date" id="todo_date" required="required" size="27"/>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            Todo
+                        </td>
+                        <td>
+                            <input type="text" name="activity" id="activity" required="required" size="27">
+                        </td>
+                    </tr>
+                </table>
+                <br>
+                <button type="submit" id="todo_submit" class="button btn-success">Submit</button>
+            </form>
+            <br>
+            <div class="errornote">
+                <p style="color: black; font-family: 'Roboto Slab'">{{ error_msg }}</p>
+            </div>
+            <h4><b>Add Daily Todo List Comment</b></h4>
+            <form method="POST" action="{% url 'tutorial-2:add_todo_commentary' %}">
+                {% csrf_token %}
+                <table>
+                    <tr>
+                        <td>
+                            Date
+                        </td>
+                        <td>
+                            <input type="date" name="date" id="comment_date" required="required" size="27"/>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            Todo List Comment
+                        </td>
+                        <td>
+                            <input type="text" name="comment" id="comment" required="required" size="27">
+                        </td>
+                    </tr>
+                </table>
+                <br>
+                <button type="submit" id="comment_submit" class="button btn-success">Submit</button>
+            </form>
+            <br>
+            <div class="errornote">
+                <p style="color: black; font-family: 'Roboto Slab'">{{ commentary_error_msg }}</p>
+            </div>
+        </div>
+    </div>
+{% endblock %}
diff --git a/tutorial_2/unit_tests.py b/tutorial_2/unit_tests.py
index a4de0ad62de23661a74d2e2bf0314417b83bf9e7..eaab15fd044a3c50abe80f6c2c56c5fcc51cbeae 100644
--- a/tutorial_2/unit_tests.py
+++ b/tutorial_2/unit_tests.py
@@ -1,9 +1,81 @@
 from django.test import TestCase
-
+from django.test import Client
+from .views import index
+from .models import TodoListCommentary, TodoList
+from django.http import HttpRequest
+from django.utils import timezone
 
 # Create your tests here.
+DUMMY_TODO_ITEM = "Dummy todo item"
+DUMMY_TODO_COMMENTARY_ITEM = "Dummy todo commentary item"
+
 
 class Tutorial2UnitTest(TestCase):
 
-    def test_true(self):
-        self.assertTrue(1 + 1, 2)
+    def test_page_rendered_successfully(self):
+        response = Client().get('/tutorial-2/')
+        self.assertEqual(response.status_code, 200)
+
+    def test_name_in_page(self):
+        request = HttpRequest()
+        response = index(request)
+        html_response = response.content.decode('utf8')
+        self.assertIn('Izzan Fakhril Islam', html_response)
+
+    def test_todo_table_rendered_successfully(self):
+        request = HttpRequest()
+        response = index(request)
+        html_response = response.content.decode('utf8')
+        self.assertIn('<th><h5><b>Todo</b></h5></th>', html_response)
+
+    def test_todo_commentary_table_rendered_successfully(self):
+        request = HttpRequest()
+        response = index(request)
+        html_response = response.content.decode('utf8')
+        self.assertIn('<th><h5><b>Comment</b></h5></th>', html_response)
+
+    def test_add_todo_item_via_model(self):
+        TodoList.objects.create(
+            date=timezone.now(),
+            todo_list=DUMMY_TODO_ITEM
+        )
+        todo_count = TodoList.objects.all().count()
+        self.assertEqual(todo_count, 1)
+
+    def test_add_todo_commentary_item_via_model(self):
+        TodoListCommentary.objects.create(
+            date=timezone.now(),
+            comment=DUMMY_TODO_COMMENTARY_ITEM
+        )
+        todo_commentary_count = TodoListCommentary.objects.all().count()
+        self.assertEqual(todo_commentary_count, 1)
+
+    def test_POST_request_create_todo(self):
+        self.client.post(
+            '/tutorial-2/add_todo/',
+            data={
+                'date': '2019-09-12T15:15',
+                'activity': DUMMY_TODO_ITEM
+            }
+        )
+        todo_count = TodoList.objects.all().count()
+        page_response = self.client.get('/tutorial-2/')
+        html_response = page_response.content.decode('utf8')
+
+        self.assertEqual(todo_count, 1)
+        self.assertIn(DUMMY_TODO_ITEM, html_response)
+
+    def test_POST_request_create_todo_commentary(self):
+        self.client.post(
+            '/tutorial-2/add_todo_commentary/',
+            data={
+                'date': '2019-09-12',
+                'comment': DUMMY_TODO_COMMENTARY_ITEM
+            }
+        )
+        todo_comment_count = TodoListCommentary.objects.all().count()
+        page_response = self.client.get('/tutorial-2/')
+        html_response = page_response.content.decode('utf8')
+
+        self.assertEqual(todo_comment_count, 1)
+        self.assertIn(DUMMY_TODO_COMMENTARY_ITEM, html_response)
diff --git a/tutorial_2/urls.py b/tutorial_2/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3be594985d7f0702a418c79e4c3662680820498
--- /dev/null
+++ b/tutorial_2/urls.py
@@ -0,0 +1,8 @@
+from django.conf.urls import url
+from .views import index, add_todo, add_todo_commentary
+
+urlpatterns = [
+    url(r'^$', index, name='index'),
+    url(r'add_todo/$', add_todo, name='add_todo'),
+    url(r'add_todo_commentary/$', add_todo_commentary, name='add_todo_commentary')
+]
diff --git a/tutorial_2/views.py b/tutorial_2/views.py
index 91ea44a218fbd2f408430959283f0419c921093e..423522ff52a8525d894b7b9aa0ceacf223f507fe 100644
--- a/tutorial_2/views.py
+++ b/tutorial_2/views.py
@@ -1,3 +1,66 @@
-from django.shortcuts import render
+from django.shortcuts import render, redirect
+from .models import TodoList, TodoListCommentary
+from datetime import datetime
+from django.http import HttpResponseRedirect
+from django.urls import reverse
+import pytz
 
 # Create your views here.
+todo = {}
+todo_commentary = {}
+HTML_FILE = "tutorial_2.html"
+URL = "/tutorial-2/"
+response = {
+    'author': 'Izzan Fakhril Islam',
+    'npm': '1606875806',
+}
+
+
+def index(request):
+    todo_dict = TodoList.objects.all().values()
+    todo_commentary_dict = TodoListCommentary.objects.all().values()
+    response['todos_dict'] = convert_queryset_into_json(todo_dict)
+    response['todos_commentary_dict'] = convert_queryset_into_json(todo_commentary_dict)
+
+    return render(request, HTML_FILE, response)
+
+
+def add_todo(request):
+    if request.method == 'POST':
+        try:
+            date = datetime.strptime(request.POST['date'], '%Y-%m-%dT%H:%M')
+            TodoList.objects.create(
+                todo_list=request.POST['activity'],
+                date=date
+            )
+            return HttpResponseRedirect(reverse('tutorial-2:index'))
+        except ValueError:
+            todo = TodoList.objects.all().values()
+            response['error_msg'] = 'ERROR: Failed to add Todo List'
+            response['todos_dict'] = todo
+
+            return render(request, HTML_FILE, response)
+
+
+def add_todo_commentary(request):
+    if request.method == 'POST':
+        try:
+            date = datetime.strptime(request.POST['date'], '%Y-%m-%d')
+            TodoListCommentary.objects.create(
+                comment=request.POST['comment'],
+                date=date
+            )
+            return HttpResponseRedirect(reverse('tutorial-2:index'))
+        except ValueError:
+            todo_commentary = TodoListCommentary.objects.all().values()
+            response['commentary_error_msg'] = 'ERROR: Failed to add Todo List Commentary'
+            response['todos_commentary_dict'] = todo_commentary
+
+            return render(request, HTML_FILE, response)
+
+
+def convert_queryset_into_json(queryset_dict):
+    res = []
+    for data in queryset_dict:
+        res.append(data)
+    return res