Marquette merupakan proyek Django untuk tugas mata kuliah Pemrograman Berbasis Platform Ganjil 2024/2025 oleh Steven Setiawan dengan NPM 2306152260.
PWS Link : http://steven-setiawan-marquette.pbp.cs.ui.ac.id/
-
- [Implementasi Checklist Tugas 6]
- [Jelaskan manfaat dari penggunaan JavaScript dalam pengembangan aplikasi web]
- [Jelaskan fungsi dari penggunaan await saat menggunakan fetch()]
- [Mengapa kita perlu menggunakan decorator csrf_exempt?]
- [Mengapa pembersihan input tidak dilakukan di frontend saja?]
Membuat sebuah proyek Django baru
Untuk membuat proyek Django baru, langkah pertama yang saya lakukan adalah membuat folder dengan nama proyek yang saya inginkan.
Untuk melakukan itu, saya menjalankan command berikut:
mkdir marquette
cd marquette
Setelah itu, saya membuat dan mengaktifkan virtual environment pada folder tersebut dengan menggunakan command berikut:
python -m venv env
env\Scripts\activate
Kemudian, saya membuat file requirements.txt dan mengisinya dengan dependency proyek yang saya butuhkan. Lalu, saya menjalankan command berikut untuk menginstall seluruh module tersebut di virtual environment saya.
pip install -r requirements.txt
Setelah melakukan penginstallan dependency, saya membuat sebuah proyek Django baru dengan nama marquette menggunakan command berikut:
django-admin startproject marquette .
Di dalam folder marquette, saya menambahkan ALLOWED_HOSTS dalam settings.py agar dapat dilakukan deployment secara local.
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]Setelah menambahkan ALLOWED_HOSTS tersebut, saya melakukan deployment secara local dengan menggunakan command:
python manage.py migrate
python manage.py runserver
Deployment berhasil dan dapat diakses melalui http://127.0.0.1:8000/
Membuat aplikasi main pada proyek
Setelah deployment berhasil dilakukan di local, saya membuat aplikasi main pada proyek dengan menjalankan:
python manage.py startapp main
Terlihat ada folder baru pada root folder dengan nama main. Hal ini menandakan bahwa aplikasi main berhasil ditambahkan pada proyek.
Melakukan routing pada proyek agar dapat menjalankan aplikasi main
Selanjutnya, agar aplikasi main dapat dijalankan, kita perlu terlebih dahulu melakukan routing pada proyek. Hal ini dilakukan dengan membuka file `settings.py` pada direktori proyek `marquette` dan menambahkan `main` pada variabel `INSTALLED_APPS`.INSTALLED_APPS = [
...,
'main'
]Membuat model pada aplikasi main
Setelah melakukan routing agar dapat menjalankan aplikasi main, selanjutnya kita akan membuat model. Hal ini dilakukan dengan memodifikasi file models.py pada folder main. Berikut adalah kode yang saya tambahkan:
class Product(models.Model) :
name = models.CharField(max_length=255)
price = models.IntegerField()
description = models.TextField()Sebelum melanjutkan proyek ini, terlebih dahulu saya melakukan migration pada models yang saya buat dengan menjalankan:
python manage.py makemigrations
python manage.py migrate
Migration dilakukan untuk merefleksikan perubahan dalam model ke database schema (memastikan model dicatat dalam database schema).
Membuat sebuah fungsi pada views.py
Kemudian, saya membuat folder baru pada main dengan nama templates dan membuat file baru bernama main.html. main.html berfungsi sebagai display tampilan dari aplikasi main saya. Berikut adalah kode pada main.html saya:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ app_name }}</title>
<h1>Welcome to {{ app_name }}</h1>
<h2>We're selling all Coquette items y'all ever need!</h2>
<h4>Made by {{ name }} from {{ class }} class</h4>
</head>
<body>
</body>
</html>Untuk mengisi nilai/value pada template variables app_name, name, dan class, saya kemudian membuat fungsi pada views.py di folder main. Berikut adalah kode yang saya tambahkan:
def show_main(request) :
context = {
'app_name' : 'Marquette',
'name' : 'Steven Setiawan',
'class' : 'PBP D'
}
return render(request, "main.html", context)Membuat routing pada urls.py aplikasi main
Selanjutnya, saya melakukan konfigurasi routing urls.py agar aplikasi main dapat diakses melalui peramban ketika proyek dijalankan. Saya membuat file baru bernama urls.py pada direktori main dan menambahkan kode berikut:
from django.urls import path
from main.views import show_main
app_name = 'main'
urlpatterns = [
path('', show_main, name='show_main'),
]Setelahnya, saya menambahkan routingan URL dari aplikasi main ke urls.py pada direktori proyek. Berikut adalah file urls.py setelah dilakukan modifikasi:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('main.urls')),
]Melakukan deployment ke PWS
Setelah proyek berhasil dibuat, saya akan menyimpannya pada repository github serta mendeploynya pada PWS.
-
GitHub
Untuk menyimpan program yang telah saya buat dalam GitHub, terlebih dahulu saya membuat file
.gitignoreyang berisikan nama-nama file/folder yang tidak ingin saya simpan di dalam repository GitHub.Kemudian, saya membuat repository baru bernama
marquettemelalui web https://github.com/ dan kemudian menjalankan command berikut:git init git branch -M main git remote add origin https://github.com/setiawans/marquette.git git add . git commit -m "initial commit" git push -u origin main -
PWS
Setelah menyimpan program di GitHub, selanjutnya saya akan melakukan deployment menggunakan PWS. Terlebih dahulu saya menambahkan
ALLOWED_HOSTSpadasettings.pydi direktori proyek.ALLOWED_HOSTS = ["localhost", "127.0.0.1", "steven-setiawan-marquette.pbp.cs.ui.ac.id"]
Selanjutnya, saya menjalankan command berikut:
git remote add pws http://pbp.cs.ui.ac.id/steven.setiawan/marquette git branch -M master git push pws masterApabila ke depannya saya perlu melakukan update pada deployment tersebut, saya dapat menjalankan command berikut:
git branch -M main git push pws main:master
Buatlah bagan yang berisi request client ke web aplikasi berbasis Django beserta responnya dan jelaskan pada bagan tersebut kaitan antara urls.py, views.py, models.py, dan berkas html.
Ketika user/client mengakses aplikasi, request akan dikirimkan ke webserver. Request ini akan diolah di urls.py dan kemudian diteruskan ke views.py yang sesuai/bersangkutan.
Setelah itu, views.py akan berinteraksi/berkomunikasi dengan models.py yang juga berkomunikasi dengan database untuk melakukan proses read/write data jika diperlukan.
Setelah data diproses dan diterima, data akan didisplay oleh views.py dengan menggunakan templates yang berisikan berkas html. Halaman html inilah yang akan ditampilkan dan diakses oleh user/client.
Git merupakan sebuah version control system yang memungkinkan kita selaku pengembang untuk melacak perubahan-perubahan kode dari waktu ke waktu. Git mempermudah kita melakukan kolaborasi dengan pengembang lainnya secara online dengan memanfaatkan fitur branch yang berbeda. Dengan demikian, setiap anggota tim dapat bekerja tanpa mengganggu kode satu sama lain. Selanjutnya, Git memudahkan pengembang dalam menyimpan proyek-proyek yang telah mereka buat tanpa perlu khawatir adanya kerusakan atau kehilangan pada kode mereka. Selain fungsi-fungsi yang telah disebutkan di atas, Git masih memiliki banyak sekali fungsi lainnya. Secara umum, Git meningkatkan efisiensi setiap pengembang dalam melakukan pengembangan perangkat lunak.
Menurut Anda, dari semua framework yang ada, mengapa framework Django dijadikan permulaan pembelajaran pengembangan perangkat lunak?
Menurut saya, Django cocok menjadi titik awal pembelajaran pengembangan perangkat lunak karena framework ini berbasiskan bahasa pemrograman Python, yang terkenal ramah untuk dipelajari oleh pemula. Selanjutnya, Django menawarkan arsitektur yang terstruktur sekaligus mudah sekali untuk digunakan oleh para pemula, yakni arsitektur MVT atau Model-View-Template. Selain itu, Django juga memungkinkan pengembangan secara fullstack, yang mendukung hubungan antara front-end dan back-end secara sekaligus dalam satu framework.
Model pada Django disebut sebagai ORM (Object-Relational Mapping) dikarenakan Django memetakan objek-objeknya dengan database relasional. ORM menjadi interpreter yang memungkinkan kita berinteraksi dengan database tanpa perlu menuliskan query-query SQL secara manual.
Membuat input form untuk menambahkan objek model pada app sebelumnya
Sebelum membuat input form, karena page utama dan form kita memiliki beberapa bagian kode yang sama, maka kita dapat membuat suatu templates umum untuk mengurangi pengulangan kode yang repetitif.
Dalam mengimplementasikan hal tersebut, terlebih dahulu saya membuat direktori baru bernama templates/ pada direktori utama dan mengisinya dengan berkas base.html. Berikut adalah isi dari base.html:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% block meta %} {% endblock meta %}
</head>
<body>
{% block content %} {% endblock content %}
</body>
</html>Setelah itu, agar base.html bisa digunakan sebagai template pada proyek kita, kita perlu mengubah settings.py terlebih dahulu. Berikut adalah bagian yang saya modifikasi dari settings.py:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
...
},
]Setelah mengubah settings.py, saya langsung mengaplikasikan base.html pada berkas main.html yang terletak di direktori main/templates/. Berikut adalah hasil modifikasi dari main.html:
{% extends 'base.html' %}
{% block meta %}
<title>{{ app_name }}</title>
{% endblock meta %}
{% block content %}
<h1>Welcome to {{ app_name }}</h1>
<h2>We're selling all Coquette items y'all ever need!</h2>
<h4>Made by {{ name }} from {{ class }} class</h4>
{% endblock content %}Selanjutnya, sebelum membuat form, terlebih dahulu kita mengubah primary key dari models yang sebelumnya berbentuk integer menjadi UUID. Hal ini dilakukan untuk mencegah terjadinya serangan IDOR pada proyek yang kita buat. Berikut adalah hasil modifikasi yang saya lakukan pada models.py:
from django.db import models
import uuid
class Product(models.Model) :
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)
price = models.IntegerField()
description = models.TextField()Setelah itu, kita lakukan migrasi terlebih dahulu dengan menggunakan perintah berikut:
python manage.py makemigrations
python manage.py migrate
Kita telah berhasil mengamankan proyek kita dari serangan IDOR. Serangan ini sendiri berfokus pada eksploitasi primary key dari setiap objek model yang telah kita buat, dengan demikian, penyerang dapat mengakses objek model yang bukan miliknya.
Kemudian, untuk membuat form, kita tambahkan berkas forms.py pada direktori main. Kemudian, saya mengisi forms.py dengan kode berikut:
from django.forms import ModelForm
from main.models import Product
class ProductForm(ModelForm) :
class Meta :
model = Product
fields = ["name", "price", "description"]Setelah itu, saya melakukan sedikit modifikasi pada views.py yaitu melakukan import model dan form serta mengimport redirect dari library django.shortcuts.
from django.shortcuts import render, redirect
from main.forms import ProductForm
from main.models import ProductSelanjutnya, masih di views.py, saya menambahkan fungsi baru bernama create_product. Berikut adalah potongan kode dari fungsi tersebut:
def create_product(request) :
form = ProductForm(request.POST or None)
if form.is_valid() and request.method == "POST" :
form.save()
return redirect('main:show_main')
context = {'form' : form}
return render(request, "create_product.html", context)Secara general, fungsi tersebut akan menampilkan page create_product.html kepada user. Apabila form disubmit (request method POST) dan isinya valid (form.is_valid()), maka data yang diinput akan disimpan pada database dan fungsi akan melakukan redirect ke page utama.
Kemudian, saya juga menambahkan sedikit potongan kode pada fungsi show_main. Berikut adalah hasil modifikasi pada fungsi show_main:
def show_main(request) :
products = Product.objects.all()
context = {
'app_name' : 'Marquette',
'name' : 'Steven Setiawan',
'class' : 'PBP D',
'products' : products # Modifikasi dilakukan disini
}
return render(request, "main.html", context)Penambahan kode tersebut dilakukan untuk nantinya menampilkan seluruh list product pada main.html.
Selanjutnya, kita akan menambahkan views create_product ke urls.py. Berikut adalah hasil modifikasi yang telah saya lakukan:
from django.urls import path
from main.views import show_main, create_product
app_name = 'main'
urlpatterns = [
path('', show_main, name='show_main'),
path('create-product', create_product, name='create_product'),
]Setelah itu, agar fungsi create_product bisa menampilkan form yang telah kita buat, kita tambahkan berkas create_product.html pada direktori main/templates/. Berikut adalah isi dari create_product.html:
{% extends 'base.html' %}
{% block content %}
<h1>Add New Product</h1>
<form method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
<tr>
<td></td>
<td>
<input type="submit" value="Add Product" />
</td>
</tr>
</table>
</form>
{% endblock %}Kemudian, kita modifikasi juga main.html untuk menampilkan seluruh data product yang telah kita buat serta tombol "Add New Product" yang akan melakukan redirect ke halaman form. Berikut adalah potongan kode yang saya tambahkan:
...
{% if not products %}
<p>Belum ada data product pada Marquette.</p>
{% else %}
<table>
<tr>
<th>Product Name</th>
<th>Price</th>
<th>Description</th>
</tr>
{% comment %} Berikut cara memperlihatkan data product di bawah baris ini
{% endcomment %}
{% for product in products %}
<tr>
<td>{{product.name}}</td>
<td>{{product.price}}</td>
<td>{{product.description}}</td>
</tr>
{% endfor %}
</table>
{% endif %}
<br />
<a href="{% url 'main:create_product' %}">
<button>Add New Product</button>
</a>
{% endblock content %}Form telah berhasil diimplementasikan pada proyek kita! Untuk mengecek apakah proyek dapat berjalan dengan baik, jalankan perintah berikut:
python manage.py runserver
Menambahkan 4 fungsi views baru untuk melihat objek yang sudah ditambahkan dalam format XML, JSON, XML by ID, dan JSON by id
Kemudian, karena sebelumnya kita telah mengimplementasikan pembuatan objek product yang baru menggunakan form, tentu kita perlu mengetahui cara mengakses masing-masing product tersebut. Untuk itu, kita perlu membuat views yang menampilkan data dalam format XML maupun JSON.
Pertama-tama, kita import library HttpResponse dan serializers dengan menambahkan potongan kode berikut pada views.py di direktori main:
from django.http import HttpResponse
from django.core import serializersKemudian, untuk menampilkan data, baik secara keseluruhan ataupun berdasarkan hasil filtering UUID, dalam format XML, saya menambahkan views berikut dalam views.py:
def show_xml(request) :
data = Product.objects.all()
return HttpResponse(serializers.serialize("xml", data), content_type="application/xml")
def show_xml_by_id(request, id) :
data = Product.objects.filter(pk = id)
return HttpResponse(serializers.serialize("xml", data), content_type="application/xml")Pada potongan kode tersebut, fungsi show_xml akan mengembalikan seluruh objek yang ada pada database dalam format XML. Hal ini dilakukan dengan mengambil seluruh objek dari model Product, kemudian mengubahnya menjadi format XML dengan memanfaatkan serializers. Fungsi kedua juga mirip, tetapi terdapat filtering pada objek yang diambil, yaitu berdasarkan id.
Hal yang sama juga diimplementasikan untuk menampilkan data dalam bentuk JSON. Berikut adalah potongan kodenya:
def show_json(request) :
data = Product.objects.all()
return HttpResponse(serializers.serialize("json", data), content_type="application/json")
def show_json_by_id(request, id) :
data = Product.objects.filter(pk = id)
return HttpResponse(serializers.serialize("json", data), content_type="application/json")Membuat routing URL untuk masing-masing views
Untuk membuat routing URL dari masing-masing views tersebut, terlebih dahulu saya melakukan import views ke berkas urls.py di direktori main/.
from main.views import show_main, create_product, show_xml, show_json, show_xml_by_id, show_json_by_idSetelah melakukan import views, saya kemudian menambahkan path untuk masing-masing view tersebut.
urlpatterns = [
...
path('xml/', show_xml, name='show_xml'),
path('json/', show_json, name='show_json'),
path('xml/<str:id>/', show_xml_by_id, name='show_xml_by_id'),
path('json/<str:id>/', show_json_by_id, name='show_json_by_id'),
]Path pertama dan kedua berfungsi untuk menampilkan seluruh data dalam format XML dan JSON. Sedangkan, path ketiga dan keempat akan menampilkan data sesuai dengan filtering UUID, hal ini terlihat pada bagian kode <str:id>. Dengan demikian, misalkan kita ingin melihat product dengan UUID tertentu dalam format JSON, kita hanya perlu membuka URL http://127.0.0.1:8000/json/<UUID>.
Dalam mengimplementasikan sebuah platform, data delivery sangat diperlukan untuk proses pertukaran data antara satu bagian/stack dengan bagian lainnya. Dengan adanya mekanisme data delivery ini, proses pengelolaan data, terutama antara front-end dan back-end, akan berjalan jauh lebih baik dan efisien. Hal ini didukung dengan fleksibilitas format data seperti XML dan JSON. Apabila suatu platform tidak mengimplementasikan mekanisme ini, platform tersebut tidak akan berfungsi secara maksimal karena data-data yang masuk tidak dapat dikelola dengan baik oleh berbagai komponen/bagian platform tersebut.
Menurut diri saya pribadi, JSON masih lebih baik dibandingkan XML karena tingkat readability kode yang lebih tinggi. Hal ini jugalah yang menyebabkan JSON lebih populer dibandingkan XML. Perbandingan readability ini didasarkan pada fakta bahwa struktur JSON mirip dengan struktur dictionary python yang mengimplementasikan key:value pair, sehingga JSON lebih umum dan mudah untuk dipahami oleh para pengembang. Hal ini juga didukung dengan keringkasan serta simplesitas JSON dibandingkan XML, yang terkesan lebih bertele-tele. Selain itu, JSON juga sudah terintegrasi dengan bahasa JavaScript, sehingga memudahkan implementasinya dalam pengembangan platform. Walaupun begitu, XML masih berguna untuk beberapa kasus proyek yang memerlukan implementasi kompleks (yang tidak dapat dilakukan oleh JSON), sehingga XML tidak semena-mena menjadi bahasa yang useless atau tidak berguna karena tidak sebaik JSON dalam proyek skala kecil.
Jelaskan fungsi dari method is_valid() pada form Django dan mengapa kita membutuhkan method tersebut?
Method is_valid() berfungsi untuk memvalidasi data yang diinput/dimasukkan oleh user pada form yang telah kita buat. Method ini akan mengecek apakah input yang dimasukkan oleh user sesuai dengan aturan yang telah kita buat sebelumnya. Apabila input dari user dianggap valid, maka method akan mengembalikan respon True dan data pada form akan diproses dan disimpan pada database.
Method is_valid() diperlukan untuk memastikan kevalidan data dari input yang dimasukkan oleh user. Dengan demikian, kita dapat memastikan integritas serta keamanan platform yang telah kita buat. Selain itu, adanya validasi ini akan memastikan konsistensi serta kebersihan database dan memudahkan proses maintenance yang dilakukan oleh para pengembang ke depannya.
Mengapa kita membutuhkan csrf_token saat membuat form di Django? Apa yang dapat terjadi jika kita tidak menambahkan csrf_token pada form Django? Bagaimana hal tersebut dapat dimanfaatkan oleh penyerang?
csrf_token dibutuhkan dalam pembuatan form di Django untuk mencegah terjadinya exploit CSRF (Cross-Site Request Forgery). CSRF atau sering dikenal sebagai one-click attacks adalah sebuah kerentanan pada keamanan website di mana penyerang akan mengirimkan permintaan berbahaya sebagai user yang sudah terauntetikasi.
Apabila suatu form tidak mengimplementasikan csrf_token, maka penyerang dapat dengan mudah melancarkan serangan CSRF. Hal ini didasarkan pada fakta bahwa platform akan menganggap setiap request yang dilakukan adalah valid dan benar dari user. Dengan demikian, penyerang dapat mengirimkan well crafted malicious request untuk mengeksekusi tindakan berbahaya.
Dengan mengimplementasikan csrf_token, setiap request yang dilakukan oleh user dapat dicek kevalidannya, dikarenakan token ini akan digenerate oleh server-side application dan tidak dapat diketahui oleh penyerang. Dengan demikian, apabila penyerang mengirimkan request melalui form, platform dapat langsung mengetahui bahwa request tersebut bukanlah request dari pengguna, melainkan dari penyerang.
Mengimplementasikan fungsi registrasi, login, dan logout
-
Registrasi
Dalam mengimplementasikan fungsi registrasi, terlebih dahulu saya melakukan import beberapa library yang saya perlukan, seperti
UserCreationFormdanmessagespada berkasviews.py. Berikut adalah kode yang saya tambahkan:from django.contrib.auth.forms import UserCreationForm from django.contrib import messages
Setelah menambahkan library tersebut, saya juga menambahkan fungsi register pada
views.pyyang berfungsi untuk membuat form registrasi secara otomatis serta membuat akun pengguna ketika form di-submit. Berikut adalah potongan kode yang saya tambahkan: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)
Setelah itu, untuk menampilkan form registrasi, kita tambahkan berkas
register.htmlpada direktorimain/templates. Berikut adalah isi dariregister.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 %}Kemudian, agar fungsi registrasi ini bisa digunakan, kita import fungsi tersebut pada
urls.pydan menambah path url-nya ke dalamurlpatterns. Berikut adalah kode yang saya tambahkan padaurls.py:from main.views import show_main, create_product, show_xml, show_json, show_xml_by_id, show_json_by_id, register ... urlpatterns = [ ... path('register/', register, name='register'), ] ...Mekanisme
registerberhasil dibuat! -
Login
Dalam mengimplementasikan fungsi login, sama seperti saat membuat fungsi registrasi, terlebih dahulu kita import library-library yang diperlukan pada
views.py. Berikut adalah kode yang saya tambahkan:from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.contrib.auth import authenticate, login
Selanjutnya, tambahkan fungsi
login_userpadaviews.py. Fungsi ini akan berfungsi untuk melakukan autentikasi pengguna pada website kita. Apabila login berhasil, maka pengguna akan di-redirect ke halaman utama kita. Berikut adalah kode yang saya tambahkan:def login_user(request): if request.method == 'POST': form = AuthenticationForm(data=request.POST) if form.is_valid(): user = form.get_user() login(request, user) return redirect('main:show_main') else: form = AuthenticationForm(request) context = {'form': form} return render(request, 'login.html', context)
Setelahnya, untuk menampilkan fungsi login tersebut, kita buat berkas baru bernama
login.htmlpada direktorimain/templates. Berikut adalah isi darilogin.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> {{ form.as_table }} <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, kita import fungsi login tersebut dan menambahkan path url ke dalam
urlpatternspadaurls.py. Berikut adalah kode yang saya tambahkan:from main.views import show_main, create_product, show_xml, show_json, show_xml_by_id, show_json_by_id, register, login_user ... urlpatterns = [ ... path('login/', login_user, name='login'), ] ...
Mekanisme
loginberhasil dibuat! -
Logout
Terakhir, kita akan membuat fungsi logout. Seperti sebelumnya, terlebih dahulu kita lakukan import library yang kita butuhkan pada
views.py. Berikut adalah kode yang saya tambahkan:from django.contrib.auth import authenticate, login, logout # Menambahkan logout
Selanjutnya, kita tambahkan juga fungsi
logout_userberikut padaviews.py.def logout_user(request): logout(request) return redirect('main:login')
Fungsi
logout_userini akan menerima request dari user dan melakukan proses logout atau keluar dari sesi yang sedang berjalan. Nantinya setelah logout, user akan dialihkan ke halaman login.Selanjutnya, untuk menampilkan tombol logout, kita tambahkan hyperlink berikut pada berkas
main.htmldi direktorimain/templates:... <a href="{% url 'main:logout' %}"> <button>Logout</button> </a> ...
Kemudian, kita import fungsi tersebut dan menambahkan path url ke dalam
urlpatternspadaurls.py. Berikut adalah kode yang saya tambahkan:from main.views import show_main, create_product, show_xml, show_json, show_xml_by_id, show_json_by_id, register, login_user, logout_user ... urlpatterns = [ ... path('logout/', logout_user, name='logout'), ] ...
Mekanisme
logoutberhasil dibuat!
Selain ketiga mekanisme ini, diperlukan adanya restriksi akses bagi pengguna yang belum melakukan login. Hal ini dapat dilakukan dengan menambahkan kode berikut pada views.py:
from django.contrib.auth.decorators import login_required
...
@login_required(login_url='/login')
def show_main(request):
...Dengan menambahkan kode ini, halaman utama yang ditampilkan oleh fungsi show_main hanya dapat diakses oleh pengguna yang sudah melakukan login. Apabila pengguna belum login, maka mereka akan diminta untuk melakukan login pada path url /login terlebih dahulu.
Selain itu, karena kita ingin melakukan deployment, tentu kita perlu mempersiapkan website kita untuk environtment production. Untuk melakukan hal tersebut, saya melakukan modifikasi pada berkas settings.py di subdirektori marquette. Berikut adalah potongan kode yang saya tambahkan:
...
import os
...
PRODUCTION = os.getenv("PRODUCTION", False)
DEBUG = not PRODUCTION
...Menampilkan detail informasi pengguna yang sedang logged in
Setiap pengguna yang berhasil logged in ke dalam website kita tentu harus memiliki datanya masing-masing yang berbeda dan tidak dapat diakses oleh pengguna lainnya. Dengan demikian, diperlukan suatu mekanisme yang dapat membedakan setiap pengguna pada Django. Hal ini dapat diimplementasikan dengan memanfaatkan cookies.
Untuk mengimplementasikan cookies, terlebih dahulu kita melakukan import library-library yang diperlukan pada berkas views.py. Berikut adalah kode yang saya tambahkan:
import datetime
from django.http import HttpResponse, HttpResponseRedirect
from django.urls import reverseSetelah melakukan import library, kita lakukan modifikasi pada fungsi login_user. Kita akan menambahkan cookie bernama last_login yang menyimpan waktu terakhir pengguna melakukan login. Berikut adalah fungsi login_user setelah mengalami modifikasi:
def login_user(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
user = form.get_user()
login(request, user)
response = HttpResponseRedirect(reverse("main:show_main"))
response.set_cookie('last_login', str(datetime.datetime.now()))
return response
else:
form = AuthenticationForm(request)
context = {'form': form}
return render(request, 'login.html', context)Setelahnya, kita modifikasi juga fungsi show_main dengan menambahkan key last_login yang nantinya akan ditampilkan melalui main.html. Berikut adalah hasil modifikasi show_main:
@login_required(login_url='/login')
def show_main(request) :
products = Product.objects.filter(user=request.user)
context = {
'app_name' : 'Marquette',
'name': request.user.username,
'class' : 'PBP D',
'products' : products,
'last_login': request.COOKIES['last_login'],
}
return render(request, "main.html", context)Kemudian, karena sebelumnya kita telah menambahkan cookie pada saat login, maka kita juga harus menghapusnya saat melakukan logout. Untuk melakukan hal tersebut, saya memodifikasi fungsi logout_user. Berikut adalah hasil modifikasinya:
def logout_user(request):
logout(request)
response = HttpResponseRedirect(reverse('main:login'))
response.delete_cookie('last_login')
return responseTerakhir, saya menampilkan value dari last_login yang ada pada context fungsi show_main dengan menambahkan kode berikut pada berkas main.html:
...
<h5>Sesi terakhir login: {{ last_login }}</h5>
...cookies berhasil diimplementasikan! Untuk mengecek data-data seperti last_login, seessionid, dan csrftoken, kita dapat memanfaatkan fitur inspect elemen atau developer tools.
Berikut adalah salah satu contoh hasil tampilan detail dari suatu pengguna:

Terlihat pada sisi kiri, username yang telah login dan cookie dengan nama last_login berhasil kita implementasikan. Selain itu, terdapat berbagai informasi seperti csrftoken dan sessionid pada bagian kanan tampilan.
Menghubungkan model Product dengan User
Agar setiap pengguna memiliki objek Product-nya masing-masing serta hanya dapat melihat objek Product-nya sendiri, maka kita perlu menghubungkan model Product dengan User. Terlebih dahulu, kita tambahkan import berikut pada berkas models.py di subdirektori main:
...
from django.contrib.auth.models import User
...Selanjutnya, kita lakukan modifikasi pada model Product. Berikut adalah hasil modifikasi saya:
class Product(models.Model) :
user = models.ForeignKey(User, on_delete=models.CASCADE)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)
price = models.IntegerField()
description = models.TextField()Dengan menambahkan field user dan menginisalisasinya sebagai ForeignKey, maka kita telah berhasil menghubungkan Product dengan User, sehingga setiap objek Product hanya dimiliki oleh pengguna yang menambahkannya.
Selanjutnya, kita perlu menyimpan nama pengguna yang membuat objek Product baru. Dengan demikian, kita perlu melakukan sedikit perubahan pada fungsi create_product di berkas views.py. Berikut adalah hasil modifikasi saya:
def create_product(request) :
form = ProductForm(request.POST or None)
if form.is_valid() and request.method == "POST" :
product_entry = form.save(commit=False)
product_entry.user = request.user
product_entry.save()
return redirect('main:show_main')
context = {'form' : form}
return render(request, "create_product.html", context)Setelah itu, saya kembali memastikan bahwa value dari field name pada context di fungsi show_main telah menampilkan nama pengguna. Berikut adalah potongan kodenya:
@login_required(login_url='/login')
def show_main(request) :
products = Product.objects.filter(user=request.user)
context = {
'app_name' : 'Marquette',
'name': request.user.username,
...
}Setelah melakukan seluruh perubahan tersebut, selanjutnya agar perubahan models dapat tercatat, kita harus melakukan migrasi. Berikut adalah perintah yang saya jalankan:
python manage.py makemigrations
python manage.py migrate
Model Product telah berhasil dihubungkan dengan User!
Membuat dua akun pengguna dengan masing-masing tiga dummy data
Saat pertama kali kita membuka website, akan terlihat tampilan login page seperti berikut:

Kita tekan tombol Register Now, maka kita akan dipindahkan ke path /register/. Berikut adalah tampilan halaman registrasi:

Setelah melakukan registrasi, kita akan kembali ke halaman login. Kemudian, masukkan username dan password yang sebelumnya telah dibuat. Apabila login berhasil, maka kita akan dialihkan ke halaman utama. Berikut adalah tampilan halaman utama:

Kemudian, untuk membuat product baru, tekan tombol Add New Product. Masukkan data pada field name, price, dan description, kemudian tekan Add Product. Berikut adalah tampilan halaman create-product:

Lakukan proses yang sama sebanyak tiga kali pada kedua akun pengguna. Berikut adalah hasilnya:

Terlihat bahwa kedua pengguna memiliki dummy data yang berbeda. Ini mengindikasikan bahwa mekanisme login serta penghubungan model Product dengan User kita telah berhasil.
Perbedaan antara kedua method ini terletak pada parameter yang dibutuhkan untuk menjalankannya. Pada HttpResponseRedirect(), method membutuhkan URL yang lengkap sebagai parameter. Sedangkan, pada method redirect, parameter yang dibutuhkan lebih fleksibel. Method ini dapat menerima models, views, dan url. Nantinya, parameter ini akan dikonversi menjadi sebuah URL, lalu kemudian mengembalikan HttpResponseRedirect().
Dalam proyek yang telah kita buat, cara kerja penghubungan Product dengan User dapat dilihat pada saat kita membuat objek Product baru. Setiap kali seorang pengguna membuat entri Product baru, maka proyek akan secara langsung dikaitkan dan disimpan sebagai kepunyaan/milik dari pengguna tersebut.
Setiap objek Product akan memiliki seorang user yang bersifat One-to-Many (terlihat pada bagian ForeignKey), yaitu satu pengguna dapat memiliki lebih dari satu objek Product. Hal ini terlihat pada potongan kode models.py berikut:
class Product(models.Model) :
user = models.ForeignKey(User, on_delete=models.CASCADE)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)
price = models.IntegerField()
description = models.TextField()Apa perbedaan antara authentication dan authorization, apakah yang dilakukan saat pengguna login? Jelaskan bagaimana Django mengimplementasikan kedua konsep tersebut.
-
Authentication Authentication adalah proses verifikasi identitas dari pengguna. Pada proyek ini, authentication dilakukan melalui fungsi login.
-
Authorization Authorization adalah proses memberikan izin/privilege atau kewenangan pada pengguna yang sudah terautentikasi untuk mengakses fungsi-fungsi atau bagian-bagian tertentu pada proyek yang telah kita buat.
Bagaimana Django mengingat pengguna yang telah login? Jelaskan kegunaan lain dari cookies dan apakah semua cookies aman digunakan?
Untuk mengingat pengguna yang telah melalui proses login, Django memanfaatkan mekanisme cookies dan sessions. Pada saat melakukan proses login, server akan menyimpan session id masing-masing pengguna dan meng-assign-nya sebagai cookies pada pengguna tersebut. Cookies sendiri selalu dikirimkan saat seorang pengguna melakukan request kepada server. Dengan demikian, server dapat mengetahui siapa yang sedang mengirimkan request tersebut. Cookies sendiri akan tetap berada dalam browser selama session tersebut masih berlangsung atau belum expired.
Selain untuk mengingat dan membedakan pengguna yang telah login, cookies juga dapat digunakan untuk menyimpan aktivitas yang telah dilakukan oleh pengguna, contohnya saja fitur penyimpanan Cart atau keranjang belanja pada platform e-commerce. Selain itu, cookies juga dapat digunakan untuk menyimpan kustomisasi atau preferensi pengguna, sehingga pengguna tidak perlu melakukan kustomisasi berulang kali setiap melakukan tindakan login. Beberapa fitur lainnya seperti Remember me juga memanfaatkan cookies ini.
Walaupun cookies memiliki banyak manfaat, tetapi masih terdapat berbagai celah keamanan di dalamnya. Berdasarkan hasil pengamatan hingga saat ini, tidak semua cookies aman untuk digunakan. Ada beberapa vulnerability yang rawan terjadi, salah satunya adalah session hijacking. Cookies yang tidak dibuat dan dilindungi dengan baik rentan untuk dicuri oleh pihak tidak bertanggung jawab untuk mengambil alih akun pengguna tersebut. Selain itu, cookie juga rentan terhadap serangan XSS (Cross-Site Scripting) apabila mekanisme penerimaan query input pada website tidak diatur dengan baik serta MitM (Man-in-the-Middle Attack) jika koneksi pada website tidak memanfaatkan protokol HTTPS.
Implementasi fungsi edit dan hapus Product
Sebelum mengimplementasikan fungsi edit dan hapus ini, kita harus melakukan import library berikut pada views.py:
from django.shortcuts import render, redirect, reverse
from django.http import HttpResponse, HttpResponseRedirect-
Edit
Untuk menambahakan fungsi edit, pertama-tama kita tambahkan potongan kode berikut pada
views.py:def edit_product(request, id) : product = Product.objects.get(pk=id) form = ProductForm(request.POST or None, instance=product) if form.is_valid() and request.method == "POST" : form.save() return HttpResponseRedirect(reverse('main:show_main')) context = {'form': form} return render(request, "edit_product.html", context)
Setelah itu, kita tambahkan berkas
edit_product.html. Untuk isinya, akan dibahas lebih lanjut pada step berikutnya.Kemudian, kita lakukan import fungsi
edit_productserta menambahkan path url ke dalamurlpatternspada berkasurls.pydi direktorimain. Berikut adalah potongan kodenya:from main.views import edit_product ... path('edit-product/<uuid:id>', edit_product, name='edit_product'), ...
-
Delete
Untuk menambahkan fungsi hapus atau delete, pertama-tama kita tambahkan potongan kode berikut pada berkas
views.py:def delete_product(request, id) : product = Product.objects.get(pk=id) product.delete() return HttpResponseRedirect(reverse('main:show_main'))
Setelah itu, kita lakukan import fungsi
delete_productdan menambahkan path url ke dalamurlpatternspada berkasurls.pydi direktorimain. Berikut adalah potongan kodenya:from main.views import delete_product ... path('delete-product/<uuid:id>', delete_product, name='delete_product'), ...
Setelah itu, tambahkan button
editdandeletepada masing-masingcard-productyang akan diimplementasikan selanjutnya. Namun, secara garis besar, berikut adalah potongan kode untuk menampilkan button tersebut:... <tr> ... <td> <a href="{% url 'main:edit_mood' mood_entry.pk %}"> <button> Edit </button> </a> </td> <td> <a href="{% url 'main:delete_mood' mood_entry.pk %}"> <button> Delete </button> </a> </td> </tr> ...
Melakukan Kustomisasi Design menggunakan Tailwind CSS
Sebelum melakukan kustomisasi website yang telah kita buat, terlebih dahulu kita harus menambahkan Tailwind CSS pada berkas templates/base.html kita. Berikut adalah potongan kode yang saya tambahkan:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% block meta %} {% endblock meta %}
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
{% block content %} {% endblock content %}
</body>
</html>Selanjutnya, kita lakukan konfigurasi Static Files pada aplikasi website kita, yaitu dengan menambahkan potongan kode berikut di settings.py:
...
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
...
]
...
STATIC_URL = '/static/'
if DEBUG:
STATICFILES_DIRS = [
BASE_DIR / 'static'
]
else:
STATIC_ROOT = BASE_DIR / 'static'
...Kemudian, karena saya ingin menggunakan font dari Google Font, saya menambahkan potongan kode berikut pada base.html:
...
<style>
@import url('https://fonts.googleapis.com/css2?family=Satisfy&display=swap');
</style>
<link rel="stylesheet" href="{% static 'css/global.css' %}"/>
...Lalu, saya juga membuat file global.css pada direktori static/css dengan isi sebagai berikut:
.satisfy-regular {
font-family: "Satisfy", cursive;
font-weight: 400;
font-style: normal;
}Selanjutnya, setelah menambahkan konfigurasi-konfigurasi tersebut, saya melakukan modifikasi pada berkas login.html, register.html, create_product, dan edit_product agar terlihat lebih menarik.
Tidak lupa juga, untuk menampilkan objek Product yang telah dibuat, saya menambahkan satu berkas baru bernama card_product.html. Sehingga, setiap kali objek Product dibuat, tampilannya akan mengikuti berkas html ini.
Setelahnya, saya juga menambahkan berkas card_info.html untuk menampilkan info pengguna yang sedang login dan memasukkannya ke dalam berkas main.html.
Semua berkas html ini dibuat dengan memanfaatkan Tailwind CSS sehingga menjadi responsive dan dapat digunakan pada device manapun.
Terakhir, saya juga menambahkan navbar.html, sehingga pengguna dapat dengan mudah berganti section yang mereka inginkan.
Jika terdapat beberapa CSS selector untuk suatu elemen HTML, jelaskan urutan prioritas pengambilan CSS selector tersebut!
Jika terdapat beberapa CSS selector untuk suatu elemen HTML, berikut adalah urutan prioritasnya dari yang tertinggi hingga terendah:
-
Inline Style
Selector ini memiliki prioritas tertinggi dan langsung diaplikasikan menggunakan atribut
style. -
ID Selectors
Selector ini memiliki prioritas tertinggi kedua, biasanya dapat diidentifikasi dengan menggunakan atribut
iddari suatu elemen. -
Classes dan Pseudo-classes
Selector ini berada pada posisi ketiga yang biasanya digunakan pada nama
classmaupunpseudo-classes. -
Attributes
Selector ini berada pada posisi keempat yang biasanya digunakan pada atribut-atribut html.
-
Elements dan Pseudo-elements
Selector ini berada pada posisi terendah yang biasanya digunakan pada elemen-elemen serta pseudo-element dari HTML.
Berikut adalah contoh dari urutan prioritas CSS tersebut:
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
h1 {
background-color: red;
color: white;
}
#second {
background-color: black;
color: white;
}
.third {
background-color: pink;
color: blue;
}
#second1 {
color: blue;
}
.third1 {
color: red;
}
</style>
</head>
<body>
<h1 id="second" class="third">
Prioritas selector ID tertinggi kedua.
</h1>
<h1>
Selector element memiliki prioritas terendah.
</h1>
<h1 class="third">
Selector untuk classes lebih tinggi prioritasnya dibandingkan selector elemen.
</h1>
<h2 style="color: green;"
id="second1"
class="third1">
Inline CSS memiliki prioritas tertinggi.
</h2>
</body>
</html>Referensi: GeeksForGeeks
Mengapa responsive design menjadi konsep yang penting dalam pengembangan aplikasi web? Berikan contoh aplikasi yang sudah dan belum menerapkan responsive design!
Alasan dibalik pentingnya responsive design dari aplikasi website yang kita buat adalah target pasar dari aplikasi itu sendiri. Mengingat setiap pengguna belum tentu memiliki device atau perangkat yang sama dengan yang kita miliki, maka tentu untuk mengakomodasi perbedaan tersebut, kita perlu membuat aplikasi web yang dapat menyesuaikan tampilannya dengan dimensi perangkat yang digunakan oleh pengguna.
Aplikasi-aplikasi yang sudah responsive dapat dengan mudah kita temui pada website yang kita gunakan sehari-hari, seperti Youtube, Twitter, Instagram, LinkedIn, dan masih banyak lagi. Kemudian, untuk aplikasi yang belum responsive dapat kita lihat pada SIAKNG. Ketika kita menggunakan SIAKNG dari perangkat mobile, maka tampilannya masih menggunakan tampilan browser pada perangkat seperti PC maupun laptop, yang tentu sangat tidak nyaman untuk digunakan.
Jelaskan perbedaan antara margin, border, dan padding, serta cara untuk mengimplementasikan ketiga hal tersebut!
-
Margin
marginmerupakan ruang yang berada di luar elemen yang memisahkan satu elemen dengan elemen lainnya. Kita dapat mengaturmarginsisi atas, bawah, kiri, maupun kanan dari elemen yang kita inginkan. Untuk mengimplementasikanmargin, kita dapat menuliskan kode sepertimargin-top: 12px;, yang berfungsi untuk memberikanmargindengan elemen di atasnya sebesar 12px. -
Border
bordermerupakan garis yang mengelilingi elemen dan berfungsi untuk memberikan batas yang dapat diliat secara visual oleh pengguna.borderdapat dikustomisasi, baik itu garis, warna, maupun gayanya. Berikut adalah salah satu contoh pengimplementasianborder, yaituborder: 5px solid red;yang akan membuat border di sekitar elemen dengan ketebalan 5px, style garis solid (tidak terputus), dan berwarna merah. -
Padding
paddingmerupakan ruang yang berada di dalam elemen, antara konten yang kita miliki dengan border. Mirip denganmargin, kita dapat mengimplementasikanpaddinguntuk berbagai sisi elemen, sepertipadding-bottom: 25px, yang berfungsi memberikanpaddingantara bagian bawah elemen dengan border sebesar 25px.
Secara umum, flex box dan grid layout merupakan metode yang digunakan untuk mengatur tata letak elemen maupun kontainer yang memudahkan pengimplementasian responsive design website.
-
Flex Box
Flex Boxberfokus pada penataan elemen satu dimensi, baik itu satu baris horizontal maupun satu kolom vertikal.Flex Boxmemudahkan kita dalam mengatur ruang serta elemen dalam suatu kontainer secara otomatis. -
Grid Layout
Grid Layoutberfokus pada penataan elemen dua dimensi, sehingga memungkinkan pengaturan baris serta kolom secara sekaligus.Grid Layoutmemudahkan kita dalam mengatur ruang serta elemen ketika bekerja dalam suatugrid.
Dalam pemanfaatannya, Flex Box lebih sering digunakan dalam bagian-bagian satu dimensi seperti Navigation Bar (NavBar), sedangkan Grid Layout lebih sering diimplementasikan pada bento dashboard yang memerlukan tata letak dua dimensi untuk elemen-elemen di dalamnya.
Implementasi AJAX GET
Dalam mengimplementasikan AJAX GET, kita harus melakukan beberapa perubahan kode pada berkas views.py. Berikut adalah potongan kode yang saya modifikasi:
...
@login_required(login_url='/login')
def show_main(request) :
context = { # Menghapus field mood_entries
'app_name' : 'marquette',
'name': request.user.username,
'class' : 'PBP D',
'last_login': request.COOKIES['last_login'],
}
return render(request, "main.html", context)
...
def show_xml(request) :
data = Product.objects.filter(user=request.user) # Perubahan disini
return HttpResponse(serializers.serialize("xml", data), content_type="application/xml")
def show_json(request) :
data = Product.objects.filter(user=request.user) # Perubahan disini
return HttpResponse(serializers.serialize("json", data), content_type="application/json")Setelah itu, kita lakukan modifikasi pada berkas main.html seperti berikut:
...
<div id="product_cards"></div>
...
<script>
async function getProductEntries() {
return fetch("{% url 'main:show_json' %}").then((res) => res.json());
}
async function refreshProductEntries() {
document.getElementById("product_cards").innerHTML = "";
document.getElementById("product_cards").className = "";
const productEntries = await getProductEntries();
let htmlString = "";
let classNameString = "";
if (productEntries.length == 0) {
classNameString = "flex flex-col items-center justify-center min-h-[24rem] p-6";
htmlString = `
<div class="flex flex-col items-center justify-center min-h-[24rem]">
<img src="{% static 'image/no_product.png' %}" alt="No Product" class="w-32 h-32 mb-4"/>
<p class="text-center text-gray-600 mt-4">Belum ada data Product pada marquette.</p>
</div>
`;
} else {
classNameString = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 w-full";
productEntries.forEach((item) => {
const name = DOMPurify.sanitize(item.fields.name);
const price = DOMPurify.sanitize(item.fields.price);
const description = DOMPurify.sanitize(item.fields.description);
htmlString += `
<div class="relative bg-white shadow-md rounded-lg overflow-hidden border border-[#ffc6d9] h-80">
<div class="bg-[#ffe1ed] text-gray-800 p-4 border-b border-[#ffc6d9]">
<h3 class="font-bold text-xl mb-2 text-[#ff6b9d] truncate">${name}</h3>
<p class="product-price text-gray-600 font-bold" data-price="${price}"></p>
</div>
<div class="p-4 max-h-40 overflow-y-auto">
<p class="font-semibold text-lg mb-2 text-[#ff6b9d]">Description</p>
<p class="text-gray-700 mb-2 break-words whitespace-normal overflow-wrap-normal">${description}</p>
</div>
<div class="flex justify-end space-x-2 p-4 bg-[#fff0f5] absolute bottom-0 right-0 left-0">
<a href="/edit-product/${item.pk}" class="bg-[#ff9ec3] hover:bg-[#ffb6d3] text-white rounded-full px-4 py-2 transition duration-300 shadow-md">
Edit
</a>
<a href="/delete-product/${item.pk}" class="bg-[#ff6b9d] hover:bg-[#ff8fb3] text-white rounded-full px-4 py-2 transition duration-300 shadow-md">
Delete
</a>
</div>
</div>
</div>
`;
});
}
document.getElementById("product_cards").className = classNameString;
document.getElementById("product_cards").innerHTML = htmlString;
function formatRupiah(price) {
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR' }).format(price);
}
document.querySelectorAll(".product-price").forEach(function(priceElement) {
const price = priceElement.getAttribute("data-price");
priceElement.innerText = formatRupiah(price);
});
}
refreshProductEntries();
...
</script>Impelementasi AJAX POST
Dalam mengimplementasikan AJAX POST, terlebih dahulu kita lakukan modifikasi pada berkas views.py kita. Berikut adalah potongan kode yang saya tambahkan:
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
...
@csrf_exempt
@require_POST
def create_ajax(request) :
user = request.user
name = strip_tags(request.POST.get('name'))
price = strip_tags(request.POST.get('price'))
description = strip_tags(request.POST.get('description'))
new_product = Product(
user = user,
name = name,
price = price,
description = description
)
if (not name.strip() or not price.strip() or not description.strip()) :
return HttpResponse(b"BAD REQUEST", status = 400)
else :
new_product.save()
return HttpResponse(b"CREATED", status = 201)Kemudian, kita lakukan routing fungsi create_ajax tersebut pada urls.py. Berikut adalah potongan kode yang saya tambahkan:
from main.views import ..., add create_ajax
...
urlpatterns = [
...
path('create-ajax', create_ajax, name='create_ajax'),
...
]Kemudian, kita akan membuat modal yang memanfaatkan fungsi create_ajax yang kita telah buat tersebut. Berikut adalah potongan kode yang saya tambahkan:
<button data-modal-target="crudModal" data-modal-toggle="crudModal" class="ml-auto bg-[#c78ca0] hover:bg-[#d9a7a4] text-white font-medium py-2 px-4 rounded-lg transition duration-300 ease-in-out w-full sm:w-auto text-center" onclick="showModal();">
Add New Product with AJAX
</button>
...
<div id="crudModal" tabindex="-1" aria-hidden="true" class="hidden fixed inset-0 z-50 w-full flex items-center justify-center bg-gray-800 bg-opacity-50 overflow-x-hidden overflow-y-auto transition-opacity duration-300 ease-out">
<div id="crudModalContent" class="relative bg-white rounded-lg shadow-lg w-5/6 sm:w-3/4 md:w-1/2 lg:w-1/3 mx-4 sm:mx-0 transform scale-95 opacity-0 transition-transform transition-opacity duration-300 ease-out">
<div class="flex items-center justify-between p-4 border-b rounded-t">
<h3 class="text-xl font-semibold text-gray-900"> Add New Product </h3>
<button type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" id="closeModalBtn">
<span class="sr-only">Close modal</span>
</button>
</div>
<div class="px-6 py-4 space-y-6 form-style">
<form id="productEntryForm">
<div class="mb-4">
<label for="name" class="block text-sm font-medium text-gray-700">Name</label>
<input type="text" id="name" name="name" class="mt-1 block w-full border border-gray-300 rounded-md p-2 hover:border-indigo-700" placeholder="Enter your product name" required>
</div>
<div class="mb-4">
<label for="price" class="block text-sm font-medium text-gray-700">Price</label>
<input type="number" id="price" name="price" class="mt-1 block w-full border border-gray-300 rounded-md p-2 hover:border-indigo-700" placeholder="Enter your product price" required>
</div>
<div class="mb-4">
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
<textarea id="description" name="description" rows="3" class="mt-1 block w-full h-52 resize-none border border-gray-300 rounded-md p-2 hover:border-indigo-700" placeholder="Describe your product" required></textarea>
</div>
</form>
</div>
<div class="flex flex-col space-y-2 md:flex-row md:space-y-0 md:space-x-2 p-6 border-t border-gray-200 rounded-b justify-center md:justify-end">
<button type="submit" id="submitProductEntry" form="productEntryForm" class="bg-[#ff9ec3] hover:bg-[#ffb6d3] text-white rounded-full px-4 py-2 transition duration-300 shadow-md"> Save </button>
<button type="button" class="bg-[#ff6b9d] hover:bg-[#ff8fb3] text-white rounded-full px-4 py-2 transition duration-300 shadow-md" id="cancelButton"> Cancel </button>
</div>
</div>
</div>
<script>
const modal = document.getElementById('crudModal');
const modalContent = document.getElementById('crudModalContent');
function showModal() {
const modal = document.getElementById('crudModal');
const modalContent = document.getElementById('crudModalContent');
document.getElementById("productEntryForm").reset();
modal.classList.remove('hidden');
setTimeout(() => {
modalContent.classList.remove('opacity-0', 'scale-95');
modalContent.classList.add('opacity-100', 'scale-100');
}, 50);
}
function hideModal() {
const modal = document.getElementById('crudModal');
const modalContent = document.getElementById('crudModalContent');
modalContent.classList.remove('opacity-100', 'scale-100');
modalContent.classList.add('opacity-0', 'scale-95');
setTimeout(() => {
modal.classList.add('hidden');
}, 150);
}
document.getElementById("cancelButton").addEventListener("click", hideModal);
document.getElementById("closeModalBtn").addEventListener("click", hideModal);
async function addProductEntry() {
try {
const response = await fetch("{% url 'main:create_ajax' %}", {
method: "POST",
body: new FormData(document.querySelector('#productEntryForm')),
});
if (!response.ok) {
throw new Error('Failed to create product');
}
await refreshProductEntries();
document.getElementById("productEntryForm").reset();
hideModal();
} catch (error) {
alert('Error: ' + error.message);
}
}
document.getElementById("productEntryForm").addEventListener("submit", (e) => {
e.preventDefault();
addProductEntry();
});
</script>Sekarang, kita sudah dapat menggunakan modal yang mengimplementasikan AJAX POST!
Implementasi Perlindungan pada fungsi GET dan POST
Saat ini, aplikasi kita masih rentan terhadap berbagai serangan. Untuk melindungi fungsi GET, saya menambahkan field user pada setiap objek Product yang dibuat oleh pengguna. Hal ini terlihat pada kode berikut:
@csrf_exempt
@require_POST
def create_ajax(request) :
user = request.user
name = strip_tags(request.POST.get('name'))
price = strip_tags(request.POST.get('price'))
description = strip_tags(request.POST.get('description'))
new_product = Product(
user = user, # Objek Product baru yang dibuat hanya disimpan pada user ini
name = name,
price = price,
description = description
)
...Kemudian, untuk melindungi aplikasi web saya dari serangan XSS, saya menggunakan library strip_tags. Pertama-tama, import kedua library tersebut pada berkas views.py dan forms.py. Berikut adalah implementasinya:
# views.py
from django.utils.html import strip_tags
@csrf_exempt
@require_POST
def create_ajax(request) :
user = request.user
name = strip_tags(request.POST.get('name')) # Gunakan strip_tags
price = strip_tags(request.POST.get('price')) # Gunakan strip_tags
description = strip_tags(request.POST.get('description')) # Gunakan strip_tags
...# forms.py
from django.utils.html import strip_tags
class ProductForm(ModelForm) :
class Meta :
model = Product
fields = ["name", "price", "description"]
def clean_name(self): # Gunakan strip_tags
name = self.cleaned_data["name"]
return strip_tags(name)
def clean_price(self): # Gunakan strip_tags
price = self.cleaned_data["price"]
return strip_tags(price)
def clean_description(self): # Gunakan strip_tags
description = self.cleaned_data["description"]
return strip_tags(description)Aplikasi web kita sekarang sudah aman dari serangan XSS! Selanjutnya, kita tambahkan fungsi DOMPurify. Berikut adalah potongan kode yang saya ganti pada main.html:
{% block meta %}
<title>marquette</title>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.7/dist/purify.min.js"></script>
{% endblock meta %}
...
<script>
...
async function refreshProductEntries() {
...
if (productEntries.length == 0) {
...
} else {
...
productEntries.forEach((item) => {
const name = DOMPurify.sanitize(item.fields.name);
const price = DOMPurify.sanitize(item.fields.price);
const description = DOMPurify.sanitize(item.fields.description);
...
});
}
...
}
...
</script>JavaScript memiliki banyak sekali manfaat dalam pengembangan aplikasi web, seperti:
-
Interaktivitas
JavaScript memungkinkan kita membuat aplikasi web yang lebih interaktif. Hal ini dibuktikan dengan banyaknya animasi dan elemen-elemen interaktif yang dapat dibuat oleh JavaScript. Hal ini didukung dengan banyaknya framework pengembangan aplikasi website yang menggunakan bahasa ini.
-
Kompatibilitas
JavaScript dapat digunakan pada berbagai platform (cross-platform), sehingga kita tidak perlu terlalu takut akan masalah kompatibilitas antara berbagai perangkat yang menggunakannya.
-
Asynchronous
JavaScript mendukung adanya pemrograman secara asinkronus (Asynchronous Programming), sehingga pengguna tidak perlu menunggu respon dari server dan dapat melakukan tugas lainnya ketika menggunakan website kita. Hal ini terlihat pada penggunaan fungsi
asyncataupunawaitpada aplikasi website kita.
Jelaskan fungsi dari penggunaan await ketika kita menggunakan fetch()! Apa yang akan terjadi jika kita tidak menggunakan await?
Fungsi penggunaan await ketika menggunakan fetch adalah untuk menunggu hasil dari operasi fetch sebelum melanjutkan ke operasi selanjutnya. Dengan menggunakan await, kita dapat memastikan bahwa fetch pasti sudah mendapatkan data yang diinginkan sebelum mengeksekusi baris-baris kode selanjutnya.
Apabila kita tidak menggunakan await ketika melakukan fetch, maka bisa saja aplikasi web kita tidak mendapatkan data yang diinginkan. Hal ini diakibatkan oleh promise yang belum selesai, sehingga baris-baris kode selanjutnya mungkin tidak mendapatkan data yang sesuai.
Ketika menggunakan AJAX POST, seringkali request tersebut dibuat tanpa menggunakan token CSRF. Hal ini mengakibatkan Django menolak request tersebut karena tidak melewati validasi CSRF yang ada. Untuk menangani hal tersebut, csrf_exempt digunakan. Decorator ini berfungsi untuk menonaktifkan pengecekan terhadap token CSRF. Sehingga, views yang memanfaatkan AJAX POST dapat digunakan tanpa kendala. Namun, perlu dicatat bahwa penggunaan csrf_exempt harus dilakukan secara hati-hati. Dengan menonaktifkan validasi terhadap token CSRF, kita sangat rentan untuk mengalami serangan CSRF. Dengan demikian, gunakanlah csrf_exempt hanya pada views yang aman dan tidak dapat diakses secara langsung oleh publik.
Pada tutorial PBP minggu ini, pembersihan data input pengguna dilakukan di belakang (backend) juga. Mengapa hal tersebut tidak dilakukan di frontend saja?
Ada beberapa alasan mengapa pembersihan data input juga dilakukan pada backend, salah satunya adalah masalah keamanan. Validasi data yang dilakukan pada frontend seringkali memiliki celah dan dapat dengan mudah dilewati oleh para penyerang dengan memanipulasi request yang dilakukan. Dengan demikian, melakukan validasi sekali lagi melalui backend menjadi pilihan utama. Hal ini didukung dengan fakta bahwa backend lebih tersembunyi dan tidak dapat diakses secara langsung oleh pengguna, sehingga melakukan manipulasi request untuk melewati validasinya terbilang sulit atau bahkan mustahil.
Kemudian, alasan lain validasi dilakukan di backend adalah integritas dan konsistensi data. Backend merupakan penanggung jawab utama dalam pengelolaan data suatu aplikasi web. Dengan demikian, memastikan kevalidan serta konsistensi data yang masuk dari frontend merupakan tugas dari backend. Hal ini didasarkan pada fakta bahwa bisa saja terdapat kerentanan pada validasi frontend dan melalui kerentanan tersebut, penyerang memasukkan data yang formatnya tidak sesuai dengan format yang diinginkan.




