Nama: Fahmi Ramadhan
NPM: 2206026473
Kelas: PBP A
Pada synchronous programming, program berjalan secara sekuensial atau berurutan. Artinya, operasi dieksekusi satu per satu dan menunggu hingga operasi sebelumnya selesai agar bisa melanjutkan ke operasi berikutnya.
Sementara itu, pada asynchronous programming, program dapat berjalan secara paralel atau bersamaan. Artinya, program tidak perlu menunggu suatu operasi selesai agar dapat melanjutkan ke operasi berikutnya. Untuk mengimplementasikan asynchronous programming, dapat digunakan konsep seperti callback, promise, atau async/await dalam javascript.
Dalam penerapan JavaScript dan AJAX, terdapat penerapan paradigma event-driven programming. Jelaskan maksud dari paradigma tersebut dan sebutkan salah satu contoh penerapannya pada tugas ini.
Event-driven programming adalah sebuah paradigma di mana suatu kode dapat menunggu terjadinya suatu event/peristiwa terjadi sebelum kode tersebut dieksekusi. Contohnya, pada tugas kali ini, ada tombol untuk menambahkan buku dengan id button_add
yang akan menjalankan fungsi addBook
ketika terjadi event onclick
(tombok diklik).
Penerapan asynchronous programming pada AJAX memungkinkan request ke server dan penanganan response dilakukan secara asynchronous. Artinya, program javascript tidak akan terhenti saat menunggu response dari server. Penerapannya dapat dilakukan dengan menambahkan async
dan await
pada javascript. Fungsi async
digunakan untuk menandai fungsi sebagai fungsi yang dapat mengembalikan nilai secara asynchronous, sedangkan fungsi await
digunakan untuk menunggu hasil dari fungsi async
. Berikut adalah contoh penerapannya pada tugas ini.
async function getItems() {
return fetch("{% url 'main:get_item_json' %}").then((res) => res.json())
}
async function refreshItems() {
...
const books = await getItems()
...
}
Pada PBP kali ini, penerapan AJAX dilakukan dengan menggunakan Fetch API daripada library jQuery. Bandingkanlah kedua teknologi tersebut dan tuliskan pendapat kamu teknologi manakah yang lebih baik untuk digunakan.
- Lebih banyak didukung oleh browser modern
- Menggunakan fetch dan response
- Lebih ringan karena hanya menggunakan javascript standar.
- Menggunakan promise untuk handle kode asynchronous.
- Merupakan library eksternal javascript yang telah ada sejak lama dan telah digunakan secara luas di web.
- Menggunakan callback success dan error
- Lebih berat karena menggunakan library eksternal.
- Menggunakan callback untuk handle kode asynchronous.
Menurut saya, pilihan antara Fetch API dan JQuery tergantung pada kebutuhan proyek dan preferensi pribadi. Namun secara keseluruhan, Fetch API lebih bagus untuk digunakan untuk pengembangan aplikasi web modern yang ringan karena tidak perlu menggunakan library eksternal.
1. Ubahlah kode cards data item agar dapat mendukung AJAX GET dan lakukan pengambilan item menggunakan AJAX GET.
Membuat fungsi views baru untuk get item.
def get_item_json(request):
books = Item.objects.filter(user=request.user)
return HttpResponse(serializers.serialize('json', books))
Menambahkan path-nya ke urls.py
urlpatterns = [
...
path('get-item/', get_item_json, name='get_item_json'),
...
]
Membuat async function untuk get item.
async function getItems() {
return fetch("{% url 'main:get_item_json' %}").then((res) => res.json())
}
Mengubah table menjadi cards
<div class="grid grid-cols-1 gap-4 mt-4" id="books_card"></div>
Membuat async function untuk refresh item.
async function refreshItems() {
document.getElementById("books_card").innerHTML = ""
const books = await getItems()
if (books.length != 0) {
let htmlString = ""
let totalBooks = 0
books.forEach((book, index) => {
const isLast = index === books.length - 1;
totalBooks += book.fields.amount
htmlString += `\n
<div class="bg-white last:bg-blue-200 rounded-lg shadow-lg overflow-hidden">
<div class="px-6 py-4">
<div class="font-bold text-xl">${ book.fields.name }</div>
<p class="text-gray-700 text-base mb-2">by ${ book.fields.author }</p>
<p class="text-gray-700 text-base">${ book.fields.description }</p>
</div>
<div class="px-6">
<span class="inline-block bg-blue-100 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2">Amount: ${ book.fields.amount }</span>
<span class="inline-block bg-blue-100 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2">${ book.fields.category }</span>
</div>
<div class="flex space-x-2 px-6 py-4">
<div>
<button type="submit" name="Increment" onclick="incrementBook(${book.pk})" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-2">Add one book</button>
</div>
<div>
<button type="submit" name="Decrement" onclick="decrementBook(${book.pk})" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-2">Subtract one book</button>
</div>
<div>
<button type="submit" name="Delete" onclick="deleteBook(${book.pk})" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Delete book(s)</button>
</div>
</div>
</div>`
})
document.getElementById("book_count").innerHTML = `There are currently
<span class="font-bold">${totalBooks}</span> book(s) with
<span class="font-bold">${books.length}</span> book title(s) stored in the system`
document.getElementById("books_card").innerHTML = htmlString
}
else {
document.getElementById("book_count").innerHTML = `<h5 class="font-bold text-xl">
There are no books stored in the system for this account</h5>`
}
}
Menambahkan button pada header untuk menambahkan buku.
<button id="open" class="bg-blue-100 hover:bg-blue-700 hover:text-white text-blue-500 font-bold py-2 px-4 rounded">
Add New Book
</button>
Membuat modals untuk menambahkan buku.
<!-- Overlay element -->
<div id="overlay" class="fixed hidden z-40 w-screen h-screen inset-0 bg-gray-900 bg-opacity-60"></div>
<!-- The dialog -->
<div id="dialog"
class="hidden fixed z-50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 bg-white rounded-md px-8 py-6 space-y-5 drop-shadow-lg">
<h1 class="text-2xl font-bold">Add New Book</h1>
<div class="py-5 border-t border-b border-blue-500">
<form id="form" onsubmit="return false;">
{% csrf_token %}
<div class="mb-3 flex">
<label for="name" class="font-bold mr-4 mt-2">Name:</label>
<input type="text" class="form-control w-full px-1 rounded-md shadow-sm border-gray-500 border" id="name" name="name">
</div>
<div class="mb-3 flex">
<label for="author" class="font-bold mr-4 mt-2">Author:</label>
<input type="text" class="form-control w-full px-1 rounded-md shadow-sm border-gray-500 border" id="author" name="author">
</div>
<div class="mb-3 flex">
<label for="category" class="font-bold mr-4 mt-2">Category:</label>
<input type="text" class="form-control w-full px-1 rounded-md shadow-sm border-gray-500 border" id="category" name="category">
</div>
<div class="mb-3 flex">
<label for="amount" class="font-bold mr-4 mt-2">Amount:</label>
<input type="number" class="form-control w-full px-1 rounded-md shadow-sm border-gray-500 border" id="amount" name="amount">
</div>
<div class="mb-3">
<label for="description" class="font-bold">Description:</label>
<textarea class="form-control mt-2 block w-full px-1 rounded-md shadow-sm border-gray-500 border" id="description" name="description"></textarea>
</div>
</form>
</div>
<div class="flex justify-end">
<!-- This button is used to close the dialog -->
<button type="button" id="close" class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded mr-2">
Close
</button>
<!-- This button is used to submit the form -->
<button type="button" id="button_add" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Add Book
</button>
</div>
</div>
Menambahkan script untuk membuka dan menutup modal.
var openButton = document.getElementById('open')
var dialog = document.getElementById('dialog')
var closeButton = document.getElementById('close')
var overlay = document.getElementById('overlay')
// show the overlay and the dialog
openButton.addEventListener('click', function () {
dialog.classList.remove('hidden')
overlay.classList.remove('hidden')
});
// hide the overlay and the dialog
closeButton.addEventListener('click', function () {
dialog.classList.add('hidden')
overlay.classList.add('hidden')
});
@csrf_exempt
def add_item_ajax(request):
if request.method == 'POST':
name = request.POST.get("name")
author = request.POST.get("author")
category = request.POST.get("category")
amount = request.POST.get("amount")
description = request.POST.get("description")
user = request.user
new_book = Item(name=name, author=author, category=category, amount=amount, description=description, user=user)
new_book.save()
return HttpResponse(b"CREATED", status=201)
return HttpResponseNotFound()
Bonus: Menambahkan fungsi ajax delete, add, dan decrement.
@csrf_exempt
def delete_item_ajax(request, book_id):
if request.method == 'DELETE':
book = Item.objects.get(pk=book_id, user=request.user)
book.delete()
return HttpResponse(b"OK", status=200)
return HttpResponseNotFound()
@csrf_exempt
def add_book_amount_ajax(request, book_id):
if request.method == 'POST':
book = Item.objects.get(pk=book_id, user=request.user)
book.amount += 1
book.save()
return HttpResponse(b"OK", status=200)
return HttpResponseNotFound()
@csrf_exempt
def dec_book_amount_ajax(request, book_id):
if request.method == 'POST':
book = Item.objects.get(pk=book_id, user=request.user)
book.amount -= 1
if book.amount < 0:
book.amount = 0
book.save()
return HttpResponse(b"OK", status=200)
return HttpResponseNotFound()
urlpatterns = [
...
path('create-ajax/', add_item_ajax, name='add_item_ajax'),
path('delete-ajax/<int:book_id>', delete_item_ajax, name='delete_item_ajax'),
path('add-book-amount-ajax/<int:book_id>', add_book_amount_ajax, name='add_book_amount_ajax'),
path('dec-book-amount-ajax/<int:book_id>', dec_book_amount_ajax, name='dec_book_amount_ajax'),
]
Menambahkan async function untuk menghubungkan modal pada main.html
ke path /create-ajax/
serta bonus (delete, add, dec)
function addBook() {
fetch("{% url 'main:add_item_ajax' %}", {
method: "POST",
body: new FormData(document.querySelector('#form'))
}).then(refreshItems)
document.getElementById("form").reset()
dialog.classList.add('hidden')
overlay.classList.add('hidden')
return false
}
document.getElementById("button_add").onclick = addBook
function deleteBook(book_id) {
fetch(`delete-ajax/${book_id}`, {
method: "DELETE",
}).then(refreshItems)
return false
}
function incrementBook(book_id) {
fetch(`add-book-amount-ajax/${book_id}`, {
method: "POST",
}).then(refreshItems)
return false
}
function decrementBook(book_id) {
fetch(`dec-book-amount-ajax/${book_id}`, {
method: "POST",
}).then(refreshItems)
return false
}
6. Lakukan refresh pada halaman utama secara asinkronus untuk menampilkan daftar item terbaru tanpa reload halaman utama secara keseluruhan.
Menambahkan refreshItems()
pada tag <script></script>
.
Menjalankan perintah python manage.py collectstatic
untuk mengumpulkan berkas static dari setiap aplikasi ke dalam suatu folder yang dapat dengan mudah disajikan pada produksi.
- Menambahkan
django-environ
padarequirements.txt
- Menjalankan perintah
pip install -r requirements.txt
- Membuat berkas `Procfile`` yang berisi:
release: django-admin migrate --noinput
web: gunicorn library_app.wsgi
- Membuat berkas
pbp-deploy.yml
pada folder.github\workflows
yang berisi:
name: Deploy
on:
push:
branches:
- main
- master
jobs:
Deployment:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- name: Cloning repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Push to Dokku server
uses: dokku/github-action@master
with:
branch: 'main'
git_remote_url: ssh://dokku@${{ secrets.DOKKU_SERVER_IP }}/${{ secrets.DOKKU_APP_NAME }}
ssh_private_key: ${{ secrets.DOKKU_SSH_PRIVATE_KEY }}
- Membuat berkas
.dockerignore
yang berisi:
**/*.pyc
**/*.pyo
**/*.mo
**/*.db
**/*.css.map
**/*.egg-info
**/*.sql.gz
**/__pycache__/
.cache
.project
.idea
.pydevproject
.idea/workspace.xml
.DS_Store
.git/
.sass-cache
.vagrant/
dist
docs
env
logs
src/{{ project_name }}/settings/local.py
src/node_modules
web/media
web/static/CACHE
stats
Dockerfile
.gitignore
Dockerfile
db.sqlite3
**/*.md
logs/
- Membuat berkas
Dockerfile
yang berisi:
FROM python:3.10-slim-buster
WORKDIR /app
ENV PYTHONUNBUFFERED=1 \
PYTHONPATH=/app \
DJANGO_SETTINGS_MODULE=library_app.settings \
PORT=8000 \
WEB_CONCURRENCY=2
# Install system packages required Django.
RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
RUN addgroup --system django \
&& adduser --system --ingroup django django
# Requirements are installed here to ensure they will be cached.
COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt
# Copy project code
COPY . .
RUN python manage.py collectstatic --noinput --clear
# Run as non-root user
RUN chown -R django:django /app
USER django
# Run application
# CMD gunicorn library_app.wsgi:application
-
Menambahkan
import environ
danimport os
padasettings.py
-
Menambahkan kode
env = environ.Env()
setelah baris kodeBASE_DIR
. -
Menambahkan kode berikut setelah baris kode
SECRET_KEY
.
PRODUCTION = env.bool('PRODUCTION', False)
- Menambahkan kode berikut setelah bagian kode
DATABASES
.
if PRODUCTION:
DEBUG = False
DATABASES = {
'default': env.db('DATABASE_URL')
}
DATABASES["default"]["ATOMIC_REQUESTS"] = True
- Mengatur static url.
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
- Menambahkan middleware whitenoise.
MIDDLEWARE = [
...
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]
-
Universal Selector digunakan untuk memilih semua elemen HTML pada halaman. selector ini cocok digunakan ketika ingin reset style atau ketika ingin memberikan style umum pada semua elemen dalam halaman, misalnya mengatur jenis font (
* {}
). -
Element Selector digunakan untuk memilih semua elemen HTML dengan nama elemen tertentu (misalnya, semua
<p>
). Selector ini cocok digunakan ketika ingin memberikan style umum pada semua elemen dengan jenis tertentu. (p {}
). -
Class Selector digunakan untuk memilih elemen berdasarkan atribut
class
yang diberikan. Dikarenakan beberapa elemen bisa memilikiclass
yang sama dengan elemen lainnya, kita bisa mengelompokkan elemen-elemen tersebut dalam kelas-kelas tertentu. Oleh karena itu, selector ini cocok digunakan ketika ingin memberikan style pada kelompok elemen dengan class tertentu yang sama (.nama-class {}
). -
ID Selector digunakan untuk memilih elemen berdasarkan atribut
id
yang diberikan. Berbeda denganclass
, atributid
bersifat unik untuk setiap elemen dalam satu filehtml
. Oleh karena itu, selector ini cocok digunakan ketika ingin memberikan style atau manipulasi spesifik pada elemen tertentu dengan ID yang unik (.value-id {}
). -
Descendant Selector digunakan untuk memilih elemen dalam hierarki tertentu. Selector ini cocok digunakan ketika ingin memberikan style pada semua elemen tertentu yang berada di dalam elemen tertentu, misalnya semua elemen
<li>
dalam elemen<ul>
(ul li {}
). -
Adjacent Selector digunakan untuk memilih suatu elemen yang bersebelahan dalam tingkat hierarki yang sama. Selector ini cocok digunakan ketika ingin memberikan style suatu elemen yang tepat didahului oleh suatu elemen tertentu, misalnya elemen
<p>
yang tepat setelah elemen<h1>
(h1 + p {}
). -
Direct-Descendant Selector digunakan untuk memilih suatu elemen yang berada dalam elemen tertentu dan hanya berbeda satu tingkat hierarki. Selector ini cocok digunakan ketika ingin memberikan style misalnya pada elemen
<li>
yang berada satu tingkat di dalam elemen<ul>
(ul > li {}
). -
Attribute Selector digunakan untuk memilih semua elemen yang atributnya diset memiliki nilai yang sama. Selector_ ini cocok digunakan ketika ingin memberikan style misalnya pada elemen
input
yang atributtype
-nya diset ke"text"
(input[type="text"] {}
).
<nav>
: Tag ini adalah sebuah semantic tag yang digunakan untuk menentukan bagian navigasi pada halaman web yang biasanya terdapat pada bagian atas halaman web dan berisikan tautan menuju halaman lain pada web tersebut.<table>
,<tr>
,<th>
, dan<td>
: Tag ini digunakan untuk membuat tabel dan mengatur elemen-elemen dalam tabel.<tr>
untuk table row,<th>
untuk table header, dan<td>
untuk table data.<title>
: Tag ini digunakan untuk menentukan judul halaman web yang akan ditampilkan pada tab judul browser.<a>
: Tag ini digunakan untuk membuat tautan atau hyperlink ke halaman web atau sumber daya lainnya. Tag ini memiliki atribut paling penting yaituhref
yang digunakan untuk menentukan alamat URL yang akan dituju saat tautan diklik.<div>
: Tag ini adalah sebuah semantic tag yang digunakan untuk mengelompokkan dan mengatur elemen-elemen HTML menjadi blok-blok konten. ini adalah elemen "divisi" yang umum digunakan untuk membuat wadah yang dapat diatur dan diberi style dengan CSS.
- Margin adalah area di luar batas elemen HTML yang memisahkan elemen tersebut dari elemen-elemen lain di sekitarnya.
- Ketika mengatur margin suatu elemen, jarak antara elemen tersebut dengan elemen-elemen lain di sekitarnya akan berubah.
- Padding adalah area yang terletak di dalam elemen, di antara batas elemen dan kontennya sendiri.
- Ketika mengatur padding suatu elemen, yang diubah adalah seberapa jauh konten elemen tersebut dari batas elemen tersebut, sehingga tidak memengaruhi elemen-elemen lain di sekitarnya.
Perbedaan antara framework CSS Tailwind dan Bootstrap. Kapan sebaiknya kita menggunakan Bootstrap daripada Tailwind, dan sebaliknya?
- Tailwind CSS mengikuti pendekatan "utility-first", yang berarti framework ini memberikan sejumlah besar kelas utilitas yang dapat ditambahkan langsung ke elemen HTML untuk merancang tampilan.
- Tailwind CSS memiliki file CSS yang lebih kecil sedikit dibandingkan Bootstrap serta hanya akan memuat kelas-kelas utilitas yang ada.
- Tailwind CSS memberikan fleksibilitas dan adaptabilitas yang tinggi terhadap proyek.
- Tailwind CSS memerlukan pengetahuan CSS yang lebih dalam karena kita akan bekerja dengan banyak kelas utilitas untuk merancang tampilan.
- Bootstrap memiliki desain yang lebih "opinionated". Ini berarti bahwa Bootstrap memiliki komponen-komponen yang telah dirancang dan ditata dengan baik, sehingga memungkinkan kita untuk membangun tampilan dengan cepat tanpa banyak penyesuaian.
- Bootstrap cenderung memiliki ukuran file yang lebih besar karena menyediakan banyak komponen dan style yang sudah jadi.
- Bootstrap sering kali menghasilkan tampilan yang lebih konsisten di seluruh proyek karena menggunakan komponen yang telah didefinisikan.
- Bootstrap lebih mudah dipelajari oleh pemula atau mereka yang tidak memiliki pengetahuan CSS yang mendalam karena komponen-komponennya telah dirancang dan ditata dengan baik.
Kita dapat menggunakan Bootstrap ketika kita ingin membuat tampilan dengan cepat dan mudah, terutama jika kita tidak memiliki banyak pengalaman dalam desain tampilan atau CSS. Sementara itu, kita dapat menggunakan Tailwind CSS ketika kita ingin membuat tampilan dengan mudah dan cepat tetapi juga masih fleksibel sehingga desain tidak monoton.
Kustomisasi desain pada template HTML yang telah dibuat pada Tugas 4 dengan menggunakan CSS framework Tailwind
Pertama-tama, saya menambahkan <script src="https://cdn.tailwindcss.com"></script>
pada base.html
agar dapat menggunakan Tailwind CSS. Kemudian, saya menambahkan atribut class="bg-blue-100"
pada elemen body di base.html
agar semua halaman memiliki warna latar belakang yang sama.
- Memindahkan konten halaman login ke tengah halaman.
<div class="flex justify-center items-center h-screen">
- Menambahkan background, padding, rounded corner, dan box shadow serta mengatur lebarnya menjadi full untuk layar kecil, 1/2 untuk layar medium, dan 1/3 untuk layar besar.
<div class = "login bg-blue-50 p-8 rounded shadow-md w-full md:w-1/2 lg:w-1/3">
- Mengatur header "Login" agar memiliki font size 2xl, bold weight, serta menambahkan bottom margin.
<h1 class="text-2xl font-bold mb-4">Login</h1>
- Pada form, saya mengubah tabel menjadi beberapa elemen
div
untuk field username dan password serta tombol login serta mengatur style-nya seperti berikut.
<form method="POST" action="">
{% csrf_token %}
<div class="mb-4">
<label class="block font-bold mb-2" for="username">
Username:
</label>
<input type="text" name="username" placeholder="Username" class="form-control w-full" id="username">
</div>
<div class="mb-4">
<label class="block font-bold mb-2" for="password">
Password:
</label>
<input type="password" name="password" placeholder="Password" class="form-control w-full" id="password">
</div>
<div class="flex justify-end">
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" type="submit">
Login
</button>
</div>
</form>
- Menambahkan margin top pada daftar messages dan pada teks "Don't have an account yet?" serta "Register Now"
{% if messages %}
<ul class="mt-4">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<p class="mt-4">Don't have an account yet? <a href="{% url 'main:register' %}" class="text-blue-500">Register Now</a></p>
- Sama seperti halaman login, saya memindahkan konten halaman register ke tengah halaman.
<div class="flex justify-center items-center h-screen">
- Menambahkan background, padding, rounded corner, dan box shadow serta mengatur lebarnya menjadi full untuk layar kecil dan 1/2 untuk layar medium atau lebih besar.
<div class = "register bg-blue-50 p-8 rounded shadow-md w-full md:w-1/2">
- Mengatur header "Register" agar memiliki font size 2xl, bold weight, serta menambahkan bottom margin.
<h1 class="text-2xl font-bold mb-4">Register</h1>
- Mengubah form dan menambahkan style menjadi seperti berikut:
<form method="POST" >
{% csrf_token %}
<table class="w-full mb-4">
{{ form.as_table }}
</table>
<div class="flex justify-end">
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" type="submit">
Register
</button>
</div>
</form>
- Menambahkan margin top pada daftar messages seperti pada halaman login.
- Masih sama seperti sebelumnya, saya memindahkan konten halaman add_book ke tengah halaman.
<div class="flex justify-center items-center h-screen">
- Menambahkan background, padding, rounded corner, dan box shadow serta mengatur lebarnya menjadi full untuk layar kecil dan 1/3 untuk layar medium atau lebih besar.
<div class = "add_book bg-blue-50 p-8 rounded shadow-md w-full md:w-1/3">
- Mengatur header "Add New Book" agar memiliki font size 2xl, bold weight, serta menambahkan bottom margin.
<h1 class="text-2xl font-bold mb-4">Add New Book</h1>
- Mengubah form dan menambahkan style menjadi seperti berikut:
<form method="POST">
{% csrf_token %}
<table class="w-full mb-4">
{{ form.as_table }}
</table>
<div class="flex justify-end">
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" type="submit">
Add Book
</button>
</div>
</form>
- Membuat fixed header yang berisikan nama aplikasi dan tombol untuk menambahkan buku.
<header class="bg-blue-500 shadow fixed w-full z-10">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex-shrink-0 flex items-center">
<h1 class="text-white font-bold text-2xl">{{ app_name }}</h1>
</div>
<div class="flex items-center justify-end">
<a href="{% url 'main:add_book' %}">
<button class="bg-blue-100 hover:bg-blue-700 hover:text-white text-blue-500 font-bold py-2 px-4 rounded">
Add New Book
</button>
</a>
</div>
</div>
</div>
</header>
- Membungkus isi konten halaman dalam container dan mengaturnya agar berada di tengah halaman.
<div class="flex justify-center">
<div class="container mt-20">
...
</div>
</div>
- Memindahkan tombol log out agar berada di samping nama user yang sedang login serta memberikannya style dan juga memindahkan informasi sesi login terakhir ke bagian atas.
<div class="flex mt-4">
<h5 class="mt-1 mr-2">logged in as: <span class="font-bold">{{name}}</span></h5>
<a href="{% url 'main:logout' %}">
<button class="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-4 rounded" type="submit">
Logout
</button>
</a>
</div>
<h5 class="mt-2">Last login session: {{ last_login }}</h5>
- Menambahkan blok
{% if books|length != 0 %}
sebelum menampilkan total buku dan tabel agar ketika tidak ada buku, header tabel tidak ditampilkan. Kemudian, ditambahkan pesan tidak ada buku pada blokelse
-nya. - Menambahkan
class="px-4 py-2 font-bold border-t border-b border-blue-500"
pada setiap header tabel. - Menambahkan
class="px-4 py-2 border-b border-blue-500 {% if forloop.last %}text-blue-500{% endif %}"
untuk setiap informasi buku pada tabel. Teks akan berwarna biru jika itu buku tersebut merupakan buku paling terakhir dalam daftar buku (BONUS). - Menambahkan atribut
title
pada tombol-tombol untuk increment buku, decrement buku, dan remove buku sebagai tooltip yang akan muncul ketika tombol di-hover.
UserCreationForm
adalah salah satu form yang disediakan oleh framework Django untuk mempermudah proses pembuatan user dalam aplikasi web. Dengan adanya hal tersebut, kita tidak perlu susah-susah menulis kode dari awal untuk membuat form dan validasi inputnya sehingga kita dapat menghemat waktu. Namun, UserCreationForm
juga memiliki kekurangan karena bentuknya yang sederhana. Jika kita ingin menambahkan fitur yang kompleks, misalnya konfirmasi email atau CAPTCHA, kita harus menulis kode sendiri.
Autentikasi adalah proses untuk memverifikasi identitas pengguna yang mengakses suatu aplikasi, misalnya proses login dalam aplikasi web. Pada Django, terdapat method authenticate
yang digunakan untuk mengautentikasi username dengan password-nya. Sementara itu, otorisasi adalah proses untuk memverifikasi apakah pengguna yang sudah terautentikasi memiliki akses pada suatu fitur dalam aplikasi web tersebut. Misalnya, pada scele, pengguna terautentikasi yang memiliki role dosen memiliki hak akses yang berbeda dengan pengguna terautentikasi yang memiliki role mahasiswa.
Autentikasi dan otorisasi merupakan hal penting untuk diimplementasikan pada suatu aplikasi web. Dengan menggabungkan keduanya, kita bisa membuat aplikasi web yang aman dengan kontrol yang tepat atas siapa saja yang dapat melakukan hal tertentu.
Apa itu cookies dalam konteks aplikasi web dan bagaimana Django menggunakan cookies untuk mengelola data sesi pengguna?
Cookies adalah sebuah potongan kecil dari data yang disimpan di sisi klien, yaitu web browser dari pengguna, agar data tersebut dapat digunakan kembali dalam request selanjutnya. Cookies biasa digunakan dalam menyimpan token autentikasi, melacak aktivitas pengguna, dan menyimpan preferensi pengguna. Cookies akan dihapus secara otomatis jika sudah mencapai waktu kedaluwarsanya.
Django memiliki dukungan bawaan untuk mengelola data sesi pengguna. Django menyediakan API untuk membaca nilai cookies dari HTTP request yang diterima browser pengguna dan kita bisa mengakses value dari dictionary request.COOKIES
. Untuk mengatur atau membuat cookies baru, kita bisa menggunakan response.set_cookie()
. Kemudian, untuk penghapusan cookies bisa menggunakan response.delete_cookie()
Apakah penggunaan cookies aman secara default dalam pengembangan web atau apakah ada risiko potensial yang harus diwaspadai?
Cookies bersifat aman karena ia hanya menyimpan data, bukan kode program, tidak dapat membaca atau menghapus data pada komputer pengguna. Namun, jika cookies tidak diatur dengan baik, misalnya terdapat informasi personal di dalamnya, ada risiko data tersebut dicuri oleh suatu script, bukan cookies-nya yang mencuri.
1. Mengimplementasikan fungsi registrasi, login, dan logout untuk memungkinkan pengguna untuk mengakses aplikasi sebelumnya dengan lancar.
Pertama-tama, saya membuka file views.py
pada subdirektori main
lalu mengimport method-method yang dibutuhkan dan menambahkan fungsi register
, login
, dan logout
berikut:
def register(request):
form = UserCreationForm()
if request.method == "POST":
form = UserCreationForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, 'Your account has been successfully created!')
return redirect('main:login')
context = {'form':form}
return render(request, 'register.html', context)
def login_user(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('main:show_main')
else:
messages.info(request, 'Sorry, incorrect username or password. Please try again.')
context = {}
return render(request, 'login.html', context)
def logout_user(request):
logout(request)
return redirect('main:login')
Setelah itu, pada direktori main/templates
, saya membuat berkas register.html
dan login.html
yang meng-extends base.html
.
register.html
{% extends 'base.html' %}
{% block meta %}
<title>Register</title>
{% endblock meta %}
{% block content %}
<div class = "login">
<h1>Register</h1>
<form method="POST" >
{% csrf_token %}
<table>
{{ form.as_table }}
<tr>
<td></td>
<td><input type="submit" name="submit" value="Daftar"/></td>
</tr>
</table>
</form>
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock content %}
login.html
{% extends 'base.html' %}
{% block meta %}
<title>Login</title>
{% endblock meta %}
{% block content %}
<div class = "login">
<h1>Login</h1>
<form method="POST" action="">
{% csrf_token %}
<table>
<tr>
<td>Username: </td>
<td><input type="text" name="username" placeholder="Username" class="form-control"></td>
</tr>
<tr>
<td>Password: </td>
<td><input type="password" name="password" placeholder="Password" class="form-control"></td>
</tr>
<tr>
<td></td>
<td><input class="btn login_btn" type="submit" value="Login"></td>
</tr>
</table>
</form>
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
Don't have an account yet? <a href="{% url 'main:register' %}">Register Now</a>
</div>
{% endblock content %}
Selanjutnya, saya menambahkan path url ke dalam urlpatterns
pada urls.py
di subdirektori main
untuk mengakses fungsi register
, login
, dan logout
tadi.
urlpatterns = [
...
path('register/', register, name='register'),
path('login/', login_user, name='login'),
path('logout/', logout_user, name='logout'),
]
Kemudian, saya melakukan restriksi akses halaman main agar hanya bisa diakses oleh pengguna yang sudah login (terautentikasi) dengan menambahkan kode @login_required(login_url='/login')
di atas fungsi show_main
2. Membuat dua akun pengguna dengan masing-masing tiga dummy data menggunakan model yang telah dibuat pada aplikasi sebelumnya untuk setiap akun di lokal.
Saat ini, kedua akun tersebut akan terhubung ke data yang sama. Oleh karena itu, selanjutnya saya akan menghubungkan model Item
dengan User
agar masing-masing user hanya melihat item-item yang telah ia buat sendiri.
Untuk menghubungkan model Item
dengan User
, saya menambahkan field baru bernama user
pada model.
from django.contrib.auth.models import User
class Item(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
...
Selanjutnya, saya mengubah fungsi show_main
dan add_book
pada views.py
menjadi sebagai berikut:
...
def show_main(request):
books = Item.objects.filter(user=request.user)
...
context = {
...
'name': request.user.username,
...
}
...
def add_book(request):
form = ItemForm(request.POST or None)
if form.is_valid() and request.method == "POST":
item = form.save(commit=False)
item.user = request.user
item.save()
return HttpResponseRedirect(reverse('main:show_main'))
...
Karena terdapat perubahan pada models, saya perlu melakukan migrasi dengan menjalankan python manage.py makemigrations
lalu python manage.py migrate
.
4. Menampilkan detail informasi pengguna yang sedang logged in seperti username dan menerapkan cookies
seperti last login
pada halaman utama aplikasi.
Pada tahap ini, saya menerapkan cookies yang bernama last_login
untuk melihat kapan terakhir kali suatu user melakukan login. Untuk itu, saya mengubah fungsi login_user
agar men-set cookies dengan key last_login
dengan value waktu sekarang. Selain itu, saya juga mengubah fungsi logout_user
agar menghapus cookies dengan key last_login
saat user melakukan logout.
def login_user(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
response = HttpResponseRedirect(reverse("main:show_main"))
response.set_cookie('last_login', str(datetime.datetime.now()))
return response
else:
messages.info(request, 'Sorry, incorrect username or password. Please try again.')
context = {}
return render(request, 'login.html', context)
def logout_user(request):
logout(request)
response = HttpResponseRedirect(reverse('main:login'))
response.delete_cookie('last_login')
return response
Kemudian, untuk menampilkan data last login ke halaman main, saya menambahkan sebuah data pada dictionary context
di fungsi show_main
dan menambahkan sebaris kode pada main.html
context = {
...
'last_login': request.COOKIES['last_login'],
}
...
<h5>Sesi terakhir login: {{ last_login }}</h5>
...
Saya menambahkan fungsi add_book_amount
, dec_book_amount
, dan remove_book
pada views.py
.
def remove_book(request, book_id):
if request.method == 'POST' and 'Remove' in request.POST:
book = Item.objects.get(id=book_id)
book.delete()
return HttpResponseRedirect(reverse('main:show_main'))
def add_book_amount(request, book_id):
if request.method == 'POST' and 'Increment' in request.POST:
book = Item.objects.get(id=book_id)
book.amount += 1
book.save()
return HttpResponseRedirect(reverse('main:show_main'))
def dec_book_amount(request, book_id):
if request.method == 'POST' and 'Decrement' in request.POST:
book = Item.objects.get(id=book_id)
book.amount -= 1
book.save()
return HttpResponseRedirect(reverse('main:show_main'))
Selanjutnya, saya menambahkan path url ke dalam urlpatterns
pada urls.py
di subdirektori main
untuk mengakses fungsi-fungsi tersebut.
urlpatterns = [
...
path('add_book_amount/<int:book_id>/', add_book_amount, name='add_book_amount'),
path('dec_book_amount/<int:book_id>/', dec_book_amount, name='dec_book_amount'),
path('remove_book/<int:book_id>/', remove_book, name='remove_book'),
]
Kemudian, saya menambahkan tombol pada tabel di main.html
untuk melakukan fungsi-fungsi di atas.
<table>
<tr>
...
<th colspan="3">Actions</th>
</tr>
{% for book in books %}
<tr>
...
<td>
<form action="{% url 'main:add_book_amount' book.id %}" method="post">
{% csrf_token %}
<button type="submit" name="Increment">➕</button>
</form>
</td>
<td>
<form action="{% url 'main:dec_book_amount' book.id %}" method="post">
{% csrf_token %}
<button type="submit" name="Decrement">➖</button>
</form>
</td>
<td>
<form action="{% url 'main:remove_book' book.id %}" method="post">
{% csrf_token %}
<button type="submit" name="Remove">❌</button>
</form>
</td>
</tr>
{% endfor %}
</table>
POST
dan GET
adalah dua metode HTTP yang digunakan saat berurusan dengan form. Berikut adalah perbedaannya.
- Data form dikemas oleh browser, di-encode untuk pengiriman, dan kemudian dikirim ke server.
- Digunakan untuk request yang dapat mengubah status sistem, seperti mengubah database
- Lebih aman untuk data sensitif seperti password karena data tidak terlihat dalam URL dan tidak muncul dalam browser history atau server log dalam bentuk plain text.
- Cocok untuk mengirim data besar atau data biner seperti gambar, serta untuk formulir administrasi dengan perlindungan tambahan seperti CSRF (Cross-site Request Forgery) protection.
- Data yang dikirimkan dikemas sebagai string dan dijadikan bagian dari URL yang dikirimkan ke server.
- Digunakan untuk request yang tidak memengaruhi status sistem.
- Data muncul dalam URL, yang berarti dapat terlihat dalam browser history dan server log sehingga kurang aman untuk data sensitif.
- Cocok untuk formulir pencarian web karena URL yang dihasilkan dapat dengan mudah di-bookmark, dibagikan, atau di-resubmit.
Sumber: https://docs.djangoproject.com/en/4.2/topics/forms/
- XML adalah sebuah markup language yang dirancang untuk menyimpan dan mengantarkan data yang mudah dibaca oleh manusia.
- XML menggunakan tag-tag yang mendefinisikan struktur data dalam dokumen.
- JSON adalah sebuah format yang digunakan untuk menyimpan, membaca, dan menukar informasi dari web server yang mudah dibaca oleh manusia.
- JSON menggunakan key-value pairs untuk merepresentasikan data seperti object pada JavaScript.
- JSON biasanya lebih efisien dalam hal ukuran file dibandingkan dengan XML.
- HTML adalah sebuah sebuah markup language yang digunakan untuk mengatur tampilan dan struktur konten di halaman web.
- HTML mengandung tag-tag bawaan untuk mengatur elemen-elemen halaman web dan biasanya memiliki atribut yang digunakan untuk menambahkan informasi tambahan mengenai elemen tersebut.
Jadi, perbedaan mendasar antara ketiganya adalah XML dan JSON digunakan untuk menyimpan dan mengirimkan data sedangkan HTML digunakan untuk mengatur tampilan halaman web.
- JSON mudah untuk dipahami oleh manusia karena menggunakan format key-value pairs yang bentuknya sering ditemui di banyak bahasa pemrograman dibandingkan dengan XML yang menggunakan tag.
- JSON didukung oleh sebagian besar bahasa pemrograman modern sehingga data dalam format JSON dapat dengan mudah diolah dan dimanipulasi di berbagai platform. Browser modern memiliki dukungan bawaan untuk melakukan parsing dan konversi data JSON menjadi object JavaScript.
- JSON memiliki format yang lebih ringan dibandingkan XML karena ukurannya yang lebih kecil, struktur yang lebih simpel, tidak adanya informasi yang redundan, seperti closing tag atau namespace sehingga mengurangi bandwidth dan waktu pemrosesan yang dibutuhkan untuk transfer dan manipulasi data.
Sebelum membuat form, saya membuat kerangka views terlebih dahulu agar kode lebih terstruktur dan nantinya akan memudahkan saya untuk memastikan konsistensi desain dan memperkecil kemungkinan redundansi kode. Untuk itu, saya membuat berkas baru bernama base.html
pada folder templates
di root folder dan menjadikannya sebagai template dasar dengan menyesuaikan isi TEMPLATES
pada settings.py
. Kemudian, saya mengubah main.html
agar meng-extends base.html
dan tag-tag html ada di dalam block content
Selanjutnya, saya membuat berkas forms.py
pada direktori main
untuk membuat struktur form penambahan buku baru.
from django.forms import ModelForm
from main.models import Item
class ItemForm(ModelForm):
class Meta:
model = Item
fields = ["name", "author", "category", "amount", "description"]
Kemudian, saya membuat fungsi add_book
yang menerima parameter request
pada views.py
untuk menerima data buku baru, menyimpannya ke database, dan kembali ke halaman utama setelah berhasil menyimpan.
def add_book(request):
form = ItemForm(request.POST or None)
if form.is_valid() and request.method == "POST":
form.save()
return HttpResponseRedirect(reverse('main:show_main'))
context = {'form': form}
return render(request, "add_book.html", context)
Setelah fungsi dibuat, saya menambahkan path url ke dalam urlpatterns
pada urls.py
di main
untuk mengakses fungsi tersebut.
urlpatterns = [
...
path('add_book', add_book, name='add_book'),
...
]
Kemudian, saya membuat berkas add_book.html
pada direktori main/templates
yang berisi fields form
untuk menambahkan data buku baru dan tombol submit untuk mengirimkan request ke fungsi add_book(request)
.
{% extends 'base.html' %}
{% block content %}
<h1>Add New Book</h1>
<form method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
<tr>
<td></td>
<td>
<input type="submit" value="Add Book"/>
</td>
</tr>
</table>
</form>
{% endblock %}
2. Menambahkan lima fungsi views
untuk melihat objek yang sudah ditambahkan dalam format HTML, XML, JSON, XML by ID, dan JSON by ID
Saya menambahkan books
yang berisi semua object Item dari database dan total_books
(BONUS) ke dalam context
pada fungsi show_main
untuk ditampilkan di halaman utama.
def show_main(request):
books = Item.objects.all()
total_books = 0
for book in books:
total_books += book.amount
context = {
'app_name': 'Library Management System',
'student_name': 'Fahmi Ramadhan',
'class': 'PBP A',
'books': books,
'total_books': total_books,
}
return render(request, "main.html", context)
Selanjutnya, saya menambahkan kode pada main.html
untuk menampilkan jumlah buku yang ada (BONUS), informasi setiap buku dalam bentuk tabel, serta tombol 'Add New Book' yang akan redirect ke add_book.html
.
{% block content %}
...
<h3>There are currently {{total_books}} books with {{books|length}} book titles stored in the system</h3>
<table>
<tr>
<th>Name</th>
<th>Author</th>
<th>Category</th>
<th>Amount</th>
<th>Description</th>
</tr>
{% for book in books %}
<tr>
<td>{{book.name}}</td>
<td>{{book.author}}</td>
<td>{{book.category}}</td>
<td>{{book.amount}}</td>
<td>{{book.description}}</td>
</tr>
{% endfor %}
</table>
<br />
<a href="{% url 'main:add_book' %}">
<button>
Add New Book
</button>
</a>
{% endblock content %}
def show_xml(request):
data = Item.objects.all()
return HttpResponse(serializers.serialize('xml', data), content_type="application/xml")
def show_json(request):
data = Item.objects.all()
return HttpResponse(serializers.serialize('json', data), content_type="application/json")
def show_xml_by_id(request, id):
data = Item.objects.filter(pk=id)
return HttpResponse(serializers.serialize("xml", data), content_type="application/xml")
def show_json_by_id(request, id):
data = Item.objects.filter(pk=id)
return HttpResponse(serializers.serialize("json", data), content_type="application/json")
Untuk membuat routing URL, saya membuka urls.py
pada direktori main
, kemudian meng-import fungsi-fungsi views
dan menambahkannya ke dalam urlpatterns
.
from django.urls import path
from main.views import show_main, add_book, show_xml, show_json, show_xml_by_id, show_json_by_id
app_name = 'main'
urlpatterns = [
path('', show_main, name='show_main'),
path('add_book', add_book, name='add_book'),
path('xml/', show_xml, name='show_xml'),
path('json/', show_json, name='show_json'),
path('xml/<int:id>/', show_xml_by_id, name='show_xml_by_id'),
path('json/<int:id>/', show_json_by_id, name='show_json_by_id'),
]
Sebelum melakukan add
-commit
-push
, saya membuat dan beralih ke branch baru bernama dev
dengan menggunakan perintah git checkout -b dev
. Kemudian, saya baru melakukan add
, commit
, serta push
menggunakan git push origin dev
.
Pertama-tama, saya membuat repositori GitHub baru bernama library-app
dengan visibilitas public.
Setelah itu, saya membuat direktori lokal baru bernama library_app
dan menginisiasi direktori tersebut sebagai repositori Git, menghubungkan repositori lokal dengan repositori GitHub, serta menambahkan file .gitignore
.
Kemudian, saya membuat virtual environment pada direktori tersebut dengan menjalankan perintah berikut:
python -m venv envSelanjutnya, saya aktifkan virtual environment tersebut dengan menjalankan perintah berikut:
env\Scripts\activate.batDalam virtual environment tersebut, saya meng-install dependencies dari berkas
requirements.txt
yang berisi:
django
gunicorn
whitenoise
psycopg2-binary
requests
urllib3
coverage
dengan menjalankan perintah berikut:
pip install -r requirements.txtSetelah semua dependencies ter-install, saya mulai membuat proyek Django dengan menjalankan perintah berikut:
django-admin startproject library_app .Setelah proyek dibuat, saya menambahkan
*
pada ALLOWED_HOST
di settings.py
agar aplikasi dapat diakses secara luas.
Untuk membuat aplikasi dengan nama main
, saya menjalankan perintah berikut:
python manage.py startapp mainKemudian, saya menambahkan
'main'
pada INSTALLED_APPS
di settings.py
yang ada di direktori library_app
.
model saya memiliki atribut sebagai berikut:
class Item(models.Model):
name = models.CharField(max_length=255)
author = models.CharField(max_length=255)
category = models.CharField(max_length=255)
amount = models.IntegerField()
description = models.TextField()
Selanjutnya, saya menjalankan perintah berikut untuk membuat migrasi model dan menerapkan migrasi tersebut ke dalam basis data lokal.
python manage.py makemigrations
python manage.py migrate
Dalam tahap ini, saya membuat fungsi show_main
pada views.py
untuk me-render tampilan HTML dengan menggunakan data yang diberikan.
from django.shortcuts import render
def show_main(request):
context = {
'app_name': 'Library Management System',
'student_name': 'Fahmi Ramadhan',
'class': 'PBP A',
'name': 'Operating System Concepts',
'author': 'Abraham Silberschatz, Peter B. Galvin, and Greg Gagne',
'category': 'Computer Science',
'amount': '10',
'description': 'Operating System Concepts book is an informative guide to operating systems with an overview of all the major aspects. The book deals with topics like computer process, operating systems and their functioning, and design. It also looks at special-purpose systems, storage management, security, distributed systems and memory.'
}
return render(request, "main.html", context)
Selanjutnya, saya membuat file main.html
pada direktori templates
di aplikasi main
dan mengisinya untuk menampilkan nama aplikasi,
identitas saya, dan lainnya dari dictionary context
sebagai berikut:
<h1>{{app_name}}</h1>
<h2>{{student_name}} - {{class}}</h2>
<p><strong>Name: </strong>{{name}}</p>
<p><strong>Author: </strong>{{author}}</p>
<p><strong>Category: </strong>{{category}}</p>
<p><strong>Amount: </strong>{{amount}}</p>
<p><strong>Description: </strong>{{description}}</p>
5. Membuat sebuah routing pada urls.py
aplikasi main
untuk memetakan fungsi yang telah dibuat pada views.py
.
Untuk membuat sebuah routing yang memetakan fungsi show_main
pada views.py
, saya membuat file urls.py
yang berisi:
from django.urls import path
from main.views import show_main
app_name = 'main'
urlpatterns = [
path('', show_main, name='show_main'),
]
Saya mengonfigurasi routing URL proyek dengan menambahkan path yang mengarah ke aplikasi main
pada urls.py
di direktori library_app
.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('main/', include('main.urls')),
]
Untuk melakukan deployment ke Adaptable, saya login ke Adaptable.io dengan menggunakan akun GitHub yang saya gunakan untuk membuat proyek. Kemudian, saya menghubungkan repositori proyek library_app
ke Adaptable untuk membuat aplikasi baru di Adaptable. Saya memilih Python App Template
sebagai template deployment dan PostgreSQL
sebagai tipe basis data yang digunakan. Selanjutnya, saya mengonfigurasikan versi Python dan start command. Kemudian, saya memasukkan nama aplikasi library-app
yang akan menjadi domain situs web aplikasi saya. Terakhir, saya centang bagian HTTP listener on PORT
dan klik Deploy App
untuk memulai proses deployment aplikasi.
- Client mengakses website dan Web Server menerima request.
- WSGI memproses server HTTP untuk situs web berbasis Python.
urls.py
berisi path yang mengarahkan request ke fungsi padaviews.py
.views.py
mengambil data darimodels.py
dan me-render HTML dari template.models.py
berisi classmodel
untuk mengelola data pada database.
Virtual environment memungkinkan kita untuk membuat lingkungan terisolasi untuk setiap proyek Django kita. Dengan ini, kita bisa dengan mudah megelola berbagai dependensi untuk masing-masing proyek Django dan menghindari konflik antara library
atau package
dengan versi yang berbeda yang mungkin dibutuhkan oleh proyek yang berbeda. Selain itu, virtual environment juga memudahkan kita dalam pemindahan proyek yang sedang dikembangkan ke host lain tanpa khawatir akan konflik antara dependensi. Meskipun kita bisa saja membuat aplikasi web berbasis Django tanpa menggunakan virtual environment, tetapi ini tidak disarankan karena akan lebih sulit untuk mengelola berbagai dependensi dan lebih berisiko terjadi konflik dengan proyek-proyek lain.
MVC, MVT, dan MVVM adalah beberapa contoh paradigma pemrograman web yang memisahkan komponen-komponen pada aplikasi, seperti logika dan tampilan aplikasi untuk memudahkan pengelolaannya.
- MVC memisahkan aplikasi menjadi tiga komponen, yaitu model, view, dan controller. Model berisi definisi dari data-data yang akan disimpan ke dalam database. Kemudian, view berhubungan dengan user interface untuk menampilkan halaman ke pengguna. Sementara itu, Controller berisi logika utama program yang mungkin memerlukan informasi dari database melalui model.
- MVT memisahkan aplikasi menjadi tiga komponen, yaitu mode, view, dan template. Sama halnya seperti MVC, model berisi definisi dari data-data yang akan disimpan ke database. Namun, perbedaan antara keduanya terletak pada view dan template. View dalam MVT melakukan fungsi yang sama dengan controller dalam MVC, sedangkan template dalam MVT melakukan fungsi yang sama dengan view dalam MVC. Django adalah salah satu framework yang menggunakan MVT.
- MVVM memisahkan juga aplikasi menjadi tiga komponen, yaitu model, view, dan view-model. Secara dasar, MVVM mirip dengan MVC, di mana model dan view dalam kedua paradigma tersebut melakukan fungsi yang serupa. Kemudian, view-model melakukan fungsi yang sama dengan controller dalam MVC.
Secara keseluruhan, ketiganya memiliki tujuan yang serupa, yaitu mengisolasi logika aplikasi dari user interface. Namun, perbedaan utama di antara ketiganya terletak pada bagaimana okmponen-komponen tersebut disusun dan berhubungan satu sama lain.
Pada file tests.py
, saya menambahkan sebuah unit test tambahan untuk mengetes apakah model benar dan apakah data berhasil dimasukkan ke database.
from django.test import TestCase, Client
from main.models import Item
class mainTest(TestCase):
def test_main_url_is_exist(self):
response = Client().get('/main/')
self.assertEqual(response.status_code, 200)
def test_main_using_main_template(self):
response = Client().get('/main/')
self.assertTemplateUsed(response, 'main.html')
class itemTest(TestCase):
def test_item(self):
item = Item.objects.create(
name="Operating System Concepts",
author="Abraham Silberschatz, Peter B. Galvin, and Greg Gagne",
category="Computer Science",
amount=10,
description="Operating System Concepts book is an informative guide to operating systems with an overview of all the major aspects. The book deals with topics like computer process, operating systems and their functioning, and design. It also looks at special-purpose systems, storage management, security, distributed systems and memory."
)
self.assertEqual(item.name, "Operating System Concepts")
self.assertEqual(item.author, "Abraham Silberschatz, Peter B. Galvin, and Greg Gagne")
self.assertEqual(item.category, "Computer Science")
self.assertEqual(item.amount, 10)
self.assertEqual(item.description, "Operating System Concepts book is an informative guide to operating systems with an overview of all the major aspects. The book deals with topics like computer process, operating systems and their functioning, and design. It also looks at special-purpose systems, storage management, security, distributed systems and memory.")
Berikut adalah hasil test dan report-nya:
(env) C:\Users\USER\library_app>coverage run --source="." manage.py test
Found 3 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.054s
OK
Destroying test database for alias 'default'...
(env) C:\Users\USER\library_app>coverage report --show-missing
Name Stmts Miss Cover Missing
------------------------------------------------------------------------
library_app\__init__.py 0 0 100%
library_app\asgi.py 4 4 0% 10-16
library_app\settings.py 18 0 100%
library_app\urls.py 3 0 100%
library_app\wsgi.py 4 4 0% 10-16
main\__init__.py 0 0 100%
main\admin.py 1 0 100%
main\apps.py 4 0 100%
main\migrations\0001_initial.py 5 0 100%
main\migrations\0002_book_author.py 4 0 100%
main\migrations\0003_rename_book_item.py 4 0 100%
main\migrations\__init__.py 0 0 100%
main\models.py 7 0 100%
main\tests.py 17 0 100%
main\urls.py 4 0 100%
main\views.py 4 0 100%
manage.py 12 2 83% 12-13
------------------------------------------------------------------------
TOTAL 91 10 89%